"""Mastodon 4.7: accounts and tokens made in one `rails runner` (it has no password grant, and tootctl boots Rails once per account), objects read from its Postgres database (`mastodon`).""" import json from core import podman from dialects.base import Stored from dialects.mastodon_api import MastodonApi VIS = {0: "public", 1: "unlisted", 2: "followers", 3: "direct", 4: "limited"} PROVISION = r''' app = Doorkeeper::Application.find_or_create_by!(name: "pasture-town") { |a| a.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"; a.scopes = "read write follow" } out = {} JSON.parse(STDIN.read).each do |u| account = Account.find_local(u["username"]) if account.nil? account = Account.new(username: u["username"]) user = User.new(email: "#{u["username"]}@mastodon.test", password: u["password"], agreement: true, approved: true, confirmed_at: Time.now.utc, account: account) user.save! account.reload end account.user.approve! unless account.user.approved? account.user.confirm unless account.user.confirmed? token = Doorkeeper::AccessToken.find_or_create_for(application: app, resource_owner: account.user, scopes: Doorkeeper::OAuth::Scopes.from_string("read write follow"), expires_in: nil, use_refresh_token: false) out[u["username"]] = token.token end puts "TOWN" + out.to_json ''' class Mastodon(MastodonApi): platform = "mastodon" caps = MastodonApi.caps | {"quote"} def provision(self, accounts): payload = json.dumps([{"username": a.username, "password": a.password} for a in accounts]) out = podman.exec_("pasture-mastodon", "bin/rails", "runner", PROVISION, input=payload, timeout=600) tokens = json.loads(next(line[4:] for line in out.splitlines() if line.startswith("TOWN"))) return [self.session_from_token(a, tokens[a.username]) for a in accounts] def _rows(self, uris): if not uris: return {} rows = podman.psql("mastodon", """ select s.id::text as local_id, s.uri, s.deleted_at is not null as deleted, s.visibility, s.text, s.spoiler_text as cw, s.edited_at is not null as edited, p.uri as parent_uri, coalesce(st.favourites_count, 0) as likes, coalesce(st.reblogs_count, 0) as boosts, coalesce(st.replies_count, 0) as replies, pl.cached_tallies as votes from statuses s left join statuses p on p.id = s.in_reply_to_id left join status_stats st on st.status_id = s.id left join polls pl on pl.status_id = s.id where s.reblog_of_id is null and s.uri = any(string_to_array(:'p1', ' '))""", " ".join(uris)) return {r["uri"]: Stored(True, r["deleted"], r["local_id"], VIS.get(r["visibility"]), r["text"], r["cw"] or None, r["edited"], r["parent_uri"], r["likes"], r["boosts"], r["replies"], r["votes"], None, r) for r in rows}