rubirai 0.0.2

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.
Files changed (53) hide show
  1. checksums.yaml +7 -0
  2. data/.github/dependabot.yml +11 -0
  3. data/.github/workflows/CI.yml +64 -0
  4. data/.github/workflows/docs.yml +32 -0
  5. data/.github/workflows/pull_request.yml +34 -0
  6. data/.gitignore +145 -0
  7. data/.rubocop.yml +41 -0
  8. data/.yardopts +7 -0
  9. data/Gemfile +19 -0
  10. data/LICENSE +661 -0
  11. data/README.md +24 -0
  12. data/Rakefile +16 -0
  13. data/examples/helper.rb +3 -0
  14. data/examples/listener_example.rb +25 -0
  15. data/examples/simple_example.rb +24 -0
  16. data/lib/rubirai.rb +66 -0
  17. data/lib/rubirai/auth.rb +73 -0
  18. data/lib/rubirai/errors.rb +26 -0
  19. data/lib/rubirai/event_recv.rb +83 -0
  20. data/lib/rubirai/event_resp.rb +129 -0
  21. data/lib/rubirai/events/bot_events.rb +53 -0
  22. data/lib/rubirai/events/event.rb +115 -0
  23. data/lib/rubirai/events/message_events.rb +77 -0
  24. data/lib/rubirai/events/request_events.rb +35 -0
  25. data/lib/rubirai/events/rubirai_events.rb +17 -0
  26. data/lib/rubirai/listener.rb +44 -0
  27. data/lib/rubirai/listing.rb +37 -0
  28. data/lib/rubirai/management.rb +200 -0
  29. data/lib/rubirai/message.rb +84 -0
  30. data/lib/rubirai/messages/message.rb +306 -0
  31. data/lib/rubirai/messages/message_chain.rb +119 -0
  32. data/lib/rubirai/multipart.rb +44 -0
  33. data/lib/rubirai/objects/group.rb +23 -0
  34. data/lib/rubirai/objects/info.rb +71 -0
  35. data/lib/rubirai/objects/user.rb +30 -0
  36. data/lib/rubirai/plugin_info.rb +19 -0
  37. data/lib/rubirai/retcode.rb +18 -0
  38. data/lib/rubirai/session.rb +26 -0
  39. data/lib/rubirai/utils.rb +62 -0
  40. data/lib/rubirai/version.rb +9 -0
  41. data/misc/common.css +11 -0
  42. data/rubirai.gemspec +24 -0
  43. data/spec/auth_spec.rb +118 -0
  44. data/spec/error_spec.rb +30 -0
  45. data/spec/events/event_spec.rb +78 -0
  46. data/spec/message_spec.rb +12 -0
  47. data/spec/messages/message_chain_spec.rb +32 -0
  48. data/spec/messages/message_spec.rb +171 -0
  49. data/spec/plugin_info_spec.rb +28 -0
  50. data/spec/rubirai_bot_spec.rb +45 -0
  51. data/spec/spec_helper.rb +31 -0
  52. data/spec/utils_spec.rb +70 -0
  53. metadata +121 -0
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubirai/messages/message'
4
+ require 'rubirai/messages/message_chain'
5
+
6
+ module Rubirai
7
+ class Bot
8
+ # Send temp message
9
+ #
10
+ # @param target_qq [Integer] target qq id
11
+ # @param group_id [Integer] group id
12
+ # @param msgs [Array<Rubirai::Message, Hash, String, Object>] messages to form a chain, can be any type
13
+ # @return [Integer] message id
14
+ def send_temp_msg(target_qq, group_id, *msgs)
15
+ chain = Rubirai::MessageChain.make(*msgs, bot: self)
16
+ resp = call :post, '/sendTempMessage', json: {
17
+ sessionKey: @session,
18
+ qq: target_qq.to_i,
19
+ group: group_id.to_i,
20
+ messageChain: chain.to_a
21
+ }
22
+ resp['messageId']
23
+ end
24
+
25
+ def send_msg(type, target_id, *msgs)
26
+ self.class.ensure_type_in type, 'group', 'friend'
27
+ chain = Rubirai::MessageChain.make(*msgs, bot: self)
28
+ resp = call :post, "/send#{type.to_s.snake_to_camel}Message", json: {
29
+ sessionKey: @session,
30
+ target: target_id.to_i,
31
+ messageChain: chain.to_a
32
+ }
33
+ resp['messageId']
34
+ end
35
+
36
+ def send_friend_msg(target_qq, *msgs)
37
+ send_msg :friend, target_qq, *msgs
38
+ end
39
+
40
+ def send_group_msg(target_group_id, *msgs)
41
+ send_msg :group, target_group_id, *msgs
42
+ end
43
+
44
+ def recall(msg_id)
45
+ call :post, '/recall', json: {
46
+ sessionKey: @session,
47
+ target: msg_id
48
+ }
49
+ nil
50
+ end
51
+
52
+ # Send image messages
53
+ #
54
+ # @param urls [Array<String>] the urls of the images
55
+ # @param kwargs [Hash] keys are one of [target, qq, group].
56
+ # @return [Array<String>] the image ids
57
+ def send_image_msg(urls, **kwargs)
58
+ urls.must_be! Array
59
+ urls.each do |url|
60
+ url.must_be! String
61
+ end
62
+ valid = %w[target qq group]
63
+ res = {
64
+ sessionKey: @session,
65
+ urls: urls
66
+ }
67
+ kwargs.each do |k, v|
68
+ res[k.to_s.downcase.to_sym] = v if valid.include? k.to_s.downcase
69
+ end
70
+ call :post, '/sendImageMessage', json: res
71
+ end
72
+
73
+ def send_nudge(target_id, subject_id, kind)
74
+ kind.to_s.downcase.must_be_one_of! %w[group friend], RubiraiError, 'kind must be one of group or friend'
75
+ call :post, '/sendNudge', json: {
76
+ sessionKey: @session,
77
+ target: target_id,
78
+ subject: subject_id,
79
+ kind: kind.to_s.capitalize
80
+ }
81
+ nil
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,306 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubirai/utils'
4
+
5
+ module Rubirai
6
+ # The message abstract class.
7
+ #
8
+ # @abstract
9
+ # @!method self.AtMessage(**kwargs)
10
+ # @param kwargs [Hash{Symbol => Object}] arguments
11
+ # @return [Rubirai::AtMessage]
12
+ # @!method self.QuoteMessage(**kwargs)
13
+ # @param kwargs [Hash{Symbol => Object}] arguments
14
+ # @return [Rubirai::QuoteMessage]
15
+ # @!method self.AtAllMessage(**kwargs)
16
+ # @param kwargs [Hash{Symbol => Object}] arguments
17
+ # @return [Rubirai::AtAllMessage]
18
+ # @!method self.FaceMessage(**kwargs)
19
+ # @param kwargs [Hash{Symbol => Object}] arguments
20
+ # @return [Rubirai::FaceMessage]
21
+ # @!method self.PlainMessage(**kwargs)
22
+ # @option text [String] the plain text
23
+ # @return [Rubirai::PlainMessage]
24
+ class Message
25
+ attr_reader :bot, :type
26
+
27
+ # Objects to {Rubirai::Message}
28
+ #
29
+ # @param msg [Rubirai::Message, Hash, Object] the object to transform to a message
30
+ # @return [Rubirai::Message] the message
31
+ def self.to_message(msg, bot = nil)
32
+ # noinspection RubyYardReturnMatch
33
+ case msg
34
+ when Message, MessageChain
35
+ msg
36
+ when Hash
37
+ Message.build_from(msg, bot)
38
+ else
39
+ PlainMessage.from(text: msg.to_s, bot: bot)
40
+ end
41
+ end
42
+
43
+ # Get all message types (subclasses)
44
+ #
45
+ # @return [Array<Symbol>] all message types
46
+ def self.all_types
47
+ %i[
48
+ Source Quote At AtAll Face Plain Image
49
+ FlashImage Voice Xml Json App Poke Forward
50
+ File MusicShare
51
+ ]
52
+ end
53
+
54
+ # Check if a type is in all message types
55
+ # @param type [Symbol] the type to check
56
+ # @return whether the type is in all message types
57
+ def self.check_type(type)
58
+ raise(RubiraiError, 'type not in all message types') unless Message.all_types.include? type
59
+ end
60
+
61
+ def self.get_msg_klass(type)
62
+ Object.const_get "Rubirai::#{type}Message"
63
+ end
64
+
65
+ def self.build_from(hash, bot = nil)
66
+ hash = hash.stringify_keys
67
+ raise(RubiraiError, 'not a valid message') unless hash.key? 'type'
68
+
69
+ type = hash['type'].to_sym
70
+ check_type type
71
+ klass = get_msg_klass type
72
+ klass.new hash, bot
73
+ end
74
+
75
+ def self.set_message(type, *attr_keys, &initialize_block)
76
+ attr_reader(*attr_keys)
77
+
78
+ metaclass.instance_eval do
79
+ define_method(:keys) do
80
+ attr_keys
81
+ end
82
+ break if attr_keys.empty?
83
+ define_method(:from) do |bot: nil, **kwargs|
84
+ res = get_msg_klass(type).new({}, bot)
85
+ attr_keys.each do |key|
86
+ res.instance_variable_set "@#{key}", kwargs[key]
87
+ end
88
+ res
89
+ end
90
+ s = +"def from_with_param(#{attr_keys.join('= nil, ')} = nil)\n"
91
+ s << "res = Rubirai::#{type}Message.new({})\n"
92
+ attr_keys.each do |key|
93
+ s << %[res.instance_variable_set("@#{key}", #{key})\n]
94
+ end
95
+ s << "res\nend"
96
+ class_eval s
97
+ end
98
+
99
+ class_eval do
100
+ define_method(:initialize) do |hash, bot = nil|
101
+ # noinspection RubySuperCallWithoutSuperclassInspection
102
+ super type, bot
103
+ initialize_block&.call(hash)
104
+ hash = hash.stringify_keys
105
+ attr_keys.each do |k|
106
+ instance_variable_set("@#{k}", hash[k.to_s.snake_to_camel(lower: true)])
107
+ end
108
+ end
109
+ end
110
+ end
111
+
112
+ def initialize(type, bot = nil)
113
+ Message.check_type type
114
+ @bot = bot
115
+ @type = type
116
+ end
117
+
118
+ def to_h
119
+ res = self.class.keys.to_h do |k|
120
+ v = instance_variable_get("@#{k}")
121
+ k = k.to_s.snake_to_camel(lower: true)
122
+ if v.is_a? MessageChain
123
+ [k, v.to_a]
124
+ elsif v&.respond_to?(:to_h)
125
+ [k, v.to_h]
126
+ else
127
+ [k, v]
128
+ end
129
+ end
130
+ res[:type] = @type.to_s
131
+ res.compact.stringify_keys
132
+ end
133
+
134
+ def self.metaclass
135
+ class << self
136
+ self
137
+ end
138
+ end
139
+ end
140
+
141
+ def self.Message(obj, bot = nil)
142
+ Message.to_message obj, bot
143
+ end
144
+
145
+ Message.all_types.each do |type|
146
+ self.class.define_method("#{type}Message".to_sym) do |**kwargs|
147
+ Message.get_msg_klass(type).from(**kwargs)
148
+ end
149
+ end
150
+
151
+ # The source message type
152
+ class SourceMessage < Message
153
+ # @!attribute [r] id
154
+ # @return [Integer] the message (chain) id
155
+ # @!attribute [r] time
156
+ # @return [Integer] the timestamp
157
+ set_message :Source, :id, :time
158
+ end
159
+
160
+ # The quote message type
161
+ class QuoteMessage < Message
162
+ # @!attribute [r] id
163
+ # @return [Integer] the original (quoted) message (chain) id
164
+ # @!attribute [r] group_id
165
+ # @return [Integer] the group id
166
+ # @!attribute [r] sender_id
167
+ # @return [Integer] the original sender's id
168
+ # @!attribute [r] target_id
169
+ # @return [Integer] the original receiver's (group or user) id
170
+ # @!attribute [r] origin
171
+ # @return [MessageChain] the original message chain
172
+ set_message :Quote, :id, :group_id, :sender_id, :target_id, :origin
173
+
174
+ def initialize(hash, bot = nil)
175
+ super :Quote, bot
176
+ @id = hash['id']
177
+ @group_id = hash['groupId']
178
+ @sender_id = hash['senderId']
179
+ @target_id = hash['targetId']
180
+ @origin = MessageChain.make(*hash['origin'], sender_id: @sender_id, bot: bot)
181
+ end
182
+ end
183
+
184
+ # The At message type
185
+ class AtMessage < Message
186
+ # @!attribute [r] target
187
+ # @return [Integer] the target group user's id
188
+ # @!attribute [r] display
189
+ # @return [String] the displayed name (not used when sending)
190
+ # @!method from(**kwargs)
191
+ # @param kwargs [Hash{Symbol => Object}] not used
192
+ # @return [AtMessage] the message object
193
+ set_message :At, :target, :display
194
+ end
195
+
196
+ # The At All message type
197
+ class AtAllMessage < Message
198
+ # @!method from(**kwargs)
199
+ # @param kwargs [Hash{Symbol => Object}] the fields to set
200
+ # @return [AtAllMessage] the message object
201
+ set_message :AtAll
202
+ end
203
+
204
+ # The QQ Face Emoji message type
205
+ class FaceMessage < Message
206
+ # @!attribute [r] face_id
207
+ # @return [Integer] the face's id
208
+ # @!attribute [r] name
209
+ # @return [String, nil] the face's name
210
+ set_message :Face, :face_id, :name
211
+ end
212
+
213
+ # The plain text message type
214
+ class PlainMessage < Message
215
+ # @!attribute [r] text
216
+ # @return [String] the text
217
+ set_message :Plain, :text
218
+ end
219
+
220
+ # The image message type.
221
+ # Only one out of the three fields is needed to form the message.
222
+ class ImageMessage < Message
223
+ # @!attribute [r] image_id
224
+ # @return [Integer, nil] the image id from mirai
225
+ # @!attribute [r] url
226
+ # @return [String, nil] the url of the image
227
+ # @!attribute [r] path
228
+ # @return [String, nil] the local path of the image
229
+ set_message :Image, :image_id, :url, :path
230
+ end
231
+
232
+ # The flash image message type
233
+ class FlashImageMessage < Message
234
+ set_message :FlashImage, :image_id, :url, :path
235
+ end
236
+
237
+ # The voice message type
238
+ class VoiceMessage < Message
239
+ set_message :Voice, :voice_id, :url, :path
240
+ end
241
+
242
+ # The xml message type
243
+ class XmlMessage < Message
244
+ # @!attribute [r] xml
245
+ # @return [String] the xml content
246
+ set_message :Xml, :xml
247
+ end
248
+
249
+ # The json message type
250
+ class JsonMessage < Message
251
+ # @!attribute [r] json
252
+ # @return [String] the json content
253
+ set_message :Json, :json
254
+ end
255
+
256
+ # The app message type
257
+ class AppMessage < Message
258
+ # @!attribute [r] content
259
+ # @return [String] the app content
260
+ set_message :App, :content
261
+ end
262
+
263
+ class PokeMessage < Message
264
+ set_message :Poke, :name
265
+ end
266
+
267
+ class ForwardMessage < Message
268
+ class Node
269
+ attr_reader :sender_id, :time, :sender_name, :message_chain
270
+
271
+ def initialize(hash, bot = nil)
272
+ @sender_id = hash['senderId']
273
+ @time = hash['time']
274
+ @sender_name = hash['senderName']
275
+ @message_chain = MessageChain.make(*hash['messageChain'], sender_id: hash['senderId'], bot: bot)
276
+ end
277
+ end
278
+
279
+ set_message :Forward, :title, :brief, :source, :summary, :node_list
280
+
281
+ def initialize(hash, bot = nil)
282
+ super :Forward, bot
283
+ @title = hash['title']
284
+ @brief = hash['brief']
285
+ @source = hash['source']
286
+ @summary = hash['summary']
287
+ @node_list = hash['nodeList'].each do |chain|
288
+ Node.new chain, bot
289
+ end
290
+ end
291
+ end
292
+
293
+ class FileMessage < Message
294
+ set_message :File, :id, :internal_id, :name, :size
295
+ end
296
+
297
+ class MusicShareMessage < Message
298
+ def self.all_kinds
299
+ %w[NeteaseCloudMusic QQMusic MiguMusic]
300
+ end
301
+
302
+ set_message :MusicShare, :kind, :title, :summary, :jump_url, :picture_url, :music_url, :brief do |hash|
303
+ raise(RubiraiError, 'non valid music type') unless all_kinds.include? hash['kind']
304
+ end
305
+ end
306
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubirai/utils'
4
+ require 'rubirai/messages/message'
5
+
6
+ module Rubirai
7
+ class MessageChain
8
+ include Enumerable
9
+
10
+ attr_reader :bot, :sender_id, :send_time, :messages, :read_only
11
+
12
+ # Makes a message chain from a list of messages
13
+ #
14
+ # @param messages [Array<Rubirai::Message, Rubirai::MessageChain, Hash, String, Object>] a list of messages
15
+ # @param sender_id [Integer, nil]
16
+ # @param bot [Rubirai::Bot, nil]
17
+ # @return [Rubirai::MessageChain] the message chain
18
+ def self.make(*messages, sender_id: nil, bot: nil)
19
+ chain = new(bot, sender_id: sender_id)
20
+ result = []
21
+ messages.map { |msg| Message.to_message(msg, bot) }.each do |msg|
22
+ if !result.empty? && result[-1].is_a?(PlainMessage) && msg.is_a?(PlainMessage)
23
+ result[-1] = PlainMessage.from(text: result[-1].text + msg.text, bot: bot)
24
+ else
25
+ result.append msg
26
+ end
27
+ end
28
+ chain.extend(*result)
29
+ chain
30
+ end
31
+
32
+ # Append messages to this message chain
33
+ #
34
+ # @param messages [Array<Rubirai::Message, Hash>] a list of messages
35
+ # @return [Rubirai::MessageChain] self
36
+ def extend(*messages)
37
+ messages.each do |msg|
38
+ internal_append msg
39
+ end
40
+ self
41
+ end
42
+
43
+ alias << extend
44
+ alias append extend
45
+
46
+ def concat(msg_chain)
47
+ msg_chain.to_a.each do |msg|
48
+ internal_append msg
49
+ end
50
+ self
51
+ end
52
+
53
+ def [](idx)
54
+ @messages[idx]
55
+ end
56
+
57
+ def each(&block)
58
+ @messages.each(&block)
59
+ end
60
+
61
+ def length
62
+ @messages.length
63
+ end
64
+
65
+ def size
66
+ @messages.size
67
+ end
68
+
69
+ def empty?
70
+ @messages.empty?
71
+ end
72
+
73
+ # @param bot [Rubirai::Bot, nil]
74
+ # @param source [Array, nil]
75
+ # @param sender_id [Integer, nil]
76
+ def initialize(bot = nil, source = nil, sender_id: nil)
77
+ @bot = bot
78
+ @sender_id = sender_id
79
+ @messages = []
80
+ return unless source
81
+ raise(MiraiError, 'source is not array') unless source.is_a? Array
82
+ raise(MiraiError, 'length is zero') if source.empty?
83
+
84
+ if source[0]['type'] == 'Source'
85
+ @sender_id = source[0]['id']
86
+ @send_time = source[0]['time']
87
+ extend(*source.drop(1))
88
+ else
89
+ extend(*source)
90
+ end
91
+ end
92
+
93
+ def to_a
94
+ @messages.map(&:to_h)
95
+ end
96
+
97
+ private
98
+
99
+ def internal_append(msg)
100
+ msg.must_be! [Message, MessageChain, Hash], RubiraiError, 'msg must be Message, MessageChain, or Hash'
101
+
102
+ case msg
103
+ when Message
104
+ @messages.append msg
105
+ when MessageChain
106
+ @messages.append(*msg.messages)
107
+ else
108
+ @messages.append Message.build_from(msg, @bot)
109
+ end
110
+
111
+ self
112
+ end
113
+ end
114
+
115
+ # Makes a message chain. See {MessageChain#make}.
116
+ def self.MessageChain(*messages, sender_id: nil, bot: nil)
117
+ MessageChain.make(*messages, sender_id: sender_id, bot: bot)
118
+ end
119
+ end