talkbird 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/.gitignore +13 -0
- data/.reek.yml +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +49 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/README.md +7 -0
- data/Rakefile +8 -0
- data/bin/console +8 -0
- data/bin/setup +8 -0
- data/lib/talkbird.rb +27 -0
- data/lib/talkbird/client.rb +123 -0
- data/lib/talkbird/entity/channel.rb +91 -0
- data/lib/talkbird/entity/message.rb +36 -0
- data/lib/talkbird/entity/user.rb +168 -0
- data/lib/talkbird/instrumentation/event.rb +105 -0
- data/lib/talkbird/result.rb +21 -0
- data/lib/talkbird/result/basic.rb +10 -0
- data/lib/talkbird/result/exception.rb +15 -0
- data/lib/talkbird/result/failure.rb +24 -0
- data/lib/talkbird/result/paginated_success.rb +62 -0
- data/lib/talkbird/result/success.rb +31 -0
- data/lib/talkbird/version.rb +7 -0
- data/talkbird.gemspec +37 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 978edc8d5c34ca0fe0fd8ff76d1777784d374ac57c06042c9fdafc57696acdfd
|
4
|
+
data.tar.gz: c9689d6c654bbe36cc8b9c58185dbaf72657853c0bbbbdb51efab40677eac5d6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 84a9813b6be54f37fe91f8286d76682ae88371b0ca6ab583a975c4eb9a33d8f94fba91769abc4a4a4875475879fa467a9b297c89120e8048c40a38942caff949
|
7
|
+
data.tar.gz: f93e25ae58c4616c8f6ba5a47683f72e85ac9d4cb5ccff389589ceac5f7ef4929d3b80ed7e6a9eb4ea5ec69cee8844f0ec8f880f5200c5cdba22423f6eee639c
|
data/.gitignore
ADDED
data/.reek.yml
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
---
|
2
|
+
|
3
|
+
# Generic smell configuration
|
4
|
+
detectors:
|
5
|
+
# According to a study done by Card, Church, and Agresti (1986) and a study
|
6
|
+
# done by Card and Glass (1990) small routines with 32 or fewer lines of code
|
7
|
+
# were not corelated with lower cost or fault rate.
|
8
|
+
#
|
9
|
+
# Evidence suggested that larger routines (65 lines of code or more) were
|
10
|
+
# cheaper to develop per line of code.
|
11
|
+
# -- Code Complete 2
|
12
|
+
#
|
13
|
+
# Since Ruby is more terse than a lot of languages and because there seems to
|
14
|
+
# be a relationship between error rate and structural complexity, the limit
|
15
|
+
# should probably be slightly lower. However, the default value of 5 is too
|
16
|
+
# low.
|
17
|
+
TooManyStatements:
|
18
|
+
max_statements: 20
|
19
|
+
TooManyMethods:
|
20
|
+
max_methods: 30
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
AllCops:
|
2
|
+
TargetRubyVersion: 2.4.2
|
3
|
+
|
4
|
+
MethodLength:
|
5
|
+
Max: 20
|
6
|
+
|
7
|
+
BlockLength:
|
8
|
+
Max: 70
|
9
|
+
|
10
|
+
Exclude:
|
11
|
+
- 'Rakefile'
|
12
|
+
- 'rakelib/**/*.rake'
|
13
|
+
- 'spec/**/*.rb'
|
14
|
+
|
15
|
+
Metrics/LineLength:
|
16
|
+
Max: 80
|
17
|
+
|
18
|
+
Naming/RescuedExceptionsVariableName:
|
19
|
+
PreferredName: exception
|
20
|
+
|
21
|
+
Style/GuardClause:
|
22
|
+
Enabled: false
|
23
|
+
|
24
|
+
Style/Next:
|
25
|
+
Enabled: false
|
26
|
+
|
27
|
+
Style/SymbolArray:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
Style/SymbolProc:
|
31
|
+
Enabled: false
|
32
|
+
|
33
|
+
Style/WordArray:
|
34
|
+
Enabled: false
|
35
|
+
|
36
|
+
Style/YodaCondition:
|
37
|
+
EnforcedStyle: forbid_for_equality_operators_only
|
38
|
+
|
39
|
+
Layout/EmptyLinesAroundClassBody:
|
40
|
+
EnforcedStyle: empty_lines_except_namespace
|
41
|
+
|
42
|
+
Layout/EmptyLinesAroundModuleBody:
|
43
|
+
EnforcedStyle: empty_lines_except_namespace
|
44
|
+
|
45
|
+
Layout/MultilineMethodCallIndentation:
|
46
|
+
EnforcedStyle: aligned
|
47
|
+
|
48
|
+
Layout/SpaceInsidePercentLiteralDelimiters:
|
49
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/console
ADDED
data/bin/setup
ADDED
data/lib/talkbird.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'singleton'
|
4
|
+
require 'addressable'
|
5
|
+
require 'http'
|
6
|
+
require 'multi_json'
|
7
|
+
require 'active_support/notifications'
|
8
|
+
|
9
|
+
require 'talkbird/version'
|
10
|
+
require 'talkbird/client'
|
11
|
+
|
12
|
+
require 'talkbird/result'
|
13
|
+
require 'talkbird/result/basic'
|
14
|
+
require 'talkbird/result/success'
|
15
|
+
require 'talkbird/result/paginated_success'
|
16
|
+
require 'talkbird/result/failure'
|
17
|
+
require 'talkbird/result/exception'
|
18
|
+
|
19
|
+
require 'talkbird/instrumentation/event'
|
20
|
+
|
21
|
+
require 'talkbird/entity/channel'
|
22
|
+
require 'talkbird/entity/message'
|
23
|
+
require 'talkbird/entity/user'
|
24
|
+
|
25
|
+
# Unofficial SendBird API client.
|
26
|
+
module Talkbird
|
27
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
# Class that handles the basic calls to SendBird
|
5
|
+
class Client
|
6
|
+
|
7
|
+
VERSION = 'v3'
|
8
|
+
SCHEME = 'https'
|
9
|
+
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def token
|
15
|
+
token = ENV['SENDBIRD_API_TOKEN'].to_s
|
16
|
+
|
17
|
+
if token.empty?
|
18
|
+
raise ArgumentError, 'Missing SendBird API token from ENV'
|
19
|
+
else
|
20
|
+
token
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def application_id
|
25
|
+
app_id = ENV['SENDBIRD_APP_ID'].to_s
|
26
|
+
|
27
|
+
if app_id.empty?
|
28
|
+
raise ArgumentError, 'Missing SendBird application ID from ENV'
|
29
|
+
else
|
30
|
+
app_id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def host
|
35
|
+
"api-#{application_id}.sendbird.com"
|
36
|
+
end
|
37
|
+
|
38
|
+
def uri(path, params = {})
|
39
|
+
if path.is_a?(HTTP::URI)
|
40
|
+
build_uri_from_existing(path, params)
|
41
|
+
else
|
42
|
+
build_uri_from_partial_path(path, params)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def request(method, path, opts = {})
|
47
|
+
default_headers = {
|
48
|
+
'Api-Token' => token,
|
49
|
+
'Content-Type' => 'application/json; charset=utf8'
|
50
|
+
}
|
51
|
+
|
52
|
+
req = HTTP::Request.new(
|
53
|
+
verb: method,
|
54
|
+
uri: uri(path, opts[:params]),
|
55
|
+
headers: (opts[:headers] || {}).merge(default_headers),
|
56
|
+
body: MultiJson.dump(opts[:body])
|
57
|
+
)
|
58
|
+
|
59
|
+
response = Client.instance.request(req, opts)
|
60
|
+
Talkbird::Result.create(response)
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def build_uri_from_existing(path, params = {})
|
66
|
+
uri = Addressable::URI.parse(path)
|
67
|
+
extra_params = (params || {})
|
68
|
+
.transform_keys { |key| key.to_s.downcase }
|
69
|
+
opts = {
|
70
|
+
scheme: uri.scheme,
|
71
|
+
host: uri.host,
|
72
|
+
path: uri.path
|
73
|
+
}
|
74
|
+
|
75
|
+
# Remove the token from the existing query as it is most likely invalid
|
76
|
+
# anyway.
|
77
|
+
query = (uri.query_values || {}).reject { |name, _val| name == 'token' }
|
78
|
+
|
79
|
+
opts[:query_values] = if query.empty? && extra_params.empty?
|
80
|
+
nil
|
81
|
+
else
|
82
|
+
query.merge(extra_params)
|
83
|
+
end
|
84
|
+
|
85
|
+
Addressable::URI.new(opts)
|
86
|
+
end
|
87
|
+
|
88
|
+
def build_uri_from_partial_path(partial_path, params = {})
|
89
|
+
path = [Client::VERSION, partial_path].join('/')
|
90
|
+
opts = {
|
91
|
+
scheme: Client::SCHEME,
|
92
|
+
host: Client.host,
|
93
|
+
path: path
|
94
|
+
}
|
95
|
+
|
96
|
+
# Adding the `query_values` when the `params` is an empty hash
|
97
|
+
# will add a `?` at the end of the URL.
|
98
|
+
opts[:query_values] = params if params && !params.empty?
|
99
|
+
|
100
|
+
Addressable::URI.new(opts)
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
def initialize
|
106
|
+
@http = HTTP.use(
|
107
|
+
instrumentation: {
|
108
|
+
instrumenter: ActiveSupport::Notifications.instrumenter,
|
109
|
+
namespace: Talkbird::Instrumentation::Event::NAMESPACE
|
110
|
+
}
|
111
|
+
)
|
112
|
+
|
113
|
+
Instrumentation::Event.register_instrumentation_for_request
|
114
|
+
Instrumentation::Event.register_instrumentation_for_response
|
115
|
+
end
|
116
|
+
|
117
|
+
def request(req, opts = {})
|
118
|
+
options = HTTP::Options.new(opts)
|
119
|
+
@http.perform(req, options)
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Entity
|
5
|
+
# SendBird channel
|
6
|
+
class Channel
|
7
|
+
|
8
|
+
DEFAULTS = {
|
9
|
+
distinct: true,
|
10
|
+
is_ephemeral: true
|
11
|
+
}.freeze
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def find(from, to)
|
16
|
+
result = Client.request(
|
17
|
+
:get,
|
18
|
+
"users/#{from}/my_group_channels",
|
19
|
+
params: {
|
20
|
+
members_exactly_in: to,
|
21
|
+
order: 'latest_last_message',
|
22
|
+
distinct_mode: 'distinct',
|
23
|
+
public_mode: 'private',
|
24
|
+
show_member: true,
|
25
|
+
limit: 1
|
26
|
+
}
|
27
|
+
)
|
28
|
+
|
29
|
+
if result.is_a?(Result::Success)
|
30
|
+
Channel.new(result.body[:channels].first)
|
31
|
+
else
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def create(from, to, opts = {})
|
37
|
+
body = DEFAULTS.merge(opts)
|
38
|
+
|
39
|
+
body[:channel_url] = opts.fetch(:id) { SecureRandom.uuid }
|
40
|
+
body[:user_ids] = [from, to]
|
41
|
+
|
42
|
+
result = Client.request(
|
43
|
+
:post,
|
44
|
+
'group_channels',
|
45
|
+
body: body
|
46
|
+
)
|
47
|
+
|
48
|
+
if result.is_a?(Result::Success)
|
49
|
+
Channel.new(result.body)
|
50
|
+
else
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_or_create(from, to)
|
56
|
+
Channel.find(from, to) || Channel.create(from, to)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def initialize(data = {})
|
62
|
+
@data = data
|
63
|
+
end
|
64
|
+
|
65
|
+
def id
|
66
|
+
@data[:channel_url]
|
67
|
+
end
|
68
|
+
|
69
|
+
def update(message)
|
70
|
+
body = {
|
71
|
+
user_id: message.sender.id,
|
72
|
+
message: message.body,
|
73
|
+
message_type: 'MESG',
|
74
|
+
}
|
75
|
+
|
76
|
+
result = Client.request(
|
77
|
+
:post,
|
78
|
+
"group_channels/#{id}/messages",
|
79
|
+
body: body
|
80
|
+
)
|
81
|
+
|
82
|
+
if result.is_a?(Result::Success)
|
83
|
+
Entity::Message.build(result.body)
|
84
|
+
else
|
85
|
+
false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Entity
|
5
|
+
# SendBird message
|
6
|
+
class Message
|
7
|
+
|
8
|
+
attr_reader :sender
|
9
|
+
attr_reader :receiver
|
10
|
+
attr_reader :body
|
11
|
+
|
12
|
+
class << self
|
13
|
+
|
14
|
+
def build(response)
|
15
|
+
response
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(from, to, body)
|
21
|
+
@sender = User.find_or_create(from)
|
22
|
+
@receiver = User.find_or_create(to)
|
23
|
+
@body = body
|
24
|
+
end
|
25
|
+
|
26
|
+
def deliver
|
27
|
+
return false if !sender || !receiver
|
28
|
+
|
29
|
+
channel = Entity::Channel.find_or_create(sender.id, receiver.id)
|
30
|
+
puts channel.inspect
|
31
|
+
channel.update(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Entity
|
5
|
+
# A SendBird User entity.
|
6
|
+
#
|
7
|
+
# Users can chat with each other by participanting in open channels and
|
8
|
+
# joining group channels. They are identified by their own unique ID,
|
9
|
+
# and may have a customized nickname and profile image.
|
10
|
+
#
|
11
|
+
# Various attributes of each user, as well as their actions can be
|
12
|
+
# managed through the API.
|
13
|
+
class User
|
14
|
+
|
15
|
+
DEFAULTS = {
|
16
|
+
nickname: '',
|
17
|
+
profile_url: '',
|
18
|
+
issue_session_token: true
|
19
|
+
}.freeze
|
20
|
+
|
21
|
+
class << self
|
22
|
+
|
23
|
+
# Find a user with a specific ID.
|
24
|
+
#
|
25
|
+
# @param id [String] The user's unique ID
|
26
|
+
# @return [User, Boolean]
|
27
|
+
def find(id)
|
28
|
+
result = Client.request(:get, "users/#{id}")
|
29
|
+
|
30
|
+
if result.is_a?(Result::Success)
|
31
|
+
User.new(result.body)
|
32
|
+
else
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def create(id, opts = {})
|
38
|
+
body = DEFAULTS.merge(opts)
|
39
|
+
result = Client.request(:post, 'users', body: body)
|
40
|
+
|
41
|
+
if result.is_a?(Result::Success)
|
42
|
+
User.new(result.body)
|
43
|
+
else
|
44
|
+
false
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def find_or_create(id, opts = {})
|
49
|
+
if id.is_a?(Entity::User)
|
50
|
+
id
|
51
|
+
else
|
52
|
+
User.find(id) || User.create(id, opts)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Find all the users in the application.
|
57
|
+
#
|
58
|
+
# WARNING: This may take a lot of time.
|
59
|
+
def all
|
60
|
+
result = Client.request(:get, 'users')
|
61
|
+
|
62
|
+
if result.is_a?(Result::Success)
|
63
|
+
result.body[:users].map { |data| User.new(data) }
|
64
|
+
else
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(data = {})
|
72
|
+
@data = data
|
73
|
+
end
|
74
|
+
|
75
|
+
## Basic user properties
|
76
|
+
#
|
77
|
+
# The unique ID of the user
|
78
|
+
def id
|
79
|
+
@data[:user_id]
|
80
|
+
end
|
81
|
+
|
82
|
+
# The user's nickname.
|
83
|
+
def nickname
|
84
|
+
@data[:nickname]
|
85
|
+
end
|
86
|
+
|
87
|
+
# The URL of the user's profile image.
|
88
|
+
def profile_url
|
89
|
+
@data[:profile_url]
|
90
|
+
end
|
91
|
+
|
92
|
+
# An opaque string that identifies the user.
|
93
|
+
#
|
94
|
+
# It is recommended that every user has their own access token and
|
95
|
+
# provides it uopn login for security.
|
96
|
+
def access_token
|
97
|
+
@data[:access_token]
|
98
|
+
end
|
99
|
+
|
100
|
+
# An array of inforation of session tokens that identifies the user
|
101
|
+
# session and which have no validity after their own expiration time.
|
102
|
+
#
|
103
|
+
# Each of items consists of two `session_token` and `expires_at`
|
104
|
+
# properties. The `session_token` is an opaque string and `expires_at`
|
105
|
+
# is a validation period of the session token.
|
106
|
+
#
|
107
|
+
# It is recommended that a new session token is periodically isseud to
|
108
|
+
# every user, and provided upon the user's login for security.
|
109
|
+
def session_tokens
|
110
|
+
@data[:session_tokens]
|
111
|
+
end
|
112
|
+
|
113
|
+
# Indicates if the user has ever logged into the application so far.
|
114
|
+
def ever_logged_in?
|
115
|
+
@data[:has_ever_logged_in]
|
116
|
+
end
|
117
|
+
|
118
|
+
# Indicates whether the user is currently active within the application.
|
119
|
+
def active?
|
120
|
+
@data[:active]
|
121
|
+
end
|
122
|
+
|
123
|
+
# Indicates whether the user is currently connected to a SendBird server.
|
124
|
+
def online?
|
125
|
+
@data[:online]
|
126
|
+
end
|
127
|
+
|
128
|
+
# An array of unique identifies of the user which are used as discovering
|
129
|
+
# keys when searching and adding friends.
|
130
|
+
def discovery_keys
|
131
|
+
@data[:discovery_keys]
|
132
|
+
end
|
133
|
+
|
134
|
+
# The time recoreded when the user goes offline, to indicate when they
|
135
|
+
# were last seen online, in Unix miliseconds format.
|
136
|
+
#
|
137
|
+
# If the user is online, the value is set to 0.
|
138
|
+
def last_seen_at
|
139
|
+
@data[:last_seen_at]
|
140
|
+
end
|
141
|
+
|
142
|
+
# An array of key-value pair items which store additional user
|
143
|
+
# information.
|
144
|
+
def metadata
|
145
|
+
@data[:metadata]
|
146
|
+
end
|
147
|
+
|
148
|
+
# Send a message to a user
|
149
|
+
#
|
150
|
+
# @param to [String] The Sendbird user ID that should receive the message
|
151
|
+
# @param text [String] The message body
|
152
|
+
#
|
153
|
+
# @return [Boolean]
|
154
|
+
def message(to, text)
|
155
|
+
Entity::Message.new(self, to, text).deliver
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_h
|
159
|
+
@data.to_h
|
160
|
+
end
|
161
|
+
|
162
|
+
def to_s
|
163
|
+
"#<Talkbird::Entity::User:#{id} active=#{active?} online=#{online?}>"
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Instrumentation
|
5
|
+
# General instrumentation event for an ActiveSupport
|
6
|
+
# notification.
|
7
|
+
class Event
|
8
|
+
|
9
|
+
NAMESPACE = 'sendbird'
|
10
|
+
START_EVENT = 'start_request.sendbird'
|
11
|
+
END_EVENT = 'request.sendbird'
|
12
|
+
|
13
|
+
class << self
|
14
|
+
|
15
|
+
def register_instrumentation_for_request
|
16
|
+
ActiveSupport::Notifications.subscribe(START_EVENT) do |*params|
|
17
|
+
data = Talkbird::Instrumentation::Event.new(params)
|
18
|
+
$stdout.puts data
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def register_instrumentation_for_response
|
23
|
+
ActiveSupport::Notifications.subscribe(END_EVENT) do |*params|
|
24
|
+
data = Talkbird::Instrumentation::Event.new(params)
|
25
|
+
$stdout.puts data
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def debug?
|
30
|
+
ENV.key?('SENDBIRD_DEBUG')
|
31
|
+
end
|
32
|
+
|
33
|
+
def normalized_request_data(payload)
|
34
|
+
data = payload[:request]
|
35
|
+
|
36
|
+
hsh = {
|
37
|
+
type: 'request',
|
38
|
+
method: data.verb.upcase,
|
39
|
+
url: anonymize_app_id_from_url(data.uri)
|
40
|
+
}
|
41
|
+
|
42
|
+
hsh[:body] = data.body if Event.debug?
|
43
|
+
hsh
|
44
|
+
end
|
45
|
+
|
46
|
+
def normalized_response_data(payload)
|
47
|
+
data = payload[:response]
|
48
|
+
|
49
|
+
hsh = {
|
50
|
+
type: 'response',
|
51
|
+
code: data.status,
|
52
|
+
url: anonymize_app_id_from_url(data.uri)
|
53
|
+
}
|
54
|
+
|
55
|
+
hsh[:body] = data.body if Event.debug?
|
56
|
+
hsh
|
57
|
+
end
|
58
|
+
|
59
|
+
def anonymize_app_id_from_url(url)
|
60
|
+
app_id = Talkbird::Client.application_id.downcase
|
61
|
+
|
62
|
+
url.to_s.gsub(app_id, '...')
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
# Array of expected params:
|
68
|
+
# * name
|
69
|
+
# * start_time
|
70
|
+
# * finish_time
|
71
|
+
# * id of the event
|
72
|
+
# * payload (simple hash with either :request or :response)
|
73
|
+
def initialize(params)
|
74
|
+
@name = params[0]
|
75
|
+
@start_time = params[1]
|
76
|
+
@finish_time = params[2]
|
77
|
+
@id = params[3]
|
78
|
+
@payload = params[4]
|
79
|
+
end
|
80
|
+
|
81
|
+
def normalized_payload
|
82
|
+
if @name == START_EVENT
|
83
|
+
Event.normalized_request_data(@payload)
|
84
|
+
elsif @name == END_EVENT
|
85
|
+
Event.normalized_response_data(@payload)
|
86
|
+
else
|
87
|
+
{}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def to_h
|
92
|
+
data = normalized_payload
|
93
|
+
|
94
|
+
{
|
95
|
+
id: @id
|
96
|
+
}.merge(data)
|
97
|
+
end
|
98
|
+
|
99
|
+
def to_s
|
100
|
+
to_h.map { |name, val| "#{name}=#{val}" }.join(' ')
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
# Encapsulation of the SendBird API responses.
|
5
|
+
module Result
|
6
|
+
|
7
|
+
# Select the right result type based on the response.
|
8
|
+
def self.create(response)
|
9
|
+
status_code = response.code
|
10
|
+
|
11
|
+
if 200 <= status_code && status_code < 400
|
12
|
+
PaginatedSuccess.new(response).reduce
|
13
|
+
else
|
14
|
+
Result::Failure.new(response)
|
15
|
+
end
|
16
|
+
rescue StandardError => exception
|
17
|
+
Result::Exception.new(response, exception)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Result
|
5
|
+
# Class representing a result as an exception.
|
6
|
+
class Exception < Basic
|
7
|
+
|
8
|
+
def initialize(exception, result)
|
9
|
+
@exception = exception
|
10
|
+
@result = result
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Result
|
5
|
+
class Failure < Basic
|
6
|
+
|
7
|
+
attr_reader :result
|
8
|
+
|
9
|
+
def initialize(result)
|
10
|
+
@result = result
|
11
|
+
@body = MultiJson.load(result.body.to_s, symbolize_keys: true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def code
|
15
|
+
@body[:code]
|
16
|
+
end
|
17
|
+
|
18
|
+
def message
|
19
|
+
@body[:message]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Result
|
5
|
+
# A variation of the result which should support composition.
|
6
|
+
class PaginatedSuccess
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
def deep_merge(data, elem)
|
11
|
+
data.merge(elem) do |key, oldval, newval|
|
12
|
+
if key == :next
|
13
|
+
nil
|
14
|
+
else
|
15
|
+
oldval + newval
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(result)
|
23
|
+
@result = result
|
24
|
+
end
|
25
|
+
|
26
|
+
def reduce
|
27
|
+
body = compose.reduce({}) { |data, el| PaginatedSuccess.deep_merge(data, el.body) }
|
28
|
+
response = HTTP::Response.new(
|
29
|
+
status: @result.status,
|
30
|
+
version: @result.version,
|
31
|
+
body: MultiJson.dump(body),
|
32
|
+
headers: @result.headers
|
33
|
+
)
|
34
|
+
|
35
|
+
Result::Success.new(response, body)
|
36
|
+
end
|
37
|
+
|
38
|
+
def compose
|
39
|
+
body = MultiJson.load(@result.body.to_s, symbolize_keys: true)
|
40
|
+
token = body[:next].to_s
|
41
|
+
|
42
|
+
if !token.empty?
|
43
|
+
[
|
44
|
+
Result::Success.new(@result, body),
|
45
|
+
next_page(token)
|
46
|
+
].flatten
|
47
|
+
else
|
48
|
+
[Result::Success.new(@result, body)]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def next_page(token)
|
53
|
+
Talkbird::Client.request(
|
54
|
+
:get,
|
55
|
+
@result.uri,
|
56
|
+
params: { token: token, limit: 100 }
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Talkbird
|
4
|
+
module Result
|
5
|
+
# Success monad (sort of).
|
6
|
+
class Success
|
7
|
+
|
8
|
+
attr_reader :body
|
9
|
+
|
10
|
+
def initialize(result, body = nil)
|
11
|
+
@result = result
|
12
|
+
@body = parse_body(body)
|
13
|
+
end
|
14
|
+
|
15
|
+
def status_code
|
16
|
+
@result.code
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_body(body)
|
20
|
+
if body.nil?
|
21
|
+
MultiJson.load(@result.body.to_s)
|
22
|
+
elsif body.is_a?(String)
|
23
|
+
MultiJson.load(body)
|
24
|
+
else
|
25
|
+
body
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/talkbird.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'talkbird/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'talkbird'
|
9
|
+
spec.version = Talkbird::VERSION
|
10
|
+
spec.authors = 'Andrei Maxim'
|
11
|
+
spec.email = 'andrei@andreimaxim.ro'
|
12
|
+
|
13
|
+
spec.summary = 'Unofficial gem for the SendBird API'
|
14
|
+
spec.homepage = 'https://github.com/andreimaxim/talkbird'
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added
|
18
|
+
# into git.
|
19
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
20
|
+
`git ls-files -z`
|
21
|
+
.split("\x0")
|
22
|
+
.reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
|
25
|
+
spec.bindir = 'exe'
|
26
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
|
+
spec.require_paths = ['lib']
|
28
|
+
|
29
|
+
spec.add_dependency 'activesupport', '>= 5.0'
|
30
|
+
spec.add_dependency 'multi_json', '~> 1.14'
|
31
|
+
spec.add_dependency 'http', '~> 4.2'
|
32
|
+
|
33
|
+
spec.add_development_dependency 'bundler', '~> 1.17'
|
34
|
+
spec.add_development_dependency 'pry', '~> 0.12.2'
|
35
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
36
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: talkbird
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andrei Maxim
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-11-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.14'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.14'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: http
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '4.2'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '4.2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.17'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.17'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.12.2
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.12.2
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '10.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '10.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '3.0'
|
111
|
+
description:
|
112
|
+
email: andrei@andreimaxim.ro
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- ".gitignore"
|
118
|
+
- ".reek.yml"
|
119
|
+
- ".rspec"
|
120
|
+
- ".rubocop.yml"
|
121
|
+
- ".travis.yml"
|
122
|
+
- Gemfile
|
123
|
+
- README.md
|
124
|
+
- Rakefile
|
125
|
+
- bin/console
|
126
|
+
- bin/setup
|
127
|
+
- lib/talkbird.rb
|
128
|
+
- lib/talkbird/client.rb
|
129
|
+
- lib/talkbird/entity/channel.rb
|
130
|
+
- lib/talkbird/entity/message.rb
|
131
|
+
- lib/talkbird/entity/user.rb
|
132
|
+
- lib/talkbird/instrumentation/event.rb
|
133
|
+
- lib/talkbird/result.rb
|
134
|
+
- lib/talkbird/result/basic.rb
|
135
|
+
- lib/talkbird/result/exception.rb
|
136
|
+
- lib/talkbird/result/failure.rb
|
137
|
+
- lib/talkbird/result/paginated_success.rb
|
138
|
+
- lib/talkbird/result/success.rb
|
139
|
+
- lib/talkbird/version.rb
|
140
|
+
- talkbird.gemspec
|
141
|
+
homepage: https://github.com/andreimaxim/talkbird
|
142
|
+
licenses: []
|
143
|
+
metadata: {}
|
144
|
+
post_install_message:
|
145
|
+
rdoc_options: []
|
146
|
+
require_paths:
|
147
|
+
- lib
|
148
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
requirements: []
|
159
|
+
rubygems_version: 3.0.3
|
160
|
+
signing_key:
|
161
|
+
specification_version: 4
|
162
|
+
summary: Unofficial gem for the SendBird API
|
163
|
+
test_files: []
|