x 0.15.4 → 0.16.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 +6 -0
- data/README.md +3 -3
- data/lib/x/media_uploader.rb +29 -25
- data/lib/x/version.rb +1 -1
- data/sig/x.rbs +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc174269cbe283b3f360fee2c9a443272d0c1d3aab08cf609d552f22323dabc7
|
4
|
+
data.tar.gz: a2f50383c52f0e499f8afe275ce100cd9ca6f8231bd5fdd1f18c1bfdd7bf5191
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9d9d70a98d38037f5a9606ec4770bc1fb79b7201a4c6f0a3d6a8a7e9360d360730d42606059fe40365ef008bab229cafe14feef5c3558518528afe2c15f6676
|
7
|
+
data.tar.gz: bceff542ee42df84cc8b96df31e311db88bafb7ecec138c2caf7c07d2c106c535750c1cdc603d22e1743586798f79f9d624c9073dee282093f89dc305e886286
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## [0.16.0] - 2025-06-24
|
2
|
+
* Remove media_type parameter from non-chunked upload and append methods (f1f38b5)
|
3
|
+
* Fix media upload (dcb418a)
|
4
|
+
* Add await_processing! method to handle media upload failures (6cfc973)
|
5
|
+
* Move media_category in body for media upload (b790636)
|
6
|
+
|
1
7
|
## [0.15.4] - 2025-05-02
|
2
8
|
* Use dedicated endpoints for chunked media upload (d54d0d0)
|
3
9
|
|
data/README.md
CHANGED
@@ -23,7 +23,7 @@ Or, if Bundler is not being used to manage dependencies:
|
|
23
23
|
|
24
24
|
## Usage
|
25
25
|
|
26
|
-
First, obtain X
|
26
|
+
First, obtain X credentials from <https://developer.x.com>.
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
require "x"
|
@@ -131,9 +131,9 @@ Many thanks to our sponsors (listed in order of when they sponsored this project
|
|
131
131
|
|
132
132
|
## Development
|
133
133
|
|
134
|
-
1.
|
134
|
+
1. Clone the repo:
|
135
135
|
|
136
|
-
git
|
136
|
+
git clone git@github.com:sferik/x-ruby.git
|
137
137
|
|
138
138
|
2. Enter the repo’s directory:
|
139
139
|
|
data/lib/x/media_uploader.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require "json"
|
1
2
|
require "securerandom"
|
2
3
|
require "tmpdir"
|
3
4
|
|
@@ -16,32 +17,38 @@ module X
|
|
16
17
|
"png" => PNG_MIME_TYPE, "srt" => SUBRIP_MIME_TYPE, "webp" => WEBP_MIME_TYPE}.freeze
|
17
18
|
PROCESSING_INFO_STATES = %w[failed succeeded].freeze
|
18
19
|
|
19
|
-
def upload(client:, file_path:, media_category:,
|
20
|
-
boundary: SecureRandom.hex)
|
20
|
+
def upload(client:, file_path:, media_category:, boundary: SecureRandom.hex)
|
21
21
|
validate!(file_path:, media_category:)
|
22
|
-
upload_body = construct_upload_body(file_path:,
|
23
|
-
headers = {"Content-Type" => "multipart/form-data
|
24
|
-
client.post("media/upload
|
22
|
+
upload_body = construct_upload_body(file_path:, media_category:, boundary:)
|
23
|
+
headers = {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
|
24
|
+
client.post("media/upload", upload_body, headers:)&.fetch("data")
|
25
25
|
end
|
26
26
|
|
27
|
-
def chunked_upload(client:, file_path:, media_category:, media_type: infer_media_type(file_path,
|
28
|
-
|
27
|
+
def chunked_upload(client:, file_path:, media_category:, media_type: infer_media_type(file_path, media_category),
|
28
|
+
boundary: SecureRandom.hex, chunk_size_mb: 1)
|
29
29
|
validate!(file_path:, media_category:)
|
30
30
|
media = init(client:, file_path:, media_type:, media_category:)
|
31
31
|
chunk_size = chunk_size_mb * BYTES_PER_MB
|
32
|
-
append(client:, file_paths: split(file_path, chunk_size), media:,
|
32
|
+
append(client:, file_paths: split(file_path, chunk_size), media:, boundary:)
|
33
33
|
client.post("media/upload/#{media["id"]}/finalize")&.fetch("data")
|
34
34
|
end
|
35
35
|
|
36
36
|
def await_processing(client:, media:)
|
37
37
|
loop do
|
38
38
|
status = client.get("media/upload?command=STATUS&media_id=#{media["id"]}")&.fetch("data")
|
39
|
-
return status if !status["processing_info"] || PROCESSING_INFO_STATES.include?(status["processing_info"]["state"])
|
39
|
+
return status if status.nil? || !status["processing_info"] || PROCESSING_INFO_STATES.include?(status["processing_info"]["state"])
|
40
40
|
|
41
41
|
sleep status["processing_info"]["check_after_secs"].to_i
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
def await_processing!(client:, media:)
|
46
|
+
status = await_processing(client:, media:)
|
47
|
+
raise "Media processing failed" if status&.dig("processing_info", "state") == "failed"
|
48
|
+
|
49
|
+
status
|
50
|
+
end
|
51
|
+
|
45
52
|
private
|
46
53
|
|
47
54
|
def validate!(file_path:, media_category:)
|
@@ -57,22 +64,18 @@ module X
|
|
57
64
|
when TWEET_GIF, DM_GIF then GIF_MIME_TYPE
|
58
65
|
when TWEET_VIDEO, DM_VIDEO then MP4_MIME_TYPE
|
59
66
|
when SUBTITLES then SUBRIP_MIME_TYPE
|
60
|
-
else MIME_TYPE_MAP
|
67
|
+
else MIME_TYPE_MAP.fetch(File.extname(file_path).delete(".").downcase, DEFAULT_MIME_TYPE)
|
61
68
|
end
|
62
69
|
end
|
63
70
|
|
64
71
|
def split(file_path, chunk_size)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
File.binwrite(path, chunk)
|
72
|
-
file_paths << path
|
73
|
-
end
|
72
|
+
file_size = File.size(file_path)
|
73
|
+
segment_count = (file_size.to_f / chunk_size).ceil
|
74
|
+
(0...segment_count).map do |segment_index|
|
75
|
+
segment_path = "#{Dir.mktmpdir}/x#{format("%03d", segment_index + 1)}"
|
76
|
+
File.binwrite(segment_path, File.binread(file_path, chunk_size, segment_index * chunk_size))
|
77
|
+
segment_path
|
74
78
|
end
|
75
|
-
file_paths
|
76
79
|
end
|
77
80
|
|
78
81
|
def init(client:, file_path:, media_type:, media_category:)
|
@@ -81,11 +84,11 @@ module X
|
|
81
84
|
client.post("media/upload/initialize", data)&.fetch("data")
|
82
85
|
end
|
83
86
|
|
84
|
-
def append(client:, file_paths:, media:,
|
87
|
+
def append(client:, file_paths:, media:, boundary: SecureRandom.hex)
|
85
88
|
threads = file_paths.map.with_index do |file_path, index|
|
86
89
|
Thread.new do
|
87
|
-
upload_body = construct_upload_body(file_path:,
|
88
|
-
headers = {"Content-Type" => "multipart/form-data
|
90
|
+
upload_body = construct_upload_body(file_path:, segment_index: index, boundary:)
|
91
|
+
headers = {"Content-Type" => "multipart/form-data; boundary=#{boundary}"}
|
89
92
|
upload_chunk(client:, media_id: media["id"], upload_body:, file_path:, headers:)
|
90
93
|
end
|
91
94
|
end
|
@@ -107,12 +110,13 @@ module X
|
|
107
110
|
Dir.delete(dirname) if Dir.empty?(dirname)
|
108
111
|
end
|
109
112
|
|
110
|
-
def construct_upload_body(file_path:,
|
113
|
+
def construct_upload_body(file_path:, media_category: nil, segment_index: nil, boundary: SecureRandom.hex)
|
111
114
|
body = ""
|
112
115
|
body += "--#{boundary}\r\nContent-Disposition: form-data; name=\"segment_index\"\r\n\r\n#{segment_index}\r\n" if segment_index
|
116
|
+
body += "--#{boundary}\r\nContent-Disposition: form-data; name=\"media_category\"\r\n\r\n#{media_category}\r\n" if media_category
|
113
117
|
"#{body}--#{boundary}\r\n" \
|
114
118
|
"Content-Disposition: form-data; name=\"media\"; filename=\"#{File.basename(file_path)}\"\r\n" \
|
115
|
-
"Content-Type: #{
|
119
|
+
"Content-Type: #{DEFAULT_MIME_TYPE}\r\n\r\n" \
|
116
120
|
"#{File.binread(file_path)}\r\n" \
|
117
121
|
"--#{boundary}--\r\n"
|
118
122
|
end
|
data/lib/x/version.rb
CHANGED
data/sig/x.rbs
CHANGED
@@ -265,19 +265,20 @@ module X
|
|
265
265
|
PROCESSING_INFO_STATES: Array[String]
|
266
266
|
extend MediaUploader
|
267
267
|
|
268
|
-
def upload: (client: Client, file_path: String, media_category: String, ?
|
268
|
+
def upload: (client: Client, file_path: 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
|
+
def await_processing!: (client: Client, media: untyped) -> untyped
|
271
272
|
|
272
273
|
private
|
273
274
|
def validate!: (file_path: String, media_category: String) -> nil
|
274
275
|
def infer_media_type: (String file_path, String media_category) -> String
|
275
276
|
def split: (String file_path, Integer chunk_size) -> Array[String]
|
276
277
|
def init: (client: Client, file_path: String, media_type: String, media_category: String) -> untyped
|
277
|
-
def append: (client: Client, file_paths: Array[String], media: untyped,
|
278
|
+
def append: (client: Client, file_paths: Array[String], media: untyped, ?boundary: String) -> Array[String]
|
278
279
|
def upload_chunk: (client: Client, media_id: String, upload_body: String, file_path: String, ?headers: Hash[String, String]) -> Integer?
|
279
280
|
def cleanup_file: (String file_path) -> Integer?
|
280
281
|
def finalize: (client: Client, media: untyped) -> untyped
|
281
|
-
def construct_upload_body: (file_path: String,
|
282
|
+
def construct_upload_body: (file_path: String, ?media_category: String, ?segment_index: Integer, ?boundary: String) -> String
|
282
283
|
end
|
283
284
|
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.16.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik Berlin
|
@@ -93,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
93
|
- !ruby/object:Gem::Version
|
94
94
|
version: '0'
|
95
95
|
requirements: []
|
96
|
-
rubygems_version: 3.6.
|
96
|
+
rubygems_version: 3.6.9
|
97
97
|
specification_version: 4
|
98
98
|
summary: A Ruby interface to the X API.
|
99
99
|
test_files: []
|