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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 731556dfa3e6417d5e0ea9305d6a6b87cc87bd360d71277df0b3dd1c5c8400b5
4
- data.tar.gz: a1170fee36003355b65fe7a6a2732eb750ab0681bae74539d3f1eb17bfd96f65
3
+ metadata.gz: cc174269cbe283b3f360fee2c9a443272d0c1d3aab08cf609d552f22323dabc7
4
+ data.tar.gz: a2f50383c52f0e499f8afe275ce100cd9ca6f8231bd5fdd1f18c1bfdd7bf5191
5
5
  SHA512:
6
- metadata.gz: ec96213eaf3a727ba153c9df7f4f92649b45d3089944d3c02bf6c56960a593845818b7e9e4c947b0317dc6ec4325c7563652d9bcebd911c677171ba1d397fd21
7
- data.tar.gz: dc3d6fb361ec7de40ac47d065c3840dd4972f7a32ebb17ed3969b56fc6257272d82c66d035b298956ac1209548c54e46530c75dbea3d23b8ebcd341480670a41
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 credentails from <https://developer.x.com>.
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. Checkout and repo:
134
+ 1. Clone the repo:
135
135
 
136
- git checkout git@github.com:sferik/x-ruby.git
136
+ git clone git@github.com:sferik/x-ruby.git
137
137
 
138
138
  2. Enter the repo’s directory:
139
139
 
@@ -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:, media_type: infer_media_type(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:, media_type:, boundary:)
23
- headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
24
- client.post("media/upload?media_category=#{media_category}", upload_body, headers:)
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
- media_category), boundary: SecureRandom.hex, chunk_size_mb: 1)
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:, media_type:, boundary:)
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[File.extname(file_path).delete(".").downcase] || DEFAULT_MIME_TYPE
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
- file_number = -1
66
- file_paths = [] # @type var file_paths: Array[String]
67
-
68
- File.open(file_path, "rb") do |f|
69
- while (chunk = f.read(chunk_size))
70
- path = "#{Dir.mktmpdir}/x#{format("%03d", file_number += 1)}"
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:, media_type:, boundary: SecureRandom.hex)
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:, media_type:, segment_index: index, boundary:)
88
- headers = {"Content-Type" => "multipart/form-data, boundary=#{boundary}"}
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:, media_type:, segment_index: nil, boundary: SecureRandom.hex)
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: #{media_type}\r\n\r\n" \
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
@@ -1,5 +1,5 @@
1
1
  require "rubygems/version"
2
2
 
3
3
  module X
4
- VERSION = Gem::Version.create("0.15.4")
4
+ VERSION = Gem::Version.create("0.16.0")
5
5
  end
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, ?media_type: String, ?boundary: String) -> untyped
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, media_type: String, ?boundary: String) -> Array[String]
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, media_type: String, ?segment_index: Integer, ?boundary: String) -> 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.15.4
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.8
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: []