Module: Clacky::Channel::Adapters::Wecom::MediaDownloader
- Defined in:
- lib/clacky/server/channel/adapters/wecom/media_downloader.rb
Overview
Downloads and decrypts media files from WeCom message URLs.
WeCom long-connection bot messages include a per-resource aeskey. Encryption: AES-256-CBC, key=base64_decode(aeskey), iv=key, no PKCS7 padding. However some files are sent unencrypted — detect via magic bytes before decrypting.
Constant Summary collapse
- HTTP_TIMEOUT =
30- MAGIC_SIGNATURES =
Check if data looks like a plain (unencrypted) file via magic bytes.
[ "\xFF\xD8\xFF", # JPEG "\x89PNG\r\n\x1a\n", # PNG "GIF8", # GIF "%PDF", # PDF "PK\x03\x04", # ZIP (docx/xlsx) "\xD0\xCF\x11\xE0", # OLE2 (doc/xls) "RIFF", # WAV/WebP ].map { |s| s.b }.freeze
Class Method Summary collapse
-
.detect_mime(data) ⇒ String
Detect MIME type from magic bytes.
-
.download(url, aeskey) ⇒ Hash
Download and decrypt a WeCom media resource.
Class Method Details
.detect_mime(data) ⇒ String
Detect MIME type from magic bytes
99 100 101 102 103 104 105 106 107 108 |
# File 'lib/clacky/server/channel/adapters/wecom/media_downloader.rb', line 99 def self.detect_mime(data) return "application/octet-stream" if data.nil? || data.empty? d = data.b return "image/jpeg" if d.start_with?("\xFF\xD8\xFF".b) return "image/png" if d.start_with?("\x89PNG\r\n\x1a\n".b) return "image/gif" if d.start_with?("GIF8".b) return "image/webp" if d.start_with?("RIFF".b) && d.byteslice(8, 4) == "WEBP".b return "image/bmp" if d.start_with?("BM".b) "image/jpeg" # fallback for unknown image formats end |
.download(url, aeskey) ⇒ Hash
Download and decrypt a WeCom media resource.
23 24 25 26 27 28 29 30 31 32 33 34 |
# File 'lib/clacky/server/channel/adapters/wecom/media_downloader.rb', line 23 def self.download(url, aeskey) response = fetch(url) body = response.body.dup.force_encoding("BINARY") if aeskey && !aeskey.empty? && !looks_plain?(body) body = decrypt(body, aeskey) end content_type = detect_mime(body) filename = extract_filename(response.headers["content-disposition"].to_s) { body: body, content_type: content_type, filename: filename } end |