Class: WeChat::Bot::Client
- Inherits:
-
Object
- Object
- WeChat::Bot::Client
- Defined in:
- lib/wechat/bot/client.rb
Overview
微信 API 类
Instance Method Summary collapse
-
#alive? ⇒ Boolean
获取是否在线(存活).
-
#contacts ⇒ Hash
获取所有联系人列表.
-
#create_group(*users) ⇒ Hash<Object, Object>
创建群组.
-
#download_image(message_id) ⇒ TempFile
下载图片.
-
#initialize(bot) ⇒ Client
constructor
A new instance of Client.
-
#logged? ⇒ Boolean
获取登录状态.
-
#login ⇒ Object
微信登录.
-
#login_loading ⇒ Object
微信登录后初始化工作.
-
#login_status(uuid) ⇒ Array
处理微信登录.
-
#logout ⇒ void
登出.
-
#qr_uuid ⇒ String
获取生成二维码的唯一识别 ID.
-
#send(type, username, content) ⇒ Hash<Object,Object>
消息发送.
-
#send_emoticon(username, emoticon_id) ⇒ Hash<Object,Object>
发送表情.
-
#send_text(username, text) ⇒ Hash<Object,Object>
发送消息.
-
#show_qr_code(uuid) ⇒ Object
获取二维码图片.
-
#start_runloop_thread ⇒ Object
Runloop 监听.
-
#store_login_data(redirect_url) ⇒ Object
保存登录返回的数据信息.
-
#sync_check ⇒ Hash
检查微信状态.
-
#sync_messages ⇒ void
获取微信消息数据.
-
#update_notice_status ⇒ Object
更新通知状态(关闭手机提醒通知).
Constructor Details
#initialize(bot) ⇒ Client
Returns a new instance of Client.
8 9 10 11 |
# File 'lib/wechat/bot/client.rb', line 8 def initialize(bot) @bot = bot clone! end |
Instance Method Details
#alive? ⇒ Boolean
获取是否在线(存活)
501 502 503 |
# File 'lib/wechat/bot/client.rb', line 501 def alive? @is_alive end |
#contacts ⇒ Hash
获取所有联系人列表
好友、群组、订阅号、公众号和特殊号
315 316 317 318 319 320 321 322 323 324 325 326 327 |
# File 'lib/wechat/bot/client.rb', line 315 def contacts query = { "r" => , "pass_ticket" => store(:pass_ticket), "skey" => store(:skey) } url = "#{store(:index_url)}/webwxgetcontact?#{URI.encode_www_form(query)}" r = @session.post(url, json: {}) data = r.parse(:json) @bot.contact_list.batch_sync(data["MemberList"]) end |
#create_group(*users) ⇒ Hash<Object, Object>
创建群组
462 463 464 465 466 467 468 469 470 471 472 |
# File 'lib/wechat/bot/client.rb', line 462 def create_group(*users) url = "#{store(:index_url)}/webwxcreatechatroom?r=#{}" params = params_base_request.merge({ "Topic" => "", "MemberCount" => users.size, "MemberList" => users.map { |u| { "UserName" => u } } }) r = @session.post(url, json: params) r.parse(:json) end |
#download_image(message_id) ⇒ TempFile
下载图片
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 |
# File 'lib/wechat/bot/client.rb', line 437 def download_image() url = "#{store(:index_url)}/webwxgetmsgimg" params = { "msgid" => , "skey" => store(:skey) } r = @session.get(url, params: params) # body = r.body # FIXME: 不知道什么原因,下载的是空字节 # 返回的 headers 是 {"Connection"=>"close", "Content-Length"=>"0"} temp_file = Tempfile.new(["emoticon", ".gif"]) while data = r.readpartial temp_file.write data end temp_file.close temp_file end |
#logged? ⇒ Boolean
获取登录状态
494 495 496 |
# File 'lib/wechat/bot/client.rb', line 494 def logged? @is_logged end |
#login ⇒ Object
微信登录
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/wechat/bot/client.rb', line 14 def login return @bot.logger.info("你已经登录") if logged? check_count = 0 until logged? check_count += 1 @bot.logger.debug "尝试登录 (#{check_count})..." uuid = qr_uuid until uuid @bot.logger.info "重新尝试获取登录二维码 ..." sleep 1 end show_qr_code(uuid) until logged? status, status_data = login_status(uuid) case status when :logged @is_logged = true store_login_data(status_data["redirect_uri"]) break when :scaned @bot.logger.info "请在手机微信确认登录 ..." when :timeout @bot.logger.info "扫描超时,重新获取登录二维码 ..." break end end break if logged? end @bot.logger.info "等待加载登录后所需资源 ..." login_loading update_notice_status @bot.logger.info "用户 [#{@bot.profile.nickname}] 登录成功!" start_runloop_thread end |
#login_loading ⇒ Object
微信登录后初始化工作
掉线后 300 秒可以重新使用此 api 登录获取的联系人和群ID保持不变
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/wechat/bot/client.rb', line 197 def login_loading url = "#{store(:index_url)}/webwxinit?r=#{}" r = @session.post(url, json: params_base_request) data = r.parse(:json) store( sync_key: data["SyncKey"], invite_start_count: data["InviteStartCount"].to_i, ) # 保存当前用户信息和最近聊天列表 @bot.profile.parse(data["User"]) @bot.contact_list.batch_sync(data["ContactList"]) r end |
#login_status(uuid) ⇒ Array
处理微信登录
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 |
# File 'lib/wechat/bot/client.rb', line 143 def login_status(uuid) = params = { "loginicon" => "true", "uuid" => uuid, "tip" => 0, "r" => .to_i / 1579, "_" => , } r = @session.get(File.join(@bot.config.auth_url, "cgi-bin/mmwebwx-bin/login"), params: params) data = r.parse(:js) status = case data["code"] when 200 then :logged when 201 then :scaned when 408 then :waiting else :timeout end [status, data] end |
#logout ⇒ void
This method returns an undefined value.
登出
477 478 479 480 481 482 483 484 485 486 487 488 489 |
# File 'lib/wechat/bot/client.rb', line 477 def logout url = "#{store(:index_url)}/webwxlogout" params = { "redirect" => 1, "type" => 1, "skey" => store(:skey) } @session.get(url, params: params) @bot.logger.info "用户 [#{@bot.profile.nickname}] 登出成功!" clone! end |
#qr_uuid ⇒ String
获取生成二维码的唯一识别 ID
98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/wechat/bot/client.rb', line 98 def qr_uuid params = { "appid" => @bot.config.app_id, "fun" => "new", "lang" => "zh_CN", "_" => , } @bot.logger.info "获取登录唯一标识 ..." r = @session.get(File.join(@bot.config.auth_url, "jslogin") , params: params) data = r.parse(:js) return data["uuid"] if data["code"] == 200 end |
#send(type, username, content) ⇒ Hash<Object,Object>
消息发送
338 339 340 341 342 343 344 345 |
# File 'lib/wechat/bot/client.rb', line 338 def send(type, username, content) case type when :emoticon send_emoticon(username, content) else send_text(username, content) end end |
#send_emoticon(username, emoticon_id) ⇒ Hash<Object,Object>
发送表情
支持微信表情和自定义表情
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 |
# File 'lib/wechat/bot/client.rb', line 407 def send_emoticon(username, emoticon_id) query = { 'fun' => 'sys', 'pass_ticket' => store(:pass_ticket), 'lang' => 'zh_CN' } url = "#{store(:index_url)}/webwxsendemoticon?#{URI.encode_www_form(query)}" params = params_base_request.merge({ "Scene" => 0, "Msg" => { "Type" => 47, 'EmojiFlag' => 2, "FromUserName" => @bot.profile.username, "ToUserName" => username, "LocalID" => , "ClientMsgId" => , }, }) emoticon_key = emoticon_id.include?("@") ? "MediaId" : "EMoticonMd5" params["Msg"][emoticon_key] = emoticon_id r = @session.post(url, json: params) r.parse(:json) end |
#send_text(username, text) ⇒ Hash<Object,Object>
发送消息
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 |
# File 'lib/wechat/bot/client.rb', line 352 def send_text(username, text) url = "#{store(:index_url)}/webwxsendmsg" params = params_base_request.merge({ "Scene" => 0, "Msg" => { "Type" => 1, "FromUserName" => @bot.profile.username, "ToUserName" => username, "Content" => text, "LocalID" => , "ClientMsgId" => , }, }) r = @session.post(url, json: params) r.parse(:json) end |
#show_qr_code(uuid) ⇒ Object
获取二维码图片
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/wechat/bot/client.rb', line 114 def show_qr_code(uuid) @bot.logger.info "获取登录用扫描二维码 ... " url = File.join(@bot.config.auth_url, "l", uuid) qrcode = RQRCode::QRCode.new(url) # image = qrcode.as_png( # resize_gte_to: false, # resize_exactly_to: false, # fill: "white", # color: "black", # size: 120, # border_modules: 4, # module_px_size: 6, # ) # IO.write(QR_FILENAME, image.to_s) svg = qrcode.as_ansi( light: "\033[47m", dark: "\033[40m", fill_character: " ", quiet_zone_size: 2 ) puts svg end |
#start_runloop_thread ⇒ Object
Runloop 监听
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 |
# File 'lib/wechat/bot/client.rb', line 58 def start_runloop_thread @is_alive = true retry_count = 0 Thread.new do while alive? begin status = sync_check if status[:retcode] == "0" if status[:selector].nil? @is_alive = false elsif status[:selector] != "0" end elsif status[:retcode] == "1100" @bot.logger.info("账户在手机上进行登出操作") @is_alive = false break elsif [ "1101", "1102" ].include?(status[:retcode]) @bot.logger.info("账户在手机上进行登出或在其他地方进行登录操作操作") @is_alive = false break end retry_count = 0 rescue Exception => e retry_count += 1 @bot.logger.fatal(e) end sleep 1 end logout end end |
#store_login_data(redirect_url) ⇒ Object
保存登录返回的数据信息
redirect_uri 有效时间是从扫码成功后算起大概是 300 秒,在此期间可以重新登录,但获取的联系人和群 ID 会改变
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/wechat/bot/client.rb', line 169 def store_login_data(redirect_url) host = URI.parse(redirect_url).host r = @session.get(redirect_url) data = r.parse(:xml) store( skey: data["error"]["skey"], sid: data["error"]["wxsid"], uin: data["error"]["wxuin"], device_id: "e#{rand.to_s[2..17]}", pass_ticket: data["error"]["pass_ticket"], ) @bot.config.servers.each do |server| if host == server[:index] update_servers(server) break end end raise RuntimeError, "没有匹配到对于的微信服务器: #{host}" unless store(:index_url) r end |
#sync_check ⇒ Hash
检查微信状态
状态会包含是否有新消息、用户状态变化等
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/wechat/bot/client.rb', line 244 def sync_check url = "#{store(:push_url)}/synccheck" params = { "r" => , "skey" => store(:skey), "sid" => store(:sid), "uin" => store(:uin), "deviceid" => store(:device_id), "synckey" => params_sync_key, "_" => , } r = @session.get(url, params: params, timeout: [10, 60]) data = r.parse(:js)["synccheck"] # raise RuntimeException "微信数据同步异常,原始返回内容:#{r.to_s}" if data.nil? @bot.logger.debug "HeartBeat: retcode/selector #{data[:retcode]}/#{data[:selector]}" data end |
#sync_messages ⇒ void
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 |
# File 'lib/wechat/bot/client.rb', line 269 def query = { "sid" => store(:sid), "skey" => store(:skey), "pass_ticket" => store(:pass_ticket) } url = "#{store(:index_url)}/webwxsync?#{URI.encode_www_form(query)}" params = params_base_request.merge({ "SyncKey" => store(:sync_key), "rr" => "-#{}" }) r = @session.post(url, json: params, timeout: [10, 60]) data = r.parse(:json) @bot.logger.debug "Message: A/M/D/CM #{data["AddMsgCount"]}/#{data["ModContactCount"]}/#{data["DelContactCount"]}/#{data["ModChatRoomMemberCount"]}" store(:sync_key, data["SyncCheckKey"]) # 更新已存在的群聊信息、增加新的群聊信息 @bot.contact_list.batch_sync(data["ModContactList"]) if data["ModContactCount"] > 0 if data["AddMsgCount"] > 0 data["AddMsgList"].each do |msg| next if msg["FromUserName"] == @bot.profile.username = Message.new(msg, @bot) events = [:message] events.push(:text) if .kind == Message::Kind::Text events.push(:group) if msg["ToUserName"].include?("@@") events.each do |event, *args| @bot.handlers.dispatch(event, , args) end end end data end |
#update_notice_status ⇒ Object
更新通知状态(关闭手机提醒通知)
需要解密参数 Code 的值的作用,目前都用的是 3
217 218 219 220 221 222 223 224 225 226 227 228 |
# File 'lib/wechat/bot/client.rb', line 217 def update_notice_status url = "#{store(:index_url)}/webwxstatusnotify?lang=zh_CN&pass_ticket=#{store(:pass_ticket)}" params = params_base_request.merge({ "Code" => 3, "FromUserName" => @bot.profile.username, "ToUserName" => @bot.profile.username, "ClientMsgId" => }) r = @session.post(url, json: params) r end |