Class: Clacky::Channel::Adapters::Feishu::MessageParser

Inherits:
Object
  • Object
show all
Defined in:
lib/clacky/server/channel/adapters/feishu/message_parser.rb

Overview

Parses incoming Feishu webhook events into a standardized InboundMessage format.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(data) ⇒ MessageParser

Returns a new instance of MessageParser.



21
22
23
# File 'lib/clacky/server/channel/adapters/feishu/message_parser.rb', line 21

def initialize(data)
  @data = data
end

Class Method Details

.parse(body) ⇒ Hash?

Parse a Feishu webhook event body

Parameters:

  • body (String, Hash)

    Raw webhook body

Returns:

  • (Hash, nil)

    Standardized inbound message, or nil if not a message event



14
15
16
17
18
19
# File 'lib/clacky/server/channel/adapters/feishu/message_parser.rb', line 14

def self.parse(body)
  data = body.is_a?(Hash) ? body : JSON.parse(body)
  new(data).parse
rescue JSON::ParserError
  nil
end

Instance Method Details

#extract_doc_urls(text) ⇒ Array<String>

Extract Feishu document URLs from text. Matches: /docx/TOKEN, /docs/TOKEN, /wiki/TOKEN

Parameters:

  • text (String)

Returns:

  • (Array<String>)

    matched URLs



111
112
113
114
115
# File 'lib/clacky/server/channel/adapters/feishu/message_parser.rb', line 111

def extract_doc_urls(text)
  return [] if text.nil? || text.empty?

  text.scan(%r{https?://[a-zA-Z0-9._-]+\.(?:feishu\.cn|larksuite\.com)/(?:docx|docs|wiki)/[A-Za-z0-9_-]+(?:\?[^\s]*)?})
end

#parseHash?

Returns Inbound message or nil.

Returns:

  • (Hash, nil)

    Inbound message or nil



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/clacky/server/channel/adapters/feishu/message_parser.rb', line 26

def parse
  # Handle verification challenge
  if @data["type"] == "url_verification"
    return { type: :challenge, challenge: @data["challenge"] }
  end

  header = @data["header"]
  return nil unless header

  event_type = header["event_type"]

  case event_type
  when "im.message.receive_v1"
    parse_message_event
  else
    nil
  end
end

#parse_message_eventHash?

Parse message.receive event

Returns:

  • (Hash, nil)


48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/clacky/server/channel/adapters/feishu/message_parser.rb', line 48

def parse_message_event
  event = @data["event"]
  return nil unless event

  message = event["message"]
  sender = event["sender"]
  return nil unless message && sender

  msg_type = message["message_type"]
  Clacky::Logger.info("[feishu] msg_type=#{msg_type} content=#{message["content"].to_s[0..300]}")
  return nil unless %w[text image file].include?(msg_type)

  content_raw = message["content"]
  return nil unless content_raw

  content = JSON.parse(content_raw)
  text = ""
  image_keys = []
  file_attachments = []

  case msg_type
  when "text"
    text = strip_mentions(content["text"].to_s.strip)
    return nil if text.empty?
  when "image"
    image_keys = [content["image_key"]].compact
    return nil if image_keys.empty?
  when "file"
    file_key = content["file_key"]
    file_name = content["file_name"]
    return nil unless file_key
    file_attachments = [{ key: file_key, name: file_name.to_s }]
  end

  chat_id = message["chat_id"]
  message_id = message["message_id"]
  user_id = sender.dig("sender_id", "open_id")
  chat_type = message["chat_type"] == "p2p" ? :direct : :group
  create_time = message["create_time"]&.to_i
  timestamp = create_time ? Time.at(create_time / 1000.0) : Time.now

  {
    type: :message,
    platform: :feishu,
    chat_id: chat_id,
    user_id: user_id,
    text: text,
    image_keys: image_keys,
    file_attachments: file_attachments,
    doc_urls: extract_doc_urls(text),
    message_id: message_id,
    timestamp: timestamp,
    chat_type: chat_type,
    raw: @data
  }
rescue JSON::ParserError
  nil
end

#strip_mentions(text) ⇒ String

Strip bot @mentions from message text

Parameters:

  • text (String)

Returns:

  • (String)


120
121
122
123
# File 'lib/clacky/server/channel/adapters/feishu/message_parser.rb', line 120

def strip_mentions(text)
  # Feishu mentions are formatted as <at user_id="...">Name</at>
  text.gsub(/<at[^>]*>.*?<\/at>/, "").strip
end