wechat-bot 0.1.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,2 @@
1
+ module WeChat::Bot
2
+ 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(obj)
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(obj)
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
@@ -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