Class: Shep::Session

Inherits:
Object
  • Object
show all
Defined in:
lib/shep/session.rb

Overview

Represents a connection to a Mastodon (or equivalent) server.

## Conventions

‘fetch_*` methods retrieve a single Mastodon object, an `Entity` subinstance.

‘each_*` methods retrieve multiple objects, also `Entity` subinstances. If called with a block, the block is evaluated on each item in turn and the block’s result is ignored. Otherwise, it returns an ‘Enumerator` which can be used in the usual ways.

Some examples:

# Evaluate a block on each status
session.each_status() { |status| do_thing(status) }

# Retrieve the last 100 statuses in an array
statuses = session.each_status(, limit: 100).to_a

# Retrieve the last 200 statuses via an enumerator and do
# extra transformation on the result before collecting
# them in an array.
statuses = session.each_status(, limit: 200)
    .select{|status| BESTIES.include? status..username }
    .map{|status| status.id}
    .to_a

The actual web API “paginates” the output. That is, it returns the first 40 (or so) items and then provides a link to the next chunk. Shep’s ‘each_*` methods handle this for you automatically. This means that unless you use `limit:`, the `each_*` methods will retrieve all available items, at least until you reach the rate limit (see below).

Note that it is safe to leave an ‘each_*` methods block with `break`, `return`, an exception, or any other such mechanism.

The remaining Mastodon API methods will in some way modify the state of the server and return an Entity subinstance on success.

All API calls throw an exception on failure.

## Rate Limits

Mastodon servers restrict the number of times you can use a specific endpoint within a time period as a way to prevent abuse. Shep provides several tools for handling these limits gracefully.

  1. The method #rate_limit will return a Struct that tells you how many requests you have left and when the count is reset.

  2. If a rate limit is exceeded, the method will throw an Error::RateLimit exception instead of an ordinary Error::Http exception.

  3. If the Session is created with argument ‘rate_limit_retry:` set to true, the Session will instead wait out the reset time and try again.

If you enable the wait-and-retry mechanism, you can also provide a hook function (i.e. a thing that responds to ‘call`) via constructor argument `retry_hook:`. This is called with one argument, the result of #rate_limit for the limited API endpoint, immediately before Shep starts waiting for the limit to reset.

The built-in wait time takes the callback’s execution time into account so it’s possible to use the callback to do your own waiting and use that time more productively.

Alternately, all of the ‘each_*` methods have a `limit:` parameter so it’s easy to avoid making too many API calls and many have a ‘max_id:` parameter that allows you to continue where you left off.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host:, token: nil, user_agent: "ShepRubyGem/#{Shep::Version}", ua_comment: nil, rate_limit_retry: false, retry_hook: nil, logger: nil, debug_http: false) ⇒ Session

Initialize a new Shep::Session.

By default, the User-Agent header is set to the gem’s identifier, but may be overridden with the ‘user_agent` parameter. It is your responsibility to make sure it is formatted correctly. You can also append comment text to the given User-Agent string with `ua_comment`; this lets you add a comment to the default text.

Parameter ‘logger` may be a `Logger` object, `nil`, or a `Symbol` whose value is the name of one of the supported log levels. In the latter case, a new Logger is created and set to that level. If `nil` is given, a dummy `Logger` is created and used.

If ‘debug_http` is true, compression is disabled and the transactions are sent to `STDERR` via `Net::HTTP.set_debug_output`.

WARNING: this opens a serious security hole and should not be used in production.

Parameters:

  • host (String)

    Hostname of the server

  • token (String) (defaults to: nil)

    Bearer token; optional

  • user_agent (String) (defaults to: "ShepRubyGem/#{Shep::Version}")

    User-Agent string to use

  • ua_comment (String) (defaults to: nil)

    Comment part of User-Agent string

  • rate_limit_retry (Boolean) (defaults to: false)

    Handle request limits by waiting for the count to reset and trying again.

  • retry_hook (Proc) (defaults to: nil)

    One-argument hook function to call before waiting for the rate limit to reset

  • logger (Logger) (defaults to: nil)

    The logger or mode; optional

  • debug_http (Boolean) (defaults to: false)

    Enable ‘Net::HTTP` debugging; **insecure!**

Raises:



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/shep/session.rb', line 132

def initialize(host:,
               token:               nil,
               user_agent:          "ShepRubyGem/#{Shep::Version}",
               ua_comment:          nil,

               rate_limit_retry:    false,
               retry_hook:          nil,

               logger:              nil,
               debug_http:          false)
  @host = host
  @token = token
  @logger = init_logger(logger)

  @user_agent = user_agent
  @user_agent += " #{ua_comment}" if ua_comment
  @user_agent.freeze

  @rate_limit_retry = rate_limit_retry
  @retry_hook = retry_hook

  @debug_http = debug_http

  @rate_limit = Struct.new(:limit, :remaining, :reset).new

  raise Error::Caller.new("retry_hook: must a callable or nil") unless
    @retry_hook == nil || @retry_hook.respond_to?(:call)
end

Instance Attribute Details

#hostString (readonly)

The Server’s hostname

Returns:

  • (String)

    the current value of host



90
91
92
# File 'lib/shep/session.rb', line 90

def host
  @host
end

#loggerLogger (readonly)

The logger object

Returns:

  • (Logger)

    the current value of logger



90
91
92
# File 'lib/shep/session.rb', line 90

def logger
  @logger
end

#user_agentString (readonly)

User-Agent string; frozen

Returns:

  • (String)

    the current value of user_agent



90
91
92
# File 'lib/shep/session.rb', line 90

def user_agent
  @user_agent
end

Instance Method Details

#delete_status(id) ⇒ Entity::Status

Delete the status at ID.

Returns:

See Also:



823
# File 'lib/shep/session.rb', line 823

def delete_status(id) = rest_delete("statuses/#{id}", Entity::Status)

#dismiss_notification(id) ⇒ Object

Dismiss the notification with the given ID.

Warning: due to the complexity involved in repeatably sending a notification to an account, there is limited test coverage for this method.



832
833
834
835
836
# File 'lib/shep/session.rb', line 832

def dismiss_notification(id)
  url = rest_uri("notifications/#{id}/dismiss", {})
  basic_rest_post_or_put(url, {})
  return nil
end

#each_boost_acct(status_id, limit: nil) {|item| ... } ⇒ Enumerator

Retrieve each Entity::Account that boosted the given status.

Parameters:

  • limit (Integer) (defaults to: nil)

    Maximum number of items to retrieve

Yields:

  • (item)

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



574
575
576
577
578
579
580
# File 'lib/shep/session.rb', line 574

def each_boost_acct(status_id,
                    limit: nil,
                    &block)
  query = magically_get_caller_kwargs(binding, method(__method__))
  rest_get_seq("statuses/#{status_id}/reblogged_by",
               Entity::Account, query, block)
end

#each_fave_acct(status_id, limit: nil) {|item| ... } ⇒ Enumerator

Retrieve each account that favourited the given status.

Parameters:

  • limit (Integer) (defaults to: nil)

    Maximum number of items to retrieve

Yields:

  • (item)

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



594
595
596
597
598
599
600
# File 'lib/shep/session.rb', line 594

def each_fave_acct(status_id,
                   limit: nil,
                   &block)
  query = magically_get_caller_kwargs(binding, method(__method__))
  rest_get_seq("statuses/#{status_id}/favourited_by",
               Entity::Account, query, block)
end

#each_follower(account_id, limit: nil) {|item| ... } ⇒ Enumerator

Retrieve the follower list of an account.

As of Mastodon 4.0, no longer requires a token.

Parameters:

  • account_id (String)

    The account

  • limit (Integer) (defaults to: nil)

    Maximum number of items to retrieve

Yields:

  • (item)

    Optional; applied to each item

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



366
367
368
369
370
371
372
# File 'lib/shep/session.rb', line 366

def each_follower(,
                  limit: nil,
                  &block)
  query = magically_get_caller_kwargs(binding, method(__method__))
  return rest_get_seq("accounts/#{}/followers", Entity::Account,
                      query, block)
end

#each_following(account_id, limit: nil) {|item| ... } ⇒ Enumerator

Retrieve the list of accounts this account follows

Parameters:

  • account_id (String)

    The account

  • limit (Integer) (defaults to: nil)

    Maximum number of items to retrieve

Yields:

  • (item)

    Optional; applied to each item

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



386
387
388
389
390
391
392
# File 'lib/shep/session.rb', line 386

def each_following(,
                   limit: nil,
                   &block)
  query = magically_get_caller_kwargs(binding, method(__method__))
  return rest_get_seq("accounts/#{}/following", Entity::Account,
                      query, block)
end

#each_home_status(limit: nil, local: false, max_id: "", remote: false, only_media: false) {|item| ... } ⇒ Enumerator

Retrieve each Entity::Status in the home timeline.

Requires token.

Parameters:

  • limit (Integer) (defaults to: nil)

    maximum number of items to retrieve

  • max_id (String) (defaults to: "")

    retrieve results older than this ID.

  • local (Boolean) (defaults to: false)

    retrieve only local statuses

  • remote (Boolean) (defaults to: false)

    retrieve only remote statuses

  • only_media (Boolean) (defaults to: false)

    retrieve only media status

Yields:

  • (item)

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



551
552
553
554
555
556
557
558
559
# File 'lib/shep/session.rb', line 551

def each_home_status(limit:              nil,
                     local:              false,
                     max_id:             "",
                     remote:             false,
                     only_media:         false,
                     &block)
  query = magically_get_caller_kwargs(binding, method(__method__))
  rest_get_seq("timelines/home", Entity::Status, query, block)
end

#each_notification(types: [], exclude_types: [], limit: nil, account_id: nil) {|item| ... } ⇒ Enumerator

Retrieve each notification.

Requires a bearer token.

Notification types are indicated by of the following symbols:

‘:mention`, `:status`, `:reblog`, `:follow`, `:follow_request` `:favourite`, `:poll`, `:update`, `:admin.sign_up`, or `:admin.report`

This method will throw an ‘Error::Caller` exception if an unknown value is used.

Parameters:

  • types (Array<String>) (defaults to: [])

    list of notifications types to enumerate; others are ignoredn

  • exclude_types (Array<String>) (defaults to: [])

    types of notifications to exclude

  • limit (Integer) (defaults to: nil)

    Maximum number of items to retrieve

  • account_id (String) (defaults to: nil)

    Only retrieve notifications from the account with this ID.

Yields:

  • (item)

    Applied to each Notification

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# File 'lib/shep/session.rb', line 634

def each_notification(types:            [],
                      exclude_types:    [],
                      limit:            nil,
                      account_id:       nil,
                      &block)
  allowed_notifications = %i{mention status reblog follow follow_request
                             favourite poll update admin.sign_up
                             admin.report}

  # Remove duplicates and convert strings to symbols
  [types, exclude_types].each{|param|
    param.map!{|item| item.intern}
    param.uniq!
  }

  # Now, ensure there are no incorrect notification types.
  (types + exclude_types).each{|filter|
    assert("Unknown notification type: #{filter}") {
      allowed_notifications.include?(filter.intern)
    }
  }

  query = magically_get_caller_kwargs(binding, method(__method__))
  rest_get_seq("notifications", Entity::Notification, query, block)
end

#each_public_status(limit: nil, max_id: "", local: false, remote: false, only_media: false) {|item| ... } ⇒ Enumerator

Retrieve the instance’s public timeline(s)

May require a token depending on the instance’s settings.

Parameters:

  • limit (Integer) (defaults to: nil)

    Max. items to retrieve.

  • max_id (String) (defaults to: "")

    retrieve results older than this ID.

  • local (Boolean) (defaults to: false)

    Retrieve only local statuses

  • remote (Boolean) (defaults to: false)

    Retrieve only remote statuses

  • only_media (Boolean) (defaults to: false)

    Retrieve only statuses with media

Yields:

  • (item)

    Optional; applied to each item

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



456
457
458
459
460
461
462
463
464
# File 'lib/shep/session.rb', line 456

def each_public_status(limit:              nil,
                       max_id:             "",
                       local:              false,
                       remote:             false,
                       only_media:         false,
                       &block)
  query = magically_get_caller_kwargs(binding, method(__method__))
  rest_get_seq("timelines/public", Entity::Status, query, block)
end

#each_status(account_id, limit: nil, max_id: "", only_media: false, exclude_replies: false, exclude_reblogs: false, pinned: false, tagged: nil) {|item| ... } ⇒ Enumerator

Retrieve the account’s statuses

Parameters:

  • account_id (String)

    The ID of the account

  • limit (Integer) (defaults to: nil)

    Maximum number of accounts to retrieve

  • max_id (String) (defaults to: "")

    retrieve results older than this ID.

  • only_media (Boolean) (defaults to: false)

    If true, filter for statuses with media

  • exclude_replies (Boolean) (defaults to: false)

    If true, exclude replies

  • exclude_reblogs (Boolean) (defaults to: false)

    If true, exclude boosts

  • pinned (Boolean) (defaults to: false)

    If true, filter for pinned statuses

  • tagged (String) (defaults to: nil)

    Filter for statuses containing the given tag

Yields:

  • (item)

    Optional; applied to each item

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



422
423
424
425
426
427
428
429
430
431
432
433
# File 'lib/shep/session.rb', line 422

def each_status(,
                limit:              nil,
                max_id:             "",
                only_media:         false,
                exclude_replies:    false,
                exclude_reblogs:    false,
                pinned:             false,
                tagged:             nil,
                &block)
  query = magically_get_caller_kwargs(binding, method(__method__))
  rest_get_seq("accounts/#{}/statuses", Entity::Status, query, block)
end

#each_tag_status(hashtag_s, limit: nil, max_id: "", local: false, remote: false, only_media: false, all: [], none: []) {|item| ... } ⇒ Enumerator

Retrieve a tag’s timeline.

The tag may either be a String (containing one hashtag) or an Array containing one or more. If more than one hashtag is given, all statuses containing any of the given hashtags are retrieved. (This uses the ‘any[]` parameter in the API.)

There is currently no check for contradictory tag lists.

Parameters:

  • hashtag_s (String, Array<String>)

    Hashtag(s) to retrieve.

  • limit (Integer) (defaults to: nil)

    maximum number of items to retrieve

  • max_id (String) (defaults to: "")

    return results older than this ID.

  • local (Boolean) (defaults to: false)

    retrieve only local statuses

  • remote (Boolean) (defaults to: false)

    retrieve only remote statuses

  • only_media (Boolean) (defaults to: false)

    retrieve only media status

  • all (Array<String>) (defaults to: [])

    list of other tags that must also be present.

  • none (Array<String>) (defaults to: [])

    list of tags that are excluded.

Yields:

  • (item)

    block to apply to each Status; optional

Yield Parameters:

Returns:

  • (Enumerator)

    if block is not given, otherwise self

See Also:



499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/shep/session.rb', line 499

def each_tag_status(hashtag_s,
                    limit:              nil,
                    max_id:             "",
                    local:              false,
                    remote:             false,
                    only_media:         false,
                    all:                [],
                    none:               [],
                    &block)

  query = magically_get_caller_kwargs(binding, method(__method__))

  any = []
  if hashtag_s.is_a?(Array)
    hashtag_s = hashtag_s.dup
    hashtag = hashtag_s.shift
    any = hashtag_s
  else
    hashtag = hashtag_s
  end

  assert("Empty hashtag!") { hashtag && !hashtag.empty? }

  query[:any] = any unless any.empty?

  rest_get_seq("timelines/tag/#{hashtag}", Entity::Status, query, block)
end

#edit_status(id, status, media_ids: [], spoiler_text: "", language: "en") ⇒ Entity::Status

Update the status with the given Id.

Requires token with sufficient permission for the account that owns the status.

Notionally, this method will change all of the affected status parts each time it’s invoked, passing the default parameter if none is given. This is because it is unclear how the API handles omitted fields so we just don’t do that. (You can force it to omit an argument by setting it to nil; this may or may not work for you.)

Parameters:

  • id (String)

    id of the status to edit

  • status (String)

    new status text

  • media_ids (Array<String>) (defaults to: [])

    array of media object IDs to attach to this status.

  • spoiler_text (String) (defaults to: "")

    Sets or clears the content warning. Non-empty value also sets the ‘sensitive` field.

  • language (String) (defaults to: "en")

    The language of the status; defaults to “en”. (Apologies for the anglocentrism; this should be consistent.)

Returns:

See Also:



739
740
741
742
743
744
745
746
747
748
749
750
# File 'lib/shep/session.rb', line 739

def edit_status(id, status,
                media_ids:          [],
                spoiler_text:       "",
                language:           "en")
  formhash = magically_get_caller_kwargs(binding, method(__method__),
                                         strip_ignorables: false)
  formhash[:status] = status
  formhash[:sensitive] = !!spoiler_text && !spoiler_text.empty?

  formdata = formhash2array(formhash)
  return rest_put("statuses/#{id}", Entity::Status, formdata)
end

#fetch_account(id) ⇒ Entity::Account

Fetch user details by ID

Parameters:

  • id (String)

    the ID code of the account

Returns:

See Also:



238
239
240
# File 'lib/shep/session.rb', line 238

def (id)
  return rest_get("accounts/#{id}", Entity::Account, {})
end

#fetch_account_by_username(handle) ⇒ Entity::Account?

Fetch user details by username.

The username must belong to a user on the current server.

Parameters:

  • handle (String)

    the account’s username with or without the leading ‘@’ character (e.g. @benoitmandelbot)

Returns:

See Also:



253
254
255
256
257
258
259
# File 'lib/shep/session.rb', line 253

def (handle)
  return rest_get("accounts/lookup", Entity::Account, {acct: handle})
rescue Error::Http => oopsie
  # As a special case, return nil if the lookup fails
  return nil if oopsie.response.is_a?(Net::HTTPNotFound)
  raise oopsie
end

#fetch_context(id) ⇒ Entity::Context

Fetch the context (parent and child status) of status at ‘id’



285
# File 'lib/shep/session.rb', line 285

def fetch_context(id) = rest_get("statuses/#{id}/context", Entity::Context, {})

#fetch_notification(ntfn_id) ⇒ Entity::Notification

Fetch an individual notification by ID.

Requires a token with sufficient permissions.

Parameters:

  • ntfn_id (String)

    the notification ID

Returns:

See Also:



270
271
# File 'lib/shep/session.rb', line 270

def fetch_notification(ntfn_id) =
rest_get("notifications/#{ntfn_id}", Entity::Notification, {})

#fetch_status(id) ⇒ Entity::Status

Fetch a single status



278
# File 'lib/shep/session.rb', line 278

def fetch_status(id) = rest_get("statuses/#{id}", Entity::Status, {})

#fetch_status_src(id) ⇒ Entity::StatusSource

Fetch the editable source of status at id.

Requires token.



294
295
# File 'lib/shep/session.rb', line 294

def fetch_status_src(id) = rest_get("statuses/#{id}/source",
Entity::StatusSource, {})

#fetch_status_with_media(id, media_dir = '.', refetch: true) ⇒ Entity::Status, Hash

Fetch the given status and also any attached media.

Media is downloaded into the given directory unless a file with the expected name is already there (and ‘refetch` is not `true`).

Filenames are chosen by the function; the second return value (a ‘Hash`) can be used to find them. Value order also corresponds to the order of the returned `Status`’s ‘media_attachments` field.

Note that intermediate files unique temporary names while downloading is in progress. This means it is safe to set ‘refetch` to false even if a previous download attempt failed. However, it is would be necessary to delete the intermediate file, which has the suffic “.tmp”.

Parameters:

  • id (String)

    ID of the status to retrieve

  • media_dir (String) (defaults to: '.')

    Path to the download directory

  • refetch (Boolean) (defaults to: true)

    Fetch the media even if it is already present

Returns:

  • (Entity::Status, Hash)

    the Status and a Hash mapping the media URL to the corresponding local file.

See Also:



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'lib/shep/session.rb', line 325

def fetch_status_with_media(id, media_dir = '.', refetch: true)
  status = fetch_status(id)
  media = {}

  status.media_attachments.each { |ma|
    outfile = File.join(media_dir, File.basename(ma.url.path))

    if !refetch && File.exist?(outfile)
      @logger.info "Found '#{outfile}'; skipping."
    else
      tmp = File.join(media_dir, SecureRandom.uuid + '.tmp')
      begin
        basic_get_binary(ma.url, tmp)
        FileUtils.mv(tmp, outfile)
      rescue Error::Http => e
        FileUtils.rm(tmp, force: true)
        raise e
      end
    end

    media[ma.url.to_s] = outfile
  }

  return [status, media]
end

#post_status(text, visibility: :private, media_ids: [], spoiler_text: "", language: "") ⇒ Entity::Status

Post a status containing the given text at the specified visibility with zero or more media attachments.

visibility can be one of ‘public’, ‘private’, ‘unlisted’ or ‘direct’; these can be strings of symbols)

media_ids is an array containing the ID strings of any media that may need to be attached.

Parameters:

  • visibility (Symbol) (defaults to: :private)

    Status visibility; one of :public, :public, :unlisted or :direct

  • media_ids (Array<String>) (defaults to: [])

    List of IDs of attached media items.

  • spoiler_text (String) (defaults to: "")

    Content warning if non-empty string. Also sets ‘sensitive` to true.

  • language (String) (defaults to: "")

    ISO language code

Returns:

Raises:

See Also:



686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
# File 'lib/shep/session.rb', line 686

def post_status(text,
                visibility:       :private,
                media_ids:        [],
                spoiler_text:     "",
                language:         "")
  raise Error::Caller.new("Invalid visibility: #{visibility}") unless
    %i{public unlisted private direct}.include? visibility.intern

  query = magically_get_caller_kwargs(binding, method(__method__))
  query[:status] = text
  query[:sensitive] = true if
    spoiler_text && !spoiler_text.empty?

  # We need to convert to an array of keys and values rather than
  # a hash because passing an array argument requires duplicate
  # keys.  This causes Net::HTTP to submit it as a multipart form,
  # but we can cope.
  formdata = formhash2array(query)

  return rest_post("statuses", Entity::Status, formdata)
end

#rate_limitStruct.new(:limit, :remaining, :reset)

Return the rate limit information from the last REST request.

The result is a Struct with the following fields:

  • limit Integer - Number of allowed requests per time period

  • remaining Integer - Number of requests you have left

  • reset Time - Future time when the limit resets

Note that different types of operations have different rate limits. For example, most endpoints can be called up to 300 times within 5 minutes but no more than 30 media uploads are allowed within a 30 minute time period.

Note also that some Shep methods will perform multiple API requests; this is only ever the rate limit information from the latest of these.

Returns:

  • (Struct.new(:limit, :remaining, :reset))

See Also:



200
# File 'lib/shep/session.rb', line 200

def rate_limit = @rate_limit.dup.freeze

#rate_limit_descString

Return a human-readable summary of the rate limit.

text

Returns:

  • (String)

    ‘rate_limit()`’s result as nicely-formatted



207
208
209
210
211
212
213
# File 'lib/shep/session.rb', line 207

def rate_limit_desc
  rem = (@rate_limit.remaining || '?').to_s
  lim = (@rate_limit.limit || '?').to_s
  reset = @rate_limit.reset ? (@rate_limit.reset - Time.now).round : '?'

  return "#{rem}/#{lim}, #{reset}s"
end

#upload_media(path, content_type: nil, description: nil, focus_x: nil, focus_y: nil) ⇒ Entity::MediaAttachment

Upload the media contained in the file at ‘path’.

Requires token with sufficient permission for the account that owns the status.

Note that Mastodon processes attachments asynchronously, so the attachment may not be available for display when this method returns. Posting an unprocessed status as an attachment works as expected but it’s unclear what happens between posting and when the processing task completes. Usually, this shouldn’t matter to you.

If a rate limit is reached during a call to this method and ‘rate_limit_retry:` was set, the media file to upload should not be touched in any way until the method returns.

Parameters:

  • path (String)

    Path to the media file.

  • content_type (String) (defaults to: nil)

    MIME type of the media attachment. The default us usually all you need.

  • description (String) (defaults to: nil)

    The image description text.

  • focus_x (Float) (defaults to: nil)

    The horizontal coordinate of the focus on a range of -1.0 to 1.0. This is the point in the image that will be the center of the thumbnail. If set, ‘focus_y:` must also be set to a valid coordinate.

  • focus_y (Float) (defaults to: nil)

    The vertical coordinate of the focus.

Returns:

See Also:



790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
# File 'lib/shep/session.rb', line 790

def upload_media(path,
                 content_type: nil,
                 description: nil,
                 focus_x: nil,
                 focus_y: nil)
  formdata = [
    ['filename', File.basename(path)],
    ['file', File.open(path, "rb"), {content_type: content_type}],
  ]

  formdata.push ['description', description] if description

  # Focus args are more exacting so we do some checks here.
  !!focus_x == !!focus_y or
    raise Error::Caller.new("Args 'focus_x/y' must *both* be set or unset.")

  if focus_x
    raise Error::Caller.new("focus_x/y not a float between -1 and 1") unless
      (focus_x.is_a?(Float) && focus_y.is_a?(Float) &&
       focus_x >= -1.0 && focus_x <= 1.0 &&
       focus_y >= -1.0 && focus_y <= 1.0)

    formdata.push ['focus', "#{focus_x},#{focus_y}"]
  end

  return rest_post("media", Entity::MediaAttachment, formdata, v2: true)
end

#verify_credentialsEntity::Account

Return the Entity::Account object for the token we’re using.

Requires a token (obviously).



227
228
229
# File 'lib/shep/session.rb', line 227

def verify_credentials
  return rest_get('accounts/verify_credentials', Entity::Account, {})
end