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.
- checksums.yaml +7 -0
- data/.github/dependabot.yml +11 -0
- data/.github/workflows/CI.yml +64 -0
- data/.github/workflows/docs.yml +32 -0
- data/.github/workflows/pull_request.yml +34 -0
- data/.gitignore +145 -0
- data/.rubocop.yml +41 -0
- data/.yardopts +7 -0
- data/Gemfile +19 -0
- data/LICENSE +661 -0
- data/README.md +24 -0
- data/Rakefile +16 -0
- data/examples/helper.rb +3 -0
- data/examples/listener_example.rb +25 -0
- data/examples/simple_example.rb +24 -0
- data/lib/rubirai.rb +66 -0
- data/lib/rubirai/auth.rb +73 -0
- data/lib/rubirai/errors.rb +26 -0
- data/lib/rubirai/event_recv.rb +83 -0
- data/lib/rubirai/event_resp.rb +129 -0
- data/lib/rubirai/events/bot_events.rb +53 -0
- data/lib/rubirai/events/event.rb +115 -0
- data/lib/rubirai/events/message_events.rb +77 -0
- data/lib/rubirai/events/request_events.rb +35 -0
- data/lib/rubirai/events/rubirai_events.rb +17 -0
- data/lib/rubirai/listener.rb +44 -0
- data/lib/rubirai/listing.rb +37 -0
- data/lib/rubirai/management.rb +200 -0
- data/lib/rubirai/message.rb +84 -0
- data/lib/rubirai/messages/message.rb +306 -0
- data/lib/rubirai/messages/message_chain.rb +119 -0
- data/lib/rubirai/multipart.rb +44 -0
- data/lib/rubirai/objects/group.rb +23 -0
- data/lib/rubirai/objects/info.rb +71 -0
- data/lib/rubirai/objects/user.rb +30 -0
- data/lib/rubirai/plugin_info.rb +19 -0
- data/lib/rubirai/retcode.rb +18 -0
- data/lib/rubirai/session.rb +26 -0
- data/lib/rubirai/utils.rb +62 -0
- data/lib/rubirai/version.rb +9 -0
- data/misc/common.css +11 -0
- data/rubirai.gemspec +24 -0
- data/spec/auth_spec.rb +118 -0
- data/spec/error_spec.rb +30 -0
- data/spec/events/event_spec.rb +78 -0
- data/spec/message_spec.rb +12 -0
- data/spec/messages/message_chain_spec.rb +32 -0
- data/spec/messages/message_spec.rb +171 -0
- data/spec/plugin_info_spec.rb +28 -0
- data/spec/rubirai_bot_spec.rb +45 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/utils_spec.rb +70 -0
- 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
|
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
|