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",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[project.scripts]
 | 
			
		||||
register = "gotosocial.register:cli"
 | 
			
		||||
 | 
			
		||||
[build-system]
 | 
			
		||||
requires = ["hatchling"]
 | 
			
		||||
build-backend = "hatchling.build"
 | 
			
		||||
 | 
			
		||||
@ -1,37 +1,112 @@
 | 
			
		||||
# script to register an API
 | 
			
		||||
 | 
			
		||||
import requests
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import json
 | 
			
		||||
import argparse
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Application:
 | 
			
		||||
    def __init__(self, base_url, name):
 | 
			
		||||
    def __init__(self, base_url, name, scopes="write"):
 | 
			
		||||
        self.base_url = base_url
 | 
			
		||||
        self.name = name
 | 
			
		||||
        self.scopes = scopes
 | 
			
		||||
        self.headers = {"Content-Type": "application/json"}
 | 
			
		||||
 | 
			
		||||
    def register_app(self):
 | 
			
		||||
        response = requests.post(
 | 
			
		||||
            f"{self.base_url}/api/v1/statuses",
 | 
			
		||||
            data={
 | 
			
		||||
    def register(self):
 | 
			
		||||
        app_json = Path(f"./{self.name}_app.json")
 | 
			
		||||
        if app_json.is_file():
 | 
			
		||||
            print(f"Found {app_json} file")
 | 
			
		||||
            print(f"Looks like you've already registered {self.name}")
 | 
			
		||||
            return False
 | 
			
		||||
        data = {
 | 
			
		||||
            "client_name": self.name,
 | 
			
		||||
            "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,
 | 
			
		||||
            )
 | 
			
		||||
            response.raise_for_status()
 | 
			
		||||
            json_r = response.json()
 | 
			
		||||
            self.client_id = json_r["client_id"]
 | 
			
		||||
            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 \
 | 
			
		||||
#   -X POST \
 | 
			
		||||
#   -H 'Content-Type:application/json' \
 | 
			
		||||
#   -d '{
 | 
			
		||||
#         "client_name": "your_app_name",
 | 
			
		||||
#         "redirect_uris": "urn:ietf:wg:oauth:2.0:oob",
 | 
			
		||||
#         "scopes": "read"
 | 
			
		||||
#       }' \
 | 
			
		||||
#   'https://example.org/api/v1/apps'
 | 
			
		||||
def cli():
 | 
			
		||||
    ap = argparse.ArgumentParser()
 | 
			
		||||
    ap.add_argument("-u", "--url", required=True, type=str, help="GoToSocial server")
 | 
			
		||||
    ap.add_argument("-n", "--name", required=True, type=str, help="Application name")
 | 
			
		||||
    ap.add_argument("-s", "--scopes", default="write", type=str, help="Scope")
 | 
			
		||||
    ap.add_argument("-t", "--token", type=str, help="OOB token")
 | 
			
		||||
    args = ap.parse_args()
 | 
			
		||||
    app = Application(args.url, args.name, args.scopes)
 | 
			
		||||
    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