x-uploads 1.0.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 +7 -0
- data/.yardopts +8 -0
- data/CHANGELOG.md +171 -0
- data/LICENSE.txt +21 -0
- data/README.md +98 -0
- data/lib/x/uploads/account.rb +100 -0
- data/lib/x/uploads/alt_text_failed.rb +81 -0
- data/lib/x/uploads/api.rb +310 -0
- data/lib/x/uploads/chunked_upload_failed.rb +89 -0
- data/lib/x/uploads/chunks.rb +325 -0
- data/lib/x/uploads/error.rb +25 -0
- data/lib/x/uploads/gif.rb +88 -0
- data/lib/x/uploads/invalid_media.rb +19 -0
- data/lib/x/uploads/invalid_media_type.rb +12 -0
- data/lib/x/uploads/json_classes.rb +14 -0
- data/lib/x/uploads/media_processing_check_failed.rb +85 -0
- data/lib/x/uploads/media_processing_failed.rb +43 -0
- data/lib/x/uploads/media_processing_timeout.rb +55 -0
- data/lib/x/uploads/media_upload.rb +565 -0
- data/lib/x/uploads/metadata.rb +97 -0
- data/lib/x/uploads/missing_media_data.rb +47 -0
- data/lib/x/uploads/multipart.rb +63 -0
- data/lib/x/uploads/signature.rb +104 -0
- data/lib/x/uploads/source.rb +440 -0
- data/lib/x/uploads/uploaded_media.rb +369 -0
- data/lib/x/uploads/utils.rb +330 -0
- data/lib/x/uploads/validator.rb +391 -0
- data/lib/x/uploads/version.rb +24 -0
- data/lib/x/uploads.rb +18 -0
- data/sig/manifest.yaml +6 -0
- data/sig/x-uploads.rbs +139 -0
- metadata +98 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "account"
|
|
4
|
+
require_relative "media_upload"
|
|
5
|
+
require_relative "metadata"
|
|
6
|
+
require_relative "utils"
|
|
7
|
+
|
|
8
|
+
module X
|
|
9
|
+
module Uploads
|
|
10
|
+
# The upload methods mixed into a client, each of which calls an uploader with the client
|
|
11
|
+
#
|
|
12
|
+
# The x gem includes it into X::Client. With x-core and x-uploads alone, include it yourself:
|
|
13
|
+
# X::Client.include(X::Uploads::API). Each method passes the object it is included into to an uploader as its
|
|
14
|
+
# client, which is an X::Client, so it belongs in X::Client or a subclass.
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
17
|
+
module API
|
|
18
|
+
# Upload media and wait for it to be processed
|
|
19
|
+
#
|
|
20
|
+
# The media is a path, or an IO open on it. Media given as a String or a Pathname is read from the file it names,
|
|
21
|
+
# and media given as a File or a Tempfile through that IO, a chunk at a time, so media of any size uploads
|
|
22
|
+
# without being held in memory; media given as any other IO, such as a StringIO, is read to its end and held.
|
|
23
|
+
#
|
|
24
|
+
# A video or subtitles upload in chunks, which send their media type, as a single request does not. The media
|
|
25
|
+
# category is inferred from the bytes the media begins with, or else from the name of its file, unless
|
|
26
|
+
# media_category says what it is. The chunks are sent by threads of their own, so the on_response of the client
|
|
27
|
+
# runs on those threads for the response of each chunk.
|
|
28
|
+
#
|
|
29
|
+
# An image, and a GIF that a single request takes, upload in a single request, which takes no chunks and no
|
|
30
|
+
# media type, so chunk_size, concurrency, and media_type are ignored for them: a chunk_size or a concurrency
|
|
31
|
+
# that is not valid still raises, but none is sent. Media given shared: true uploads in chunks, and uses all
|
|
32
|
+
# three.
|
|
33
|
+
#
|
|
34
|
+
# Each chunk is a request a rate limit can refuse, which fails the upload with ChunkedUploadFailed unless the
|
|
35
|
+
# client retries it, which it does only max_rate_limit_retries times, 0 by default, so upload a large video with
|
|
36
|
+
# a client whose max_rate_limit_retries is set, such as X::Client.new(max_rate_limit_retries: 3).
|
|
37
|
+
#
|
|
38
|
+
# @api public
|
|
39
|
+
# @param media [String, Pathname, IO, StringIO] the path to the media to upload, or an IO open on it
|
|
40
|
+
# @param options [Hash] the options of {MediaUpload.upload}
|
|
41
|
+
# @option options [String, Symbol, nil] :media_category (nil) the media category, in any case, inferred when nil
|
|
42
|
+
# from the bytes the media begins with, or else from the name of its file
|
|
43
|
+
# @option options [String, nil] :alt_text (nil) alt text describing the media, of 1 to 1,000 characters, added
|
|
44
|
+
# once the media is uploaded and processed, or nil for none
|
|
45
|
+
# @option options [Integer, Float, nil] :processing_timeout (600) the seconds to wait for media that X processes,
|
|
46
|
+
# such as a video, to process, of at least 0, or nil to wait for as long as processing takes
|
|
47
|
+
# @option options [String, nil] :media_type (nil) the MIME type of media uploaded in chunks, inferred from the
|
|
48
|
+
# media and category when nil; ignored for media uploaded in a single request, such as an image
|
|
49
|
+
# @option options [Integer, nil] :chunk_size (nil) the size of each chunk in bytes, of at most 5,242,880, or nil
|
|
50
|
+
# for MediaUpload::DEFAULT_CHUNK_SIZE, or as much more as the media needs; ignored for media uploaded in a
|
|
51
|
+
# single request, such as an image
|
|
52
|
+
# @option options [Integer] :concurrency (4) the number of chunks uploaded at once, of 1 to
|
|
53
|
+
# MediaUpload::MAX_CONCURRENCY; ignored for media uploaded in a single request, such as an image
|
|
54
|
+
# @option options [Boolean, nil] :shared (nil) whether the media can be sent in more than one direct message, or
|
|
55
|
+
# nil to leave it to the API; media that is shared uploads in chunks
|
|
56
|
+
# @option options [Array<Integer, String>, nil] :additional_owners (nil) the identifiers of the users, other than
|
|
57
|
+
# the one who uploads it, who may use the media, or nil for none
|
|
58
|
+
# @return [UploadedMedia] the uploaded media, which holds the upload response, or the processing status of
|
|
59
|
+
# media that X processes
|
|
60
|
+
# @raise [ArgumentError] if the media is neither a path nor an IO, or is a String that holds a NUL byte or a
|
|
61
|
+
# line break, as the contents of media given in place of its path do
|
|
62
|
+
# @raise [InvalidMedia] if the file does not exist
|
|
63
|
+
# @raise [InvalidMedia] if the media cannot be read, or is empty, which holds nothing to upload
|
|
64
|
+
# @raise [InvalidMedia] if the media is larger than the API takes of its category, whatever the account: 5
|
|
65
|
+
# megabytes of an image, 15 of a GIF, and one of subtitles, or larger than the 16 gigabytes it takes of any
|
|
66
|
+
# @raise [ArgumentError] if the media category is invalid, the alt text is empty or longer than the API takes,
|
|
67
|
+
# the chunk size is not a positive Integer, is larger than a segment the API takes, or would need more
|
|
68
|
+
# segments than the API numbers, the concurrency is not 1 to MAX_CONCURRENCY, the processing timeout is
|
|
69
|
+
# neither nil nor a finite number of seconds of at least 0, shared is neither true, false, nor nil, or
|
|
70
|
+
# additional_owners is neither nil nor an Array of at least one user identifier
|
|
71
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for a request
|
|
72
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
73
|
+
# leaves out the value: the request
|
|
74
|
+
# that initializes an upload in chunks, or the single request of any other, raises it as it is, and a later
|
|
75
|
+
# request that opens a connection fails with it as the cause of the ChunkedUploadFailed,
|
|
76
|
+
# MediaProcessingCheckFailed, or AltTextFailed it raises
|
|
77
|
+
# @raise [InvalidMediaType] if no media category is given for media whose type neither its bytes nor the name of
|
|
78
|
+
# its file names, or the category does not take the type of the media
|
|
79
|
+
# @raise [MissingMediaData] if a response of the upload holds no media, or carries no body at all
|
|
80
|
+
# @raise [ChunkedUploadFailed] if media uploaded in chunks is initialized, but a chunk cannot be appended, or it
|
|
81
|
+
# cannot be finalized, with the media it initialized
|
|
82
|
+
# @raise [MediaProcessingFailed] if media processing failed, with the status X reported
|
|
83
|
+
# @raise [MediaProcessingTimeout] if the media is still processing once the processing timeout would pass
|
|
84
|
+
# @raise [MediaProcessingCheckFailed] if the media is uploaded, but a check of its processing fails, as when the
|
|
85
|
+
# API answers it with an error, with the media it uploaded
|
|
86
|
+
# @raise [AltTextFailed] if the media is uploaded, but its alt text cannot be added, with the media it uploaded
|
|
87
|
+
# @raise [TokenReportFailed] if save_tokens raises for the tokens of a refresh a request of the upload made, with
|
|
88
|
+
# the tokens, rather than the media, whatever the upload had done by then
|
|
89
|
+
# @example Upload an image with alt text and post it
|
|
90
|
+
# media = client.upload_media("cat.jpg", alt_text: "A cat asleep on a keyboard")
|
|
91
|
+
# client.create_post("Look at this cat", media_ids: [media])
|
|
92
|
+
# @example Upload an image held in memory, whose category its signature names
|
|
93
|
+
# client.upload_media(StringIO.new(File.binread("cat.png")))
|
|
94
|
+
# @example Upload media of a category no signature names
|
|
95
|
+
# client.upload_media(StringIO.new(subtitles), media_category: "subtitles")
|
|
96
|
+
def upload_media(media, **options) # steep:ignore DifferentMethodParameterKind
|
|
97
|
+
MediaUpload.upload(media, client: _ = self, **Utils.without_client(options))
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Upload media in chunks, without waiting for it to be processed
|
|
101
|
+
#
|
|
102
|
+
# It is the way to upload media without waiting for X to process it: {#upload_media} waits for the processing of
|
|
103
|
+
# media X processes, such as a video, and this does not. It uploads the media as {#upload_media} uploads a video,
|
|
104
|
+
# a chunk at a time, but returns once the upload is finalized, so that the caller can go on while X processes a
|
|
105
|
+
# long video, and wait for it with {#await_media_processing} or {#await_media_processing!} when it needs it. It
|
|
106
|
+
# uploads in chunks whatever the media, an image as well, and adds no alt text. The
|
|
107
|
+
# chunks are sent by threads of their own, so the on_response of the client runs on those threads for the
|
|
108
|
+
# response of each chunk.
|
|
109
|
+
#
|
|
110
|
+
# Each chunk is a request a rate limit can refuse, which fails the upload with ChunkedUploadFailed unless the
|
|
111
|
+
# client retries it, which it does only max_rate_limit_retries times, 0 by default, so upload a large video with
|
|
112
|
+
# a client whose max_rate_limit_retries is set, such as X::Client.new(max_rate_limit_retries: 3).
|
|
113
|
+
#
|
|
114
|
+
# @api public
|
|
115
|
+
# @param media [String, Pathname, IO, StringIO] the path to the media to upload, or an IO open on it
|
|
116
|
+
# @param options [Hash] the options of {MediaUpload.chunked_upload}
|
|
117
|
+
# @option options [String, Symbol, nil] :media_category (nil) the media category, in any case, inferred when nil
|
|
118
|
+
# from the bytes the media begins with, or else from the name of its file
|
|
119
|
+
# @option options [String, nil] :media_type (nil) the MIME type of the media, sent as it is given, or inferred
|
|
120
|
+
# from the media and category when nil
|
|
121
|
+
# @option options [Integer, nil] :chunk_size (nil) the size of each chunk in bytes, of at most 5,242,880, or nil
|
|
122
|
+
# for MediaUpload::DEFAULT_CHUNK_SIZE, or as much more as the media needs
|
|
123
|
+
# @option options [Integer] :concurrency (4) the number of chunks uploaded at once, of 1 to
|
|
124
|
+
# MediaUpload::MAX_CONCURRENCY
|
|
125
|
+
# @option options [Boolean, nil] :shared (nil) whether the media can be sent in more than one direct message, or
|
|
126
|
+
# nil to leave it to the API
|
|
127
|
+
# @option options [Array<Integer, String>, nil] :additional_owners (nil) the identifiers of the users, other than
|
|
128
|
+
# the one who uploads it, who may use the media, or nil for none
|
|
129
|
+
# @return [UploadedMedia] the uploaded media, which holds the response that finalized the upload, and the
|
|
130
|
+
# processing status of media that X processes
|
|
131
|
+
# @raise [ArgumentError] if the media is neither a path nor an IO, or is a String that holds a NUL byte or a
|
|
132
|
+
# line break, as the contents of media given in place of its path do
|
|
133
|
+
# @raise [InvalidMedia] if the file does not exist, the media cannot be read, or is empty, or it is larger than
|
|
134
|
+
# the API takes of its category
|
|
135
|
+
# @raise [ArgumentError] if the media category is invalid, the chunk size is not a positive Integer, is larger
|
|
136
|
+
# than a segment the API takes, or would need more segments than the API numbers, the concurrency is not 1 to
|
|
137
|
+
# MAX_CONCURRENCY, shared is neither true, false, nor nil, or additional_owners is neither nil nor an Array of
|
|
138
|
+
# at least one user identifier
|
|
139
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for a request
|
|
140
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
141
|
+
# leaves out the value: the request
|
|
142
|
+
# that initializes the upload raises it as it is, and a chunk or the finalize that opens a connection fails
|
|
143
|
+
# with it as the cause of the ChunkedUploadFailed it raises
|
|
144
|
+
# @raise [InvalidMediaType] if no media type is given and none can be inferred, or the one the media is, read
|
|
145
|
+
# from its bytes or else from the name of its file, is not one the category takes
|
|
146
|
+
# @raise [MissingMediaData] if the response that initializes the upload holds no media to append the chunks to
|
|
147
|
+
# @raise [ChunkedUploadFailed] if the upload is initialized, but a chunk cannot be appended, or it cannot be
|
|
148
|
+
# finalized, with the media it initialized
|
|
149
|
+
# @raise [TokenReportFailed] if save_tokens raises for the tokens of a refresh a request of the upload made, with
|
|
150
|
+
# the tokens, rather than the media, whatever the upload had done by then
|
|
151
|
+
# @example Upload a long video, and wait for X to process it once it is needed
|
|
152
|
+
# video = client.chunked_upload_media("talk.mp4", concurrency: 8)
|
|
153
|
+
# client.create_post("Watch the talk", media_ids: [client.await_media_processing!(video)])
|
|
154
|
+
def chunked_upload_media(media, **options) # steep:ignore DifferentMethodParameterKind
|
|
155
|
+
MediaUpload.chunked_upload(media, client: _ = self, **Utils.without_client(options))
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Wait until media has been processed, whether its processing succeeded or failed
|
|
159
|
+
#
|
|
160
|
+
# It returns the status X reported, which failed? tells a failure by, and ready? a success by;
|
|
161
|
+
# await_media_processing! raises for a failure instead. It waits through any other state, one X does not
|
|
162
|
+
# document among them, as it waits while processing is pending or in progress. Media that already says its
|
|
163
|
+
# processing succeeded or failed, or holds an upload response that names no processing, such as that of an
|
|
164
|
+
# image, is returned as it is, without a request.
|
|
165
|
+
#
|
|
166
|
+
# @api public
|
|
167
|
+
# @param media [UploadedMedia, Hash, #media_key, String, Integer] the uploaded media, media that has a media key,
|
|
168
|
+
# such as X::Media, the media key, or the media identifier
|
|
169
|
+
# @param options [Hash] the options of {MediaUpload.await_processing}
|
|
170
|
+
# @option options [Integer, Float, nil] :processing_timeout (600) the seconds from now to wait for processing to
|
|
171
|
+
# finish, checks and all, of at least 0, or nil to wait for as long as processing takes
|
|
172
|
+
# @return [UploadedMedia] the uploaded media, which holds the processing status, failed or not, or the media
|
|
173
|
+
# given, as uploaded media, if its processing has already ended
|
|
174
|
+
# @raise [ArgumentError] if the processing timeout is neither nil nor a finite number of seconds of at least 0
|
|
175
|
+
# @raise [ArgumentError] if the media given is nil, holds no identifier, or is neither media, a media key, nor a
|
|
176
|
+
# media identifier, or its media key names none
|
|
177
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for the request
|
|
178
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
179
|
+
# leaves out the value
|
|
180
|
+
# @raise [MissingMediaData] if a status response holds no media or carries no body at all
|
|
181
|
+
# @raise [MediaProcessingTimeout] if the media is still processing once the processing timeout would pass
|
|
182
|
+
# @example Wait for a video uploaded with chunked_upload_media
|
|
183
|
+
# video = client.await_media_processing(video)
|
|
184
|
+
# warn "#{video.id} failed to process" if video.failed?
|
|
185
|
+
def await_media_processing(media, **options) # steep:ignore DifferentMethodParameterKind
|
|
186
|
+
MediaUpload.await_processing(media, client: _ = self, **Utils.without_client(options))
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Wait until media has been processed, raising if its processing failed
|
|
190
|
+
#
|
|
191
|
+
# @api public
|
|
192
|
+
# @param media [UploadedMedia, Hash, #media_key, String, Integer] the uploaded media, media that has a media key,
|
|
193
|
+
# such as X::Media, the media key, or the media identifier
|
|
194
|
+
# @param options [Hash] the options of {MediaUpload.await_processing!}
|
|
195
|
+
# @option options [Integer, Float, nil] :processing_timeout (600) the seconds from now to wait for processing to
|
|
196
|
+
# finish, checks and all, of at least 0, or nil to wait for as long as processing takes
|
|
197
|
+
# @return [UploadedMedia] the uploaded media, which holds the processing status, or the media given, as uploaded
|
|
198
|
+
# media, if its processing has already succeeded
|
|
199
|
+
# @raise [ArgumentError] if the processing timeout is neither nil nor a finite number of seconds of at least 0
|
|
200
|
+
# @raise [ArgumentError] if the media given is nil, holds no identifier, or is neither media, a media key, nor a
|
|
201
|
+
# media identifier, or its media key names none
|
|
202
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for the request
|
|
203
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
204
|
+
# leaves out the value
|
|
205
|
+
# @raise [MissingMediaData] if a status response holds no media or carries no body at all
|
|
206
|
+
# @raise [MediaProcessingFailed] if media processing failed, with the status X reported, or the media given,
|
|
207
|
+
# without a request, if it already says its processing failed
|
|
208
|
+
# @raise [MediaProcessingTimeout] if the media is still processing once the processing timeout would pass
|
|
209
|
+
# @example Wait for a video uploaded with chunked_upload_media, raising if X could not process it
|
|
210
|
+
# client.await_media_processing!(video)
|
|
211
|
+
def await_media_processing!(media, **options) # steep:ignore DifferentMethodParameterKind
|
|
212
|
+
MediaUpload.await_processing!(media, client: _ = self, **Utils.without_client(options))
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Describe uploaded media with alt text, for people who cannot see it
|
|
216
|
+
#
|
|
217
|
+
# @api public
|
|
218
|
+
# @param media [UploadedMedia, Hash, #media_key, String, Integer] the uploaded media, media that has a media key,
|
|
219
|
+
# such as X::Media, the media key, or the media identifier
|
|
220
|
+
# @param text [String] the alt text, of 1 to 1,000 characters
|
|
221
|
+
# @return [UploadedMedia] the media given, as uploaded media, which a call can be chained to
|
|
222
|
+
# @raise [ArgumentError] if the alt text is empty or longer than the API takes, before a request
|
|
223
|
+
# @raise [ArgumentError] if the media given is nil, holds no identifier, or is neither media, a media key, nor a
|
|
224
|
+
# media identifier, or its media key names none
|
|
225
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for the request
|
|
226
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
227
|
+
# leaves out the value
|
|
228
|
+
# @raise [MissingMediaData] if the response holds no metadata or carries no body at all
|
|
229
|
+
# @example Describe an image
|
|
230
|
+
# client.add_alt_text(media, "A cat asleep on a keyboard")
|
|
231
|
+
# @example Describe an image as it is uploaded
|
|
232
|
+
# media = client.add_alt_text(client.upload_media("cat.jpg"), "A cat asleep on a keyboard")
|
|
233
|
+
def add_alt_text(media, text)
|
|
234
|
+
Metadata.add_alt_text(media, text, client: _ = self)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# Attach uploaded subtitles to an uploaded video
|
|
238
|
+
#
|
|
239
|
+
# @api public
|
|
240
|
+
# @param video [UploadedMedia, Hash, #media_key, String, Integer] the uploaded video, media that has a media
|
|
241
|
+
# key, such as X::Media, or its media identifier
|
|
242
|
+
# @param subtitles [UploadedMedia, Hash, #media_key, String, Integer] the uploaded subtitles, media that has a
|
|
243
|
+
# media key, or their media identifier
|
|
244
|
+
# @param language_code [String] the language of the subtitles, such as EN
|
|
245
|
+
# @param options [Hash] the options of {Metadata.add_subtitles}
|
|
246
|
+
# @option options [String, nil] :display_name (nil) the name of the language shown to viewers, such as English,
|
|
247
|
+
# or nil for none
|
|
248
|
+
# @option options [String, Symbol] :media_category ("tweet_video") the category the video was uploaded as,
|
|
249
|
+
# tweet_video or amplify_video, in any case, or TweetVideo or AmplifyVideo, as the subtitles endpoint names them
|
|
250
|
+
# @return [UploadedMedia] the video given, as uploaded media, which a call can be chained to
|
|
251
|
+
# @raise [ArgumentError] if the media category is neither tweet_video nor amplify_video, or the language code is
|
|
252
|
+
# not two letters
|
|
253
|
+
# @raise [ArgumentError] if the video or the subtitles are nil, hold no identifier, are neither media, a media
|
|
254
|
+
# key, nor a media identifier, have a media key that names none, or an identifier the API does not take
|
|
255
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for the request
|
|
256
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
257
|
+
# leaves out the value
|
|
258
|
+
# @raise [MissingMediaData] if the response holds no metadata or carries no body at all
|
|
259
|
+
# @example Subtitle a video in English
|
|
260
|
+
# client.add_subtitles(video, subtitles, "EN", display_name: "English")
|
|
261
|
+
def add_subtitles(video, subtitles, language_code, **options) # steep:ignore DifferentMethodParameterKind
|
|
262
|
+
Metadata.add_subtitles(video, subtitles, language_code, client: _ = self, **Utils.without_client(options))
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
# Update the profile image of the authenticated user from a file
|
|
266
|
+
#
|
|
267
|
+
# @api public
|
|
268
|
+
# @param media [String, Pathname, IO, StringIO] the path to the image, or an IO that reads it
|
|
269
|
+
# @return [void]
|
|
270
|
+
# @raise [InvalidMedia] if the file does not exist
|
|
271
|
+
# @raise [ArgumentError] if the media is neither a path nor an IO
|
|
272
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for the request
|
|
273
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
274
|
+
# leaves out the value
|
|
275
|
+
# @raise [InvalidMedia] if the media cannot be read, is empty, or is larger than the 700 kilobytes the API takes
|
|
276
|
+
# @raise [InvalidMediaType] if the image does not begin with the signature of a GIF, a JPEG, or a PNG
|
|
277
|
+
# @example Update the profile image
|
|
278
|
+
# client.update_profile_image("avatar.png")
|
|
279
|
+
def update_profile_image(media)
|
|
280
|
+
Account.update_profile_image(media, client: _ = self)
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
# Update the profile banner of the authenticated user from a file
|
|
284
|
+
#
|
|
285
|
+
# @api public
|
|
286
|
+
# @param media [String, Pathname, IO, StringIO] the path to the image, or an IO that reads it
|
|
287
|
+
# @param options [Hash] the options of {Account.update_profile_banner}, which give the region of the image to use
|
|
288
|
+
# @option options [Integer, nil] :width (nil) the width of the region, in pixels, of at least 1
|
|
289
|
+
# @option options [Integer, nil] :height (nil) the height of the region, in pixels, of at least 1
|
|
290
|
+
# @option options [Integer, nil] :offset_left (nil) the pixels by which the region is offset from the left, of at
|
|
291
|
+
# least 0
|
|
292
|
+
# @option options [Integer, nil] :offset_top (nil) the pixels by which the region is offset from the top, of at
|
|
293
|
+
# least 0
|
|
294
|
+
# @return [void]
|
|
295
|
+
# @raise [InvalidMedia] if the file does not exist
|
|
296
|
+
# @raise [ArgumentError] if the media is neither a path nor an IO, or a width, height, or offset is neither nil
|
|
297
|
+
# nor an Integer of the pixels it takes
|
|
298
|
+
# @raise [ArgumentError] if the client was given no proxy_url and the proxy the environment names for the request
|
|
299
|
+
# cannot be parsed, or is not an http or https URL with a host, with a message that names the variables read and
|
|
300
|
+
# leaves out the value
|
|
301
|
+
# @raise [InvalidMedia] if the media cannot be read, is empty, or is larger than the 5 megabytes X takes
|
|
302
|
+
# @raise [InvalidMediaType] if the image does not begin with the signature of a GIF, a JPEG, or a PNG
|
|
303
|
+
# @example Update the profile banner
|
|
304
|
+
# client.update_profile_banner("banner.png", width: 1500, height: 500)
|
|
305
|
+
def update_profile_banner(media, **options) # steep:ignore DifferentMethodParameterKind
|
|
306
|
+
Account.update_profile_banner(media, client: _ = self, **Utils.without_client(options))
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
310
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "timeout"
|
|
4
|
+
require_relative "error"
|
|
5
|
+
require_relative "uploaded_media"
|
|
6
|
+
|
|
7
|
+
module X
|
|
8
|
+
# Error raised when a chunked upload is initialized, but its chunks cannot be appended or it cannot be finalized
|
|
9
|
+
#
|
|
10
|
+
# The media is created, and its identifier given, once the upload is initialized, so the media is not lost to a
|
|
11
|
+
# chunk or a finalize that fails, as one the API answers with a server error, to media that can no longer be read,
|
|
12
|
+
# as a file deleted, closed, or shrunk once the upload was initialized cannot, or to any other error that ends the
|
|
13
|
+
# upload, as one the on_response hook of the client raises, or a thread for the chunks that cannot be started: the
|
|
14
|
+
# error holds the media the upload initialized, which names it by its identifier and its media key. The error that
|
|
15
|
+
# failed the upload is the cause, whose message the message ends with. A Timeout::Error, as Timeout.timeout raises
|
|
16
|
+
# around the upload, is raised as it is, so that a rescue of it still catches it, unless one raised with its class,
|
|
17
|
+
# as by Timeout.timeout(5, Timeout::Error), lands in the save_tokens of a refresh, which raises TokenReportFailed,
|
|
18
|
+
# holding the tokens. A TokenReportFailed, which a chunk or the finalize raises when save_tokens raised for the
|
|
19
|
+
# tokens of a refresh it made, is raised as it is too, rather than as this error, so that the rescue of it that
|
|
20
|
+
# stores the tokens it holds catches it around an upload as around any other request.
|
|
21
|
+
#
|
|
22
|
+
# @api public
|
|
23
|
+
class ChunkedUploadFailed < Uploads::Error
|
|
24
|
+
# Finish a chunked upload, raising this error, which holds the media, on failure
|
|
25
|
+
#
|
|
26
|
+
# Internal to x-uploads: a chunked upload appends its chunks and finalizes the media through it, and calls it
|
|
27
|
+
# with __send__, since it is private. A Timeout::Error, a TokenReportFailed, which holds the tokens of a refresh
|
|
28
|
+
# that save_tokens raised for, and an exception that is not a StandardError, such as an Interrupt, are raised as
|
|
29
|
+
# they are. The error holds the identifier and media key of the media alone, since the
|
|
30
|
+
# rest of the response that initialized the upload, such as its expires_after_secs, says nothing of whether the
|
|
31
|
+
# media can be attached to a post, so that its ready? is false and await_media_processing checks it.
|
|
32
|
+
#
|
|
33
|
+
# @api private
|
|
34
|
+
# @param media [Hash{String => Object}] the media the upload initialized
|
|
35
|
+
# @yield appends the chunks and finalizes the upload
|
|
36
|
+
# @return [Object] what the block returns
|
|
37
|
+
# @raise [ChunkedUploadFailed] if the block raises a StandardError other than a Timeout::Error or a
|
|
38
|
+
# TokenReportFailed, such as an error of the X API, or one of reading the media, as a file deleted, closed, or
|
|
39
|
+
# shrunk once the upload was initialized raises
|
|
40
|
+
# @raise [TokenReportFailed] if save_tokens raises for the tokens of a refresh the block made, with the tokens
|
|
41
|
+
# @example Finish a chunked upload, keeping the media if it fails
|
|
42
|
+
# X::ChunkedUploadFailed.__send__(:keeping, media) { Uploads::Chunks.finalize(client:, media:) }
|
|
43
|
+
def self.keeping(media)
|
|
44
|
+
yield
|
|
45
|
+
rescue Timeout::Error, TokenReportFailed
|
|
46
|
+
raise
|
|
47
|
+
rescue
|
|
48
|
+
raise new(media: media.slice("id", "media_key"))
|
|
49
|
+
end
|
|
50
|
+
private_class_method :keeping
|
|
51
|
+
|
|
52
|
+
# The media the upload initialized, which names it by its identifier
|
|
53
|
+
# @api public
|
|
54
|
+
# @return [UploadedMedia, nil] the media, or nil if none was given
|
|
55
|
+
# @example Read the identifier of the media an upload left unfinished
|
|
56
|
+
# rescue X::ChunkedUploadFailed => e
|
|
57
|
+
# e.media.id # => 1880028106020515840
|
|
58
|
+
attr_reader :media
|
|
59
|
+
|
|
60
|
+
# Initialize the error with the media the upload initialized
|
|
61
|
+
#
|
|
62
|
+
# The message is the one given, or else names the media by its identifier, when media that holds one was given.
|
|
63
|
+
#
|
|
64
|
+
# @api public
|
|
65
|
+
# @param message [String, nil] the message, or nil for one that names the media
|
|
66
|
+
# @param media [UploadedMedia, Hash{String => Object}, nil] the media the upload initialized, a Hash of which is
|
|
67
|
+
# held as uploaded media
|
|
68
|
+
# @return [ChunkedUploadFailed] a new error
|
|
69
|
+
# @example Raise the error for media whose upload could not be finished
|
|
70
|
+
# raise X::ChunkedUploadFailed.new(media: media)
|
|
71
|
+
# @example Raise the error with a message of its own, as a test stub may
|
|
72
|
+
# raise X::ChunkedUploadFailed, "The upload could not be finished"
|
|
73
|
+
def initialize(message = nil, media: nil)
|
|
74
|
+
@media = media.is_a?(Hash) ? UploadedMedia.new(media) : media
|
|
75
|
+
super(message || ["Media", media&.[]("id"), "was initialized, but its upload could not be finished"].compact.join(" "))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# The message, ending with why the upload could not be finished
|
|
79
|
+
#
|
|
80
|
+
# It ends with the message of the error that failed the upload, which is the cause, if there is one, whether the
|
|
81
|
+
# message was given or named the media.
|
|
82
|
+
#
|
|
83
|
+
# @api public
|
|
84
|
+
# @return [String] the message
|
|
85
|
+
# @example Read why the upload could not be finished
|
|
86
|
+
# error.message # => "Media 7 was initialized, but its upload could not be finished: Service Unavailable"
|
|
87
|
+
def to_s = [super, cause&.message].compact.join(": ")
|
|
88
|
+
end
|
|
89
|
+
end
|