mastoposter-oss_images/mastoposter/__main__.py

166 lines
5.2 KiB
Python
Raw Normal View History

2022-08-24 08:09:41 +03:00
#!/usr/bin/env python3
"""
mastoposter - configurable reposter from Mastodon-compatible Fediverse servers
Copyright (C) 2022-2023 hatkidchan <hatkidchan@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
"""
from argparse import ArgumentParser
2022-08-24 08:09:41 +03:00
from asyncio import run
from configparser import ConfigParser, ExtendedInterpolation
2023-01-26 13:44:30 +03:00
from logging import (
INFO,
Formatter,
Logger,
StreamHandler,
getLevelName,
getLogger,
)
from os import getenv
from sys import stdout
2022-08-26 14:32:55 +03:00
from typing import AsyncGenerator, Callable, List
2023-03-07 10:26:45 +03:00
from httpx import Client, HTTPTransport
from mastoposter import (
execute_integrations,
load_integrations_from,
__version__,
2023-05-10 09:19:22 +03:00
__description__,
)
from mastoposter.integrations import FilteredIntegration
from mastoposter.sources import websocket_source
from mastoposter.types import Account, Status
from mastoposter.utils import normalize_config
WSOCK_TEMPLATE = "wss://{instance}/api/v1/streaming"
VERIFY_CREDS_TEMPLATE = "https://{instance}/api/v1/accounts/verify_credentials"
2022-08-24 08:09:41 +03:00
logger = getLogger()
2022-08-24 08:09:41 +03:00
def init_logger(loglevel: int = INFO):
2022-11-01 13:04:31 +03:00
stdout_handler = StreamHandler(stdout)
stdout_handler.setLevel(loglevel)
2022-11-01 13:04:31 +03:00
formatter = Formatter("[%(asctime)s][%(levelname)5s:%(name)s] %(message)s")
stdout_handler.setFormatter(formatter)
logger.addHandler(stdout_handler)
2022-11-01 13:37:47 +03:00
logger.setLevel(loglevel)
2023-01-26 13:44:30 +03:00
for log in logger.manager.loggerDict.values():
if isinstance(log, Logger):
log.setLevel(loglevel)
2022-11-01 13:04:31 +03:00
2022-08-24 08:09:41 +03:00
async def listen(
source: Callable[..., AsyncGenerator[Status, None]],
drains: List[FilteredIntegration],
2022-08-24 08:09:41 +03:00
user: str,
replies_to_other_accounts_should_not_be_skipped: bool = False,
2022-08-24 08:09:41 +03:00
/,
**kwargs,
):
logger.info("Starting listening...")
2022-08-24 08:09:41 +03:00
async for status in source(**kwargs):
2022-11-01 14:33:47 +03:00
logger.info("New status: %s", status.uri)
logger.debug("Got status: %r", status)
if status.account.id != user and user != "all":
logger.info(
"Skipping status %s (account.id=%r != %r)",
status.uri,
status.account.id,
user,
)
2022-08-24 08:09:41 +03:00
continue
# TODO: add option/filter to handle that
if status.visibility in ("direct",):
logger.info(
"Skipping post %s (status.visibility=%r)",
status.uri,
status.visibility,
)
2022-08-24 08:09:41 +03:00
continue
# TODO: find a better way to handle threads
2022-08-24 08:09:41 +03:00
if (
status.in_reply_to_account_id is not None
and status.in_reply_to_account_id != user
) and not replies_to_other_accounts_should_not_be_skipped:
logger.info(
"Skipping post %s because it's a reply to another person",
status.uri,
)
2022-08-24 08:09:41 +03:00
continue
2023-05-13 21:50:11 +03:00
logger.info(await execute_integrations(status, drains))
2022-08-24 08:09:41 +03:00
def main():
2023-05-10 09:19:22 +03:00
parser = ArgumentParser(prog="mastoposter", description=__description__)
parser.add_argument(
"config", nargs="?", default=getenv("MASTOPOSTER_CONFIG_FILE")
)
parser.add_argument("-v", action="version", version=__version__)
args = parser.parse_args()
2022-08-24 08:09:41 +03:00
if not args.config:
raise RuntimeError("No config file. Aborting")
conf = ConfigParser(interpolation=ExtendedInterpolation())
conf.read(args.config)
2022-11-02 20:11:38 +03:00
init_logger(getLevelName(conf["main"].get("loglevel", "INFO")))
normalize_config(conf)
modules: List[FilteredIntegration] = load_integrations_from(conf)
2023-03-07 10:26:45 +03:00
retries: int = conf["main"].getint("http-retries", 5)
2022-08-24 08:09:41 +03:00
logger.info("Loaded %d integrations", len(modules))
user_id: str = conf["main"]["user"]
if user_id == "auto":
logger.info("config.main.user is set to auto, getting user ID")
2023-03-07 10:26:45 +03:00
with Client(transport=HTTPTransport(retries=retries)) as c:
rq = c.get(
VERIFY_CREDS_TEMPLATE.format(**conf["main"]),
params={"access_token": conf["main"]["token"]},
)
user_id = Account.from_dict(rq.json()).id
2022-11-01 14:33:47 +03:00
logger.info("account.id=%s", user_id)
url = conf["main"].get(
"streaming_url",
"wss://{}/api/v1/streaming".format(conf["main"]["instance"]),
)
2022-08-24 08:09:41 +03:00
run(
listen(
websocket_source,
modules,
user_id,
2022-08-24 08:09:41 +03:00
url=url,
replies_to_other_accounts_should_not_be_skipped=conf[
"main"
].getboolean(
"replies_to_other_accounts_should_not_be_skipped", False
),
reconnect=conf["main"].getboolean("auto_reconnect", False),
2023-03-07 10:26:45 +03:00
reconnect_delay=conf["main"].getfloat("reconnect_delay", 1.0),
2022-08-24 08:09:41 +03:00
list=conf["main"]["list"],
access_token=conf["main"]["token"],
)
)
if __name__ == "__main__":
2023-05-08 17:04:32 +03:00
main()