Compare commits
5 Commits
b772f8aeaf
...
48f94ad8ad
Author | SHA1 | Date | |
---|---|---|---|
|
48f94ad8ad | ||
|
37169128a0 | ||
|
1e143f35bc | ||
|
471285d7d7 | ||
|
d1633d8040 |
41
README.md
41
README.md
@ -1,3 +1,40 @@
|
|||||||
## GoToSocial
|
# gotosocial
|
||||||
|
|
||||||
Putting together some client code for working with GoToSocial in Python
|
A library for building GoToSocial bots
|
||||||
|
|
||||||
|
## client
|
||||||
|
|
||||||
|
This is a variant on my Mastodon bot code and hasn't been properly tested yet: I'll document it when I've done that.
|
||||||
|
|
||||||
|
## register
|
||||||
|
|
||||||
|
A utility for making it easier to register an app and get an access token for
|
||||||
|
a GoToSocial account - it's a Python script which automates as much of [the client API instructions](https://docs.gotosocial.org/en/latest/api/authentication/) as possible. There's still some manual stuff required for authenticating.
|
||||||
|
|
||||||
|
This project is maintained with [uv](https://docs.astral.sh/uv/): install it, check out this repo and then you can run the script with the uv commands below and it should take care of dependencies.
|
||||||
|
|
||||||
|
To register an app, you first need to give it an identity on the server - in the examples I've called the server `https://your.server/` and the app `mybot`
|
||||||
|
|
||||||
|
uv run register -u https://your.server/ -n mybot
|
||||||
|
|
||||||
|
This will create an app on the server and write out the client id and secret to a file in the current directory called `mybot_app.json`. It will also write a URL to the command line for the next step
|
||||||
|
|
||||||
|
The second step is to visit that URL in an incognito browser window. You'll be prompted to authenticate - use the email address and password for the GoToSocial account which you want to post to via the app.
|
||||||
|
|
||||||
|
Once you authenticate, you should be taken to a page which has a message like:
|
||||||
|
|
||||||
|
Hi account!
|
||||||
|
|
||||||
|
Here's your out-of-band token with scope "write", use it wisely:
|
||||||
|
|
||||||
|
ABIGLONGSTRINGOFLETTERSANDNUMBERS
|
||||||
|
|
||||||
|
Copy the out-of-band token and do the third step straight away, as the token will expire.
|
||||||
|
|
||||||
|
The third step exchanges the above token for a permanent access token, by running the script again in the same directory, passing the OOB token with the -t flag:
|
||||||
|
|
||||||
|
uv run register -u https://your.server/ -n mybot -t ABIGLONGSTRINGOFLETTERSANDNUMBERS
|
||||||
|
|
||||||
|
If this is successful, your access token - another, different string of letters and numbers - will be printed to the command prompt. The access token is also written out as a json file with the name `mybot_at.json`.
|
||||||
|
|
||||||
|
You should now be able to use the access token to post to your GoToSocial account.
|
||||||
|
@ -11,6 +11,9 @@ dependencies = [
|
|||||||
"requests>=2.32.3",
|
"requests>=2.32.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
register = "gotosocial.register:cli"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["hatchling"]
|
requires = ["hatchling"]
|
||||||
build-backend = "hatchling.build"
|
build-backend = "hatchling.build"
|
||||||
|
@ -1,37 +1,112 @@
|
|||||||
# script to register an API
|
# script to register an API
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
import json
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
class Application:
|
class Application:
|
||||||
def __init__(self, base_url, name):
|
def __init__(self, base_url, name, scopes="write"):
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
self.name = name
|
self.name = name
|
||||||
|
self.scopes = scopes
|
||||||
self.headers = {"Content-Type": "application/json"}
|
self.headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
def register_app(self):
|
def register(self):
|
||||||
response = requests.post(
|
app_json = Path(f"./{self.name}_app.json")
|
||||||
f"{self.base_url}/api/v1/statuses",
|
if app_json.is_file():
|
||||||
|
print(f"Found {app_json} file")
|
||||||
|
print(f"Looks like you've already registered {self.name}")
|
||||||
|
return False
|
||||||
data = {
|
data = {
|
||||||
"client_name": self.name,
|
"client_name": self.name,
|
||||||
"redirect_uris": "urn:ietf:wg:oauth:2.0:oob",
|
"redirect_uris": "urn:ietf:wg:oauth:2.0:oob",
|
||||||
"scopes": "write",
|
"scopes": self.scopes,
|
||||||
},
|
}
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.base_url}/api/v1/apps",
|
||||||
|
data=json.dumps(data),
|
||||||
headers=self.headers,
|
headers=self.headers,
|
||||||
)
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
json_r = response.json()
|
json_r = response.json()
|
||||||
self.client_id = json_r["client_id"]
|
self.client_id = json_r["client_id"]
|
||||||
self.client_secret = json_r["client_secret"]
|
self.client_secret = json_r["client_secret"]
|
||||||
print(f"Registered {self.name} as {self.client_id}")
|
print(f"Registered {self.name}, client ID is {self.client_id}")
|
||||||
|
with open(app_json, "w") as jfh:
|
||||||
|
json.dump(json_r, jfh, indent=2)
|
||||||
|
print(f"Wrote response to {app_json}")
|
||||||
|
auth_url = f"{self.base_url}/oauth/authorize?client_id={self.client_id}&redirect_uri=urn:ietf:wg:oauth:2.0:oob&response_type=code&scope={self.scopes}"
|
||||||
|
print("Visit this URL in a private browser window to authenticate:")
|
||||||
|
print(auth_url)
|
||||||
|
print("\nThen run this script again with the OOB token, like:")
|
||||||
|
print("> uv run register -u {self.base_ur} -n {self.name} -t OOB_TOKEB")
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
print("Something went wrong:")
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_auth_token(self, token):
|
||||||
|
app_json = Path(f"./{self.name}_app.json")
|
||||||
|
if not app_json.is_file():
|
||||||
|
print(f"No {app_json} file found")
|
||||||
|
print("You need to register the app before getting a token")
|
||||||
|
return False
|
||||||
|
try:
|
||||||
|
with open(app_json, "r") as jfh:
|
||||||
|
cf = json.load(jfh)
|
||||||
|
data = {
|
||||||
|
"client_id": cf["client_id"],
|
||||||
|
"client_secret": cf["client_secret"],
|
||||||
|
"redirect_uri": "urn:ietf:wg:oauth:2.0:oob",
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": token,
|
||||||
|
}
|
||||||
|
response = requests.post(
|
||||||
|
f"{self.base_url}/oauth/token",
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=self.headers,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
json_r = response.json()
|
||||||
|
access_token = json_r["access_token"]
|
||||||
|
print(f"Got access token: {access_token}")
|
||||||
|
at_json = self.unique_at_file()
|
||||||
|
with open(at_json, "w") as jfh:
|
||||||
|
json.dump(json_r, jfh, indent=2)
|
||||||
|
print(f"Wrote response to {at_json}")
|
||||||
|
except Exception as e:
|
||||||
|
print("Something went wrong:")
|
||||||
|
print(e)
|
||||||
|
print("Note that this may be because your OOB token has expired")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def unique_at_file(self):
|
||||||
|
fn = f"./{self.name}_at.json"
|
||||||
|
n = 0
|
||||||
|
while Path(fn).is_file():
|
||||||
|
print(f"File {fn} already exists")
|
||||||
|
n += 1
|
||||||
|
fn = f"./{self.name}_at_{n}.json"
|
||||||
|
return fn
|
||||||
|
|
||||||
|
|
||||||
# curl \
|
def cli():
|
||||||
# -X POST \
|
ap = argparse.ArgumentParser()
|
||||||
# -H 'Content-Type:application/json' \
|
ap.add_argument("-u", "--url", required=True, type=str, help="GoToSocial server")
|
||||||
# -d '{
|
ap.add_argument("-n", "--name", required=True, type=str, help="Application name")
|
||||||
# "client_name": "your_app_name",
|
ap.add_argument("-s", "--scopes", default="write", type=str, help="Scope")
|
||||||
# "redirect_uris": "urn:ietf:wg:oauth:2.0:oob",
|
ap.add_argument("-t", "--token", type=str, help="OOB token")
|
||||||
# "scopes": "read"
|
args = ap.parse_args()
|
||||||
# }' \
|
app = Application(args.url, args.name, args.scopes)
|
||||||
# 'https://example.org/api/v1/apps'
|
if args.token:
|
||||||
|
app.get_auth_token(args.token)
|
||||||
|
else:
|
||||||
|
app.register()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
cli()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user