post version 0, zero debugging yet
technically I'm vibecoding this and I'm the LLM here
This commit is contained in:
		
							parent
							
								
									92d36ddc7e
								
							
						
					
					
						commit
						c0b4d8692b
					
				
							
								
								
									
										125
									
								
								patchodon.py
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								patchodon.py
									
									
									
									
									
								
							|  | @ -3,9 +3,118 @@ | ||||||
| import sys, os | import sys, os | ||||||
| import argparse | import argparse | ||||||
| import requests | import requests | ||||||
|  | import hashlib | ||||||
|  | import time | ||||||
|  | 
 | ||||||
|  | # NOTES: html2text: html2text | ||||||
|  | # the replies are listed by context, should be link-listed to avoid issues, | ||||||
|  | # should specify next hash to provide some kind of a filter | ||||||
|  | # visibility public+unlisted, all unlisted, all private, all direct | ||||||
| 
 | 
 | ||||||
| VERSION = "0.1.0" | VERSION = "0.1.0" | ||||||
| 
 | 
 | ||||||
|  | DPASTE_URL = "https://dpaste.com" | ||||||
|  | 
 | ||||||
|  | STATUS_LENGTH_LIMIT = 400 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def trace(x): | ||||||
|  |     sys.stderr.write(sys.argv[0] + ": " + x + "\n") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def api_token(args): | ||||||
|  |     if args.debug_api_token: | ||||||
|  |         return args.debug_api_token | ||||||
|  |     if args.env_api_token: | ||||||
|  |         return os.environ["PATCHODON_API_TOKEN"] | ||||||
|  |     raise ("API token not specified") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def do_post_status(args, body, parent=None, optional=None): | ||||||
|  |     if len(body) > STATUS_LENGTH_LIMIT: | ||||||
|  |         raise ("required status body too long") | ||||||
|  | 
 | ||||||
|  |     if not args.instance_url: | ||||||
|  |         raise ("mastodon instance not specified") | ||||||
|  | 
 | ||||||
|  |     token = api_token(args) | ||||||
|  | 
 | ||||||
|  |     headers = {"Authorization": f"Bearer {token}"} | ||||||
|  |     st = body + ( | ||||||
|  |         "\n" + optional[0 : (STATUS_LENGTH_LIMIT - len(body) - 1)] | ||||||
|  |         if optional | ||||||
|  |         else "" | ||||||
|  |     ) | ||||||
|  |     data = {status: st, visibility: "unlisted"} | ||||||
|  |     if parent: | ||||||
|  |         data["in_reply_to_id"] = parent | ||||||
|  | 
 | ||||||
|  |     r = requests.post( | ||||||
|  |         args.instance_url + "/api/v1/statuses", data=data, headers=headers | ||||||
|  |     ) | ||||||
|  |     if r.status_code != 200: | ||||||
|  |         raise ("mastodon status posting failed ({r.status_code})") | ||||||
|  | 
 | ||||||
|  |     return r.json.id | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def do_pastebin_file(file): | ||||||
|  |     # DPASTE API USE RULES: | ||||||
|  |     # - user-agent must be set properly | ||||||
|  |     # - 1 second between requests | ||||||
|  |     trace(f"pasting {file}...") | ||||||
|  |     r = requests.post( | ||||||
|  |         DPASTE_URL + "/api/v2/", | ||||||
|  |         data={ | ||||||
|  |             "contents": open(file, "r").read(), | ||||||
|  |             "syntax": "diff", | ||||||
|  |             "title": os.path.basename(file), | ||||||
|  |             "expiry_days": 30, | ||||||
|  |         }, | ||||||
|  |         headers={"User-agent": f"patchodon v{VERSION}"}, | ||||||
|  |     ) | ||||||
|  |     if r.status_code != 201: | ||||||
|  |         raise (f"dpaste POST failed for `{file}'") | ||||||
|  |     sleep(1.1) | ||||||
|  |     return r.headers["location"] + ".txt" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def split_off_diff(s): | ||||||
|  |     return s.split("\ndiff --git ")[0] | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def do_post(args): | ||||||
|  |     files = args.patchfile | ||||||
|  |     if not files: | ||||||
|  |         trace("reading patchfile series from stdin") | ||||||
|  |         files = sys.stdin.readlines().map(lambda x: x.rstrip(chars="\n")) | ||||||
|  |     n_patches = len(files) | ||||||
|  |     hashes = files.map(lambda x: hashlib.sha1(open(x, "r").read()).hexdigest()) | ||||||
|  |     short_hashes = hashes.map(lambda x: x[0:8]) | ||||||
|  |     full_hash = hashlib.sha1(hashes.join(" ")).hexdigest() | ||||||
|  |     paste_raw_urls = map(do_pastebin_file, files) | ||||||
|  |     trace("posting the header...") | ||||||
|  |     parent_post_id, url = do_post_status( | ||||||
|  |         args, | ||||||
|  |         args.subject | ||||||
|  |         + f"\n[patchodon hash {full_hash} shorts {short_hashes.join(' ')}]", | ||||||
|  |     ) | ||||||
|  |     for fn, pst, hsh, series in zip( | ||||||
|  |         files, paste_raw_urls, hashes, range(n_patches) | ||||||
|  |     ): | ||||||
|  |         trace(f"posting patch {series+1}/{n_patches}...") | ||||||
|  |         parent_post_id, _ = do_post_status( | ||||||
|  |             args, | ||||||
|  |             f"[patchodon {series+1}/{n_patches} {hsh}\n{pst}\n", | ||||||
|  |             parent=parent_post_id, | ||||||
|  |             optional=split_off_diff(open(fn, "r").read()), | ||||||
|  |         ) | ||||||
|  |     print(url) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def do_get(args): | ||||||
|  |     pass | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def main(): | def main(): | ||||||
|     ap = argparse.ArgumentParser( |     ap = argparse.ArgumentParser( | ||||||
|  | @ -38,7 +147,13 @@ def main(): | ||||||
|             "-i", "--instance-url", help="mastodon instance URL to post to" |             "-i", "--instance-url", help="mastodon instance URL to post to" | ||||||
|         ) |         ) | ||||||
|         post.add_argument( |         post.add_argument( | ||||||
|             "-r", "--recipient", help="recipient to tag in the initial post" |             "-s", | ||||||
|  |             "--subject", | ||||||
|  |             default="", | ||||||
|  |             help=( | ||||||
|  |                 "start of the initial post (e.g. to @tag someone and name the" | ||||||
|  |                 " project and topic)" | ||||||
|  |             ), | ||||||
|         ) |         ) | ||||||
|         post.add_argument( |         post.add_argument( | ||||||
|             "patchfile", |             "patchfile", | ||||||
|  | @ -93,7 +208,13 @@ def main(): | ||||||
|         ), |         ), | ||||||
|     ) |     ) | ||||||
|     args = ap.parse_args() |     args = ap.parse_args() | ||||||
|     print(args) | 
 | ||||||
|  |     if args.command == "post": | ||||||
|  |         do_post(args) | ||||||
|  |     elif args.command == "get": | ||||||
|  |         do_get(args) | ||||||
|  |     else: | ||||||
|  |         raise ("fatal: args borked") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue