all repos — pleroma_announce @ v0.1

small tool for posting announcements to pleroma

plann.py (view raw)

  1#!/usr/bin/env python3
  2
  3"""small tool to post, schedule and cancel announcements on pleroma"""
  4
  5import argparse
  6import datetime
  7import json
  8import logging
  9import os
 10
 11import dotenv
 12import requests
 13
 14from poll import PleromaPoll
 15from status import PleromaScheduledStatus
 16
 17__all__ = ["publish_status", "list_scheduled_statuses", "cancel_scheduled_status"]
 18
 19dotenv.load_dotenv()
 20APP_TOKEN=os.getenv("APP_TOKEN")
 21HOST=os.getenv("HOST")
 22
 23
 24def publish_status(text: str,
 25        visibility: str = "unlisted",
 26        scheduled_at: datetime.datetime = None,
 27        poll: PleromaPoll = None):
 28    """publish or schedule a status"""
 29
 30    url = f"{HOST}/api/v1/statuses"
 31    headers = {"Authorization": f"Bearer {APP_TOKEN}"}
 32
 33    payload = {
 34            "status": text,
 35            "visibilty": visibility
 36            }
 37
 38    if scheduled_at is not None:
 39        payload["scheduled_at"] = scheduled_at.isoformat()
 40    elif poll is not None:
 41        payload["poll"] = {
 42                "options": poll.options,
 43                "expires_in": poll.expires_in,
 44                "multiple": poll.multiple,
 45                "hide_totals": poll.hide_totals
 46                }
 47    req = requests.post(url, headers=headers, json=payload)
 48    if req.ok:
 49        logging.debug("publish_status: %s", req.text)
 50        logging.info("publish_status: published status")
 51    else:
 52        logging.error("publish_status: %s", req.text)
 53
 54
 55def publish_status_helper(args: dict):
 56    """publish_status wrapper to use with argparse"""
 57
 58    if args.poll_option is not None:
 59        poll = PleromaPoll(options=args.poll_option,
 60                expires_in=datetime.timedelta(days=args.expires_in),
 61                multiple=args.multiple,
 62                hide_totals=args.hide_totals)
 63        publish_status(args.text, visibility=args.visibility,
 64                scheduled_at=args.datetime, poll=poll)
 65    else:
 66        publish_status(args.text, visibility=args.visibility,
 67                scheduled_at=args.datetime)
 68
 69
 70def list_scheduled_statuses() -> list[PleromaScheduledStatus]:
 71    """get a list of scheduled statuses"""
 72    url = f"{HOST}/api/v1/scheduled_statuses"
 73    headers = {"Authorization": f"Bearer {APP_TOKEN}"}
 74
 75    out = []
 76    req = requests.get(url, headers=headers)
 77    if req.ok:
 78        logging.debug("list_scheduled_statuses: %s", req.text)
 79        logging.info("got a list of scheduled statuses")
 80        statuses_raw = json.loads(req.text)
 81        for s in statuses_raw:
 82            out.append(PleromaScheduledStatus.from_response(s))
 83    else:
 84        logging.error("list_scheduled_statuses: %s", req.text)
 85
 86    return out
 87
 88
 89def list_scheduled_statuses_helper(args: dict):
 90    """list_scheduled_statuses wrapper to use with argparse"""
 91
 92    for status in list_scheduled_statuses():
 93        print(status)
 94
 95
 96def cancel_scheduled_status(s_id: str):
 97    """cancel a scheduled status with id"""
 98    url = f"{HOST}/api/v1/scheduled_statuses/{s_id}"
 99    headers = {"Authorization": f"Bearer {APP_TOKEN}"}
100
101    req = requests.delete(url, headers=headers)
102    if req.ok:
103        logging.debug("cancel_scheduled_status: %s", req.text)
104        logging.info("cancel_scheduled_status: cancelled status %s", s_id)
105    else:
106        logging.error("cancel_scheduled_status: %s", req.text)
107
108
109def cancel_scheduled_status_helper(args: dict):
110    """cancel_scheduled_status wrapper to use with argparse"""
111    prompt = "are you sure you want to cancel ALL these statuses? [Yy] "
112
113    statuses = list_scheduled_statuses()
114
115    if args.id is not None:
116        if args.id in [s.s_id for s in statuses]:
117            cancel_scheduled_status(args.id)
118        else:
119            logging.error("no such status")
120            return
121    elif args.all:
122        for s in statuses:
123            print(s)
124        if not args.yes:
125            try:
126                assert input(prompt).lower() == 'y'
127            except AssertionError:
128                return
129
130        for s in statuses:
131            cancel_scheduled_status(s.s_id)
132
133
134def main():
135    """entry point for interactive use"""
136
137    parser = argparse.ArgumentParser(prog="plann")
138    parser.add_argument("-v", "--verbose", action="count", default=0,
139            help="increase verbosity")
140    subparsers = parser.add_subparsers(help="subcommands (see individual ones for help)")
141
142    publish_parser = subparsers.add_parser("post",
143            description="post or schedule a status")
144    publish_parser.add_argument("text", help="text of a status")
145    publish_parser.add_argument("-d", "--datetime", type=datetime.datetime.fromisoformat,
146            help="date and time to which status will be scheduled")
147    publish_parser.add_argument("-s", "--visibility", type=str,
148            choices=["public", "unlisted", "private", "direct"], default="unlisted",
149            help="status visibility (default: unlisted)")
150    publish_poll_group = publish_parser.add_argument_group("poll")
151    publish_poll_group.add_argument("-p", "--poll-option", type=str, metavar="OPT",
152            action="extend", nargs="+",
153            help="add a poll option")
154    publish_poll_group.add_argument("-e", "--expires-in", type=int, metavar="DAYS",
155            default=1,
156            help="number of days in which poll will expire (default: 1 day)")
157    publish_poll_group.add_argument("-m", "--multiple", action="store_true",
158            help="allow multiple choices")
159    publish_poll_group.add_argument("-t", "--hide_totals", action="store_true",
160            help="hide totals until poll is expired")
161    publish_parser.set_defaults(func=publish_status_helper)
162
163    cancel_parser = subparsers.add_parser("cancel",
164            description="cancel one or all scheduled statuses")
165    cancel_parser.add_argument("-y", "--yes", action="store_true",
166            help="don't ask for confirmation (only for -a option)")
167    cancel_group = cancel_parser.add_mutually_exclusive_group()
168    cancel_group.add_argument("-i", "--id", type=str,
169            help="scheduled post id to cancel")
170    cancel_group.add_argument("-a", "--all", action="store_true",
171            help="cancel all scheduled posts")
172    cancel_parser.set_defaults(func=cancel_scheduled_status_helper)
173
174    list_parser = subparsers.add_parser("list",
175            description="list scheduled statuses")
176    list_parser.set_defaults(func=list_scheduled_statuses_helper)
177
178    args = parser.parse_args()
179
180    loglevels = (logging.ERROR, logging.WARNING, logging.INFO, logging.DEBUG)
181    logging.basicConfig(level=loglevels[min(args.verbose, len(loglevels))])
182
183    try:
184        args.func(args)
185    except AttributeError:
186        logging.error("please specify a subcommand (one of {post, list, cancel})")
187
188
189if __name__ == '__main__':
190    main()