simple_tweet 1.0.0 → 2.1.0

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: dba99628f81892c32f4a763db2dfa62d8f0594e7596b8ba0213a1e6a26d9d217
4
- data.tar.gz: 506db3714b3c8988e136984efb9ecf6dc462915be06dd385fd9918a7c3c2d0d2
3
+ metadata.gz: 3e6a6c9e3275f29c3b1aefcc8bb0c9fb8959d596777f057493cd6027c584abb5
4
+ data.tar.gz: 0d6bcec1bf0c2995ede08425c78512d96027b20346a0f1c966e0b03b7e8a29bd
5
5
  SHA512:
6
- metadata.gz: 74126d63ca533d0c9d36f5bf306ab2bf2164e08465e6e652c265a44948e4a52bd758005846e3487e7216ff1e5ea13028bd370dacc546e17d54e5ef37bdd39777
7
- data.tar.gz: 8504a4a3d65f608c981710339e3158a58ab3e3f2841d03ccf32abf1744962a9c78a50d28c1fb9bf9d0e260b49f49ddae221b70c11878811205f993fc99a4778b
6
+ metadata.gz: 306443b68c1b9ba56b96d78f462981f1af82eb288339a55cbd490dd15c8deaa78d00da7e3dccc5ec9b6a3988ba792dc5bfd0ded53840082cc57abc7857851e25
7
+ data.tar.gz: c247c2c61666c96b80358c7b303f680a782db1e9b381aa859baeb32169f67d0602a209d9d674f99c2d6400e11c0357b37ccb35b0e2532269f3124f9e0ac7c360
data/README.md CHANGED
@@ -26,7 +26,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
26
26
 
27
27
  ## Contributing
28
28
 
29
- Bug reports and pull requests are welcome on GitHub at https://github.com/nna774/simple_tweet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nna774/simple_tweet/blob/main/CODE_OF_CONDUCT.md).
29
+ Bug reports and pull requests are welcome on GitHub at https://github.com/nota/simple_tweet. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/nota/simple_tweet/blob/main/CODE_OF_CONDUCT.md).
30
30
 
31
31
  ## License
32
32
 
@@ -34,4 +34,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
34
34
 
35
35
  ## Code of Conduct
36
36
 
37
- Everyone interacting in the SimpleTweet project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nna774/simple_tweet/blob/main/CODE_OF_CONDUCT.md).
37
+ Everyone interacting in the SimpleTweet project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/nota/simple_tweet/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,156 @@
1
+ require "cgi"
2
+ require "oauth"
3
+ require "json"
4
+ require "net/http/post/multipart"
5
+
6
+ module SimpleTweet
7
+ module V1
8
+ # Client provides only tweet
9
+ class Client
10
+ TW_API_ORIGIN = "https://api.twitter.com"
11
+ TW_UPLOAD_ORIGIN = "https://upload.twitter.com"
12
+ TW_MEDIA_UPLOAD_PATH = "/1.1/media/upload.json"
13
+ APPEND_PER = 5 * (1 << 20)
14
+
15
+ def initialize(consumer_key:, consumer_secret:, access_token:, access_token_secret:, max_append_retry: 3)
16
+ @consumer_key_ = consumer_key
17
+ @consumer_secret_ = consumer_secret
18
+ @access_token_ = access_token
19
+ @access_token_secret_ = access_token_secret
20
+ @max_append_retry_ = max_append_retry
21
+ end
22
+
23
+ # https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/post-statuses-update
24
+ def tweet(message:, media_ids: [])
25
+ path = "/1.1/statuses/update.json?status=#{::CGI.escape(message)}"
26
+ path += "&media_ids=#{media_ids.join(",")}" unless media_ids.empty?
27
+ Tweet.from_response(access_token.post(path))
28
+ end
29
+
30
+ # media_type is mime_type
31
+ def tweet_with_media(message:, media_type:, media:)
32
+ media_ids = upload_media(media_type: media_type, media: media)
33
+ tweet(message: message, media_ids: media_ids)
34
+ end
35
+
36
+ private
37
+
38
+ def access_token(site: TW_API_ORIGIN)
39
+ consumer = ::OAuth::Consumer.new(@consumer_key_, @consumer_secret_, site: site)
40
+ ::OAuth::AccessToken.new(consumer, @access_token_, @access_token_secret_)
41
+ end
42
+
43
+ def request(req)
44
+ @client ||= access_token(site: TW_UPLOAD_ORIGIN)
45
+ @client.sign! req
46
+
47
+ url = ::URI.parse(TW_UPLOAD_ORIGIN + TW_MEDIA_UPLOAD_PATH)
48
+ https = ::Net::HTTP.new(url.host, url.port)
49
+ https.use_ssl = true
50
+
51
+ https.start do |http|
52
+ http.request req
53
+ end
54
+ end
55
+
56
+ # https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/api-reference/post-media-upload
57
+ ## maybe todo: multiple image
58
+ def upload_media(media_type:, media:)
59
+ return upload_video(video: media) if media_type == "video/mp4"
60
+
61
+ req = ::Net::HTTP::Post::Multipart.new(
62
+ TW_MEDIA_UPLOAD_PATH,
63
+ media: ::UploadIO.new(media, media_type),
64
+ media_category: "tweet_image"
65
+ )
66
+ res = ::JSON.parse(request(req).body)
67
+ [res["media_id_string"]]
68
+ end
69
+
70
+ def init(video:)
71
+ init_req = ::Net::HTTP::Post::Multipart.new(
72
+ TW_MEDIA_UPLOAD_PATH,
73
+ command: "INIT",
74
+ total_bytes: video.size,
75
+ media_type: "video/mp4"
76
+ )
77
+ init_res = request(init_req)
78
+ raise UploadMediaError unless init_res.code == "202"
79
+
80
+ ::JSON.parse(init_res.body)
81
+ end
82
+
83
+ def append(video:, media_id:, index:, retry_count: 0)
84
+ append_req = ::Net::HTTP::Post::Multipart.new(
85
+ TW_MEDIA_UPLOAD_PATH,
86
+ command: "APPEND",
87
+ media_id: media_id,
88
+ media: video.read(APPEND_PER),
89
+ segment_index: index
90
+ )
91
+ return if request(append_req).code == "204"
92
+ raise UploadMediaError unless retry_count <= @max_append_retry_
93
+
94
+ append(video: video, media_id: media_id, index: index, retry_count: retry_count + 1)
95
+ end
96
+
97
+ def finalize(media_id:)
98
+ finalize_req = ::Net::HTTP::Post::Multipart.new(
99
+ TW_MEDIA_UPLOAD_PATH,
100
+ command: "FINALIZE",
101
+ media_id: media_id
102
+ )
103
+ finalize_res = request(finalize_req)
104
+ raise UploadMediaError unless finalize_res.code == "201"
105
+
106
+ ::JSON.parse(finalize_res.body)
107
+ end
108
+
109
+ def status(media_id:)
110
+ status_req = ::Net::HTTP::Post::Multipart.new(
111
+ TW_MEDIA_UPLOAD_PATH,
112
+ command: "STATUS",
113
+ media_id: media_id
114
+ )
115
+ status_res = request(status_req)
116
+ raise UploadMediaError unless status_res.code == "200"
117
+
118
+ ::JSON.parse(status_res.body)
119
+ end
120
+
121
+ # https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/api-reference/post-media-upload-init
122
+ def upload_video(video:)
123
+ init_res = init(video: video)
124
+ media_id = init_res["media_id_string"]
125
+
126
+ chunks_needed = (video.size - 1) / APPEND_PER + 1
127
+ chunks_needed.times do |i|
128
+ append(video: video, media_id: media_id, index: i)
129
+ end
130
+
131
+ finalize_res = finalize(media_id: media_id)
132
+
133
+ if finalize_res["processing_info"]
134
+ retry_after = finalize_res["processing_info"]["check_after_secs"] || 5
135
+ loop do
136
+ sleep retry_after
137
+
138
+ status_res = status(media_id: media_id)
139
+ raise UploadMediaError if status_res["processing_info"].nil?
140
+ break if status_res["processing_info"]["state"] == "succeeded"
141
+
142
+ if status_res["processing_info"]["state"] == "in_progress"
143
+ retry_after = status_res["processing_info"]["check_after_secs"] || 5
144
+ next
145
+ end
146
+
147
+ # status_res_json["processing_info"]["state"] == "failed"
148
+ raise UploadMediaError
149
+ end
150
+ end
151
+
152
+ [media_id]
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,21 @@
1
+ require "json"
2
+
3
+ module SimpleTweet
4
+ module V2
5
+ # mediaのuploadはapi 1.1しか用意されていないため、それを使う。
6
+ class Client < V1::Client
7
+ TW_TWEET_PATH = "/2/tweets"
8
+ UA = "SimpleTweet/#{SimpleTweet::VERSION}"
9
+
10
+ def tweet(message:, media_ids: [])
11
+ json = {
12
+ text: message,
13
+ }
14
+ json[:media] = { media_ids: media_ids } unless media_ids.empty?
15
+ header = { "User-Agent": UA, "content-type": "application/json" }
16
+ res = access_token.post(TW_TWEET_PATH, json.to_json, header)
17
+ ::JSON.parse(res.body, symbolize_names: true)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SimpleTweet
4
- VERSION = "1.0.0"
4
+ VERSION = "2.1.0"
5
5
  end
data/lib/simple_tweet.rb CHANGED
@@ -1,163 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "simple_tweet/version"
4
- require "cgi"
5
- require "oauth"
4
+ require_relative "simple_tweet/v1_client"
5
+ require_relative "simple_tweet/v2_client"
6
6
  require "json"
7
- require "net/http/post/multipart"
8
7
 
9
8
  module SimpleTweet
10
9
  class Error < ::StandardError; end
11
10
  class UploadMediaError < Error; end
12
11
 
13
- # Client provides only tweet
14
- class Client
15
- TW_API_ORIGIN = "https://api.twitter.com"
16
- TW_UPLOAD_ORIGIN = "https://upload.twitter.com"
17
- TW_MEDIA_UPLOAD_PATH = "/1.1/media/upload.json"
18
- APPEND_PER = 5 * (1 << 20)
19
-
20
- def initialize(consumer_key:, consumer_secret:, access_token:, access_token_secret:, max_append_retry: 3)
21
- @consumer_key_ = consumer_key
22
- @consumer_secret_ = consumer_secret
23
- @access_token_ = access_token
24
- @access_token_secret_ = access_token_secret
25
- @max_append_retry_ = max_append_retry
26
- end
27
-
28
- # https://developer.twitter.com/en/docs/twitter-api/v1/tweets/post-and-engage/api-reference/post-statuses-update
29
- def tweet(message:, media_ids: [])
30
- path = "/1.1/statuses/update.json?status=#{::CGI.escape(message)}"
31
- path += "&media_ids=#{media_ids.join(",")}" unless media_ids.empty?
32
- Tweet.from_response(access_token.post(path))
33
- end
34
-
35
- # media_type is mime_type
36
- def tweet_with_media(message:, media_type:, media:)
37
- media_ids = upload_media(media_type: media_type, media: media)
38
- tweet(message: message, media_ids: media_ids)
39
- end
40
-
41
- private
42
-
43
- def access_token(site: TW_API_ORIGIN)
44
- consumer = ::OAuth::Consumer.new(@consumer_key_, @consumer_secret_, site: site)
45
- ::OAuth::AccessToken.new(consumer, @access_token_, @access_token_secret_)
46
- end
47
-
48
- def request(req)
49
- @client ||= access_token(site: TW_UPLOAD_ORIGIN)
50
- @client.sign! req
51
-
52
- url = ::URI.parse(TW_UPLOAD_ORIGIN + TW_MEDIA_UPLOAD_PATH)
53
- https = ::Net::HTTP.new(url.host, url.port)
54
- https.use_ssl = true
55
-
56
- https.start do |http|
57
- http.request req
58
- end
59
- end
60
-
61
- # https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/api-reference/post-media-upload
62
- ## maybe todo: multiple image
63
- def upload_media(media_type:, media:)
64
- return upload_video(video: media) if media_type == "video/mp4"
65
-
66
- req = ::Net::HTTP::Post::Multipart.new(
67
- TW_MEDIA_UPLOAD_PATH,
68
- media: ::UploadIO.new(media, media_type),
69
- media_category: "tweet_image"
70
- )
71
- res = ::JSON.parse(request(req).body)
72
- [res["media_id_string"]]
73
- end
74
-
75
- def init(video:)
76
- init_req = ::Net::HTTP::Post::Multipart.new(
77
- TW_MEDIA_UPLOAD_PATH,
78
- command: "INIT",
79
- total_bytes: video.size,
80
- media_type: "video/mp4"
81
- )
82
- init_res = request(init_req)
83
- raise UploadMediaError unless init_res.code == "202"
84
-
85
- ::JSON.parse(init_res.body)
86
- end
87
-
88
- def append(video:, media_id:, index:, retry_count: 0)
89
- append_req = ::Net::HTTP::Post::Multipart.new(
90
- TW_MEDIA_UPLOAD_PATH,
91
- command: "APPEND",
92
- media_id: media_id,
93
- media: video.read(APPEND_PER),
94
- segment_index: index
95
- )
96
- return if request(append_req).code == "204"
97
- raise UploadMediaError unless retry_count <= @max_append_retry_
98
-
99
- append(video: video, media_id: media_id, index: index, retry_count: retry_count + 1)
100
- end
101
-
102
- def finalize(media_id:)
103
- finalize_req = ::Net::HTTP::Post::Multipart.new(
104
- TW_MEDIA_UPLOAD_PATH,
105
- command: "FINALIZE",
106
- media_id: media_id
107
- )
108
- finalize_res = request(finalize_req)
109
- raise UploadMediaError unless finalize_res.code == "201"
110
-
111
- ::JSON.parse(finalize_res.body)
112
- end
113
-
114
- def status(media_id:)
115
- status_req = ::Net::HTTP::Post::Multipart.new(
116
- TW_MEDIA_UPLOAD_PATH,
117
- command: "STATUS",
118
- media_id: media_id
119
- )
120
- status_res = request(status_req)
121
- raise UploadMediaError unless status_res.code == "200"
122
-
123
- ::JSON.parse(status_res.body)
124
- end
125
-
126
- # https://developer.twitter.com/en/docs/twitter-api/v1/media/upload-media/api-reference/post-media-upload-init
127
- def upload_video(video:)
128
- init_res = init(video: video)
129
- media_id = init_res["media_id_string"]
130
-
131
- chunks_needed = (video.size - 1) / APPEND_PER + 1
132
- chunks_needed.times do |i|
133
- append(video: video, media_id: media_id, index: i)
134
- end
135
-
136
- finalize_res = finalize(media_id: media_id)
137
-
138
- if finalize_res["processing_info"]
139
- retry_after = finalize_res["processing_info"]["check_after_secs"] || 5
140
- loop do
141
- sleep retry_after
142
-
143
- status_res = status(media_id: media_id)
144
- raise UploadMediaError if status_res["processing_info"].nil?
145
- break if status_res["processing_info"]["state"] == "succeeded"
146
-
147
- if status_res["processing_info"]["state"] == "in_progress"
148
- retry_after = status_res["processing_info"]["check_after_secs"] || 5
149
- next
150
- end
151
-
152
- # status_res_json["processing_info"]["state"] == "failed"
153
- raise UploadMediaError
154
- end
155
- end
156
-
157
- [media_id]
158
- end
159
- end
160
-
161
12
  # Twitter::Tweetに近いinterfaceを提供する。
162
13
  ## 使ってない部分のhashの中身のhashとかは正規化されてないので、必要になったら足す必要がある。
163
14
  ### todo: entities, media, ...
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: simple_tweet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kugayama Nana
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-02-15 00:00:00.000000000 Z
11
+ date: 2023-06-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: multipart-post
@@ -53,6 +53,8 @@ files:
53
53
  - README.md
54
54
  - Rakefile
55
55
  - lib/simple_tweet.rb
56
+ - lib/simple_tweet/v1_client.rb
57
+ - lib/simple_tweet/v2_client.rb
56
58
  - lib/simple_tweet/version.rb
57
59
  - sig/simple_tweet.rbs
58
60
  - simple_tweet.gemspec