class Clacky::Channel::Adapters::Wecom::Adapter
Receives messages via WebSocket long connection and sends via bot API.
WeCom (Enterprise WeChat) adapter.
def self.env_keys
def self.env_keys %w[IM_WECOM_BOT_ID IM_WECOM_SECRET] end
def self.platform_config(data)
def self.platform_config(data) { bot_id: data["IM_WECOM_BOT_ID"], secret: data["IM_WECOM_SECRET"] } end
def self.platform_id
def self.platform_id :wecom end
def self.set_env_data(data, config)
def self.set_env_data(data, config) data["IM_WECOM_BOT_ID"] = config[:bot_id] data["IM_WECOM_SECRET"] = config[:secret] end
def handle_raw_message(raw)
def handle_raw_message(raw) msgtype = raw["msgtype"] return unless %w[text image file].include?(msgtype) chat_id = raw["chatid"] || raw.dig("from", "userid") return unless chat_id user_id = raw.dig("from", "userid") chat_type = raw["chattype"] == "group" ? :group : :direct text = "" files = [] case msgtype when "text" text = raw.dig("text", "content").to_s.strip return if text.empty? when "image" url = raw.dig("image", "url") aeskey = raw.dig("image", "aeskey") return unless url result = MediaDownloader.download(url, aeskey) mime = MediaDownloader.detect_mime(result[:body]) if result[:body].bytesize > MAX_IMAGE_BYTES @ws_client.send_message(chat_id, "Image too large (#{(result[:body].bytesize / 1024.0).round(0).to_i}KB), max #{MAX_IMAGE_BYTES / 1024}KB") return end require "base64" data_url = "data:#{mime};base64,#{Base64.strict_encode64(result[:body])}" files = [{ name: "image.jpg", mime_type: mime, data_url: data_url }] when "file" url = raw.dig("file", "url") aeskey = raw.dig("file", "aeskey") return unless url filename = raw.dig("file", "name") || raw.dig("file", "filename") || "attachment" result = MediaDownloader.download(url, aeskey) filename = result[:filename] || filename saved = Clacky::Utils::FileProcessor.save(body: result[:body], filename: filename) files = [saved] end event = { type: :message, platform: :wecom, chat_id: chat_id, user_id: user_id, text: text, files: files, message_id: raw["msgid"], timestamp: raw["create_time"] ? Time.at(raw["create_time"]) : Time.now, chat_type: chat_type, raw: raw } @on_message&.call(event) rescue => e Clacky::Logger.error("[WecomAdapter] handle_raw_message error: #{e.message}\n#{e.backtrace.first(3).join("\n")}") begin @ws_client.send_message(chat_id, "Error processing message: #{e.message}") if chat_id rescue nil end end
def initialize(config)
def initialize(config) @config = config @ws_client = WSClient.new( bot_id: config[:bot_id], secret: config[:secret], ws_url: config[:ws_url] || WSClient::WS_URL ) @running = false @on_message = nil end
def send_file(chat_id, path, name: nil)
def send_file(chat_id, path, name: nil) @ws_client.send_file(chat_id, path, name: name) end
def send_text(chat_id, text, reply_to: nil)
def send_text(chat_id, text, reply_to: nil) @ws_client.send_message(chat_id, text) end
def start(&on_message)
def start(&on_message) @running = true @on_message = on_message @ws_client.start do |raw| handle_raw_message(raw) end rescue WSClient::AuthError => e Clacky::Logger.error("[WecomAdapter] Authentication failed, not retrying: #{e.message}") end
def stop
def stop @running = false @ws_client.stop end
def validate_config(config)
def validate_config(config) errors = [] errors << "bot_id is required" if config[:bot_id].nil? || config[:bot_id].empty? errors << "secret is required" if config[:secret].nil? || config[:secret].empty? errors end