x 0.16.0 → 0.17.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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +2 -1
- data/lib/x/connection.rb +1 -1
- data/lib/x/media_upload_validator.rb +17 -0
- data/lib/x/media_uploader.rb +15 -17
- data/lib/x/oauth_authenticator.rb +1 -1
- data/lib/x/request_builder.rb +2 -3
- data/lib/x/version.rb +1 -1
- data/sig/x.rbs +10 -3
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4df136d018c47058c7524faada6292a1464b34f2f0e98f2a76e8491630b28d1c
|
|
4
|
+
data.tar.gz: f7101280a194f40bef57a82e9670cd6ddf409e80b7563072384b8f8c7687cb63
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 24599b2cb2604a711a1a810cb21b0631ce26519dcee977e12bf494fdde1be55c08bf4e2716b6c281918010847e8c1bd72cd236a44f0343ece70a66f4a908ab1f
|
|
7
|
+
data.tar.gz: 84cdf631377ed38f0400a0f82d6dc42609839eeb4ba81d77526017af8541365152adb4f3e73f2722ae2645a494cf697976c1ae85039c9bd2b82edb1efe97136f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
## [0.17.0] - 2025-12-02
|
|
2
|
+
* Add MediaUploader.upload_binary method (9f2f108)
|
|
3
|
+
* Don't forward filename during media upload (492214d)
|
|
4
|
+
|
|
1
5
|
## [0.16.0] - 2025-06-24
|
|
2
6
|
* Remove media_type parameter from non-chunked upload and append methods (f1f38b5)
|
|
3
7
|
* Fix media upload (dcb418a)
|
data/README.md
CHANGED
data/lib/x/connection.rb
CHANGED
|
@@ -13,7 +13,7 @@ module X
|
|
|
13
13
|
DEFAULT_OPEN_TIMEOUT = 60 # seconds
|
|
14
14
|
DEFAULT_READ_TIMEOUT = 60 # seconds
|
|
15
15
|
DEFAULT_WRITE_TIMEOUT = 60 # seconds
|
|
16
|
-
DEFAULT_DEBUG_OUTPUT = File.open(
|
|
16
|
+
DEFAULT_DEBUG_OUTPUT = File.open(IO::NULL, "w")
|
|
17
17
|
NETWORK_ERRORS = [
|
|
18
18
|
Errno::ECONNREFUSED,
|
|
19
19
|
Errno::ECONNRESET,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module X
|
|
2
|
+
module MediaUploadValidator
|
|
3
|
+
module_function
|
|
4
|
+
|
|
5
|
+
MEDIA_CATEGORIES = %w[dm_gif dm_image dm_video subtitles tweet_gif tweet_image tweet_video].freeze
|
|
6
|
+
|
|
7
|
+
def validate_file_path!(file_path:)
|
|
8
|
+
raise "File not found: #{file_path}" unless File.exist?(file_path)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def validate_media_category!(media_category:)
|
|
12
|
+
return if MEDIA_CATEGORIES.include?(media_category.downcase)
|
|
13
|
+
|
|
14
|
+
raise ArgumentError, "Invalid media_category: #{media_category}. Valid values: #{MEDIA_CATEGORIES.join(", ")}"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/x/media_uploader.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
require "json"
|
|
2
2
|
require "securerandom"
|
|
3
3
|
require "tmpdir"
|
|
4
|
+
require_relative "media_upload_validator"
|
|
4
5
|
|
|
5
6
|
module X
|
|
6
7
|
module MediaUploader
|
|
@@ -8,8 +9,7 @@ module X
|
|
|
8
9
|
|
|
9
10
|
MAX_RETRIES = 3
|
|
10
11
|
BYTES_PER_MB = 1_048_576
|
|
11
|
-
|
|
12
|
-
DM_GIF, DM_IMAGE, DM_VIDEO, SUBTITLES, TWEET_GIF, TWEET_IMAGE, TWEET_VIDEO = MEDIA_CATEGORIES
|
|
12
|
+
DM_GIF, DM_IMAGE, DM_VIDEO, SUBTITLES, TWEET_GIF, TWEET_IMAGE, TWEET_VIDEO = MediaUploadValidator::MEDIA_CATEGORIES
|
|
13
13
|
DEFAULT_MIME_TYPE = "application/octet-stream".freeze
|
|
14
14
|
MIME_TYPES = %w[image/gif image/jpeg video/mp4 image/png application/x-subrip image/webp].freeze
|
|
15
15
|
GIF_MIME_TYPE, JPEG_MIME_TYPE, MP4_MIME_TYPE, PNG_MIME_TYPE, SUBRIP_MIME_TYPE, WEBP_MIME_TYPE = MIME_TYPES
|
|
@@ -18,15 +18,21 @@ module X
|
|
|
18
18
|
PROCESSING_INFO_STATES = %w[failed succeeded].freeze
|
|
19
19
|
|
|
20
20
|
def upload(client:, file_path:, media_category:, boundary: SecureRandom.hex)
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
MediaUploadValidator.validate_file_path!(file_path:)
|
|
22
|
+
upload_binary(client:, content: File.binread(file_path), media_category:, boundary:)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def upload_binary(client:, content:, media_category:, boundary: SecureRandom.hex)
|
|
26
|
+
MediaUploadValidator.validate_media_category!(media_category:)
|
|
27
|
+
upload_body = construct_upload_body(content:, media_category:, boundary:)
|
|
23
28
|
headers = {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
|
|
24
29
|
client.post("media/upload", upload_body, headers:)&.fetch("data")
|
|
25
30
|
end
|
|
26
31
|
|
|
27
32
|
def chunked_upload(client:, file_path:, media_category:, media_type: infer_media_type(file_path, media_category),
|
|
28
33
|
boundary: SecureRandom.hex, chunk_size_mb: 1)
|
|
29
|
-
|
|
34
|
+
MediaUploadValidator.validate_file_path!(file_path:)
|
|
35
|
+
MediaUploadValidator.validate_media_category!(media_category:)
|
|
30
36
|
media = init(client:, file_path:, media_type:, media_category:)
|
|
31
37
|
chunk_size = chunk_size_mb * BYTES_PER_MB
|
|
32
38
|
append(client:, file_paths: split(file_path, chunk_size), media:, boundary:)
|
|
@@ -51,14 +57,6 @@ module X
|
|
|
51
57
|
|
|
52
58
|
private
|
|
53
59
|
|
|
54
|
-
def validate!(file_path:, media_category:)
|
|
55
|
-
raise "File not found: #{file_path}" unless File.exist?(file_path)
|
|
56
|
-
|
|
57
|
-
return if MEDIA_CATEGORIES.include?(media_category.downcase)
|
|
58
|
-
|
|
59
|
-
raise ArgumentError, "Invalid media_category: #{media_category}. Valid values: #{MEDIA_CATEGORIES.join(", ")}"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
60
|
def infer_media_type(file_path, media_category)
|
|
63
61
|
case media_category.downcase
|
|
64
62
|
when TWEET_GIF, DM_GIF then GIF_MIME_TYPE
|
|
@@ -87,7 +85,7 @@ module X
|
|
|
87
85
|
def append(client:, file_paths:, media:, boundary: SecureRandom.hex)
|
|
88
86
|
threads = file_paths.map.with_index do |file_path, index|
|
|
89
87
|
Thread.new do
|
|
90
|
-
upload_body = construct_upload_body(file_path
|
|
88
|
+
upload_body = construct_upload_body(content: File.binread(file_path), segment_index: index, boundary:)
|
|
91
89
|
headers = {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
|
|
92
90
|
upload_chunk(client:, media_id: media["id"], upload_body:, file_path:, headers:)
|
|
93
91
|
end
|
|
@@ -110,14 +108,14 @@ module X
|
|
|
110
108
|
Dir.delete(dirname) if Dir.empty?(dirname)
|
|
111
109
|
end
|
|
112
110
|
|
|
113
|
-
def construct_upload_body(
|
|
111
|
+
def construct_upload_body(content:, media_category: nil, segment_index: nil, boundary: SecureRandom.hex)
|
|
114
112
|
body = ""
|
|
115
113
|
body += "--#{boundary}\r\nContent-Disposition: form-data; name=\"segment_index\"\r\n\r\n#{segment_index}\r\n" if segment_index
|
|
116
114
|
body += "--#{boundary}\r\nContent-Disposition: form-data; name=\"media_category\"\r\n\r\n#{media_category}\r\n" if media_category
|
|
117
115
|
"#{body}--#{boundary}\r\n" \
|
|
118
|
-
"Content-Disposition: form-data; name=\"media\"
|
|
116
|
+
"Content-Disposition: form-data; name=\"media\"\r\n" \
|
|
119
117
|
"Content-Type: #{DEFAULT_MIME_TYPE}\r\n\r\n" \
|
|
120
|
-
"#{
|
|
118
|
+
"#{content}\r\n" \
|
|
121
119
|
"--#{boundary}--\r\n"
|
|
122
120
|
end
|
|
123
121
|
end
|
data/lib/x/request_builder.rb
CHANGED
|
@@ -38,14 +38,13 @@ module X
|
|
|
38
38
|
|
|
39
39
|
def add_authentication(request:, authenticator:)
|
|
40
40
|
authenticator.header(request).each do |key, value|
|
|
41
|
-
request
|
|
41
|
+
request[key] = value
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def add_headers(request:, headers:)
|
|
46
46
|
DEFAULT_HEADERS.merge(headers).each do |key, value|
|
|
47
|
-
request
|
|
48
|
-
request.add_field(key, value)
|
|
47
|
+
request[key] = value
|
|
49
48
|
end
|
|
50
49
|
end
|
|
51
50
|
|
data/lib/x/version.rb
CHANGED
data/sig/x.rbs
CHANGED
|
@@ -245,7 +245,6 @@ module X
|
|
|
245
245
|
module MediaUploader
|
|
246
246
|
MAX_RETRIES: Integer
|
|
247
247
|
BYTES_PER_MB: Integer
|
|
248
|
-
MEDIA_CATEGORIES: Array[String]
|
|
249
248
|
DM_GIF: String
|
|
250
249
|
DM_IMAGE: String
|
|
251
250
|
DM_VIDEO: String
|
|
@@ -266,12 +265,12 @@ module X
|
|
|
266
265
|
extend MediaUploader
|
|
267
266
|
|
|
268
267
|
def upload: (client: Client, file_path: String, media_category: String, ?boundary: String) -> untyped
|
|
268
|
+
def upload_binary: (client: Client, content: String, media_category: String, ?boundary: String) -> untyped
|
|
269
269
|
def chunked_upload: (client: Client, file_path: String, media_category: String, ?media_type: String, ?boundary: String, ?chunk_size_mb: Integer) -> untyped
|
|
270
270
|
def await_processing: (client: Client, media: untyped) -> untyped
|
|
271
271
|
def await_processing!: (client: Client, media: untyped) -> untyped
|
|
272
272
|
|
|
273
273
|
private
|
|
274
|
-
def validate!: (file_path: String, media_category: String) -> nil
|
|
275
274
|
def infer_media_type: (String file_path, String media_category) -> String
|
|
276
275
|
def split: (String file_path, Integer chunk_size) -> Array[String]
|
|
277
276
|
def init: (client: Client, file_path: String, media_type: String, media_category: String) -> untyped
|
|
@@ -279,6 +278,14 @@ module X
|
|
|
279
278
|
def upload_chunk: (client: Client, media_id: String, upload_body: String, file_path: String, ?headers: Hash[String, String]) -> Integer?
|
|
280
279
|
def cleanup_file: (String file_path) -> Integer?
|
|
281
280
|
def finalize: (client: Client, media: untyped) -> untyped
|
|
282
|
-
def construct_upload_body: (
|
|
281
|
+
def construct_upload_body: (content: String, ?media_category: String, ?segment_index: Integer, ?boundary: String) -> String
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
module MediaUploadValidator
|
|
285
|
+
MEDIA_CATEGORIES: Array[String]
|
|
286
|
+
extend MediaUploadValidator
|
|
287
|
+
|
|
288
|
+
def validate_file_path!: (file_path: String) -> nil
|
|
289
|
+
def validate_media_category!: (media_category: String) -> nil
|
|
283
290
|
end
|
|
284
291
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: x
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.17.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Erik Berlin
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- lib/x/errors/too_many_requests.rb
|
|
60
60
|
- lib/x/errors/unauthorized.rb
|
|
61
61
|
- lib/x/errors/unprocessable_entity.rb
|
|
62
|
+
- lib/x/media_upload_validator.rb
|
|
62
63
|
- lib/x/media_uploader.rb
|
|
63
64
|
- lib/x/oauth_authenticator.rb
|
|
64
65
|
- lib/x/rate_limit.rb
|