vk_cozy 0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84172a36d37fa7b748679799a2baedad22b63e88f03a59ede5742e0d44731221
4
- data.tar.gz: d5370aa9ca6b774890ebeb52006102d1d28e5aa6f2acbd38c67bbdcbd9bcab18
3
+ metadata.gz: 258b9457cabc9453353250a7f51db038240ef43bb1b93b68ba4128fdfb59031d
4
+ data.tar.gz: c35f4cbf0564408024d238ca505f28b076fdb57b40dddfcb338707b7847d82fc
5
5
  SHA512:
6
- metadata.gz: 9fcd0cf360e2f41524459621f8e1f388a2bb037629218ccd7274d5946e3a3cf805237302b092d4f3c4b1a084526c2057dbefb781c4485e2b876c1da6759f996e
7
- data.tar.gz: e89c98542748a160e64ab434873e3330f9a8124f981f2d56b2b003795d9d4994f10dacb1a1a404c65f9b75561b73f26e3aca4770a4857832288b4a6499c6aad2
6
+ metadata.gz: 8fe4888bcb9db9859ac768c5ec0ba3e5c70d2adc38a3513e535254daf942c09708e9cb461a1161099d0ed9b5470d76b9863593fccdd82856ed1361333fb3681b
7
+ data.tar.gz: 0dde51243767756401df2988a07befaa65e6df3ef0dfb9e11b4e3ab58a09ea15c16ddcc70d2ab6cbb6171ac196b02349a00a52a6c568d7235c6faff10bbb1e4d
@@ -0,0 +1,50 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'net/http'
4
+ require "resolv-replace"
5
+
6
+ class Api
7
+ attr_accessor :access_token, :version
8
+
9
+ SCHEME = 'https'
10
+ HOST = 'api.vk.com'
11
+ PATH = '/method/'
12
+ PORT = 443
13
+
14
+ def initialize(access_token, version=5.92)
15
+ @access_token = access_token
16
+ @version = version
17
+ end
18
+
19
+ def request_thr(method_vk, data)
20
+ thr = Thread.new {
21
+ request(method_vk, data)
22
+ }
23
+ thr.run
24
+ # thr.value
25
+ end
26
+
27
+ def request(method_vk, data)
28
+ data = data.to_hash
29
+ data = data.merge(v: version)
30
+ data = data.merge(access_token: access_token)
31
+ data.each do |argument, value|
32
+ data[argument] = value.join(',') if value.is_a?(Array)
33
+ end
34
+ http_response = Net::HTTP.post_form(url_for_method(method_vk), data).body
35
+ # return unless http_response.present?
36
+ json_response = JSON.parse(http_response)
37
+ if json_response['error']
38
+ raise json_response['error']['error_msg']
39
+ end
40
+ json_response
41
+ end
42
+
43
+ def url_for_method(method_vk)
44
+ URI.parse("#{SCHEME}://#{HOST}#{PATH}#{method_vk}")
45
+ end
46
+
47
+ def method_missing name, **kwargs
48
+ return request(name.to_s.sub('_', '.'), kwargs)['response']
49
+ end
50
+ end
@@ -0,0 +1,55 @@
1
+ require_relative 'types/events/bot_events'
2
+ require_relative 'dispatch/views/bot/event'
3
+ require_relative 'framework/labeler/bot'
4
+ require_relative 'polling/bot_polling'
5
+
6
+ module VkCozy
7
+ class Bot
8
+ attr_reader :api
9
+
10
+ CLASS_BY_EVENT_TYPE = {
11
+ BotEventType::MESSAGE_NEW => BotMessageEvent,
12
+ BotEventType::MESSAGE_REPLY => BotMessageEvent,
13
+ BotEventType::MESSAGE_EDIT => BotMessageEvent
14
+ }
15
+
16
+ DEFAULT_EVENT_CLASS = BotEvent
17
+
18
+ def initialize(access_token, version=5.92, api=nil)
19
+ @access_token = access_token
20
+ if api.nil?
21
+ @api = Api.new(access_token, version)
22
+ else
23
+ @api = api
24
+ end
25
+ @polling = VkCozy::BotPolling.new(@api)
26
+ @labeler = VkCozy::BotLabeler.new(@api)
27
+ end
28
+
29
+ def on
30
+ return @labeler
31
+ end
32
+
33
+ def run_polling
34
+ @polling.listen do |event|
35
+ for event_raw in event['updates']
36
+ begin
37
+ @labeler.filter(parse_event(event_raw))
38
+ rescue Exception => e
39
+ raise e
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def parse_event(event_raw)
48
+ event_class = CLASS_BY_EVENT_TYPE.fetch(
49
+ event_raw['type'],
50
+ DEFAULT_EVENT_CLASS
51
+ )
52
+ return event_class.new(@api, event_raw)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,67 @@
1
+ module VkCozy
2
+ class BotEvent
3
+ attr_reader :api, :raw, :t, :type, :obj, :object, :client_info, :message, :group_id
4
+ def initialize(api, event_raw)
5
+ @api = api
6
+ @raw = event_raw
7
+
8
+ @type = event_raw['type']
9
+ @t = @type
10
+
11
+ @object = event_raw['object'].to_dot
12
+ @obj = @object
13
+
14
+ @message = @obj['message']
15
+
16
+ @client_info = @obj['client_info']
17
+
18
+ @group_id = @raw['group_id']
19
+
20
+ end
21
+
22
+ def [] key
23
+ instance_variable_get("@#{key}")
24
+ end
25
+
26
+ def to_s
27
+ "BotEvent(#{@raw.to_s})"
28
+ end
29
+ end
30
+
31
+ class BotMessageEvent < BotEvent
32
+ attr_reader :from_user, :from_chat, :from_group, :chat_id
33
+ def initialize(api, event_raw)
34
+ super(api, event_raw)
35
+ @from_user = false
36
+ @from_chat = false
37
+ @from_group = false
38
+
39
+
40
+ peer_id = @raw['object']['peer_id']
41
+ if peer_id.nil?
42
+ peer_id = @raw['object']['message']['peer_id']
43
+ end
44
+
45
+ if peer_id < 0
46
+ @from_group = true
47
+ elsif peer_id < 2e9
48
+ @from_user = true
49
+ else
50
+ from_user = true
51
+ @chat_id = peer_id - 2e9
52
+ end
53
+ end
54
+
55
+ def answer(text)
56
+ return @api.messages_send(
57
+ peer_id: @message.peer_id,
58
+ message: text,
59
+ random_id: 0
60
+ )
61
+ end
62
+
63
+ def to_s
64
+ "BotMessageEvent(#{@raw.to_s})"
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,80 @@
1
+ module VkCozy
2
+ class UserEvent
3
+ attr_accessor :api, :raw, :type,
4
+ :from_user, :from_chat, :from_group, :from_me, :to_me,
5
+ :attachments, :message_data,
6
+ :message_id, :timestamp, :text, :peer_id, :flags, :extra, :extra_values, :type_id
7
+
8
+ def initialize(api, raw_event)
9
+ @api = api
10
+ @raw = raw_event
11
+
12
+ @from_user = false
13
+ @from_chat = false
14
+ @from_group = false
15
+ @from_me = false
16
+ @to_me = false
17
+
18
+ begin
19
+ @type = UserEventType.parse(@raw[0])
20
+ list_to_attr(@raw[1, @raw.length], EVENT_ATTRS_MAPPING[@type])
21
+ rescue StandardError => e
22
+ @type = @raw[0]
23
+ end
24
+
25
+ if VkCozy::PARSE_PEER_ID_EVENTS.include?(@type)
26
+ parse_peer_id()
27
+ end
28
+
29
+ if VkCozy::PARSE_MESSAGE_FLAGS_EVENTS
30
+ parse_message()
31
+ end
32
+ end
33
+
34
+ def to_s
35
+ instance_variables.each_with_object({}) do |k, h|
36
+ h[k] = instance_variable_get("#{k}")
37
+ end.to_json
38
+ end
39
+
40
+ def answer(text)
41
+ return @api.messages_send(
42
+ peer_id: @peer_id,
43
+ message: text,
44
+ random_id: 0
45
+ )
46
+ end
47
+
48
+ private
49
+
50
+ def list_to_attr(raw, attrs)
51
+ for i in (0..[raw.length, attrs.length].min)
52
+ instance_variable_set("@#{attrs[i]}", raw[i]) if respond_to? "#{attrs[i]}="
53
+ end
54
+ end
55
+
56
+ def parse_peer_id
57
+ if @peer_id < 0
58
+ @from_group = true
59
+ @group_id = peer_id
60
+
61
+ elsif @peer_id > 2e9
62
+ @from_chat = true
63
+ @chat_id = @peer_id-2e9
64
+
65
+ if @extra_values and @extra_values.include?('from')
66
+ @user_id = @extra_values['from'].to_i
67
+ end
68
+ else
69
+ @from_user = true
70
+ @user_id = @peer_id
71
+ end
72
+ end
73
+
74
+ def parse_message
75
+ if @type == UserEventType::MESSAGE_NEW
76
+
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,34 @@
1
+ module VkCozy
2
+ class BotLabeler
3
+ attr_reader :api
4
+
5
+ def initialize(api)
6
+ @api
7
+ @rules = []
8
+ end
9
+
10
+ def filter(event)
11
+ for i in @rules
12
+ f = i[:filter]
13
+ check = f.check_bot(event)
14
+ if check
15
+ if check.is_a?(Hash)
16
+ i[:func].call(event, **check)
17
+ else
18
+ i[:func].call(event)
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ def message_handler(filter, func)
25
+ if func.is_a?(Symbol)
26
+ func = method(func)
27
+ end
28
+ @rules << {
29
+ :func => func,
30
+ :filter => filter
31
+ }
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,46 @@
1
+ module VkCozy
2
+ class BaseFilter
3
+ def check_user(event) # Method check for user-bot
4
+ raise 'Method check_user not implemented'
5
+ end
6
+
7
+ def check_bot(event) # Method check for group-bot
8
+ raise 'Method check_bot not implemented'
9
+ end
10
+ end
11
+
12
+ class Text < BaseFilter
13
+ def initialize(regex, **kwargs)
14
+ @regex = regex
15
+ @raw = kwargs
16
+ end
17
+
18
+ def check_user(event)
19
+ if event.type == VkCozy::UserEventType::MESSAGE_NEW
20
+
21
+ if event.from_me
22
+ return false
23
+ end
24
+ if event.text == @regex
25
+ return true
26
+ else
27
+ return false
28
+ end
29
+ else
30
+ return false
31
+ end
32
+ end
33
+
34
+ def check_bot(event)
35
+ if event.type == VkCozy::BotEventType::MESSAGE_NEW
36
+ if event.message.text == @regex
37
+ return true
38
+ else
39
+ return false
40
+ end
41
+ else
42
+ return false
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,35 @@
1
+ module VkCozy
2
+ class UserLabeler
3
+ attr_reader :api
4
+
5
+ def initialize(api)
6
+ @api = api
7
+ @rules = []
8
+ end
9
+
10
+ def filter(event_raw)
11
+ event = VkCozy::UserEvent.new(@api, event_raw)
12
+ for i in @rules
13
+ f = i[:filter]
14
+ check = f.check_user(event)
15
+ if check
16
+ if check.is_a?(Hash)
17
+ i[:func].call(event, **check)
18
+ else
19
+ i[:func].call(event)
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def message_handler(filter, func)
26
+ if func.is_a?(Symbol)
27
+ func = method(func)
28
+ end
29
+ @rules << {
30
+ :func => func,
31
+ :filter => filter
32
+ }
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ module VkCozy
2
+ class BotPolling
3
+ def initialize(api, group_id=nil, wait=25, rps_delay=0)
4
+ @api = api
5
+ if group_id.nil?
6
+ @group_id = @api.request('groups.getById', {})['response'][0]['id']
7
+ puts @group_id
8
+ else
9
+ @group_id = group_id
10
+ end
11
+
12
+ @rps_delay = rps_delay
13
+ @stop = false
14
+ end
15
+
16
+ def get_server
17
+ return @api.request('groups.getLongPollServer', {:group_id => @group_id})['response']
18
+ end
19
+
20
+ def get_event(server)
21
+ uri = URI.parse('%s?act=a_check&key=%s&ts=%s&wait=%s&rps_delay=%s' % [server['server'], server['key'], server['ts'], @wait, @rps_delay])
22
+ http_response = Net::HTTP.get(uri)
23
+ return JSON.parse(http_response)
24
+ end
25
+
26
+ def listen
27
+ server = get_server
28
+ until @stop do
29
+ event = get_event(server)
30
+ if not event['ts']
31
+ server = get_server
32
+ next
33
+ end
34
+ server['ts'] = event['ts']
35
+ yield event
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,39 @@
1
+ module VkCozy
2
+ class UserPolling
3
+ def initialize(api=nil, user_id=nil, mode=234, wait=25, rps_delay=0, error_handler=nil)
4
+ @api = api
5
+ if user_id.nil?
6
+ @user_id = @api.request('users.get', {})['response'][0]['id']
7
+ else
8
+ @user_id = user_id
9
+ end
10
+ @mode = mode
11
+ @wait = wait
12
+ @rps_delay = rps_delay
13
+ @stop = false
14
+ end
15
+
16
+ def get_event(server)
17
+ uri = URI.parse('https://%s?act=a_check&key=%s&ts=%s&wait=%s&mode=%s&rps_delay=%s&version=%s' % [server['server'], server['key'], server['ts'], @wait, @mode, @rps_delay, 3])
18
+ http_response = Net::HTTP.get(uri)
19
+ return JSON.parse(http_response)
20
+ end
21
+
22
+ def get_server
23
+ return @api.request('messages.getLongPollServer', {})['response']
24
+ end
25
+
26
+ def listen
27
+ server = get_server
28
+ until @stop do
29
+ event = get_event(server)
30
+ if not event['ts']
31
+ server = get_server
32
+ next
33
+ end
34
+ server['ts'] = event['ts']
35
+ yield event
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,73 @@
1
+ module VkCozy
2
+ class BotEventType
3
+ include Ruby::Enum
4
+
5
+ define :MESSAGE_NEW, 'message_new'
6
+ define :MESSAGE_REPLY, 'message_reply'
7
+ define :MESSAGE_EDIT, 'message_edit'
8
+ define :MESSAGE_EVENT, 'message_event'
9
+
10
+ define :MESSAGE_TYPING_STATE, 'message_typing_state'
11
+
12
+ define :MESSAGE_ALLOW, 'message_allow'
13
+
14
+ define :MESSAGE_DENY, 'message_deny'
15
+
16
+ define :PHOTO_NEW, 'photo_new'
17
+
18
+ define :PHOTO_COMMENT_NEW, 'photo_comment_new'
19
+ define :PHOTO_COMMENT_EDIT, 'photo_comment_edit'
20
+ define :PHOTO_COMMENT_RESTORE, 'photo_comment_restore'
21
+
22
+ define :PHOTO_COMMENT_DELETE, 'photo_comment_delete'
23
+
24
+ define :AUDIO_NEW, 'audio_new'
25
+
26
+ define :VIDEO_NEW, 'video_new'
27
+
28
+ define :VIDEO_COMMENT_NEW, 'video_comment_new'
29
+ define :VIDEO_COMMENT_EDIT, 'video_comment_edit'
30
+ define :VIDEO_COMMENT_RESTORE, 'video_comment_restore'
31
+
32
+ define :VIDEO_COMMENT_DELETE, 'video_comment_delete'
33
+
34
+ define :WALL_POST_NEW, 'wall_post_new'
35
+ define :WALL_REPOST, 'wall_repost'
36
+
37
+ define :WALL_REPLY_NEW, 'wall_reply_new'
38
+ define :WALL_REPLY_EDIT, 'wall_reply_edit'
39
+ define :WALL_REPLY_RESTORE, 'wall_reply_restore'
40
+
41
+ define :WALL_REPLY_DELETE, 'wall_reply_delete'
42
+
43
+ define :BOARD_POST_NEW, 'board_post_new'
44
+ define :BOARD_POST_EDIT, 'board_post_edit'
45
+ define :BOARD_POST_RESTORE, 'board_post_restore'
46
+
47
+ define :BOARD_POST_DELETE, 'board_post_delete'
48
+
49
+ define :MARKET_COMMENT_NEW, 'market_comment_new'
50
+ define :MARKET_COMMENT_EDIT, 'market_comment_edit'
51
+ define :MARKET_COMMENT_RESTORE, 'market_comment_restore'
52
+
53
+ define :MARKET_COMMENT_DELETE, 'market_comment_delete'
54
+
55
+ define :GROUP_LEAVE, 'group_leave'
56
+
57
+ define :GROUP_JOIN, 'group_join'
58
+
59
+ define :USER_BLOCK, 'user_block'
60
+
61
+ define :USER_UNBLOCK, 'user_unblock'
62
+
63
+ define :POLL_VOTE_NEW, 'poll_vote_new'
64
+
65
+ define :GROUP_OFFICERS_EDIT, 'group_officers_edit'
66
+
67
+ define :GROUP_CHANGE_SETTINGS, 'group_change_settings'
68
+
69
+ define :GROUP_CHANGE_PHOTO, 'group_change_photo'
70
+
71
+ define :VKPAY_TRANSACTION, 'vkpay_transaction'
72
+ end
73
+ end
@@ -0,0 +1,67 @@
1
+ module VkCozy
2
+ class UserEventType < Inum::Base
3
+ define :UNDEFINED_EVENT, -1
4
+
5
+ define :REPLACE_MESSAGE_FLAGS, 1
6
+ define :INSTALL_MESSAGE_FLAGS, 2
7
+ define :RESET_MESSAGE_FLAGS, 3
8
+
9
+ define :MESSAGE_NEW, 4
10
+ define :MESSAGE_EDIT, 5
11
+ define :IN_READ, 6
12
+ define :OUT_READ, 7
13
+ define :FRIEND_ONLINE, 8
14
+ define :FRIEND_OFFLINE, 9
15
+ define :RESET_DIALOG_FLAGS, 10
16
+ define :REPLACE_DIALOG_FLAGS, 11
17
+ define :INSTALL_DIALOG_FLAGS, 12
18
+ define :MESSAGES_DELETE, 13
19
+ define :MESSAGES_RESTORE, 14
20
+
21
+ define :MESSAGE_CHANGE, 18
22
+ define :CLEAR_MESSAGE_CACHE, 19
23
+
24
+ define :CHANGE_MAJOR_ID, 20
25
+ define :CHANGE_MINOR_ID, 21
26
+
27
+ define :CHAT_EDIT, 51
28
+ define :CHAT_INFO_EDIT, 52
29
+ define :DIALOG_TYPING_STATE, 61
30
+
31
+ define :CHAT_TYPING_STATE, 62
32
+ define :USERS_TYPING_STATE, 63
33
+ define :CHAT_VOICE_MESSAGE_STATES, 64
34
+ define :PHOTO_UPLOAD_STATE, 65
35
+ define :VIDEO_UPLOAD_STATE, 66
36
+ define :FILE_UPLOAD_STAE, 67
37
+
38
+ define :CALL, 70
39
+ define :COUNTER, 80
40
+ define :USER_INVISIBLE_CHANGE, 81
41
+ define :NOTIFICATIONS_SETTINGS_CHANGED, 114
42
+ define :CHAT_CALL, 115
43
+ define :CALLBACK_BUTTON_REPLY, 119
44
+ end
45
+
46
+ MESSAGE_EXTRA_FIELDS = [
47
+ 'peer_id', 'timestamp', 'text', 'extra_values', 'attachments', 'random_id'
48
+ ]
49
+ MSGID = 'message_id'
50
+ EVENT_ATTRS_MAPPING = {
51
+ VkCozy::UserEventType::REPLACE_MESSAGE_FLAGS => [MSGID, 'flags'] + MESSAGE_EXTRA_FIELDS,
52
+ VkCozy::UserEventType::INSTALL_MESSAGE_FLAGS => [MSGID, 'mask'] + MESSAGE_EXTRA_FIELDS,
53
+ VkCozy::UserEventType::RESET_MESSAGE_FLAGS => [MSGID, 'mask'] + MESSAGE_EXTRA_FIELDS,
54
+ VkCozy::UserEventType::MESSAGE_NEW => [MSGID, 'flags'] + MESSAGE_EXTRA_FIELDS,
55
+ VkCozy::UserEventType::MESSAGE_EDIT => [MSGID, 'mask'] + MESSAGE_EXTRA_FIELDS,
56
+ VkCozy::UserEventType::IN_READ => ['peer_id', 'local_id'],
57
+ VkCozy::UserEventType::OUT_READ => ['peer_id', 'local_id'],
58
+ VkCozy::UserEventType::FRIEND_ONLINE => ['user_id', 'extra', 'timestamp'],
59
+ VkCozy::UserEventType::FRIEND_OFFLINE => ['user_id', 'flags', 'timestamp'],
60
+ VkCozy::UserEventType::DIALOG_TYPING_STATE => ['user_id', 'flags']
61
+ }
62
+ PARSE_PEER_ID_EVENTS = EVENT_ATTRS_MAPPING.map{|k, v| if v.include?('peer_id') then k end}.select{ |i| not i.nil? }
63
+ PARSE_MESSAGE_FLAGS_EVENTS = [
64
+ VkCozy::UserEventType::REPLACE_MESSAGE_FLAGS,
65
+ VkCozy::UserEventType::MESSAGE_NEW
66
+ ]
67
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'types/events/user_events'
2
+ require_relative 'dispatch/views/user/event'
3
+ require_relative 'framework/labeler/user'
4
+ require_relative 'polling/user_polling'
5
+
6
+ module VkCozy
7
+ class User
8
+ attr_reader :api
9
+
10
+ def initialize(access_token, version=5.92, api=nil)
11
+ @access_token = access_token
12
+ if api.nil?
13
+ @api = Api.new(access_token, version)
14
+ else
15
+ @api = api
16
+ end
17
+ @polling = VkCozy::UserPolling.new(@api)
18
+ @labeler = VkCozy::UserLabeler.new(@api)
19
+ end
20
+
21
+ def on
22
+ return @labeler
23
+ end
24
+
25
+ def run_polling
26
+ @polling.listen do |event|
27
+ for update in event['updates']
28
+ begin
29
+ @labeler.filter(update)
30
+ rescue Exception => e
31
+ raise e
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vk_cozy
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.2'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Danil Konenko
@@ -60,7 +60,19 @@ extra_rdoc_files: []
60
60
  files:
61
61
  - Gemfile
62
62
  - lib/vk_cozy.rb
63
- homepage: https://rubygems.org/gems/hola
63
+ - lib/vk_cozy/api/api.rb
64
+ - lib/vk_cozy/bot.rb
65
+ - lib/vk_cozy/dispatch/views/bot/event.rb
66
+ - lib/vk_cozy/dispatch/views/user/event.rb
67
+ - lib/vk_cozy/framework/labeler/bot.rb
68
+ - lib/vk_cozy/framework/labeler/filters/filters.rb
69
+ - lib/vk_cozy/framework/labeler/user.rb
70
+ - lib/vk_cozy/polling/bot_polling.rb
71
+ - lib/vk_cozy/polling/user_polling.rb
72
+ - lib/vk_cozy/types/events/bot_events.rb
73
+ - lib/vk_cozy/types/events/user_events.rb
74
+ - lib/vk_cozy/user.rb
75
+ homepage: https://github.com/VkCozy/vk_cozy
64
76
  licenses:
65
77
  - MIT
66
78
  metadata: {}