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