wechat-bot 0.1.0.alpha

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.
@@ -0,0 +1,262 @@
1
+ require "wechat/bot/message_data/share_link"
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
+ ShareLink = :share_link
14
+ # RedPacage = :red_package
15
+ # BusinessCard = :business_card
16
+ # MusicLink = :music_link
17
+ System = :system
18
+ Unkown = :unkown
19
+ end
20
+
21
+ GROUP_MESSAGE_REGEX = /^(@\w+):<br\/>(.*)$/
22
+ AT_MESSAGE_REGEX = /@([^\s]+) (.*)/
23
+
24
+ # 原始消息
25
+ # @return [Hash<Object, Object>]
26
+ attr_reader :raw
27
+
28
+ # 事件列表
29
+ # @return [Array<Symbol>]
30
+ attr_reader :events
31
+
32
+ # @return [Core]
33
+ attr_reader :bot
34
+
35
+ # @return [Time]
36
+ attr_reader :time
37
+
38
+ # 消息类型
39
+ # @return [Message::Kind]
40
+ attr_reader :kind
41
+
42
+ # 消息来源
43
+ # @return [Contact::Kind]
44
+ attr_reader :source
45
+
46
+ # 消息发送者
47
+ #
48
+ # 用户或者群组
49
+ # @return [Contact]
50
+ attr_reader :from
51
+
52
+ # 消息正文
53
+ # @return [String]
54
+ attr_reader :message
55
+
56
+ attr_reader :media_id
57
+
58
+ attr_reader :meta_data
59
+
60
+ def initialize(raw, bot)
61
+ @raw = raw
62
+ @bot = bot
63
+
64
+ @events = []
65
+ @time = Time.now
66
+ @statusmsg_mode = nil
67
+
68
+ parse
69
+
70
+ @bot.logger.debug "Message Raw: #{@raw}"
71
+ end
72
+
73
+ # 回复消息
74
+ def reply(text, **args)
75
+ to_user = args[:username] || @from.username
76
+ to_user = @bot.contact_list.find(nickname: args[:nickname]) if args[:nickname]
77
+
78
+ message_type = args[:type] || :text
79
+ @bot.client.send(message_type, to_user, text)
80
+ end
81
+
82
+ # 解析微信消息
83
+ #
84
+ # @return [void]
85
+ def parse
86
+ parse_source
87
+ parse_kind
88
+
89
+ message = @raw["Content"].convert_emoji
90
+ message = CGI.unescape_html(message) if @kinde != Message::Kind::Text
91
+ if match = group_message(message)
92
+ from_username = match[0]
93
+ message = match[1]
94
+ end
95
+
96
+ @message = message
97
+ @from = @bot.contact_list.find(username: @raw["FromUserName"])
98
+ parse_emoticon if @kind == Message::Kind::Emoticon
99
+
100
+ case @kind
101
+ when Message::Kind::ShareLink
102
+ @meta_data = MessageData::ShareLink.parse(@message)
103
+ end
104
+
105
+ parse_events
106
+ end
107
+
108
+ # 消息匹配
109
+ #
110
+ # @param [String, Regex, Pattern] regexp 匹配规则
111
+ # @param [String, Symbol] type 消息类型
112
+ # @return [MatchData] 匹配结果
113
+ def match(regexp, type)
114
+ # text = ""
115
+ # case type
116
+ # when :ctcp
117
+ # text = ctcp_message
118
+ # when :action
119
+ # text = action_message
120
+ # else
121
+ # text = message.to_s
122
+ # type = :other
123
+ # end
124
+
125
+ # if strip_colors
126
+ # text = Cinch::Formatting.unformat(text)
127
+ # end
128
+
129
+ @message.match(regexp)
130
+ end
131
+
132
+ # 解析消息来源
133
+ #
134
+ # 特殊账户/群聊/公众号/用户
135
+ #
136
+ # @return [void]
137
+ def parse_source
138
+ @source = if @bot.config.special_users.include?(@raw["FromUserName"])
139
+ # 特殊账户
140
+ Contact::Kind::Special
141
+ elsif @raw["FromUserName"].include?("@@")
142
+ # 群聊
143
+ Contact::Kind::Group
144
+ elsif (@raw["RecommendInfo"]["VerifyFlag"] & 8) != 0
145
+ # 公众号
146
+ Contact::Kind::MP
147
+ else
148
+ # 普通用户
149
+ Contact::Kind::User
150
+ end
151
+ end
152
+
153
+ # 解析消息类型
154
+ #
155
+ # - 1: Text 文本消息
156
+ # - 3: Image 图片消息
157
+ # - 34: Voice 语言消息
158
+ # - 42: BusinessCard 名片消息
159
+ # - 47: Emoticon 微信表情
160
+ # - 49: ShareLink 分享链接消息
161
+ # - 62: ShortVideo 短视频消息
162
+ # - 1000: System 系统消息
163
+ # - Unkown 未知消息
164
+ #
165
+ # @return [void]
166
+ def parse_kind
167
+ @kind = case @raw["MsgType"]
168
+ when 1
169
+ Message::Kind::Text
170
+ when 3
171
+ Message::Kind::Image
172
+ when 34
173
+ Message::Kind::Voice
174
+ when 42
175
+ Message::Kind::BusinessCard
176
+ when 62
177
+ Message::Kind::ShortVideo
178
+ when 47
179
+ Message::Kind::Emoticon
180
+ when 49
181
+ Message::Kind::ShareLink
182
+ when 10000
183
+ Message::Kind::System
184
+ else
185
+ Message::Kind::Unkown
186
+ end
187
+ end
188
+
189
+ # 解析 Handler 的事件
190
+ #
191
+ # - `:message` 用户消息
192
+ # - `:text` 文本消息
193
+ # - `:group` 群聊消息
194
+ # - `:at_message` @ 消息
195
+ #
196
+ # @return [void]
197
+ def parse_events
198
+ @events << :message
199
+ @events << @kind
200
+ @events << @source
201
+
202
+ if @source == :group && @raw["Content"] =~ /@([^\s]+)\s+(.*)/
203
+ @events << :at_message
204
+ end
205
+ end
206
+
207
+ # 解析表情
208
+ #
209
+ # 表情分为两种:
210
+ # 1. 微信商店表情
211
+ # 1. 自定义表情
212
+ #
213
+ # @return [void]
214
+ def parse_emoticon
215
+ if @message.empty?
216
+ @media_id = @raw["MediaId"]
217
+ # TODO: 解决微信商店表情
218
+ # file = @bot.client.download_image(@raw["NewMsgId"])
219
+ else
220
+ data = MultiXml.parse(@message)
221
+ @media_id = data["msg"]["emoji"]["md5"]
222
+ end
223
+ end
224
+
225
+ def parse_share
226
+ data = MultiXml.parse(@message)
227
+
228
+ end
229
+
230
+ # 解析用户的群消息
231
+ #
232
+ # 群消息格式:
233
+ # @FromUserName:<br>Message
234
+ #
235
+ # @param [String] message 原始消息
236
+ # @return [Array<Object>] 返回两个值的数组
237
+ # - 0 from_username
238
+ # - 1 message
239
+ def group_message(message)
240
+ if match = GROUP_MESSAGE_REGEX.match(message)
241
+ return [match[1], at_message(match[2])]
242
+ end
243
+
244
+ false
245
+ end
246
+
247
+ # 尝试解析群聊中的 @ 消息
248
+ #
249
+ # 群消息格式:
250
+ # @ToNickNameUserName Message
251
+ #
252
+ # @param [String] message 原始消息
253
+ # @return [String] 文本消息,如果不是 @ 消息返回原始消息
254
+ def at_message(message)
255
+ if match = AT_MESSAGE_REGEX.match(message)
256
+ return match[2].strip
257
+ end
258
+
259
+ message
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,46 @@
1
+ module WeChat::Bot::MessageData
2
+ class ShareLink
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
@@ -0,0 +1,5 @@
1
+ module WeChat
2
+ module Bot
3
+ VERSION = "0.1.0.alpha"
4
+ end
5
+ end
@@ -0,0 +1 @@
1
+ require "wechat/bot"
@@ -0,0 +1,37 @@
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-bot"
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.add_development_dependency "bundler", "~> 1.14"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ spec.add_development_dependency "webmock"
28
+ spec.add_development_dependency "awesome_print"
29
+
30
+ spec.add_dependency "colorize", "~> 0.8.1"
31
+ spec.add_dependency "http", "~> 2.2.2"
32
+ spec.add_dependency "rqrcode", "~> 0.10.1"
33
+ spec.add_dependency "multi_xml", "~> 0.6.0"
34
+ # spec.add_dependency "representable", "~> 3.0.4"
35
+ # spec.add_dependency "roxml", "~> 3.3.1"
36
+ # spec.add_dependency "gemoji"
37
+ end
metadata ADDED
@@ -0,0 +1,206 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wechat-bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0.alpha
5
+ platform: ruby
6
+ authors:
7
+ - icyleaf
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-06-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.14'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.14'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: awesome_print
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: colorize
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 0.8.1
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 0.8.1
97
+ - !ruby/object:Gem::Dependency
98
+ name: http
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 2.2.2
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 2.2.2
111
+ - !ruby/object:Gem::Dependency
112
+ name: rqrcode
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.10.1
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.10.1
125
+ - !ruby/object:Gem::Dependency
126
+ name: multi_xml
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.6.0
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.6.0
139
+ description: WeChat Bot for Ruby with personal account
140
+ email:
141
+ - icyleaf.cn@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".rspec"
148
+ - ".rubocop.yml"
149
+ - ".ruby-gemset"
150
+ - ".ruby-version"
151
+ - ".travis.yml"
152
+ - ".vscode/launch.json"
153
+ - ".yardopts"
154
+ - CODE_OF_CONDUCT.md
155
+ - Gemfile
156
+ - LICENSE.txt
157
+ - README.md
158
+ - Rakefile
159
+ - lib/wechat-bot.rb
160
+ - lib/wechat/bot.rb
161
+ - lib/wechat/bot/cached_list.rb
162
+ - lib/wechat/bot/callback.rb
163
+ - lib/wechat/bot/client.rb
164
+ - lib/wechat/bot/configuration.rb
165
+ - lib/wechat/bot/contact.rb
166
+ - lib/wechat/bot/contact_list.rb
167
+ - lib/wechat/bot/core.rb
168
+ - lib/wechat/bot/ext/wechat_emoji_string.rb
169
+ - lib/wechat/bot/handler.rb
170
+ - lib/wechat/bot/handler_list.rb
171
+ - lib/wechat/bot/helper.rb
172
+ - lib/wechat/bot/http/adapter/js.rb
173
+ - lib/wechat/bot/http/adapter/xml.rb
174
+ - lib/wechat/bot/http/session.rb
175
+ - lib/wechat/bot/logger.rb
176
+ - lib/wechat/bot/message.rb
177
+ - lib/wechat/bot/message_data/share_link.rb
178
+ - lib/wechat/bot/pattern.rb
179
+ - lib/wechat/bot/version.rb
180
+ - lib/wechat_bot.rb
181
+ - wechat-bot.gemspec
182
+ homepage: https://github.com/icyleaf/wechat-bot
183
+ licenses:
184
+ - MIT
185
+ metadata: {}
186
+ post_install_message:
187
+ rdoc_options: []
188
+ require_paths:
189
+ - lib
190
+ required_ruby_version: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ required_rubygems_version: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - ">"
198
+ - !ruby/object:Gem::Version
199
+ version: 1.3.1
200
+ requirements: []
201
+ rubyforge_project:
202
+ rubygems_version: 2.6.12
203
+ signing_key:
204
+ specification_version: 4
205
+ summary: WeChat Bot for Ruby
206
+ test_files: []