rubirai 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubirai
4
+ class Bot
5
+ # Uploads an image to QQ server
6
+ # @return [Hash] hash string keys are: `{ imageId, url, path }`
7
+ def upload_image(path_or_io, type = :friend)
8
+ self.class.ensure_type_in type, 'friend', 'group', 'temp'
9
+ call :post, '/uploadImage', form: {
10
+ sessionKey: @session,
11
+ type: type.to_s.downcase,
12
+ img: HTTP::FormData::File.new(path_or_io)
13
+ }, headers: { content_type: 'multipart/form-data' }
14
+ end
15
+
16
+ # Uploads a voice file to QQ server
17
+ # Only group uploads available currently.
18
+ # @param path_or_io [String, Pathname, IO] path to voice file
19
+ # @return [Hash] hash string keys are: `{ voiceId, url, path }`
20
+ def upload_voice(path_or_io)
21
+ call :post, '/uploadVoice', form: {
22
+ sessionKey: @session,
23
+ type: 'group',
24
+ img: HTTP::FormData::File.new(path_or_io)
25
+ }, headers: { content_type: 'multipart/form-data' }
26
+ end
27
+
28
+ # Uploads a file to a group (currently only groups supported)
29
+ # @param path_or_io [String, Pathname, IO] path to file
30
+ # @param target [Integer] group id
31
+ # @param group_path [String] path to file in group files
32
+ # @return [String] the string id of the mirai file
33
+ def upload_file_and_send(path_or_io, target, group_path)
34
+ res = call :post, '/uploadFileAndSend', form: {
35
+ sessionKey: @session,
36
+ type: 'Group',
37
+ target: target,
38
+ path: group_path,
39
+ file: HTTP::FormData::File.new(path_or_io)
40
+ }, headers: { content_type: 'multipart/form-data' }
41
+ res['id']
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubirai
4
+ class Group
5
+ attr_reader :bot, :id, :name, :permission
6
+
7
+ # @param hash [Hash{String => Object}]
8
+ # @param bot [Rubirai::Bot, nil]
9
+ def initialize(hash, bot = nil)
10
+ hash = hash.stringify_keys
11
+ @bot = bot
12
+ @id = hash['id']
13
+ @name = hash['name']
14
+ @permission = hash['permission']
15
+ end
16
+
17
+ class Permission
18
+ OWNER = 'OWNER'
19
+ ADMINISTRATOR = 'ADMINISTRATOR'
20
+ MEMBER = 'MEMBER'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubirai/utils'
4
+
5
+ module Rubirai
6
+ # @abstract
7
+ class Info
8
+ # @!method initialize(hash, bot = nil)
9
+ # @param hash [Hash{String => Object}]
10
+ # @param bot [Rubirai::Bot, nil]
11
+ # @!method to_h
12
+ # @return [Hash{String => Object}]
13
+ def self.set_fields(*fields, **default_values)
14
+ attr_reader(*fields)
15
+
16
+ class_eval do
17
+ define_method(:initialize) do |hash, bot = nil|
18
+ # noinspection RubySuperCallWithoutSuperclassInspection
19
+ super hash, bot
20
+ fields.each do |field|
21
+ value = hash[field.to_s.snake_to_camel(lower: true)] || default_values[field]
22
+ instance_variable_set("@#{field}", value)
23
+ end
24
+ end
25
+
26
+ define_method(:to_h) do
27
+ fields.to_h do |field|
28
+ [field.to_s.snake_to_camel(lower: true), instance_variable_get(field)]
29
+ end.compact
30
+ end
31
+ end
32
+ end
33
+
34
+ def self.set_modifiable_fields(*fields)
35
+ set_fields(*fields)
36
+ attr_writer(*fields)
37
+ end
38
+
39
+ attr_reader :raw, :bot
40
+
41
+ def initialize(hash, bot = nil)
42
+ @raw = hash
43
+ @bot = bot
44
+ end
45
+ end
46
+
47
+ class GroupConfig < Info
48
+ set_modifiable_fields :name, :announcement, :confess_talk, :allow_member_invite, :auto_approve, :anonymous_chat
49
+ end
50
+
51
+ class MemberInfo < Info
52
+ set_fields :name, :nick, :special_title
53
+ attr_writer :name, :special_title
54
+ end
55
+
56
+ class GroupFileSimple < Info
57
+ set_fields :name, :id, :path, :is_file, is_file: true
58
+ end
59
+
60
+ class GroupFile < GroupFileSimple
61
+ set_fields :length,
62
+ :download_times,
63
+ :uploader_id,
64
+ :upload_time,
65
+ :last_modify_time,
66
+ :download_url,
67
+ :sha1,
68
+ :md5,
69
+ call_super: true
70
+ end
71
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'group'
4
+ require 'rubirai/errors'
5
+
6
+ module Rubirai
7
+ class User
8
+ attr_reader :bot, :id, :name, :remark
9
+
10
+ def initialize(hash, bot = nil)
11
+ hash = hash.stringify_keys
12
+ @bot = bot
13
+ @id = hash['id']
14
+ @name = hash['name'] || hash['nickname']
15
+ @remark = hash['remark']
16
+ end
17
+ end
18
+
19
+ class GroupUser < User
20
+ attr_reader :member_name, :permission, :group
21
+
22
+ def initialize(hash, bot = nil)
23
+ raise(RubiraiError, 'not a group user') unless hash.key? 'group'
24
+ super(hash, bot)
25
+ @member_name = hash['memberName']
26
+ @permission = hash['permission']
27
+ @group = Group.new(hash['group'], bot)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubirai
4
+ class Bot
5
+ # Get Mirai API plugin information such as
6
+ #
7
+ # ```ruby
8
+ # {
9
+ # 'version' => '1.0.0'
10
+ # }
11
+ # ```
12
+ #
13
+ # @return [Hash{String => Object}] plugin data
14
+ def about
15
+ v = call :get, '/about'
16
+ v['data']
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubirai
4
+ # The return codes and their explanations
5
+ RETURN_CODE = {
6
+ 0 => 'OK',
7
+ 1 => 'Wrong auth key',
8
+ 2 => 'No such bot',
9
+ 3 => 'Session disappeared',
10
+ 4 => 'Session not verified',
11
+ 5 => 'No such receiver',
12
+ 6 => 'No such file',
13
+ 10 => 'No permission',
14
+ 20 => 'Bot muted',
15
+ 30 => 'Message too long',
16
+ 400 => 'Bad request'
17
+ }.freeze
18
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubirai
4
+ class Bot
5
+ # Get the config related to this session
6
+ # @return [Hash{String => Object}] the config
7
+ def get_session_cfg
8
+ call :get, '/config', params: {
9
+ sessionKey: @session
10
+ }
11
+ end
12
+
13
+ # Set the config related to this session
14
+ # @param cache_size [Integer, nil] the cache size
15
+ # @param enable_websocket [Boolean, nil] if to enable websocket
16
+ # @return [void]
17
+ def set_session_cfg(cache_size: nil, enable_websocket: nil)
18
+ call :post, '/config', json: {
19
+ sessionKey: @session,
20
+ cacheSize: cache_size,
21
+ enableWebsocket: enable_websocket
22
+ }.compact
23
+ nil
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ # @private
4
+ class Hash
5
+ def stringify_keys!
6
+ transform_keys!(&:to_s)
7
+ end
8
+
9
+ def symbolize_keys!
10
+ transform_keys!(&:to_sym)
11
+ end
12
+
13
+ def stringify_keys
14
+ transform_keys(&:to_s)
15
+ end
16
+
17
+ def symbolize_keys
18
+ transform_keys(&:to_sym)
19
+ end
20
+ end
21
+
22
+ # @private
23
+ class String
24
+ def snake_to_camel(lower: false)
25
+ s = split('_').collect(&:capitalize).join
26
+ return s unless lower
27
+ # noinspection RubyNilAnalysis
28
+ s[0] = s[0].downcase
29
+ s
30
+ end
31
+ end
32
+
33
+ # @private
34
+ class Object
35
+ def must_be!(types, exc_type = nil, *args)
36
+ ok = case types
37
+ when Array
38
+ types.any? { |type| is_a? type }
39
+ else
40
+ is_a? types
41
+ end
42
+ self.class.raise_or_default exc_type, "assert failed: `#{self}' must be of type #{types}", *args unless ok
43
+ end
44
+
45
+ def must_be_one_of!(things, exc_type = nil, *args)
46
+ ok = case things
47
+ when Array
48
+ things.include? self
49
+ else
50
+ self == things
51
+ end
52
+ self.class.raise_or_default exc_type, "assert failed: `#{self}' must be one of: #{things}", *args unless ok
53
+ end
54
+
55
+ def self.raise_or_default(exc_type, msg, *args)
56
+ if exc_type.nil?
57
+ raise(msg)
58
+ else
59
+ raise(exc_type, *args)
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rubirai
4
+ # Rubirai version
5
+ VERSION = '0.0.2'
6
+
7
+ # mirai-api-http version
8
+ MIRAI_API_VERSION = '1.10.0'
9
+ end
data/misc/common.css ADDED
@@ -0,0 +1,11 @@
1
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&family=Noto+Sans:ital,wght@0,400;0,700;1,400&family=Roboto+Mono:ital,wght@0,400;0,500;1,400&display=swap');
2
+
3
+ body, .summary_desc, .note.title, p.signature .extras, h3.signature .extras,
4
+ h1, h2, h3, h4, h5, h6, h3.signature .aliases {
5
+ font-family: 'SF Pro', 'Noto Sans', 'Noto Sans SC', Arial, Helvetica, -apple-system, sans-serif !important;
6
+ }
7
+
8
+ pre, code, tt, dt code, table tr td dt code, ul.summary, h3.signature .aliases .names,
9
+ p.signature, h3.signature, .tags ul .name, p.inherited a {
10
+ font-family: 'Menlo', 'Roboto Mono', monospace !important;
11
+ }
data/rubirai.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'rubirai/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'rubirai'
8
+ s.version = Rubirai::VERSION
9
+ s.summary = 'A Mirai QQ bot http interface lib for Ruby.'
10
+ s.description = 'A Mirai QQ bot http interface lib for Ruby.'
11
+ s.authors = ['Rebuild']
12
+ s.email = 'admin@rebuild.moe'
13
+ s.licenses = ['AGPLv3']
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.require_paths = ['lib']
17
+
18
+ s.required_ruby_version = '>= 2.6'
19
+
20
+ s.add_dependency 'concurrent-ruby', '~> 1.1.8'
21
+ s.add_dependency 'http', '~> 5.0'
22
+ s.homepage = 'https://github.com/Shimogawa/rubirai'
23
+ s.license = 'AGPLv3'
24
+ end
data/spec/auth_spec.rb ADDED
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'auth api' do
6
+ before :all do
7
+ @mirai_bot = new_bot
8
+ end
9
+
10
+ after do
11
+ # Do nothing
12
+ end
13
+
14
+ it 'should be able to authenticate' do
15
+ stub_request(:post, @mirai_bot.gen_uri('/auth'))
16
+ .with(body: {
17
+ "authKey": @auth_key
18
+ })
19
+ .to_return(status: 200, body: %({
20
+ "code": 0,
21
+ "session": "#{@session_key}"
22
+ }))
23
+ res = @mirai_bot.auth @auth_key
24
+ expect(res).to be_a_kind_of(String)
25
+ expect(res).to eq(@session_key)
26
+ end
27
+
28
+ it 'should raise error if authenticate fails' do
29
+ mirai_bot = new_bot
30
+ stub_request(:post, @mirai_bot.gen_uri('/auth'))
31
+ .with(body: {
32
+ "authKey": @auth_key
33
+ })
34
+ .to_return(status: 200, body: %({
35
+ "code": 1,
36
+ "session": ""
37
+ }))
38
+
39
+ expect { mirai_bot.auth @auth_key }.to raise_error(
40
+ Rubirai::MiraiError,
41
+ 'Mirai error: 1 - Wrong auth key'
42
+ )
43
+ end
44
+
45
+ it 'should be able to verify' do
46
+ stub_request(:post, @mirai_bot.gen_uri('/verify'))
47
+ .with(body: {
48
+ "sessionKey": @session_key,
49
+ "qq": @qq
50
+ })
51
+ .to_return(status: 200, body: %({
52
+ "code": 0,
53
+ "session": "success"
54
+ }))
55
+
56
+ expect { @mirai_bot.verify @qq }.not_to raise_error
57
+ expect(@mirai_bot.verify(@qq)).to be_nil
58
+
59
+ expect { @mirai_bot.verify '1ab39cde' }.to raise_error(Rubirai::RubiraiError, 'Wrong format for qq')
60
+ expect { new_bot.verify @qq }.to raise_error(Rubirai::RubiraiError, 'No session provided')
61
+ end
62
+
63
+ it 'should be able to release' do
64
+ stub_request(:post, @mirai_bot.gen_uri('/release'))
65
+ .with(body: {
66
+ "sessionKey": @session_key,
67
+ "qq": @qq
68
+ })
69
+ .to_return(status: 200, body: %({
70
+ "code": 0,
71
+ "msg": "success"
72
+ }))
73
+
74
+ expect do
75
+ expect(@mirai_bot.release(@qq)).to be_nil
76
+ end.not_to raise_error
77
+ expect(@mirai_bot.session).to be_nil
78
+ end
79
+
80
+ it 'should be able to login and logout' do
81
+ mirai_bot = Rubirai::Bot.new 'test'
82
+ stub_request(:post, @mirai_bot.gen_uri('/auth'))
83
+ .with(body: {
84
+ "authKey": @auth_key
85
+ })
86
+ .to_return(status: 200, body: %({
87
+ "code": 0,
88
+ "session": "#{@session_key}"
89
+ }))
90
+ stub_request(:post, @mirai_bot.gen_uri('/verify'))
91
+ .with(body: {
92
+ "sessionKey": @session_key,
93
+ "qq": @qq
94
+ })
95
+ .to_return(status: 200, body: %({
96
+ "code": 0,
97
+ "session": "success"
98
+ }))
99
+ stub_request(:post, @mirai_bot.gen_uri('/release'))
100
+ .with(body: {
101
+ "sessionKey": @session_key,
102
+ "qq": @qq
103
+ })
104
+ .to_return(status: 200, body: %({
105
+ "code": 0,
106
+ "msg": "success"
107
+ }))
108
+ expect do
109
+ expect(mirai_bot.login(@qq, @auth_key)).to be_nil
110
+ end.not_to raise_error
111
+ expect(mirai_bot.instance_variable_get('@session')).to eq(@session_key)
112
+
113
+ expect do
114
+ expect(mirai_bot.logout).to be_nil
115
+ end.not_to raise_error
116
+ expect(mirai_bot.instance_variable_get('@session')).to be_nil
117
+ end
118
+ end