twitter_tweet_bot 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +172 -0
- data/lib/twitter_tweet_bot/api/access_token.rb +64 -0
- data/lib/twitter_tweet_bot/api/authorization/secure_code.rb +77 -0
- data/lib/twitter_tweet_bot/api/authorization.rb +71 -0
- data/lib/twitter_tweet_bot/api/http/base.rb +17 -0
- data/lib/twitter_tweet_bot/api/http/error.rb +24 -0
- data/lib/twitter_tweet_bot/api/http/get.rb +28 -0
- data/lib/twitter_tweet_bot/api/http/headers.rb +21 -0
- data/lib/twitter_tweet_bot/api/http/post.rb +46 -0
- data/lib/twitter_tweet_bot/api/http.rb +31 -0
- data/lib/twitter_tweet_bot/api/refresh_token.rb +55 -0
- data/lib/twitter_tweet_bot/api/tweet.rb +36 -0
- data/lib/twitter_tweet_bot/api/users_me.rb +36 -0
- data/lib/twitter_tweet_bot/api.rb +5 -0
- data/lib/twitter_tweet_bot/cache/caching.rb +32 -0
- data/lib/twitter_tweet_bot/cache/client_ext.rb +29 -0
- data/lib/twitter_tweet_bot/cache/configuration_ext.rb +18 -0
- data/lib/twitter_tweet_bot/cache/entity_ext/authorization.rb +16 -0
- data/lib/twitter_tweet_bot/cache/entity_ext/base.rb +31 -0
- data/lib/twitter_tweet_bot/cache/entity_ext/token.rb +16 -0
- data/lib/twitter_tweet_bot/cache/entity_ext.rb +2 -0
- data/lib/twitter_tweet_bot/cache/store.rb +41 -0
- data/lib/twitter_tweet_bot/cache.rb +13 -0
- data/lib/twitter_tweet_bot/client/api.rb +50 -0
- data/lib/twitter_tweet_bot/client/entity.rb +38 -0
- data/lib/twitter_tweet_bot/client.rb +17 -0
- data/lib/twitter_tweet_bot/configration.rb +32 -0
- data/lib/twitter_tweet_bot/entity/authorization.rb +15 -0
- data/lib/twitter_tweet_bot/entity/base.rb +32 -0
- data/lib/twitter_tweet_bot/entity/token.rb +17 -0
- data/lib/twitter_tweet_bot/entity/tweet.rb +19 -0
- data/lib/twitter_tweet_bot/entity/user.rb +20 -0
- data/lib/twitter_tweet_bot/entity.rb +4 -0
- data/lib/twitter_tweet_bot/version.rb +3 -0
- data/lib/twitter_tweet_bot.rb +35 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8a2b63d602240d7b56c182f40ec07306600818005a0b5d35b9cf6f8a93e405cf
|
4
|
+
data.tar.gz: 9c2b1c30135546c64319ddaf38bf75ee15184d9d3a7b0d42404c96380b036ba0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 453ac53d4a7240829771bee7d8a94e5b1b1a5bfebdb26352fbe63579bdad9df20ac2da805da551f8a8d5f9a20ab9c3e6361dbcc5c8c96e8a14f42e3201d0d7c9
|
7
|
+
data.tar.gz: 6ae79d1b5ac9de5a26830eb28ba6ae8a62e0df6e85beda2ae9975b3d625bfab7495b5b5d6a704dfd66e5b6327b7e04406b86dfd3d8587d0140058889ce73922a
|
data/README.md
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
# twitter_tweet_bot
|
2
|
+
Tweet Bot with Twitter's V2 API.<br/>
|
3
|
+
(by Twitter's V2 API / OAuth 2.0 with PCKE)
|
4
|
+
|
5
|
+
## Getting Started
|
6
|
+
|
7
|
+
```rb
|
8
|
+
# Gemfile
|
9
|
+
gem 'twitter_tweet_bot'
|
10
|
+
```
|
11
|
+
|
12
|
+
Then run `bundle install`.
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
```rb
|
17
|
+
require 'twitter_tweet_bot'
|
18
|
+
|
19
|
+
TwitterTweetBot.post_tweet(<ACEESS_TOKEN>, 'Yeah!')
|
20
|
+
```
|
21
|
+
|
22
|
+
1. Congiuration
|
23
|
+
2. Issue Authorization URL
|
24
|
+
3. Go to Authorization URL
|
25
|
+
4. Fetch Access Token
|
26
|
+
5. Post Tweet
|
27
|
+
|
28
|
+
<details>
|
29
|
+
|
30
|
+
<summary>Details</summary>
|
31
|
+
|
32
|
+
#### Step1. Congiuration
|
33
|
+
|
34
|
+
```rb
|
35
|
+
require 'twitter_tweet_bot'
|
36
|
+
|
37
|
+
TwitterTweetBot.configure do |config|
|
38
|
+
# Twitter's Bot Name (any)
|
39
|
+
config.name = 'iambot'
|
40
|
+
# Twitter's Client ID
|
41
|
+
config.client_id = '*****'
|
42
|
+
# Twitter's Client Secret
|
43
|
+
config.client_secret = '*****'
|
44
|
+
# Redirect URL After Authorization
|
45
|
+
config.redirect_uri = 'https://example.com/twitter/callback'
|
46
|
+
# Twitter's App Scopes with OAuth 2.0
|
47
|
+
config.scopes = ['tweet.read', 'tweet.write', 'users.read', 'offline.access']
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
#### Step2. Issue an authorization url
|
52
|
+
|
53
|
+
```rb
|
54
|
+
authorization = TwitterTweetBot.authorization
|
55
|
+
# =>
|
56
|
+
# #<TwitterTweetBot::Entity::Authorization
|
57
|
+
# @code_verifier="*****",
|
58
|
+
# @state="***",
|
59
|
+
# @url="https://twitter.com/i/oauth2/authorize?response_type=code&redirect_uri=<YOUR_REDIRECT_URI>&client_id=<YOUR_CLIENT_ID>&scope=<SCOPES>&code_challenge=*****&code_challenge_method=S256&state=***">
|
60
|
+
```
|
61
|
+
|
62
|
+
#### Step3. Rerirect (or Go) to `authorization.url`
|
63
|
+
|
64
|
+
And smash `"Authorize app"`.
|
65
|
+
|
66
|
+
If authorized, redirected to your `config.redirect_uri`.<br/>
|
67
|
+
Check **CODE** in Twitter's response.
|
68
|
+
|
69
|
+
```
|
70
|
+
e.g. https://example.com/twitter/callback?state=***&code=*****
|
71
|
+
```
|
72
|
+
|
73
|
+
#### Step4. Fetch an access token
|
74
|
+
|
75
|
+
```rb
|
76
|
+
token = TwitterTweetBot.fetch_token('<CODE>', authorization.code_verifier)
|
77
|
+
# =>
|
78
|
+
# #<TwitterTweetBot::Entity::Token
|
79
|
+
# @access_token="<YOUR_ACCESS_TOKEN>",
|
80
|
+
# @expires_in=7200,
|
81
|
+
# @refresh_token="<YOUR_REFRESH_TOKEN>",
|
82
|
+
# @scope="tweet.write users.read tweet.read offline.access",
|
83
|
+
# @token_type="bearer">
|
84
|
+
```
|
85
|
+
|
86
|
+
#### Step5. Post a tweet
|
87
|
+
|
88
|
+
```rb
|
89
|
+
TwitterTweetBot.post_tweet(token.access_token, 'Yeah!')
|
90
|
+
# =>
|
91
|
+
# #<TwitterTweetBot::Entity::Tweet
|
92
|
+
# @edit_history_tweet_ids=["0123456789"],
|
93
|
+
# @id="0123456789",
|
94
|
+
# @text="Yeah!">
|
95
|
+
```
|
96
|
+
|
97
|
+
#### Ex. Restore an access token (required `'offline.access'` in scopes)
|
98
|
+
|
99
|
+
```rb
|
100
|
+
TwitterTweetBot.refresh_token(token.refresh_token)
|
101
|
+
```
|
102
|
+
</details>
|
103
|
+
|
104
|
+
### Caching
|
105
|
+
|
106
|
+
`TwitterTweetBot` can cache follow variables automatically in any store (like `Rails.cache`).
|
107
|
+
|
108
|
+
- `code_verifier`
|
109
|
+
- `state`
|
110
|
+
- `access_token`
|
111
|
+
- `refresh_token`
|
112
|
+
|
113
|
+
If needed, require `'twitter_tweet_bot/cache'`.
|
114
|
+
|
115
|
+
```rb
|
116
|
+
require 'twitter_tweet_bot/cache'
|
117
|
+
|
118
|
+
TwitterTweetBot.post_tweet('Yeah!')
|
119
|
+
```
|
120
|
+
|
121
|
+
<details>
|
122
|
+
|
123
|
+
<summary>Details</summary>
|
124
|
+
|
125
|
+
#### Step1. Congiuration
|
126
|
+
|
127
|
+
```rb
|
128
|
+
require 'twitter_tweet_bot'
|
129
|
+
|
130
|
+
TwitterTweetBot.configure do |config|
|
131
|
+
# ...
|
132
|
+
|
133
|
+
# Any Cache Store (required `#write(key, value)` and `#read(key)` implementation).
|
134
|
+
config.cache_provider = ActiveSupport::Cache.lookup_store(:file_store, '../tmp/cache')
|
135
|
+
end
|
136
|
+
```
|
137
|
+
|
138
|
+
#### Step2. Issue an authorization url
|
139
|
+
|
140
|
+
```rb
|
141
|
+
# `code_verifier` and `state` will be cached.
|
142
|
+
TwitterTweetBot.authorization
|
143
|
+
```
|
144
|
+
|
145
|
+
#### Step3. Fetch an access token
|
146
|
+
|
147
|
+
|
148
|
+
```rb
|
149
|
+
# `access_token` and `refresh_token` will be cached.
|
150
|
+
TwitterTweetBot.fetch_token('<CODE>')
|
151
|
+
```
|
152
|
+
|
153
|
+
Don't need to pass `code_verifier`.<br/>
|
154
|
+
(resolved from cache)
|
155
|
+
|
156
|
+
#### Step4. Post a tweet
|
157
|
+
|
158
|
+
```rb
|
159
|
+
TwitterTweetBot.post_tweet('Yeah!')
|
160
|
+
```
|
161
|
+
|
162
|
+
Don't need to pass `access_token`.<br/>
|
163
|
+
(resolved from cache)
|
164
|
+
|
165
|
+
#### Ex. Check a cache
|
166
|
+
|
167
|
+
```rb
|
168
|
+
TwitterTweetBot.client.cache.read
|
169
|
+
# =>
|
170
|
+
# { :code_verifier=>"*****", :state=>"***", :access_token=>"*****", :refresh_token=>"*****" }
|
171
|
+
```
|
172
|
+
</details>
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'twitter_tweet_bot/api/http'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
module API
|
6
|
+
class AccessToken
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
include HTTP
|
10
|
+
|
11
|
+
API_ENDPOTNT = 'https://api.twitter.com/2/oauth2/token'.freeze
|
12
|
+
GRANT_TYPE = 'authorization_code'.freeze
|
13
|
+
|
14
|
+
def self.fetch(
|
15
|
+
client_id:,
|
16
|
+
client_secret:,
|
17
|
+
redirect_uri:,
|
18
|
+
code:,
|
19
|
+
code_verifier:,
|
20
|
+
**
|
21
|
+
)
|
22
|
+
new(client_id, client_secret, redirect_uri)
|
23
|
+
.fetch(code, code_verifier)
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(client_id, client_secret, redirect_uri)
|
27
|
+
@client_id = client_id
|
28
|
+
@client_secret = client_secret
|
29
|
+
@redirect_uri = redirect_uri
|
30
|
+
end
|
31
|
+
|
32
|
+
def fetch(code, code_verifier)
|
33
|
+
request(
|
34
|
+
:post_form,
|
35
|
+
API_ENDPOTNT,
|
36
|
+
body_with(code, code_verifier),
|
37
|
+
headers
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :client_id, :client_secret, :redirect_uri
|
44
|
+
|
45
|
+
def body_with(code, code_verifier)
|
46
|
+
{
|
47
|
+
grant_type: GRANT_TYPE,
|
48
|
+
code: code,
|
49
|
+
code_verifier: code_verifier,
|
50
|
+
redirect_uri: redirect_uri
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def headers
|
55
|
+
basic_authorization_header(
|
56
|
+
Base64.strict_encode64("#{client_id}:#{client_secret}")
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
private_constant :API_ENDPOTNT,
|
61
|
+
:GRANT_TYPE
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'securerandom'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module TwitterTweetBot
|
6
|
+
module API
|
7
|
+
class Authorization
|
8
|
+
class SecureCode
|
9
|
+
DEFAULT_CHALLENGE_SIZE = 64
|
10
|
+
DEFAULT_CHALLENGE_METHOD = 'S256'.freeze
|
11
|
+
DEFAULT_STATE_SIZE = 32
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def code_verifier(size = DEFAULT_CHALLENGE_SIZE)
|
15
|
+
encode(random_chars(size))
|
16
|
+
end
|
17
|
+
|
18
|
+
def state(size = DEFAULT_STATE_SIZE)
|
19
|
+
encode(random_chars(size))
|
20
|
+
end
|
21
|
+
|
22
|
+
def code_challenge(verifier, challenge_method = DEFAULT_CHALLENGE_METHOD)
|
23
|
+
case challenge_method
|
24
|
+
when 'S256'
|
25
|
+
encode(
|
26
|
+
Base64.urlsafe_encode64(digest_by_sha256(verifier), padding: false)
|
27
|
+
)
|
28
|
+
else # 'plain'
|
29
|
+
verifier
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def random_chars(size)
|
36
|
+
SecureRandom.urlsafe_base64(size / 4 * 3, false)
|
37
|
+
end
|
38
|
+
|
39
|
+
def digest_by_sha256(chars)
|
40
|
+
OpenSSL::Digest::SHA256.digest(chars)
|
41
|
+
end
|
42
|
+
|
43
|
+
def encode(chars)
|
44
|
+
chars.encode(Encoding::UTF_8)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def initialize(code_verifier: nil, code_challenge_method: nil, state: nil)
|
49
|
+
@code_verifier = code_verifier
|
50
|
+
@code_challenge_method = code_challenge_method
|
51
|
+
@state = state
|
52
|
+
end
|
53
|
+
|
54
|
+
def code_verifier
|
55
|
+
@code_verifier ||= self.class.code_verifier
|
56
|
+
end
|
57
|
+
|
58
|
+
def code_challenge
|
59
|
+
@code_challenge ||= \
|
60
|
+
self.class.code_challenge(code_verifier, code_challenge_method)
|
61
|
+
end
|
62
|
+
|
63
|
+
def code_challenge_method
|
64
|
+
@code_challenge_method ||= DEFAULT_CHALLENGE_METHOD
|
65
|
+
end
|
66
|
+
|
67
|
+
def state
|
68
|
+
@state ||= self.class.state
|
69
|
+
end
|
70
|
+
|
71
|
+
private_constant :DEFAULT_CHALLENGE_SIZE,
|
72
|
+
:DEFAULT_CHALLENGE_METHOD,
|
73
|
+
:DEFAULT_STATE_SIZE
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'twitter_tweet_bot/api/authorization/secure_code'
|
2
|
+
require 'twitter_tweet_bot/api/http'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
module API
|
6
|
+
class Authorization
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
include HTTP
|
10
|
+
|
11
|
+
AUTH_URL = 'https://twitter.com/i/oauth2/authorize'.freeze
|
12
|
+
RESPONSE_TYPE = 'code'.freeze
|
13
|
+
|
14
|
+
def self.authorization(
|
15
|
+
client_id:,
|
16
|
+
redirect_uri:,
|
17
|
+
scopes:,
|
18
|
+
code_verifier:,
|
19
|
+
code_challenge_method:,
|
20
|
+
state:,
|
21
|
+
**
|
22
|
+
)
|
23
|
+
new(client_id, redirect_uri, scopes)
|
24
|
+
.authorization(code_verifier, code_challenge_method, state)
|
25
|
+
end
|
26
|
+
|
27
|
+
def initialize(client_id, redirect_uri, scopes)
|
28
|
+
@client_id = client_id
|
29
|
+
@redirect_uri = redirect_uri
|
30
|
+
@scopes = scopes
|
31
|
+
end
|
32
|
+
|
33
|
+
def authorization(code_verifier, code_challenge_method, state)
|
34
|
+
secure_code = Authorization::SecureCode.new(
|
35
|
+
code_verifier: code_verifier,
|
36
|
+
code_challenge_method: code_challenge_method,
|
37
|
+
state: state
|
38
|
+
)
|
39
|
+
uri = build_uri(AUTH_URL, build_body(secure_code))
|
40
|
+
return as_hash(uri.to_s, secure_code)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :client_id, :redirect_uri, :scopes
|
46
|
+
|
47
|
+
def build_body(secure_code)
|
48
|
+
{
|
49
|
+
response_type: RESPONSE_TYPE,
|
50
|
+
redirect_uri: redirect_uri,
|
51
|
+
client_id: client_id,
|
52
|
+
scope: scopes.join(' '),
|
53
|
+
code_challenge: secure_code.code_challenge,
|
54
|
+
code_challenge_method: secure_code.code_challenge_method,
|
55
|
+
state: secure_code.state
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
def as_hash(url, secure_code)
|
60
|
+
{
|
61
|
+
url: url,
|
62
|
+
code_verifier: secure_code.code_verifier,
|
63
|
+
state: secure_code.state
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
private_constant :AUTH_URL,
|
68
|
+
:RESPONSE_TYPE
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
module API
|
5
|
+
module HTTP
|
6
|
+
module Base
|
7
|
+
def perform_request(uri, request)
|
8
|
+
Net::HTTP.start(
|
9
|
+
uri.host, uri.port, use_ssl: uri.scheme == 'https'
|
10
|
+
) do |http|
|
11
|
+
http.request(request)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TwitterTweetBot
|
2
|
+
module API
|
3
|
+
module HTTP
|
4
|
+
module Error
|
5
|
+
class RequestFaild < StandardError
|
6
|
+
attr_reader :code, :body
|
7
|
+
|
8
|
+
def initialize(code, body)
|
9
|
+
@code = code
|
10
|
+
@body = body
|
11
|
+
end
|
12
|
+
|
13
|
+
def inspect
|
14
|
+
"#<#{self.class} #{code} #{body}>"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def request_error!(code, body)
|
19
|
+
raise RequestFaild.new(code, body)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'twitter_tweet_bot/api/http/base'
|
4
|
+
|
5
|
+
module TwitterTweetBot
|
6
|
+
module API
|
7
|
+
module HTTP
|
8
|
+
module Get
|
9
|
+
include Base
|
10
|
+
|
11
|
+
def request_get(url, body, headers = {})
|
12
|
+
uri = build_uri(url, body)
|
13
|
+
perform_request(
|
14
|
+
uri, Net::HTTP::Get.new(uri, headers)
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build_uri(url, body)
|
21
|
+
URI.parse(url).tap do |uri|
|
22
|
+
uri.query = URI.encode_www_form(body)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
module API
|
6
|
+
module HTTP
|
7
|
+
module Headers
|
8
|
+
BASIC_AUTHORIZATION = 'Basic %<credentials>s'.freeze
|
9
|
+
BEARER_AUTHORIZATION = 'Bearer %<credentials>s'.freeze
|
10
|
+
|
11
|
+
def basic_authorization_header(credentials)
|
12
|
+
{ authorization: format(BASIC_AUTHORIZATION, credentials: credentials) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def bearer_authorization_header(credentials)
|
16
|
+
{ authorization: format(BEARER_AUTHORIZATION, credentials: credentials) }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
require 'twitter_tweet_bot/api/http/base'
|
5
|
+
|
6
|
+
module TwitterTweetBot
|
7
|
+
module API
|
8
|
+
module HTTP
|
9
|
+
module Post
|
10
|
+
include Base
|
11
|
+
|
12
|
+
JSON_CONTENT_TYPE = 'application/json'.freeze
|
13
|
+
URLENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded; charset=UTF-8'.freeze
|
14
|
+
|
15
|
+
def request_post_form(url, body, headers = {})
|
16
|
+
request_post(
|
17
|
+
url,
|
18
|
+
URI.encode_www_form(body),
|
19
|
+
headers.merge('content-type' => URLENCODED_CONTENT_TYPE)
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def request_post_json(url, body, headers = {})
|
24
|
+
request_post(
|
25
|
+
url,
|
26
|
+
body.to_json,
|
27
|
+
headers.merge('content-type' => JSON_CONTENT_TYPE)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def request_post(url, body, headers)
|
34
|
+
uri = URI.parse(url)
|
35
|
+
perform_request(
|
36
|
+
uri,
|
37
|
+
Net::HTTP::Post.new(uri.path, headers).tap { |req| req.body = body }
|
38
|
+
)
|
39
|
+
end
|
40
|
+
|
41
|
+
private_constant :JSON_CONTENT_TYPE,
|
42
|
+
:URLENCODED_CONTENT_TYPE
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'twitter_tweet_bot/api/http/error'
|
2
|
+
require 'twitter_tweet_bot/api/http/headers'
|
3
|
+
require 'twitter_tweet_bot/api/http/get'
|
4
|
+
require 'twitter_tweet_bot/api/http/post'
|
5
|
+
|
6
|
+
module TwitterTweetBot
|
7
|
+
module API
|
8
|
+
module HTTP
|
9
|
+
include Error
|
10
|
+
include Headers
|
11
|
+
include Get
|
12
|
+
include Post
|
13
|
+
|
14
|
+
def request(method, url, body, headers)
|
15
|
+
response = send("request_#{method}", url, body, headers)
|
16
|
+
success_or_fail!(response)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def success_or_fail!(response)
|
22
|
+
case response
|
23
|
+
when Net::HTTPSuccess, Net::HTTPRedirection
|
24
|
+
response.body
|
25
|
+
else
|
26
|
+
request_error!(response.code, response.body)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'twitter_tweet_bot/api/http'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
module API
|
6
|
+
class RefreshToken
|
7
|
+
private_class_method :new
|
8
|
+
|
9
|
+
include HTTP
|
10
|
+
|
11
|
+
API_ENDPOTNT = 'https://api.twitter.com/2/oauth2/token'.freeze
|
12
|
+
GRANT_TYPE = 'refresh_token'.freeze
|
13
|
+
|
14
|
+
def self.fetch(client_id:, client_secret:, refresh_token:, **)
|
15
|
+
new(client_id, client_secret)
|
16
|
+
.fetch(refresh_token)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(client_id, client_secret)
|
20
|
+
@client_id = client_id
|
21
|
+
@client_secret = client_secret
|
22
|
+
end
|
23
|
+
|
24
|
+
def fetch(refresh_token)
|
25
|
+
request(
|
26
|
+
:post_form,
|
27
|
+
API_ENDPOTNT,
|
28
|
+
body_with(refresh_token),
|
29
|
+
headers
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
attr_reader :client_id, :client_secret
|
36
|
+
|
37
|
+
def body_with(refresh_token)
|
38
|
+
{
|
39
|
+
grant_type: GRANT_TYPE,
|
40
|
+
refresh_token: refresh_token,
|
41
|
+
client_id: client_id
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def headers
|
46
|
+
basic_authorization_header(
|
47
|
+
Base64.strict_encode64("#{client_id}:#{client_secret}")
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
private_constant :API_ENDPOTNT,
|
52
|
+
:GRANT_TYPE
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'twitter_tweet_bot/api/http'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
module API
|
5
|
+
class Tweet
|
6
|
+
private_class_method :new
|
7
|
+
|
8
|
+
include HTTP
|
9
|
+
|
10
|
+
API_ENDPOTNT = 'https://api.twitter.com/2/tweets'.freeze
|
11
|
+
|
12
|
+
def self.post(access_token:, text:, **)
|
13
|
+
new(access_token).post(text)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(access_token)
|
17
|
+
@access_token = access_token
|
18
|
+
end
|
19
|
+
|
20
|
+
def post(text)
|
21
|
+
request(
|
22
|
+
:post_json,
|
23
|
+
API_ENDPOTNT,
|
24
|
+
{ text: text },
|
25
|
+
bearer_authorization_header(access_token)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :access_token
|
32
|
+
|
33
|
+
private_constant :API_ENDPOTNT
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'twitter_tweet_bot/api/http'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
module API
|
5
|
+
class UsersMe
|
6
|
+
private_class_method :new
|
7
|
+
|
8
|
+
include HTTP
|
9
|
+
|
10
|
+
API_ENDPOTNT = 'https://api.twitter.com/2/users/me'.freeze
|
11
|
+
|
12
|
+
def self.fetch(access_token:, fields:, **)
|
13
|
+
new(access_token).fetch(fields)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(access_token)
|
17
|
+
@access_token = access_token
|
18
|
+
end
|
19
|
+
|
20
|
+
def fetch(fields)
|
21
|
+
request(
|
22
|
+
:get,
|
23
|
+
API_ENDPOTNT,
|
24
|
+
fields,
|
25
|
+
bearer_authorization_header(access_token)
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_accessor :access_token
|
32
|
+
|
33
|
+
private_constant :API_ENDPOTNT
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module TwitterTweetBot
|
2
|
+
module Cache
|
3
|
+
module Caching
|
4
|
+
def cache
|
5
|
+
config.cache
|
6
|
+
end
|
7
|
+
|
8
|
+
def with_cache(&block)
|
9
|
+
current_cache = read_cache
|
10
|
+
result = yield(current_cache)
|
11
|
+
write_cache(current_cache, result)
|
12
|
+
|
13
|
+
result
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def read_cache
|
19
|
+
cache.read || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_cache(current, value)
|
23
|
+
cache.write(current.merge(**to_cache_object(value)))
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_cache_object(object)
|
27
|
+
return {} unless object.respond_to?(:to_cache)
|
28
|
+
object.to_cache
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'twitter_tweet_bot/cache/caching'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
module Cache
|
5
|
+
module ClientExt
|
6
|
+
include Caching
|
7
|
+
|
8
|
+
def authorization(*)
|
9
|
+
with_cache { super }
|
10
|
+
end
|
11
|
+
|
12
|
+
def fetch_token(*args)
|
13
|
+
with_cache { |cache| super(*args, cache[:code_verifier]) }
|
14
|
+
end
|
15
|
+
|
16
|
+
def refresh_token
|
17
|
+
with_cache { |cache| super(cache[:refresh_token]) }
|
18
|
+
end
|
19
|
+
|
20
|
+
def post_tweet(*args)
|
21
|
+
with_cache { |cache| super(cache[:access_token], *args) }
|
22
|
+
end
|
23
|
+
|
24
|
+
def users_me(*args)
|
25
|
+
with_cache { |cache| super(cache[:access_token], *args) }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'twitter_tweet_bot/cache/store'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
module Cache
|
5
|
+
module ConfigrationExt
|
6
|
+
attr_accessor :cache_provider, :cache
|
7
|
+
|
8
|
+
def initialize(cache_provider: nil, **kwargs)
|
9
|
+
@cache_provider = cache_provider
|
10
|
+
super(**kwargs)
|
11
|
+
end
|
12
|
+
|
13
|
+
def cache
|
14
|
+
@cache ||= Store.new(name, cache_provider)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
module Cache
|
6
|
+
module EntityExt
|
7
|
+
module Base
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def act_as_cache_entity(*cache_fields)
|
12
|
+
mattr_reader :cache_fields,
|
13
|
+
default: cache_fields
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_cache
|
18
|
+
cache_fields.reduce({}) do |hash, cache_field|
|
19
|
+
next hash unless respond_to?(cache_field)
|
20
|
+
|
21
|
+
value = send(cache_field)
|
22
|
+
next hash if value.nil?
|
23
|
+
hash[cache_field] = value
|
24
|
+
|
25
|
+
hash
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
module Cache
|
5
|
+
class Store
|
6
|
+
CACHE_BASE_KEY = 'twitter_tweet_bot'.freeze
|
7
|
+
|
8
|
+
def initialize(name, provider)
|
9
|
+
@name = name
|
10
|
+
@provider = provider
|
11
|
+
end
|
12
|
+
|
13
|
+
def write(value)
|
14
|
+
provider.write(cache_key, serialize_value(value))
|
15
|
+
end
|
16
|
+
|
17
|
+
def read
|
18
|
+
deserialize_value(provider.read(cache_key))
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :name, :provider
|
24
|
+
|
25
|
+
def cache_key
|
26
|
+
File.join(CACHE_BASE_KEY, name.to_s)
|
27
|
+
end
|
28
|
+
|
29
|
+
def serialize_value(value)
|
30
|
+
value.to_json
|
31
|
+
end
|
32
|
+
|
33
|
+
def deserialize_value(value)
|
34
|
+
return {} if value.nil?
|
35
|
+
JSON.parse(value, symbolize_names: true)
|
36
|
+
end
|
37
|
+
|
38
|
+
private_constant :CACHE_BASE_KEY
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'twitter_tweet_bot'
|
2
|
+
|
3
|
+
require 'twitter_tweet_bot/cache/client_ext'
|
4
|
+
require 'twitter_tweet_bot/cache/configuration_ext'
|
5
|
+
require 'twitter_tweet_bot/cache/entity_ext'
|
6
|
+
|
7
|
+
module TwitterTweetBot
|
8
|
+
Client.prepend(Cache::ClientExt)
|
9
|
+
Configration.prepend(Cache::ConfigrationExt)
|
10
|
+
|
11
|
+
Entity::Authorization.include(Cache::EntityExt::Authorization)
|
12
|
+
Entity::Token.include(Cache::EntityExt::Token)
|
13
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'twitter_tweet_bot/api'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
class Client
|
5
|
+
module API
|
6
|
+
def authorization(code_verifier = nil, code_challenge_method = nil, state = nil)
|
7
|
+
TwitterTweetBot::API::Authorization.authorization(
|
8
|
+
**params_with_config(
|
9
|
+
code_verifier: code_verifier,
|
10
|
+
code_challenge_method: code_challenge_method,
|
11
|
+
state: state
|
12
|
+
)
|
13
|
+
)
|
14
|
+
end
|
15
|
+
|
16
|
+
def fetch_token(code, code_verifier)
|
17
|
+
TwitterTweetBot::API::AccessToken.fetch(
|
18
|
+
**params_with_config(
|
19
|
+
code: code,
|
20
|
+
code_verifier: code_verifier
|
21
|
+
)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def refresh_token(refresh_token)
|
26
|
+
TwitterTweetBot::API::RefreshToken.fetch(
|
27
|
+
**params_with_config(refresh_token: refresh_token)
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def post_tweet(access_token, text)
|
32
|
+
TwitterTweetBot::API::Tweet.post(
|
33
|
+
**params_with_config(access_token: access_token, text: text)
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def users_me(access_token, fields = {})
|
38
|
+
TwitterTweetBot::API::UsersMe.fetch(
|
39
|
+
**params_with_config(access_token: access_token, fields: fields)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def params_with_config(**params)
|
46
|
+
config.to_hash.merge(**params)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'twitter_tweet_bot/entity'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
class Client
|
6
|
+
module Entity
|
7
|
+
def authorization(*)
|
8
|
+
with_entity(TwitterTweetBot::Entity::Authorization) { super }
|
9
|
+
end
|
10
|
+
|
11
|
+
def fetch_token(*)
|
12
|
+
with_entity(TwitterTweetBot::Entity::Token) { parse_json(super) }
|
13
|
+
end
|
14
|
+
|
15
|
+
def refresh_token(*)
|
16
|
+
with_entity(TwitterTweetBot::Entity::Token) { parse_json(super) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def post_tweet(*)
|
20
|
+
with_entity(TwitterTweetBot::Entity::Tweet) { parse_json(super) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def users_me(*)
|
24
|
+
with_entity(TwitterTweetBot::Entity::User) { parse_json(super) }
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def with_entity(entity_klass, &block)
|
30
|
+
entity_klass.new(yield)
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_json(body)
|
34
|
+
JSON.parse(body, symbolize_names: true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'twitter_tweet_bot/client/api'
|
2
|
+
require 'twitter_tweet_bot/client/entity'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
class Client
|
6
|
+
include API
|
7
|
+
include Entity
|
8
|
+
|
9
|
+
def initialize(config)
|
10
|
+
@config = config
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
attr_accessor :config
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module TwitterTweetBot
|
2
|
+
class Configration
|
3
|
+
attr_accessor :name,
|
4
|
+
:client_id,
|
5
|
+
:client_secret,
|
6
|
+
:redirect_uri,
|
7
|
+
:scopes
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
name: nil,
|
11
|
+
client_id: nil,
|
12
|
+
client_secret: nil,
|
13
|
+
redirect_uri: nil,
|
14
|
+
scopes: []
|
15
|
+
)
|
16
|
+
@name = name
|
17
|
+
@client_id = client_id
|
18
|
+
@client_secret = client_secret
|
19
|
+
@redirect_uri = redirect_uri
|
20
|
+
@scopes = scopes
|
21
|
+
|
22
|
+
yield self if block_given?
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_hash
|
26
|
+
instance_variables.reduce({}) do |hash, key|
|
27
|
+
hash[key[1..].to_sym] = instance_variable_get(key)
|
28
|
+
hash
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/class/attribute'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
module Entity
|
6
|
+
module Base
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
class_methods do
|
10
|
+
def act_as_entity(*fields)
|
11
|
+
class_attribute :fields,
|
12
|
+
instance_writer: false,
|
13
|
+
default: fields
|
14
|
+
|
15
|
+
attr_reader(*fields)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(json)
|
20
|
+
set_fields!(Hash(json))
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def set_fields!(hash)
|
26
|
+
fields.each do |field|
|
27
|
+
instance_variable_set(:"@#{field}", hash[field])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'twitter_tweet_bot/entity/base'
|
2
|
+
|
3
|
+
module TwitterTweetBot
|
4
|
+
module Entity
|
5
|
+
class Tweet
|
6
|
+
include Base
|
7
|
+
|
8
|
+
act_as_entity(
|
9
|
+
:id,
|
10
|
+
:text,
|
11
|
+
:edit_history_tweet_ids
|
12
|
+
)
|
13
|
+
|
14
|
+
def initialize(data)
|
15
|
+
super Hash(data)[:data]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
|
2
|
+
require 'twitter_tweet_bot/entity/base'
|
3
|
+
|
4
|
+
module TwitterTweetBot
|
5
|
+
module Entity
|
6
|
+
class User
|
7
|
+
include Base
|
8
|
+
|
9
|
+
act_as_entity(
|
10
|
+
:id,
|
11
|
+
:name,
|
12
|
+
:username
|
13
|
+
)
|
14
|
+
|
15
|
+
def initialize(data)
|
16
|
+
super Hash(data)[:data]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'twitter_tweet_bot/client'
|
2
|
+
require 'twitter_tweet_bot/configration'
|
3
|
+
require 'twitter_tweet_bot/version'
|
4
|
+
|
5
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
6
|
+
require 'active_support/core_ext/module/delegation'
|
7
|
+
|
8
|
+
module TwitterTweetBot
|
9
|
+
NoConfigrationError = Class.new(StandardError).freeze
|
10
|
+
|
11
|
+
mattr_accessor :default_config
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def configure(&block)
|
15
|
+
self.default_config = Configration.new(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def client(config = nil)
|
19
|
+
Client.new(config || default_config!)
|
20
|
+
end
|
21
|
+
|
22
|
+
delegate :authorization,
|
23
|
+
:fetch_token,
|
24
|
+
:refresh_token,
|
25
|
+
:post_tweet,
|
26
|
+
:users_me,
|
27
|
+
to: :client
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def default_config!
|
32
|
+
default_config or raise NoConfigrationError
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: twitter_tweet_bot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- SongCastle
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-05-14 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: 6.0.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 6.0.3
|
27
|
+
description: Ruby implementation for Twitter client, using V2 API and OAuth 2.0.
|
28
|
+
email: "-"
|
29
|
+
executables: []
|
30
|
+
extensions: []
|
31
|
+
extra_rdoc_files: []
|
32
|
+
files:
|
33
|
+
- README.md
|
34
|
+
- lib/twitter_tweet_bot.rb
|
35
|
+
- lib/twitter_tweet_bot/api.rb
|
36
|
+
- lib/twitter_tweet_bot/api/access_token.rb
|
37
|
+
- lib/twitter_tweet_bot/api/authorization.rb
|
38
|
+
- lib/twitter_tweet_bot/api/authorization/secure_code.rb
|
39
|
+
- lib/twitter_tweet_bot/api/http.rb
|
40
|
+
- lib/twitter_tweet_bot/api/http/base.rb
|
41
|
+
- lib/twitter_tweet_bot/api/http/error.rb
|
42
|
+
- lib/twitter_tweet_bot/api/http/get.rb
|
43
|
+
- lib/twitter_tweet_bot/api/http/headers.rb
|
44
|
+
- lib/twitter_tweet_bot/api/http/post.rb
|
45
|
+
- lib/twitter_tweet_bot/api/refresh_token.rb
|
46
|
+
- lib/twitter_tweet_bot/api/tweet.rb
|
47
|
+
- lib/twitter_tweet_bot/api/users_me.rb
|
48
|
+
- lib/twitter_tweet_bot/cache.rb
|
49
|
+
- lib/twitter_tweet_bot/cache/caching.rb
|
50
|
+
- lib/twitter_tweet_bot/cache/client_ext.rb
|
51
|
+
- lib/twitter_tweet_bot/cache/configuration_ext.rb
|
52
|
+
- lib/twitter_tweet_bot/cache/entity_ext.rb
|
53
|
+
- lib/twitter_tweet_bot/cache/entity_ext/authorization.rb
|
54
|
+
- lib/twitter_tweet_bot/cache/entity_ext/base.rb
|
55
|
+
- lib/twitter_tweet_bot/cache/entity_ext/token.rb
|
56
|
+
- lib/twitter_tweet_bot/cache/store.rb
|
57
|
+
- lib/twitter_tweet_bot/client.rb
|
58
|
+
- lib/twitter_tweet_bot/client/api.rb
|
59
|
+
- lib/twitter_tweet_bot/client/entity.rb
|
60
|
+
- lib/twitter_tweet_bot/configration.rb
|
61
|
+
- lib/twitter_tweet_bot/entity.rb
|
62
|
+
- lib/twitter_tweet_bot/entity/authorization.rb
|
63
|
+
- lib/twitter_tweet_bot/entity/base.rb
|
64
|
+
- lib/twitter_tweet_bot/entity/token.rb
|
65
|
+
- lib/twitter_tweet_bot/entity/tweet.rb
|
66
|
+
- lib/twitter_tweet_bot/entity/user.rb
|
67
|
+
- lib/twitter_tweet_bot/version.rb
|
68
|
+
homepage: https://github.com/SongCastle/twitter_tweet_bot
|
69
|
+
licenses:
|
70
|
+
- MIT
|
71
|
+
metadata: {}
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '3.0'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubygems_version: 3.0.3
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: Ruby implementation for Twitter twetting client
|
91
|
+
test_files: []
|