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,117 @@
|
|
1
|
+
require "http"
|
2
|
+
|
3
|
+
module WeChat::Bot
|
4
|
+
module HTTP
|
5
|
+
# 可保存 Cookies 的 HTTP 请求类
|
6
|
+
#
|
7
|
+
# 简单实现 Python 版本 {http://docs.python-requests.org/zh_CN/latest/user/advanced.html#session-objects requests.Session()}
|
8
|
+
class Session
|
9
|
+
# @return [HTTP::CookieJar]
|
10
|
+
attr_reader :cookies
|
11
|
+
|
12
|
+
def initialize(bot)
|
13
|
+
@bot = bot
|
14
|
+
|
15
|
+
load_cookies(@bot.config.cookies)
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [HTTP::Response]
|
19
|
+
def get(url, options = {})
|
20
|
+
request(:get, url, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [HTTP::Response]
|
24
|
+
def post(url, options = {})
|
25
|
+
request(:post, url, options)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [HTTP::Response]
|
29
|
+
def put(url, options = {})
|
30
|
+
request(:put, url, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [HTTP::Response]
|
34
|
+
def delete(url, options = {})
|
35
|
+
request(:delete, url, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [HTTP::Response]
|
39
|
+
def request(verb, url, options = {})
|
40
|
+
prepare_request(url)
|
41
|
+
|
42
|
+
if options[:timeout]
|
43
|
+
connect_timeout, read_timeout = options.delete(:timeout)
|
44
|
+
@client = @client.timeout(connect: connect_timeout, read: read_timeout)
|
45
|
+
end
|
46
|
+
|
47
|
+
response = @client.request(verb, url, options)
|
48
|
+
update_cookies(response.cookies)
|
49
|
+
|
50
|
+
@bot.logger.verbose "[#{verb.upcase}] #{url}"
|
51
|
+
@bot.logger.verbose "Options: #{options}"
|
52
|
+
@bot.logger.verbose "Response: #{response.body}"
|
53
|
+
|
54
|
+
response
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# 组装 request 基础请求参数
|
60
|
+
#
|
61
|
+
# - 设置 User-Agent
|
62
|
+
# - 设置 Cooklies
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
# @param [String] url
|
66
|
+
# @return [HTTP::Request]
|
67
|
+
def prepare_request(url)
|
68
|
+
@client = ::HTTP.headers(user_agent: @bot.config.user_agent, "Range" => "bytes=0-")
|
69
|
+
return @client if @cookies.nil?
|
70
|
+
return @client = @client.cookies(@cookies)
|
71
|
+
|
72
|
+
# TODO: 优化处理同一顶级域名的 cookies
|
73
|
+
# uri = URI(url)
|
74
|
+
# unless @cookies.empty?(uri)
|
75
|
+
# cookies = @cookies.clone
|
76
|
+
# cookies.cookies.each do |cookie|
|
77
|
+
# cookies.delete(cookie) if uri.host != cookie.domain
|
78
|
+
# end
|
79
|
+
|
80
|
+
# unless cookies.empty?(uri)
|
81
|
+
# @client = @client.cookies(@cookies)
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
# 加载外部的 Cookies 数据
|
88
|
+
#
|
89
|
+
# @api private
|
90
|
+
# @param [String, HTTP::CooieJar] cookies
|
91
|
+
# @return [void]
|
92
|
+
def load_cookies(cookies)
|
93
|
+
@cookies = ::HTTP::CookieJar.new
|
94
|
+
return if cookies.nil?
|
95
|
+
|
96
|
+
if cookies.is_a?(String)
|
97
|
+
@cookies.load(cookies) if File.exist?(cookies)
|
98
|
+
else
|
99
|
+
@cookies.add(cookies)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# 请求后更新存储的 Cookies 数据
|
104
|
+
#
|
105
|
+
# @api private
|
106
|
+
# @param [String, HTTP::CooieJar] cookies
|
107
|
+
# @return [void]
|
108
|
+
def update_cookies(cookies)
|
109
|
+
return @cookies = cookies if @cookies.nil? || @cookies.empty?
|
110
|
+
|
111
|
+
cookies.cookies.each do |cookie|
|
112
|
+
@cookies.add(cookie)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "colorize"
|
2
|
+
|
3
|
+
module WeChat::Bot
|
4
|
+
class Logger
|
5
|
+
LEVELS = [:verbose, :debug, :info, :warn, :error, :fatal]
|
6
|
+
|
7
|
+
# @return [Symbol]
|
8
|
+
attr_accessor :level
|
9
|
+
|
10
|
+
# @return [Mutex]
|
11
|
+
attr_reader :mutex
|
12
|
+
|
13
|
+
# @return [IO]
|
14
|
+
attr_reader :output
|
15
|
+
|
16
|
+
def initialize(output, bot)
|
17
|
+
@output = output
|
18
|
+
@bot = bot
|
19
|
+
@mutex = Mutex.new
|
20
|
+
@level = :info
|
21
|
+
end
|
22
|
+
|
23
|
+
def verbose(message)
|
24
|
+
log(:verbose, message)
|
25
|
+
end
|
26
|
+
|
27
|
+
def debug(message)
|
28
|
+
log(:debug, message)
|
29
|
+
end
|
30
|
+
|
31
|
+
def info(message)
|
32
|
+
log(:info, message)
|
33
|
+
end
|
34
|
+
|
35
|
+
def warn(message)
|
36
|
+
log(:warn, message)
|
37
|
+
end
|
38
|
+
|
39
|
+
def error(message)
|
40
|
+
log(:error, message)
|
41
|
+
end
|
42
|
+
|
43
|
+
def fatal(exception)
|
44
|
+
message = ["#{exception.backtrace.first}: #{exception.message} (#{exception.class})"]
|
45
|
+
message.concat(exception.backtrace[1..-1].map {|s| "\t" + s})
|
46
|
+
log(:fatal, message.join("\n"))
|
47
|
+
end
|
48
|
+
|
49
|
+
def log(level, message)
|
50
|
+
return unless can_log?(level)
|
51
|
+
return if message.to_s.empty?
|
52
|
+
|
53
|
+
@mutex.synchronize do
|
54
|
+
message = format_message(format_general(message), level)
|
55
|
+
@output.puts message
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def can_log?(level)
|
62
|
+
@level = :verbose if @bot.config.verbose
|
63
|
+
LEVELS.index(level) >= LEVELS.index(@level)
|
64
|
+
end
|
65
|
+
|
66
|
+
def format_general(message)
|
67
|
+
message
|
68
|
+
end
|
69
|
+
|
70
|
+
def format_message(message, level)
|
71
|
+
send("format_#{level}", message)
|
72
|
+
end
|
73
|
+
|
74
|
+
def format_verbose(message)
|
75
|
+
"VERBOSE [#{timestamp}] #{message.colorize(:light_black)}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def format_debug(message)
|
79
|
+
"DEBUG [#{timestamp}] #{message.colorize(:light_black)}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def format_info(message)
|
83
|
+
"INFO [#{timestamp}] #{message}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def format_warn(message)
|
87
|
+
"WRAN [#{timestamp}] #{message.colorize(:yellow)}"
|
88
|
+
end
|
89
|
+
|
90
|
+
def format_error(message)
|
91
|
+
"ERROR [#{timestamp}] #{message.colorize(:light_red)}"
|
92
|
+
end
|
93
|
+
|
94
|
+
def format_fatal(message)
|
95
|
+
"FATAL [#{timestamp}] #{message.colorize(:red)}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def timestamp
|
99
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S.%2N")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
require "wechat/bot/message_data/share_card"
|
2
|
+
|
3
|
+
module WeChat::Bot
|
4
|
+
# 微信消息
|
5
|
+
class Message
|
6
|
+
# 消息类型
|
7
|
+
module Kind
|
8
|
+
Text = :text
|
9
|
+
Image = :image
|
10
|
+
Voice = :voice
|
11
|
+
ShortVideo = :short_video
|
12
|
+
Emoticon = :emoticon
|
13
|
+
ShareCard = :share_link
|
14
|
+
# RedPacage = :red_package
|
15
|
+
# BusinessCard = :business_card
|
16
|
+
# MusicLink = :music_link
|
17
|
+
Verify = :verify
|
18
|
+
System = :system
|
19
|
+
Unkown = :unkown
|
20
|
+
end
|
21
|
+
|
22
|
+
GROUP_MESSAGE_REGEX = /^(@\w+):<br\/>(.*)$/
|
23
|
+
AT_MESSAGE_REGEX = /@([^\s]+) (.*)/
|
24
|
+
|
25
|
+
# 原始消息
|
26
|
+
# @return [Hash<Object, Object>]
|
27
|
+
attr_reader :raw
|
28
|
+
|
29
|
+
# 事件列表
|
30
|
+
# @return [Array<Symbol>]
|
31
|
+
attr_reader :events
|
32
|
+
|
33
|
+
# @return [Core]
|
34
|
+
attr_reader :bot
|
35
|
+
|
36
|
+
# @return [Time]
|
37
|
+
attr_reader :time
|
38
|
+
|
39
|
+
# 消息类型
|
40
|
+
# @return [Message::Kind]
|
41
|
+
attr_reader :kind
|
42
|
+
|
43
|
+
# 消息来源
|
44
|
+
# @return [Contact::Kind]
|
45
|
+
attr_reader :source
|
46
|
+
|
47
|
+
# 消息发送者
|
48
|
+
#
|
49
|
+
# 用户或者群组
|
50
|
+
# @return [Contact]
|
51
|
+
attr_reader :from
|
52
|
+
|
53
|
+
# 消息正文
|
54
|
+
# @return [String]
|
55
|
+
attr_reader :message
|
56
|
+
|
57
|
+
attr_reader :media_id
|
58
|
+
|
59
|
+
attr_reader :meta_data
|
60
|
+
|
61
|
+
def initialize(raw, bot)
|
62
|
+
@raw = raw
|
63
|
+
@bot = bot
|
64
|
+
|
65
|
+
@events = []
|
66
|
+
@time = Time.now
|
67
|
+
@statusmsg_mode = nil
|
68
|
+
|
69
|
+
parse
|
70
|
+
|
71
|
+
@bot.logger.verbose "Message Raw: #{@raw}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# 回复消息
|
75
|
+
def reply(text, **args)
|
76
|
+
to_user = args[:username] || @from.username
|
77
|
+
to_user = @bot.contact_list.find(nickname: args[:nickname]) if args[:nickname]
|
78
|
+
|
79
|
+
message_type = args[:type] || :text
|
80
|
+
|
81
|
+
# if @bot.config.special_users.include?(to_user) && to_user != 'filehelper'
|
82
|
+
# @bot.logger.error "特殊账户无法回复: #{to_user}"
|
83
|
+
# raise NoReplyException, "特殊账户无法回复: #{to_user}"
|
84
|
+
# end
|
85
|
+
|
86
|
+
@bot.client.send(message_type, to_user, text)
|
87
|
+
end
|
88
|
+
|
89
|
+
# 解析微信消息
|
90
|
+
#
|
91
|
+
# @return [void]
|
92
|
+
def parse
|
93
|
+
parse_source
|
94
|
+
parse_kind
|
95
|
+
|
96
|
+
message = @raw["Content"].convert_emoji
|
97
|
+
message = CGI.unescape_html(message) if @kinde != Message::Kind::Text
|
98
|
+
if match = group_message(message)
|
99
|
+
# from_username = match[0]
|
100
|
+
message = match[1]
|
101
|
+
end
|
102
|
+
|
103
|
+
@message = message
|
104
|
+
# TODO: 来自于特殊账户无法获取联系人信息,需要单独处理
|
105
|
+
@from = @bot.contact_list.find(username: @raw["FromUserName"])
|
106
|
+
parse_emoticon if @kind == Message::Kind::Emoticon
|
107
|
+
|
108
|
+
case @kind
|
109
|
+
when Message::Kind::ShareCard
|
110
|
+
@meta_data = MessageData::ShareCard.parse(@message)
|
111
|
+
end
|
112
|
+
|
113
|
+
parse_events
|
114
|
+
end
|
115
|
+
|
116
|
+
# 消息匹配
|
117
|
+
#
|
118
|
+
# @param [String, Regex, Pattern] regexp 匹配规则
|
119
|
+
# @param [String, Symbol] type 消息类型
|
120
|
+
# @return [MatchData] 匹配结果
|
121
|
+
def match(regexp, type)
|
122
|
+
# text = ""
|
123
|
+
# case type
|
124
|
+
# when :ctcp
|
125
|
+
# text = ctcp_message
|
126
|
+
# when :action
|
127
|
+
# text = action_message
|
128
|
+
# else
|
129
|
+
# text = message.to_s
|
130
|
+
# type = :other
|
131
|
+
# end
|
132
|
+
|
133
|
+
# if strip_colors
|
134
|
+
# text = Cinch::Formatting.unformat(text)
|
135
|
+
# end
|
136
|
+
|
137
|
+
@message.match(regexp)
|
138
|
+
end
|
139
|
+
|
140
|
+
def at_message?
|
141
|
+
@at_mesage == true
|
142
|
+
end
|
143
|
+
|
144
|
+
# 解析消息来源
|
145
|
+
#
|
146
|
+
# 特殊账户/群聊/公众号/用户
|
147
|
+
#
|
148
|
+
# @return [void]
|
149
|
+
def parse_source
|
150
|
+
@source = if @bot.config.special_users.include?(@raw["FromUserName"])
|
151
|
+
# 特殊账户
|
152
|
+
Contact::Kind::Special
|
153
|
+
elsif @raw["FromUserName"].include?("@@")
|
154
|
+
# 群聊
|
155
|
+
Contact::Kind::Group
|
156
|
+
elsif (@raw["RecommendInfo"]["VerifyFlag"] & 8) != 0
|
157
|
+
# 公众号
|
158
|
+
Contact::Kind::MP
|
159
|
+
else
|
160
|
+
# 普通用户
|
161
|
+
Contact::Kind::User
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# 解析消息类型
|
166
|
+
#
|
167
|
+
# - 1: Text 文本消息
|
168
|
+
# - 3: Image 图片消息
|
169
|
+
# - 34: Voice 语言消息
|
170
|
+
# - 37: 验证消息
|
171
|
+
# - 42: BusinessCard 名片消息
|
172
|
+
# - 47: Emoticon 微信表情
|
173
|
+
# - 49: ShareCard 分享链接消息
|
174
|
+
# - 62: ShortVideo 短视频消息
|
175
|
+
# - 1000: System 系统消息
|
176
|
+
# - Unkown 未知消息
|
177
|
+
#
|
178
|
+
# @return [void]
|
179
|
+
def parse_kind
|
180
|
+
@kind = case @raw["MsgType"]
|
181
|
+
when 1
|
182
|
+
Message::Kind::Text
|
183
|
+
when 3
|
184
|
+
Message::Kind::Image
|
185
|
+
when 34
|
186
|
+
Message::Kind::Voice
|
187
|
+
when 37
|
188
|
+
Message::Kind::Verify
|
189
|
+
when 42
|
190
|
+
Message::Kind::BusinessCard
|
191
|
+
when 62
|
192
|
+
Message::Kind::ShortVideo
|
193
|
+
when 47
|
194
|
+
Message::Kind::Emoticon
|
195
|
+
when 49
|
196
|
+
Message::Kind::ShareCard
|
197
|
+
when 10000
|
198
|
+
Message::Kind::System
|
199
|
+
else
|
200
|
+
Message::Kind::Unkown
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
# 解析 Handler 的事件
|
205
|
+
#
|
206
|
+
# - `:message` 用户消息
|
207
|
+
# - `:text` 文本消息
|
208
|
+
# - `:image` 图片消息
|
209
|
+
# - `:voice` 语音消息
|
210
|
+
# - `:short_video` 短视频消息
|
211
|
+
# - `:group` 群聊消息
|
212
|
+
# - `:at_message` @ 消息
|
213
|
+
#
|
214
|
+
# @return [void]
|
215
|
+
def parse_events
|
216
|
+
@events << :message
|
217
|
+
@events << @kind
|
218
|
+
@events << @source
|
219
|
+
|
220
|
+
@at_mesage = false
|
221
|
+
if @source == :group && @raw["Content"] =~ /@([^\s]+)\s+(.*)/
|
222
|
+
@events << :at_message
|
223
|
+
@at_mesage = true
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# 解析表情
|
228
|
+
#
|
229
|
+
# 表情分为两种:
|
230
|
+
# 1. 微信商店表情
|
231
|
+
# 1. 自定义表情
|
232
|
+
#
|
233
|
+
# @return [void]
|
234
|
+
def parse_emoticon
|
235
|
+
if @message.empty?
|
236
|
+
@media_id = @raw["MediaId"]
|
237
|
+
# TODO: 解决微信商店表情
|
238
|
+
# file = @bot.client.download_image(@raw["NewMsgId"])
|
239
|
+
else
|
240
|
+
data = MultiXml.parse(@message)
|
241
|
+
@media_id = data["msg"]["emoji"]["md5"]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def parse_share
|
246
|
+
# TODO: 完成解析
|
247
|
+
data = MultiXml.parse(@message)
|
248
|
+
end
|
249
|
+
|
250
|
+
# 解析用户的群消息
|
251
|
+
#
|
252
|
+
# 群消息格式:
|
253
|
+
# @FromUserName:<br>Message
|
254
|
+
#
|
255
|
+
# @param [String] message 原始消息
|
256
|
+
# @return [Array<Object>] 返回两个值的数组
|
257
|
+
# - 0 from_username
|
258
|
+
# - 1 message
|
259
|
+
def group_message(message)
|
260
|
+
if match = GROUP_MESSAGE_REGEX.match(message)
|
261
|
+
return [match[1], at_message(match[2])]
|
262
|
+
end
|
263
|
+
|
264
|
+
false
|
265
|
+
end
|
266
|
+
|
267
|
+
# 尝试解析群聊中的 @ 消息
|
268
|
+
#
|
269
|
+
# 群消息格式:
|
270
|
+
# @ToNickNameUserName Message
|
271
|
+
#
|
272
|
+
# @param [String] message 原始消息
|
273
|
+
# @return [String] 文本消息,如果不是 @ 消息返回原始消息
|
274
|
+
def at_message(message)
|
275
|
+
if match = AT_MESSAGE_REGEX.match(message)
|
276
|
+
return match[2].strip
|
277
|
+
end
|
278
|
+
|
279
|
+
message
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module WeChat::Bot::MessageData
|
2
|
+
class ShareCard
|
3
|
+
def self.parse(raw)
|
4
|
+
self.new(raw)
|
5
|
+
end
|
6
|
+
|
7
|
+
# @return [String]
|
8
|
+
attr_reader :title
|
9
|
+
|
10
|
+
# @return [String]
|
11
|
+
attr_reader :link
|
12
|
+
|
13
|
+
# @return [String]
|
14
|
+
attr_reader :description
|
15
|
+
|
16
|
+
# @return [String, Nil]
|
17
|
+
attr_reader :thumb_image
|
18
|
+
|
19
|
+
# @return [String]
|
20
|
+
attr_reader :from_user
|
21
|
+
|
22
|
+
# @return [String, Nil]
|
23
|
+
attr_reader :app
|
24
|
+
|
25
|
+
# @return [Hash<Symbol, String>, Hash<Symbol, Nil>]
|
26
|
+
attr_reader :mp
|
27
|
+
|
28
|
+
def initialize(raw)
|
29
|
+
@raw = MultiXml.parse(raw.gsub("<br/>", ""))
|
30
|
+
parse
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse
|
34
|
+
@title = @raw["msg"]["appmsg"]["title"]
|
35
|
+
@link = @raw["msg"]["appmsg"]["url"]
|
36
|
+
@description = @raw["msg"]["appmsg"]["des"]
|
37
|
+
@thumb_image = @raw["msg"]["appmsg"]["thumb_url"]
|
38
|
+
@from_user = @raw["msg"]["fromusername"]
|
39
|
+
@app = @raw["msg"]["appname"]
|
40
|
+
@mp = {
|
41
|
+
username: @raw["msg"]["sourceusername"],
|
42
|
+
nickname: @raw["msg"]["sourcedisplayname"],
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module WeChat::Bot
|
2
|
+
# 消息匹配
|
3
|
+
class Pattern
|
4
|
+
# @param [String, Regexp, NilClass, Proc, #to_s] obj 匹配规则
|
5
|
+
# @return [Regexp, nil]
|
6
|
+
def self.obj_to_r(obj, anchor = nil)
|
7
|
+
case obj
|
8
|
+
when Regexp, NilClass
|
9
|
+
return obj
|
10
|
+
else
|
11
|
+
escaped = Regexp.escape(obj.to_s)
|
12
|
+
case anchor
|
13
|
+
when :start
|
14
|
+
return Regexp.new("^" + escaped)
|
15
|
+
when :end
|
16
|
+
return Regexp.new(escaped + "$")
|
17
|
+
when nil
|
18
|
+
return Regexp.new(Regexp.escape(obj.to_s))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.resolve_proc(obj, msg = nil)
|
24
|
+
if obj.is_a?(Proc)
|
25
|
+
return resolve_proc(obj.call(msg), msg)
|
26
|
+
else
|
27
|
+
return obj
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.generate(type, argument)
|
32
|
+
case type
|
33
|
+
when :ctcp
|
34
|
+
Pattern.new(/^/, /#{Regexp.escape(argument.to_s)}(?:$| .+)/, nil)
|
35
|
+
else
|
36
|
+
raise ArgumentError, "Unsupported type: #{type.inspect}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :prefix
|
41
|
+
attr_reader :suffix
|
42
|
+
attr_reader :pattern
|
43
|
+
|
44
|
+
def initialize(prefix, pattern, suffix)
|
45
|
+
@prefix, @pattern, @suffix = prefix, pattern, suffix
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_r(msg = nil)
|
49
|
+
pattern = Pattern.resolve_proc(@pattern, msg)
|
50
|
+
|
51
|
+
case pattern
|
52
|
+
when Regexp, NilClass
|
53
|
+
prefix = Pattern.obj_to_r(Pattern.resolve_proc(@prefix, msg), :start)
|
54
|
+
suffix = Pattern.obj_to_r(Pattern.resolve_proc(@suffix, msg), :end)
|
55
|
+
/#{prefix}#{pattern}#{suffix}/
|
56
|
+
else
|
57
|
+
prefix = Pattern.obj_to_r(Pattern.resolve_proc(@prefix, msg))
|
58
|
+
suffix = Pattern.obj_to_r(Pattern.resolve_proc(@suffix, msg))
|
59
|
+
/^#{prefix}#{Pattern.obj_to_r(pattern)}#{suffix}$/
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/wechat/bot.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require "wechat/bot/version"
|
2
|
+
require "wechat/bot/ext/wechat_emoji_string"
|
3
|
+
|
4
|
+
require "wechat/bot/core"
|
5
|
+
require "wechat/bot/client"
|
6
|
+
require "wechat/bot/exception"
|
7
|
+
|
8
|
+
module WeChat::Bot
|
9
|
+
def self.new(&block)
|
10
|
+
WeChat::Bot::Core.new(&block)
|
11
|
+
end
|
12
|
+
end
|
data/lib/wechat-bot.rb
ADDED
data/lib/wechat_bot.rb
ADDED
data/wechat-bot.gemspec
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wechat/bot/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "wechat-bot2"
|
8
|
+
spec.version = WeChat::Bot::VERSION
|
9
|
+
spec.authors = ["icyleaf"]
|
10
|
+
spec.email = ["icyleaf.cn@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = "WeChat Bot for Ruby"
|
13
|
+
spec.description = "WeChat Bot for Ruby with personal account"
|
14
|
+
spec.homepage = "https://github.com/icyleaf/wechat-bot"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.required_ruby_version = ">= 2.1.0"
|
25
|
+
|
26
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
27
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
28
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
29
|
+
spec.add_development_dependency "rubocop", "~> 0.53.0"
|
30
|
+
spec.add_development_dependency "webmock"
|
31
|
+
spec.add_development_dependency "awesome_print"
|
32
|
+
|
33
|
+
spec.add_dependency "colorize", "~> 0.8.1"
|
34
|
+
spec.add_dependency "http", "~> 2.2.2"
|
35
|
+
spec.add_dependency "rqrcode", "~> 0.10.1"
|
36
|
+
spec.add_dependency "multi_xml", "~> 0.6.0"
|
37
|
+
# spec.add_dependency "representable", "~> 3.0.4"
|
38
|
+
# spec.add_dependency "roxml", "~> 3.3.1"
|
39
|
+
# spec.add_dependency "gemoji"
|
40
|
+
end
|