wechat-bot2 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +13 -0
- data/.gitignore +16 -0
- data/.rspec +3 -0
- data/.rubocop.yml +17 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +3 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +23 -0
- data/Rakefile +41 -0
- data/lib/wechat/bot/cached_list.rb +15 -0
- data/lib/wechat/bot/callback.rb +15 -0
- data/lib/wechat/bot/client.rb +574 -0
- data/lib/wechat/bot/configuration.rb +70 -0
- data/lib/wechat/bot/contact.rb +214 -0
- data/lib/wechat/bot/contact_list.rb +56 -0
- data/lib/wechat/bot/core.rb +138 -0
- data/lib/wechat/bot/exception.rb +4 -0
- data/lib/wechat/bot/ext/wechat_emoji_string.rb +16 -0
- data/lib/wechat/bot/handler.rb +92 -0
- data/lib/wechat/bot/handler_list.rb +92 -0
- data/lib/wechat/bot/helper.rb +2 -0
- data/lib/wechat/bot/http/adapter/js.rb +33 -0
- data/lib/wechat/bot/http/adapter/xml.rb +29 -0
- data/lib/wechat/bot/http/session.rb +117 -0
- data/lib/wechat/bot/logger.rb +102 -0
- data/lib/wechat/bot/message.rb +282 -0
- data/lib/wechat/bot/message_data/share_card.rb +46 -0
- data/lib/wechat/bot/pattern.rb +63 -0
- data/lib/wechat/bot/version.rb +5 -0
- data/lib/wechat/bot.rb +12 -0
- data/lib/wechat-bot.rb +3 -0
- data/lib/wechat_bot.rb +3 -0
- data/wechat-bot.gemspec +40 -0
- metadata +220 -0
@@ -0,0 +1,214 @@
|
|
1
|
+
module WeChat::Bot
|
2
|
+
# 微信联系人
|
3
|
+
#
|
4
|
+
# 可以是用户、公众号、群组等
|
5
|
+
class Contact
|
6
|
+
# 联系人分类
|
7
|
+
module Kind
|
8
|
+
User = :user
|
9
|
+
Group = :group
|
10
|
+
MP = :mp
|
11
|
+
Special = :special
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.parse(obj, bot)
|
15
|
+
self.new(bot).parse(obj)
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(bot)
|
19
|
+
@bot = bot
|
20
|
+
@data = {}
|
21
|
+
end
|
22
|
+
|
23
|
+
# 用户唯一 ID
|
24
|
+
def username
|
25
|
+
attr(:username)
|
26
|
+
end
|
27
|
+
|
28
|
+
# 用户昵称
|
29
|
+
def nickname
|
30
|
+
attr(:nickname)
|
31
|
+
end
|
32
|
+
|
33
|
+
# 备注名
|
34
|
+
def remarkname
|
35
|
+
attr(:remarkname)
|
36
|
+
end
|
37
|
+
|
38
|
+
# 群聊显示名
|
39
|
+
def displayname
|
40
|
+
attr(:displayname)
|
41
|
+
end
|
42
|
+
|
43
|
+
# 性别
|
44
|
+
def sex
|
45
|
+
attr(:sex)
|
46
|
+
end
|
47
|
+
|
48
|
+
# 个人签名
|
49
|
+
def signature
|
50
|
+
attr(:signature)
|
51
|
+
end
|
52
|
+
|
53
|
+
# 用户类型
|
54
|
+
def kind
|
55
|
+
attr(:kind)
|
56
|
+
end
|
57
|
+
|
58
|
+
# 省份
|
59
|
+
def province
|
60
|
+
attr(:province)
|
61
|
+
end
|
62
|
+
|
63
|
+
# 城市
|
64
|
+
def city
|
65
|
+
attr(:city)
|
66
|
+
end
|
67
|
+
|
68
|
+
# 是否特殊账户
|
69
|
+
def special?
|
70
|
+
kind == Kind::Special
|
71
|
+
end
|
72
|
+
|
73
|
+
# 是否群聊
|
74
|
+
def group?
|
75
|
+
kind == Kind::Group
|
76
|
+
end
|
77
|
+
|
78
|
+
# 是否公众号
|
79
|
+
def mp?
|
80
|
+
kind == Kind::MP
|
81
|
+
end
|
82
|
+
|
83
|
+
# 群组成员列表
|
84
|
+
#
|
85
|
+
# 只有群组才有内容,根据 {#kind} 或 {#group?} 来判断
|
86
|
+
# 不是群组类型的返回空数组
|
87
|
+
#
|
88
|
+
# @return [Hash]
|
89
|
+
def members
|
90
|
+
attr(:members)
|
91
|
+
end
|
92
|
+
|
93
|
+
# 联系人解析
|
94
|
+
#
|
95
|
+
# @param [Hash<Object, Object>] raw
|
96
|
+
# @return [Contact]
|
97
|
+
def parse(raw, update = false)
|
98
|
+
@raw = raw
|
99
|
+
|
100
|
+
parse_kind
|
101
|
+
parse_members
|
102
|
+
|
103
|
+
@raw.each do |key, value|
|
104
|
+
if attribute = mapping[key]
|
105
|
+
next if value.to_s.empty? && update
|
106
|
+
|
107
|
+
sync(attribute, value)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
self
|
112
|
+
end
|
113
|
+
|
114
|
+
def update(raw)
|
115
|
+
@raw = raw
|
116
|
+
parse(@raw, true)
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_s
|
120
|
+
"#<#{self.class}:#{object_id.to_s(16)} username='#{username}' nickname='#{nickname}' kind='#{kind}'>"
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
# 更新或新写入变量值
|
126
|
+
#
|
127
|
+
# @param [Symbol] attribute
|
128
|
+
# @param [String, Integer, Hash] value
|
129
|
+
# @param [Boolean] data
|
130
|
+
# @return [void]
|
131
|
+
def sync(attribute, value, data = false)
|
132
|
+
value = value.convert_emoji if attribute.to_sym == :nickname
|
133
|
+
|
134
|
+
# 满足群组类型且 nickname 为空时补充一个默认的群组名(参考微信 App 设计)
|
135
|
+
if attribute.to_sym == :nickname && value.to_s.empty? && @kind == Kind::Group
|
136
|
+
value = members.map { |m| m.nickname }.join("、")
|
137
|
+
end
|
138
|
+
|
139
|
+
if data
|
140
|
+
@data[attribute.to_sym] = value
|
141
|
+
else
|
142
|
+
instance_variable_set("@#{attribute}", value)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# 获取属性
|
147
|
+
#
|
148
|
+
# @param [Symbol] attribute
|
149
|
+
# @param [Boolean] data 默认 false
|
150
|
+
# @return [String, Integer, Hash]
|
151
|
+
def attr(attribute, data = false)
|
152
|
+
if data
|
153
|
+
@data[attribute.to_sym]
|
154
|
+
else
|
155
|
+
instance_variable_get("@#{attribute}")
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# 解析联系人类型
|
160
|
+
#
|
161
|
+
# 详见 {Contact::Kind} 成员变量
|
162
|
+
# @return [void]
|
163
|
+
def parse_kind
|
164
|
+
kind = if @bot.config.special_users.include?(@raw["UserName"])
|
165
|
+
# 特殊账户
|
166
|
+
Kind::Special
|
167
|
+
elsif @raw["UserName"].include?("@@")
|
168
|
+
# 群聊
|
169
|
+
Kind::Group
|
170
|
+
elsif @raw["VerifyFlag"] && (@raw["VerifyFlag"] & 8) != 0
|
171
|
+
# 公众号
|
172
|
+
Kind::MP
|
173
|
+
else
|
174
|
+
# 普通用户
|
175
|
+
Kind::User
|
176
|
+
end
|
177
|
+
|
178
|
+
sync(:kind, kind)
|
179
|
+
end
|
180
|
+
|
181
|
+
# 解析群组成员列表
|
182
|
+
#
|
183
|
+
# 只有群组才有内容,根据 {#kind} 或 {#group?} 来判断
|
184
|
+
#
|
185
|
+
# @return [void]
|
186
|
+
def parse_members
|
187
|
+
members = []
|
188
|
+
|
189
|
+
if @raw["MemberList"]
|
190
|
+
@raw["MemberList"].each do |m|
|
191
|
+
members.push(Contact.parse(m, @bot))
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
sync(:members, members)
|
196
|
+
end
|
197
|
+
|
198
|
+
# 字段映射
|
199
|
+
#
|
200
|
+
# @return [Hash<String, String>]
|
201
|
+
def mapping
|
202
|
+
{
|
203
|
+
"NickName" => "nickname",
|
204
|
+
"UserName" => "username",
|
205
|
+
"RemarkName" => "remarkname",
|
206
|
+
"DisplayName" => "displayname",
|
207
|
+
"Signature" => "signature",
|
208
|
+
"Sex" => "sex",
|
209
|
+
"Province" => "province",
|
210
|
+
"City" => 'city'
|
211
|
+
}
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module WeChat::Bot
|
2
|
+
# 微信联系人列表
|
3
|
+
class ContactList < CachedList
|
4
|
+
# 批量同步联系人数据
|
5
|
+
#
|
6
|
+
# 更多查看 {#sync} 接口
|
7
|
+
# @param [Array<Hash>] list 联系人数组
|
8
|
+
# @return [ContactList]
|
9
|
+
def batch_sync(list)
|
10
|
+
list.each do |item|
|
11
|
+
sync(item)
|
12
|
+
end
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def size
|
18
|
+
@cache.size
|
19
|
+
end
|
20
|
+
|
21
|
+
# 创建用户或更新用户数据
|
22
|
+
#
|
23
|
+
# @param [Hash] data 微信接口返回的单个用户数据
|
24
|
+
# @return [Contact]
|
25
|
+
def sync(data)
|
26
|
+
@mutex.synchronize do
|
27
|
+
contact = Contact.parse(data, @bot)
|
28
|
+
if @cache[contact.username]
|
29
|
+
@cache[contact.username].update(data)
|
30
|
+
else
|
31
|
+
@cache[contact.username] = contact
|
32
|
+
end
|
33
|
+
|
34
|
+
contact
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# 查找用户
|
39
|
+
#
|
40
|
+
# @param [Hash] args 接受两个参数:
|
41
|
+
# - :nickname 昵称
|
42
|
+
# - :username 用户ID
|
43
|
+
# @return [Contact]
|
44
|
+
def find(**args)
|
45
|
+
@mutex.synchronize do
|
46
|
+
return @cache[args[:username]] if args[:username]
|
47
|
+
|
48
|
+
if args[:nickname]
|
49
|
+
@cache.each do |_, contact|
|
50
|
+
return contact if contact.nickname == args[:nickname]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require "wechat/bot/configuration"
|
2
|
+
require "wechat/bot/cached_list"
|
3
|
+
require "wechat/bot/contact_list"
|
4
|
+
require "wechat/bot/contact"
|
5
|
+
require "wechat/bot/logger"
|
6
|
+
|
7
|
+
require "wechat/bot/handler_list"
|
8
|
+
require "wechat/bot/handler"
|
9
|
+
require "wechat/bot/message"
|
10
|
+
require "wechat/bot/pattern"
|
11
|
+
require "wechat/bot/callback"
|
12
|
+
|
13
|
+
require "wechat/bot/http/adapter/js"
|
14
|
+
require "wechat/bot/http/adapter/xml"
|
15
|
+
require "wechat/bot/http/session"
|
16
|
+
|
17
|
+
require "logger"
|
18
|
+
|
19
|
+
module WeChat::Bot
|
20
|
+
# 机器人的核心类
|
21
|
+
class Core
|
22
|
+
# 微信 API 客户端
|
23
|
+
#
|
24
|
+
# @return [Client]
|
25
|
+
attr_reader :client
|
26
|
+
|
27
|
+
# 当前登录用户信息
|
28
|
+
#
|
29
|
+
# @return [Contact]
|
30
|
+
attr_reader :profile
|
31
|
+
|
32
|
+
# 联系人列表
|
33
|
+
#
|
34
|
+
# @return [ContactList]
|
35
|
+
attr_reader :contact_list
|
36
|
+
|
37
|
+
# @return [Logger]
|
38
|
+
attr_accessor :logger
|
39
|
+
|
40
|
+
# @return [HandlerList]
|
41
|
+
attr_reader :handlers
|
42
|
+
|
43
|
+
# @return [Configuration]
|
44
|
+
attr_reader :config
|
45
|
+
|
46
|
+
# @return [Callback]
|
47
|
+
# @api private
|
48
|
+
attr_reader :callback
|
49
|
+
|
50
|
+
def initialize(&block)
|
51
|
+
# defaults_logger
|
52
|
+
@logger = Logger.new(STDOUT, self)
|
53
|
+
@config = Configuration.new
|
54
|
+
@handlers = HandlerList.new
|
55
|
+
@callback = Callback.new(self)
|
56
|
+
|
57
|
+
@client = Client.new(self)
|
58
|
+
@profile = Contact.new(self)
|
59
|
+
@contact_list = ContactList.new(self)
|
60
|
+
|
61
|
+
instance_eval(&block) if block_given?
|
62
|
+
end
|
63
|
+
|
64
|
+
# 消息触发器
|
65
|
+
#
|
66
|
+
# @param [String, Symbol, Integer] event
|
67
|
+
# @param [Regexp, Pattern, String] regexp
|
68
|
+
# @param [Array<Object>] args
|
69
|
+
# @yieldparam [Array<String>]
|
70
|
+
# @return [Handler]
|
71
|
+
def on(event, regexp = //, *args, &block)
|
72
|
+
event = event.to_s.to_sym
|
73
|
+
|
74
|
+
pattern = case regexp
|
75
|
+
when Pattern
|
76
|
+
regexp
|
77
|
+
when Regexp
|
78
|
+
Pattern.new(nil, regexp, nil)
|
79
|
+
else
|
80
|
+
if event == :ctcp
|
81
|
+
Pattern.generate(:ctcp, regexp)
|
82
|
+
else
|
83
|
+
Pattern.new(/^/, /#{Regexp.escape(regexp.to_s)}/, /$/)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
handler = Handler.new(self, event, pattern, {args: args, execute_in_callback: true}, &block)
|
88
|
+
@handlers.register(handler)
|
89
|
+
|
90
|
+
handler
|
91
|
+
end
|
92
|
+
|
93
|
+
# 用于设置 WeChat::Bot 的配置
|
94
|
+
# 默认无需配置,需要定制化 yield {Core#config} 进行配置
|
95
|
+
#
|
96
|
+
# @yieldparam [Struct] config
|
97
|
+
# @return [void] 没有返回值
|
98
|
+
def configure
|
99
|
+
yield @config
|
100
|
+
end
|
101
|
+
|
102
|
+
# 运行机器人
|
103
|
+
#
|
104
|
+
# @return [void]
|
105
|
+
def start
|
106
|
+
@client.login
|
107
|
+
@client.contacts
|
108
|
+
|
109
|
+
@contact_list.each do |c|
|
110
|
+
@logger.debug "Contact: #{c}"
|
111
|
+
end
|
112
|
+
|
113
|
+
while true
|
114
|
+
break unless @client.logged? || @client.alive?
|
115
|
+
sleep 1
|
116
|
+
end
|
117
|
+
rescue Interrupt => e
|
118
|
+
message = "你使用 Ctrl + C 终止了运行"
|
119
|
+
@logger.warn(message)
|
120
|
+
@client.send_text(@config.fireman, "[告警] 意外下线\n#{message}\n#{e.backtrace.join("\n")}") if @client.logged? && @client.alive?
|
121
|
+
rescue Exception => e
|
122
|
+
message = e.message
|
123
|
+
@logger.fatal(e)
|
124
|
+
@client.send_text(@config.fireman, "[告警] 意外下线\n#{message}\n#{e.backtrace.join("\n")}") if @client.logged? && @client.alive?
|
125
|
+
ensure
|
126
|
+
@client.logout if @client.logged? && @client.alive?
|
127
|
+
end
|
128
|
+
|
129
|
+
# private
|
130
|
+
|
131
|
+
# def defaults_logger
|
132
|
+
# @logger = Logger.new($stdout)
|
133
|
+
# @logger.formatter = proc do |severity, datetime, progname, msg|
|
134
|
+
# "#{severity}\t[#{datetime.strftime("%Y-%m-%d %H:%M:%S.%2N")}]: #{msg}\n"
|
135
|
+
# end
|
136
|
+
# end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module WeChat::Bot
|
2
|
+
module WeChatEmojiString
|
3
|
+
def convert_emoji
|
4
|
+
emoji_regex = /<span class="emoji emoji(\w+)"><\/span>/
|
5
|
+
if match = self.match(emoji_regex)
|
6
|
+
return self.gsub(emoji_regex, [match[1].hex].pack("U"))
|
7
|
+
end
|
8
|
+
|
9
|
+
self
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class String
|
15
|
+
include WeChat::Bot::WeChatEmojiString
|
16
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module WeChat::Bot
|
2
|
+
# Handler
|
3
|
+
class Handler
|
4
|
+
# @return [Core]
|
5
|
+
attr_reader :bot
|
6
|
+
|
7
|
+
# @return [Symbol]
|
8
|
+
attr_reader :event
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :pattern
|
12
|
+
|
13
|
+
# @return [Array]
|
14
|
+
attr_reader :args
|
15
|
+
|
16
|
+
# @return [Proc]
|
17
|
+
attr_reader :block
|
18
|
+
|
19
|
+
# @return [Symbol]
|
20
|
+
attr_reader :group
|
21
|
+
|
22
|
+
# @return [ThreadGroup]
|
23
|
+
# @api private
|
24
|
+
attr_reader :thread_group
|
25
|
+
|
26
|
+
def initialize(bot, event, pattern, options = {}, &block)
|
27
|
+
options = {
|
28
|
+
:group => nil,
|
29
|
+
:execute_in_callback => false,
|
30
|
+
:strip_colors => false,
|
31
|
+
:args => []
|
32
|
+
}.merge(options)
|
33
|
+
|
34
|
+
@bot = bot
|
35
|
+
@event = event
|
36
|
+
@pattern = pattern
|
37
|
+
@block = block
|
38
|
+
@group = options[:group]
|
39
|
+
@execute_in_callback = options[:execute_in_callback]
|
40
|
+
@args = options[:args]
|
41
|
+
|
42
|
+
@thread_group = ThreadGroup.new
|
43
|
+
end
|
44
|
+
|
45
|
+
# 执行 Handler
|
46
|
+
#
|
47
|
+
# @param [Symbol] message
|
48
|
+
# @param [String] captures
|
49
|
+
# @param [Array] arguments
|
50
|
+
# @return [Thread]
|
51
|
+
def call(message, captures, arguments)
|
52
|
+
bargs = captures + arguments
|
53
|
+
|
54
|
+
thread = Thread.new {
|
55
|
+
@bot.logger.debug "[New thread] For #{self}: #{Thread.current} -- #{@thread_group.list.size} in total."
|
56
|
+
begin
|
57
|
+
if @execute_in_callback
|
58
|
+
@bot.callback.instance_exec(message, *@args, *bargs, &@block)
|
59
|
+
else
|
60
|
+
@block.call(message, *@args, *bargs)
|
61
|
+
end
|
62
|
+
rescue => e
|
63
|
+
@bot.logger.error "[Thread error] #{e.message} -> #{e.backtrace.join("\n")}"
|
64
|
+
ensure
|
65
|
+
@bot.logger.debug "[Thread done] For #{self}: #{Thread.current} -- #{@thread_group.list.size - 1} remaining."
|
66
|
+
end
|
67
|
+
}
|
68
|
+
|
69
|
+
@thread_group.add(thread)
|
70
|
+
thread
|
71
|
+
end
|
72
|
+
|
73
|
+
# @return [void]
|
74
|
+
def stop
|
75
|
+
@bot.logger.debug "[Stopping handler] Stopping all threads of handler #{self}: #{@thread_group.list.size} threads..."
|
76
|
+
@thread_group.list.each do |thread|
|
77
|
+
Thread.new do
|
78
|
+
@bot.logger.debug "[Ending thread] Waiting 10 seconds for #{thread} to finish..."
|
79
|
+
thread.join(10)
|
80
|
+
@bot.logger.debug "[Killing thread] Killing #{thread}"
|
81
|
+
thread.kill
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# @return [String]
|
87
|
+
def to_s
|
88
|
+
# TODO maybe add the number of running threads to the output?
|
89
|
+
"#<Cinch::Handler @event=#{@event.inspect} pattern=#{@pattern.inspect}>"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module WeChat::Bot
|
2
|
+
# Handler 列表
|
3
|
+
class HandlerList
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@handlers = Hash.new {|h,k| h[k] = []}
|
8
|
+
@mutex = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# 注册 Handler
|
12
|
+
#
|
13
|
+
# @param [Handler] handler
|
14
|
+
# @return [void]
|
15
|
+
def register(handler)
|
16
|
+
@mutex.synchronize do
|
17
|
+
handler.bot.logger.debug "[on handler] Registering handler with pattern `#{handler.pattern}`, reacting on `#{handler.event}`"
|
18
|
+
@handlers[handler.event].push(handler)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# 取消注册 Handler
|
23
|
+
#
|
24
|
+
# @param [Array<Handler>] handlers
|
25
|
+
# @return [void]
|
26
|
+
def unregister(*handlers)
|
27
|
+
@mutex.synchronize do
|
28
|
+
handlers.each do |handler|
|
29
|
+
@handlers[handler.event].delete(handler)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# 分派执行 Handler
|
35
|
+
#
|
36
|
+
# @param [Symbol] event
|
37
|
+
# @param [String] message
|
38
|
+
# @param [Array<Object>] args
|
39
|
+
# @return [Array<Thread>]
|
40
|
+
def dispatch(event, message = nil, *args)
|
41
|
+
threads = []
|
42
|
+
|
43
|
+
if handlers = find(event, message)
|
44
|
+
already_run = Set.new
|
45
|
+
handlers.each do |handler|
|
46
|
+
next if already_run.include?(handler.block)
|
47
|
+
already_run.add(handler.block)
|
48
|
+
|
49
|
+
if message
|
50
|
+
captures = message.match(handler.pattern.to_r(message), event).captures
|
51
|
+
else
|
52
|
+
captures = []
|
53
|
+
end
|
54
|
+
|
55
|
+
threads.push(handler.call(message, captures, args))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
threads
|
60
|
+
end
|
61
|
+
|
62
|
+
# 查找匹配 Handler
|
63
|
+
#
|
64
|
+
# @param [Symbol] type
|
65
|
+
# @param [String] message
|
66
|
+
# @return [Hander]
|
67
|
+
def find(type, message = nil)
|
68
|
+
if handlers = @handlers[type]
|
69
|
+
if message.nil?
|
70
|
+
return handlers
|
71
|
+
end
|
72
|
+
|
73
|
+
handlers = handlers.select { |handler|
|
74
|
+
message.match(handler.pattern.to_r(message), type)
|
75
|
+
}.group_by {|handler| handler.group}
|
76
|
+
|
77
|
+
handlers.values_at(*(handlers.keys - [nil])).map(&:first) + (handlers[nil] || [])
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def each(&block)
|
82
|
+
@handlers.values.flatten.each(&block)
|
83
|
+
end
|
84
|
+
|
85
|
+
# 停止运行所有 Handler
|
86
|
+
#
|
87
|
+
# @return [void]
|
88
|
+
def stop_all
|
89
|
+
each { |h| h.stop }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "http/mime_type"
|
2
|
+
require "http/mime_type/adapter"
|
3
|
+
|
4
|
+
module WeChat::Bot
|
5
|
+
module HTTP
|
6
|
+
module MimeType
|
7
|
+
# Javascript 代码解析
|
8
|
+
# 用于解析微信 API 返回的部分 JS 代码,
|
9
|
+
# 提示:不可逆转
|
10
|
+
class JS < ::HTTP::MimeType::Adapter
|
11
|
+
# Encodes object to js
|
12
|
+
def encode(_)
|
13
|
+
"" # NO NEED encode
|
14
|
+
end
|
15
|
+
|
16
|
+
# 转换 JS 代码为 Hash
|
17
|
+
#
|
18
|
+
# @return [Hash]
|
19
|
+
def decode(str)
|
20
|
+
str.split("window.").each_with_object({}) do |item, obj|
|
21
|
+
key, value = item.split(/\s*=\s*/, 2)
|
22
|
+
next unless key || value
|
23
|
+
key = key.split(".")[-1]
|
24
|
+
obj[key] = eval(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
::HTTP::MimeType.register_adapter "text/javascript", WeChat::Bot::HTTP::MimeType::JS
|
32
|
+
::HTTP::MimeType.register_alias "text/javascript", :js
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "http/mime_type"
|
2
|
+
require "http/mime_type/adapter"
|
3
|
+
require "multi_xml"
|
4
|
+
# require "roxml"
|
5
|
+
|
6
|
+
module WeChat::Bot
|
7
|
+
module HTTP
|
8
|
+
module MimeType
|
9
|
+
# XML 代码解析
|
10
|
+
# 提示:不可逆转
|
11
|
+
class XML < ::HTTP::MimeType::Adapter
|
12
|
+
# Encodes object to js
|
13
|
+
def encode(_)
|
14
|
+
"" # NO NEED encode
|
15
|
+
end
|
16
|
+
|
17
|
+
# 转换 XML 代码为 Hash
|
18
|
+
#
|
19
|
+
# @return [Hash]
|
20
|
+
def decode(str)
|
21
|
+
MultiXml.parse(str)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
::HTTP::MimeType.register_adapter "text/xml", WeChat::Bot::HTTP::MimeType::XML
|
28
|
+
::HTTP::MimeType.register_alias "text/xml", :xml
|
29
|
+
end
|