Class: WeChat::Bot::Client
- Inherits:
-
Object
- Object
- WeChat::Bot::Client
- Defined in:
- lib/wechat/bot/client.rb
Overview
微信 API 类
Instance Method Summary collapse
- #_send ⇒ Object
-
#add_friend(username, status = 2, verify_content = '') ⇒ Object
添加好友.
-
#add_group_member(username, *users) ⇒ Object
群组添加.
-
#alive? ⇒ Boolean
获取是否在线(存活).
-
#contacts ⇒ Hash
获取所有联系人列表.
-
#create_group(*users) ⇒ Hash<Object, Object>
创建群组.
-
#delete_group_member(username, *users) ⇒ Object
删除群组成员.
-
#download_image(message_id) ⇒ TempFile
下载图片.
-
#initialize(bot) ⇒ Client
constructor
A new instance of Client.
-
#invite_group_member(username, *users) ⇒ Object
群组邀请.
-
#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_image(username, **opts) ⇒ Boolean
发送图片.
-
#send_text(username, text) ⇒ Hash<Object,Object>
发送消息.
-
#set_group_name(username, name) ⇒ Object
修改群组名称.
-
#show_qr_code(uuid) ⇒ Object
获取二维码图片.
-
#start_runloop_thread ⇒ Object
Runloop 监听.
-
#store_login_data(redirect_url) ⇒ Object
保存登录返回的数据信息.
-
#sync_check ⇒ Hash
检查微信状态.
-
#sync_messages ⇒ void
获取微信消息数据.
-
#update_group(username, fun, update_key, update_value) ⇒ Object
更新群组.
-
#update_notice_status ⇒ Object
更新通知状态(关闭手机提醒通知).
-
#upload_image(username, file) ⇒ Object
FIXME: 上传图片出问题,未能解决.
Constructor Details
#initialize(bot) ⇒ Client
Returns a new instance of Client.
10 11 12 13 |
# File 'lib/wechat/bot/client.rb', line 10 def initialize(bot) @bot = bot clone! end |
Instance Method Details
#_send ⇒ Object
327 |
# File 'lib/wechat/bot/client.rb', line 327 alias_method :_send, :send |
#add_friend(username, status = 2, verify_content = '') ⇒ Object
添加好友
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 |
# File 'lib/wechat/bot/client.rb', line 574 def add_friend(username, status = 2, verify_content='') url = api_url('webwxverifyuser', {r: , pass_ticket: store(:pass_ticket)}) params = params_base_request.merge({ "Opcode" => status, # 3 "VerifyUserListSize" => 1, "VerifyUserList" => [{ "Value" => username, "VerifyUserTicket" => ''}], "VerifyContent" => verify_content, "SceneListCount" => 1, "SceneList" => [33], "skey" => store(:skey) }) r = @session.post(url, json: params) r.parse(:json) end |
#add_group_member(username, *users) ⇒ Object
群组添加
567 568 569 |
# File 'lib/wechat/bot/client.rb', line 567 def add_group_member(username, *users) update_group(username, 'addmember', 'AddMemberList', users.join(",")) end |
#alive? ⇒ Boolean
获取是否在线(存活)
619 620 621 |
# File 'lib/wechat/bot/client.rb', line 619 def alive? @is_alive end |
#contacts ⇒ Hash
获取所有联系人列表
好友、群组、订阅号、公众号和特殊号
314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/wechat/bot/client.rb', line 314 def contacts url = api_url('webwxgetcontact', { "r" => , "pass_ticket" => store(:pass_ticket), "skey" => store(:skey) }) r = @session.post(url, json: {}) data = r.parse(:json) @bot.contact_list.batch_sync(data["MemberList"]) end |
#create_group(*users) ⇒ Hash<Object, Object>
创建群组
525 526 527 528 529 530 531 532 533 534 535 |
# File 'lib/wechat/bot/client.rb', line 525 def create_group(*users) url = api_url('webwxcreatechatroom', r: , pass_ticket: store(:pass_ticket)) 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 |
#delete_group_member(username, *users) ⇒ Object
删除群组成员
557 558 559 |
# File 'lib/wechat/bot/client.rb', line 557 def delete_group_member(username, *users) update_group(username, 'delmember', 'DelMemberList', users.join(",")) end |
#download_image(message_id) ⇒ TempFile
下载图片
500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 |
# File 'lib/wechat/bot/client.rb', line 500 def download_image() url = api_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 |
#invite_group_member(username, *users) ⇒ Object
群组邀请
562 563 564 |
# File 'lib/wechat/bot/client.rb', line 562 def invite_group_member(username, *users) update_group(username, 'invitemember', 'InviteMemberList', users.join(",")) end |
#logged? ⇒ Boolean
获取登录状态
612 613 614 |
# File 'lib/wechat/bot/client.rb', line 612 def logged? @is_logged end |
#login ⇒ Object
微信登录
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 16 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}] 登录成功!" 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 = api_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.
登出
595 596 597 598 599 600 601 602 603 604 605 606 607 |
# File 'lib/wechat/bot/client.rb', line 595 def logout url = api_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 346 347 |
# File 'lib/wechat/bot/client.rb', line 338 def send(type, username, content) case type when :emoticon send_emoticon(username, content) when :image send_image(username, content: content) else send_text(username, content) end end |
#send_emoticon(username, emoticon_id) ⇒ Hash<Object,Object>
发送表情
支持微信表情和自定义表情
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 |
# File 'lib/wechat/bot/client.rb', line 471 def send_emoticon(username, emoticon_id) url = api_url('webwxsendemoticon', { 'fun' => 'sys', 'pass_ticket' => store(:pass_ticket), 'lang' => 'zh_CN' }) 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_image(username, **opts) ⇒ Boolean
发送图片
431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 |
# File 'lib/wechat/bot/client.rb', line 431 def send_image(username, **opts) # if media_id.nil? # media_id = upload_file(image) # end if opts[:media_id] conf = {"MediaId" => opts[:media_id], "Content" => ""} elsif opts[:image] media_id = upload_image(username, opts[:image]) conf = {"MediaId" => media_id, "Content" => ""} elsif opts[:content] conf = {"MediaId" => "", "Content" => opts[:content]} else raise RuntimeException, "发送图片参数错误,须提供media_id或content" end url = "#{store(:index_url)}/webwxsendmsgimg?fun=async&f=json" params = params_base_request.merge({ "Scene" => 0, "Msg" => { "Type" => 3, "FromUserName" => @bot.profile.username, "ToUserName" => username, "LocalID" => , "ClientMsgId" => , }.merge(conf) }) r = @session.post(url, json: params) r.parse(:json) end |
#send_text(username, text) ⇒ Hash<Object,Object>
发送消息
354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/wechat/bot/client.rb', line 354 def send_text(username, text) url = api_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 |
#set_group_name(username, name) ⇒ Object
修改群组名称
552 553 554 |
# File 'lib/wechat/bot/client.rb', line 552 def set_group_name(username, name) update_group(username, 'modtopic', 'NewTopic', name) 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.nil? ? "exception" : [data[:retcode], data[:selector]].join('/')}" 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 |
# File 'lib/wechat/bot/client.rb', line 269 def url = api_url('webwxsync', { "sid" => store(:sid), "skey" => store(:skey), "pass_ticket" => store(:pass_ticket) }) 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_group(username, fun, update_key, update_value) ⇒ Object
更新群组
541 542 543 544 545 546 547 548 549 |
# File 'lib/wechat/bot/client.rb', line 541 def update_group(username, fun, update_key, update_value) url = api_url('webwxupdatechatroom', {fun: fun, pass_ticket: store(:pass_ticket)}) params = params_base_request.merge({ "ChatRoomName" => username, update_key => update_value }) r = @session.post(url, json: params) r.parse(:json) 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 = api_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 |
#upload_image(username, file) ⇒ Object
FIXME: 上传图片出问题,未能解决
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 |
# File 'lib/wechat/bot/client.rb', line 373 def upload_image(username, file) url = "#{store(:file_url)}/webwxuploadmedia?f=json" filename = File.basename(file.path) content_type = {'png'=>'image/png', 'jpg'=>'image/jpeg', 'jpeg'=>'image/jpeg'}[filename.split('.').last.downcase] || 'application/octet-stream' md5 = Digest::MD5.file(file.path).hexdigest headers = { 'Host' => 'file.wx.qq.com', 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:42.0) Gecko/20100101 Firefox/42.0', 'Accept' => '*/*', 'Accept-Language' => 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', 'Accept-Encoding' => 'gzip, deflate, br', 'Referer' => 'https://wx.qq.com/', 'Origin' => 'https://wx.qq.com', 'Connection' => 'Keep-Alive' } @media_cnt = 1 + (@media_cnt || -1) params = { 'id' => "WU_FILE_#{@media_cnt}", 'name' => filename, 'type' => content_type, 'lastModifiedDate' => 'Tue Sep 09 2014 17:47:23 GMT+0800 (CST)', 'size' => file.size, 'mediatype' => 'pic', # pic/video/doc 'uploadmediarequest' => JSON.generate( params_base_request.merge({ 'UploadType' => 2, 'ClientMediaId' => , 'TotalLen' => file.size, 'StartPos' => 0, 'DataLen' => file.size, 'MediaType' => 4, 'FromUserName' => @bot.profile.username, 'ToUserName' => username, 'FileMd5' => md5 }) ), 'webwx_data_ticket' => @session.('webwx_data_ticket'), 'pass_ticket' => store(:pass_ticket), 'filename' => ::HTTP::FormData::File.new(file, content_type: content_type) } r = @session.post(url, form: params, headers: headers) # @bot.logger.info "Response: #{r.inspect}" r.parse(:json) end |