Pular para o conteúdo principal

Blog do Dunossauro

Automatizando postagens no pixelfed

Table of Contents

Tenho trabalhado nos últimos tempos em uma forma de postar meus conteúdos na minha página e sindicar o conteúdo nas redes sociais (POSSE). Nessa experiência, tenho desenvolvido em vagarosidade glacial o sociopyta.

Na grande maioria dos casos, fazer postagens em redes sociais (silos) não oferece um grande desafio, temos documentação de APIs ou bibliotecas prontas para fazer isso. No caso do pixelfed, não temos nem documentação, nem bibliotecas :)

Esse post é sobre um “trabalho investigativo” em código-fonte alheio. Você pode pular para # solução e ver como fazer a automação dos seus posts ;)

# Iniciando a jornada

Então lá fui eu desbravar o código-fonte em PHP do servidor do pixelfed, mesmo que meu conhecimento em PHP seja… Vamos dizer… Pífio.

O fonte do controller da API do pixelfed pode ser encontrado aqui.

Ao iniciar a minha jornada, procurei por algo que criasse uma postagem. Acabei encontrando statusCreate.

Nesse endpoint fica evidente que deve ser feito um post para /api/v1/statuses usando json com algumas opções no corpo. Vou destacar as que me interessam, você pode ver as outras no código-fonte:

  • status: O texto que acompanhará a publicação das fotos
  • media_ids: Os IDs das fotos/vídeos que já foram enviados ao servidor
    • Sim… Isso precisa ser feito antes!
  • e mais …

O que nos daria um request parecido com esse (vou usar curl por considerar “universal”, mas no final vou fazer tudo usando httpx):

curl -X POST "{sua-instancia}/api/v1/statuses" \
  -H "Accept: application/json" \
  -F "status={seu-texto}" \
  -F "media_ids[]={media-id-1}" \
  -F "media_ids[]={media-id-2}" \
  # adicione mais `-F "media_ids[]={...}"` conforme necessário

Ok… Temos um ponto de partida.

Agora precisamos descobrir como enviar as fotos. Encontrei uma função chamada mediaUpload. Provavelmente é esse… hahaha

O endpoint é /api/v1/media que recebe também um json. Com o field file junto com os bytes do arquivo de média. O que nos dá algo parecido com isso:

curl -X POST "{sua-instancia}/api/v1/media" \
  -H "Accept: application/json" \
  -F "file=@{caminho-do-arquivo}"

Beleza. Se tudo der certo, vamos conseguir fazer o upload de uma midia agora e depois criar a postagem, certo?

ERRADO!

## PAT

Para enviar essas requisições, você vai precisar de um token de acesso. O que pixelfed chama de PAT (Personal Access Tokens). Para gerar um PAT você deve ir em:

https://{sua-instancia}/settings/applications

Existem duas sessões. Vá em Personal Access Tokens, clique em Create New Token, dê um nome para esse token e dê acesso de write. Isso irá gerar um token enorme. Salve ele, ele nunca mais vai ser visível!

## Uma nova tentativa

Agora podemos tentar de novo:

curl -X POST "{sua-instancia}/api/v1/media" \
  -H "Authorization: Bearer {PAT}" \
  -H "Accept: application/json" \
  -F "file=@{caminho-do-arquivo}"

YAY! Temos nossa media no servidor. O json de resposta em um field chamada id. Ele é o que devemos usar para criar a postagem.

Se você, assim como eu, for nerd de HTTP vai perceber que a resposta é 200 OK e não 201 CREATED e está tudo certo… 200 é que deu certo… Supere!

Agora que temos o id, podemos criar a postagem:

curl -X POST "{sua-instancia}/api/v1/statuses" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer {PAT}" \
  -F "status={seu-texto}" \
  -F "media_ids[]={media-id}"

YAY! [2]. Temos nossa postagem no pixelfed. O field uri é o link da nossa postagem. Com isso, a nossa jornada investigativa termina.

Novamente, 200, meu coração chora. Mas, vou superar.

# Solução

Embora tenha sido chato ler código-fonte, a solução que eu queria para o sociopyta é bastante simples… Após pegar o PAT precisamos somente fazer uma requisição para cada imagem e um para criar a postagem:

from pathlib import Path

import httpx


def post(text: str, images: list[Path], config) -> tuple[str, str]:
    base_url = config.pixelfed.api_base_url
    access_token = config.pixelfed.access_token

	post_url = f'{base_url}/api/v1/statuses'
	headers = {
		'Authorization': f'Bearer {access_token}',
		'Accept': 'application/json',
	}

	data = {
		'status': text,
		'media_ids[]': [
			upload_media(image, base_url, access_token) for image in images
		],
	}

	response = httpx.post(post_url, headers=headers, data=data)
	return 'pixelfed', response.json()['uri']


def upload_media(media: Path, instance: str, pat: str) -> str:
    media_url = f'{instance}/api/v1/media'
    headers = {'Authorization': f'Bearer {pat}', 'Accept': 'application/json'}
    response = httpx.post(
	    media_url,
		headers=headers,
		files={'file': media.read_bytes()}
	)

    return response.json()['id']

Agora, o que você vai fazer com essas funções é muito particular. Essas duas funções fazem somente o post, agora dentro do seu contexto de automação você deve chamá-las…



Comentários

Responda esse post por e-mail ✉️
Você também pode responder via fediverso:

Ao comentar nesse toot, seus comentários aparecerão na página

https://bolha.us/@dunossauro/114326692955710228