diff --git a/.env_template b/.env_template index e4d19dc..6eac1d0 100644 --- a/.env_template +++ b/.env_template @@ -4,4 +4,7 @@ FEDISEER_LEMMY_DOMAIN="fediseer.com" FEDISEER_LEMMY_USERNAME="fediseer" FEDISEER_LEMMY_PASSWORD="LemmyPassword" ADMIN_API_KEY="Password" -secret_key="VerySecretKey" \ No newline at end of file +secret_key="VerySecretKey" +MASTODON_INSTANCE=botsin.space # Use only when logging in to a mastodon proxy account +MASTODON_EMAIL=email@example.com # Use only when logging in to a mastodon proxy account +MASTODON_PASSWORD=VerySecretPass # Use only when logging in to a mastodon proxy account \ No newline at end of file diff --git a/.gitignore b/.gitignore index 42dc2db..4c69b17 100644 --- a/.gitignore +++ b/.gitignore @@ -134,4 +134,6 @@ fediseer.log fediseer*.bz2 fediseer.db private.pem -public.pem \ No newline at end of file +public.pem + +pytooter* \ No newline at end of file diff --git a/fediseer/apis/models/v1.py b/fediseer/apis/models/v1.py index 4501f2a..bf73a6d 100644 --- a/fediseer/apis/models/v1.py +++ b/fediseer/apis/models/v1.py @@ -83,11 +83,17 @@ class Models: "message": fields.String(default='OK',required=True, description="The result of this operation."), "new_key": fields.String(default=None,required=False, description="The new API key"), }) + self.input_instance_claim = api.model('ClaimInstanceInput', { + 'admin': fields.String(required=True, min_length=1, description="The username of the admin who wants to register this domain", example="admin"), + 'guarantor': fields.String(required=False, description="(Optional) The domain of the guaranteeing instance. They will receive a PM to validate you", example="admin"), + 'pm_proxy': fields.String(required=False, enum=[e.name for e in enums.PMProxy], description="(Optional) If you do receive the PM from @fediseer@fediseer.com, set this to true to make the Fediseer PM your your API key via @fediseer@botsin.space. For this to work, ensure that botsin.space is not blocked in your instance and optimally follow @fediseer@botsin.space as well. If set, this will be used permanently for communication to your instance."), + }) self.input_api_key_reset = api.model('ApiKeyResetInput', { 'admin_username': fields.String(required=False, description="If a username is given, their API key will be reset. Otherwise the user's whose API key was provided will be reset. This allows can be initiated by other instance admins or the fediseer.", example="admin"), 'return_new_key': fields.Boolean(required=False, default=False, description="If True, the key will be returned as part of the response instead of PM'd. Fediseer will still PM a notification to the target admin account."), 'sysadmins': fields.Integer(required=False, default=None, min=0, max=100, description="Report how many system administrators this instance currently has."), 'moderators': fields.Integer(required=False, default=None, min=0, max=1000, description="Report how many instance moderators this instance currently has."), + 'pm_proxy': fields.String(required=False, enum=[e.name for e in enums.PMProxy], description="(Optional) If you do receive the PM from @fediseer@fediseer.com, set this to true to make the Fediseer PM your your API key via @fediseer@botsin.space. For this to work, ensure that botsin.space is not blocked in your instance and optimally follow @fediseer@botsin.space as well. If set, this will be used permanently for communication to your instance."), }) self.response_model_reports = api.model('ActivityReport', { 'source_domain': fields.String(description="The instance domain which initiated this activity", example="lemmy.dbzer0.com"), diff --git a/fediseer/apis/v1/base.py b/fediseer/apis/v1/base.py index bfb4219..387e524 100644 --- a/fediseer/apis/v1/base.py +++ b/fediseer/apis/v1/base.py @@ -11,7 +11,6 @@ from fediseer.utils import hash_api_key from fediseer.messaging import activitypub_pm from pythorhead import Lemmy from fediseer.fediverse import get_admin_for_software, get_nodeinfo -from fediseer.consts import SUPPORTED_SOFTWARE api = Namespace('v1', 'API Version 1' ) diff --git a/fediseer/apis/v1/endorsements.py b/fediseer/apis/v1/endorsements.py index 5d150d1..7bd0b5e 100644 --- a/fediseer/apis/v1/endorsements.py +++ b/fediseer/apis/v1/endorsements.py @@ -145,12 +145,15 @@ class Endorsements(Resource): db.session.add(new_report) db.session.commit() if not database.has_recent_endorsement(target_instance.id): - activitypub_pm.pm_admins( - message=f"Your instance has just been [endorsed](https://fediseer.com/faq#what-is-an-endorsement) by {instance.domain}", - domain=target_instance.domain, - software=target_instance.software, - instance=target_instance, - ) + try: + activitypub_pm.pm_admins( + message=f"Your instance has just been [endorsed](https://fediseer.com/faq#what-is-an-endorsement) by {instance.domain}", + domain=target_instance.domain, + software=target_instance.software, + instance=target_instance, + ) + except: + pass logger.info(f"{instance.domain} Endorsed {domain}") return {"message":'Changed'}, 200 @@ -236,11 +239,14 @@ class Endorsements(Resource): ) db.session.add(new_report) db.session.commit() - activitypub_pm.pm_admins( - message=f"Oh no. {instance.domain} has just withdrawn the endorsement of your instance", - domain=target_instance.domain, - software=target_instance.software, - instance=target_instance, - ) + try: + activitypub_pm.pm_admins( + message=f"Oh no. {instance.domain} has just withdrawn the endorsement of your instance", + domain=target_instance.domain, + software=target_instance.software, + instance=target_instance, + ) + except: + pass logger.info(f"{instance.domain} Withdrew endorsement from {domain}") return {"message":'Changed'}, 200 diff --git a/fediseer/apis/v1/guarantees.py b/fediseer/apis/v1/guarantees.py index dc68dbe..be3d06f 100644 --- a/fediseer/apis/v1/guarantees.py +++ b/fediseer/apis/v1/guarantees.py @@ -113,20 +113,26 @@ class Guarantees(Resource): ) db.session.add(new_report) db.session.commit() - activitypub_pm.pm_admins( - message=f"Congratulations! Your instance has just been [guaranteed](https://fediseer.com/faq#what-is-a-guarantee) by {instance.domain}. \n\nThis is an automated PM by the [Fediseer](https://fediseer.com) service. Replies will not be read.\nPlease contact @db0@lemmy.dbzer0.com for further inquiries.", - domain=target_instance.domain, - software=target_instance.software, - instance=target_instance, - ) + try: + activitypub_pm.pm_admins( + message=f"Congratulations! Your instance has just been [guaranteed](https://fediseer.com/faq#what-is-a-guarantee) by {instance.domain}. \n\nThis is an automated PM by the [Fediseer](https://fediseer.com) service. Replies will not be read.\nPlease contact @db0@lemmy.dbzer0.com for further inquiries.", + domain=target_instance.domain, + software=target_instance.software, + instance=target_instance, + ) + except: + pass orphan_ids = database.get_guarantee_chain(target_instance.id) for orphan in database.get_instances_by_ids(orphan_ids): - activitypub_pm.pm_admins( - message=f"Phew! You guarantor chain has been repaired as {instance.domain} has guaranteed for {domain}.", - domain=orphan.domain, - software=orphan.software, - instance=orphan, - ) + try: + activitypub_pm.pm_admins( + message=f"Phew! You guarantor chain has been repaired as {instance.domain} has guaranteed for {domain}.", + domain=orphan.domain, + software=orphan.software, + instance=orphan, + ) + except: + pass orphan.unset_as_orphan() logger.info(f"{instance.domain} Guaranteed for {domain}") return {"message":'Changed'}, 200 diff --git a/fediseer/apis/v1/whitelist.py b/fediseer/apis/v1/whitelist.py index e4c0533..ead00c1 100644 --- a/fediseer/apis/v1/whitelist.py +++ b/fediseer/apis/v1/whitelist.py @@ -1,6 +1,7 @@ from fediseer.apis.v1.base import * from fediseer.messaging import activitypub_pm from fediseer.classes.user import User, Claim +from fediseer import enums class Whitelist(Resource): get_parser = reqparse.RequestParser() @@ -8,7 +9,7 @@ class Whitelist(Resource): get_parser.add_argument("endorsements", required=False, default=0, type=int, help="Limit to this amount of endorsements of more", location="args") get_parser.add_argument("guarantors", required=False, default=1, type=int, help="Limit to this amount of guarantors of more", location="args") get_parser.add_argument("csv", required=False, type=bool, help="Set to true to return just the domains as a csv. Mutually exclusive with domains", location="args") - get_parser.add_argument("domains", required=False, type=bool, help="Set to true to return just the domains as a list. Mutually exclusive with csv", location="args") + get_parser.add_argument("domains", required=False, type=str, help="Set to true to return just the domains as a list. Mutually exclusive with csv", location="args") @api.expect(get_parser) @cache.cached(timeout=10, query_string=True) @@ -49,9 +50,10 @@ class WhitelistDomain(Resource): put_parser.add_argument("Client-Agent", default="unknown:0:unknown", type=str, required=False, help="The client name and version.", location="headers") put_parser.add_argument("admin", required=True, type=str, help="The username of the admin who wants to register this domain", location="json") put_parser.add_argument("guarantor", required=False, type=str, help="(Optional) The domain of the guaranteeing instance. They will receive a PM to validate you", location="json") + put_parser.add_argument("pm_proxy", required=False, default=False, type=str, help="(Optional) If you do receive the PM from @fediseer@fediseer.com, set this to true to make the Fediseer PM your your API key via @fediseer@botsin.space. For this to work, ensure that botsin.space is not blocked in your instance and optimally follow @fediseer@botsin.space as well. If set, this will be used permanently for communication to your instance.", location="json") - @api.expect(put_parser) + @api.expect(put_parser,models.input_instance_claim, validate=True) @api.marshal_with(models.response_model_instances, code=200, description='Instances') @api.response(400, 'Bad Request', models.response_model_error) def put(self, domain): @@ -76,7 +78,16 @@ class WhitelistDomain(Resource): existing_claim = database.find_claim(f"@{self.args.admin}@{domain}") if existing_claim: raise e.Forbidden(f"You have already claimed this instance as this admin. Please use the PATCH method to reset your API key.") - api_key = activitypub_pm.pm_new_api_key(domain, self.args.admin, instance.software) + if self.args.pm_proxy is not None: + proxy = enums.PMProxy[self.args.pm_proxy] + if instance.pm_proxy != proxy: + instance.pm_proxy = proxy + api_key = activitypub_pm.pm_new_api_key( + domain=domain, + username=self.args.admin, + software=instance.software, + proxy=instance.pm_proxy, + ) if not api_key: raise e.BadRequest("Failed to generate API Key") new_user = User( @@ -93,12 +104,15 @@ class WhitelistDomain(Resource): db.session.add(new_claim) db.session.commit() if guarantor_instance: - activitypub_pm.pm_admins( - message=f"New instance {domain} was just registered with the Fediseer and have asked you to guarantee for them!", - domain=guarantor_instance.domain, - software=guarantor_instance.software, - instance=guarantor_instance, - ) + try: + activitypub_pm.pm_admins( + message=f"New instance {domain} was just registered with the Fediseer and have asked you to guarantee for them!", + domain=guarantor_instance.domain, + software=guarantor_instance.software, + instance=guarantor_instance, + ) + except: + pass return instance.get_details(),200 patch_parser = reqparse.RequestParser() @@ -108,6 +122,7 @@ class WhitelistDomain(Resource): patch_parser.add_argument("return_new_key", default=False, required=False, type=bool, help="If True, the key will be returned as part of the response instead of PM'd. IT will still PM a notification to you.", location="json") patch_parser.add_argument("sysadmins", default=None, required=False, type=int, help="How many sysadmins this instance has.", location="json") patch_parser.add_argument("moderators", default=None, required=False, type=int, help="How many moderators this instance has.", location="json") + patch_parser.add_argument("pm_proxy", required=False, default=False, type=str, help="(Optional) If you do receive the PM from @fediseer@fediseer.com, set this to true to make the Fediseer PM your your API key via @fediseer@botsin.space. For this to work, ensure that botsin.space is not blocked in your instance and optimally follow @fediseer@botsin.space as well. If set, this will be used permanently for communication to your instance.", location="json") @api.expect(patch_parser,models.input_api_key_reset, validate=True) @@ -133,6 +148,13 @@ class WhitelistDomain(Resource): if self.args.moderators is not None and instance.moderators != self.args.moderators: instance.moderators = self.args.moderators changed = True + if self.args.pm_proxy is not None: + logger.debug(self.args.pm_proxy) + proxy = enums.PMProxy[self.args.pm_proxy] + if instance.pm_proxy != proxy: + activitypub_pm.pm_new_proxy_switch(proxy,instance.pm_proxy,instance,user.username) + instance.pm_proxy = proxy + changed = True if self.args.admin_username: requestor = None if self.args.admin_username != user.username or user.username == "fediseer": @@ -148,9 +170,21 @@ class WhitelistDomain(Resource): if self.args.return_new_key: if requestor is None: requestor = f"{user.username}@{requestor_instance.domain}" - new_key = activitypub_pm.pm_new_key_notification(domain, self.args.admin_username, instance.software, requestor=requestor) + new_key = activitypub_pm.pm_new_key_notification( + domain=domain, + username=self.args.admin_username, + software=instance.software, + requestor=requestor, + proxy=instance.pm_proxy, + ) else: - new_key = activitypub_pm.pm_new_api_key(domain, self.args.admin_username, instance.software, requestor=requestor) + new_key = activitypub_pm.pm_new_api_key( + domain=domain, + username=self.args.admin_username, + software=instance.software, + requestor=requestor, + proxy=instance.pm_proxy, + ) user.api_key = hash_api_key(new_key) changed = True db.session.commit() diff --git a/fediseer/classes/instance.py b/fediseer/classes/instance.py index 81c0ba7..956e96a 100644 --- a/fediseer/classes/instance.py +++ b/fediseer/classes/instance.py @@ -8,6 +8,7 @@ from sqlalchemy.dialects.postgresql import UUID from loguru import logger from fediseer.flask import db, SQLITE_MODE +from fediseer import enums uuid_column_type = lambda: UUID(as_uuid=True) if not SQLITE_MODE else db.String(36) @@ -82,12 +83,14 @@ class Instance(db.Model): created = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) updated = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) oprhan_since = db.Column(db.DateTime, nullable=True) - + open_registrations = db.Column(db.Boolean, unique=False, nullable=False, index=True) email_verify = db.Column(db.Boolean, unique=False, nullable=False, index=True) software = db.Column(db.String(50), unique=False, nullable=False, index=True) sysadmins = db.Column(db.Integer, unique=False, nullable=True) moderators = db.Column(db.Integer, unique=False, nullable=True) + pm_proxy = db.Column(Enum(enums.PMProxy), default=enums.PMProxy.NONE, nullable=False) + approvals = db.relationship("Endorsement", back_populates="approving_instance", cascade="all, delete-orphan", foreign_keys=[Endorsement.approving_id]) endorsements = db.relationship("Endorsement", back_populates="endorsed_instance", cascade="all, delete-orphan", foreign_keys=[Endorsement.endorsed_id]) diff --git a/fediseer/consts.py b/fediseer/consts.py index 0c979db..377ddfa 100644 --- a/fediseer/consts.py +++ b/fediseer/consts.py @@ -1,6 +1,11 @@ -FEDISEER_VERSION = "0.13.0" +FEDISEER_VERSION = "0.14.0" SUPPORTED_SOFTWARE = [ "lemmy", "mastodon", "friendica", + "pleroma", + "akkoma", + "firefish", + "iceshrimp", + "misskey", ] diff --git a/fediseer/enums.py b/fediseer/enums.py index 734112b..9f87bec 100644 --- a/fediseer/enums.py +++ b/fediseer/enums.py @@ -10,3 +10,7 @@ class ReportActivity(enum.Enum): ADDED = 0 DELETED = 1 MODIFIED = 2 + +class PMProxy(enum.Enum): + NONE = 0 + MASTODON = 1 diff --git a/fediseer/fediverse.py b/fediseer/fediverse.py index 90b9c6a..8fa5992 100644 --- a/fediseer/fediverse.py +++ b/fediseer/fediverse.py @@ -3,19 +3,19 @@ from loguru import logger from pythorhead import Lemmy from fediseer.consts import FEDISEER_VERSION -def get_lemmy_admins(domain): +def get_lemmy_admins(domain,software): requested_lemmy = Lemmy(f"https://{domain}") try: site = requested_lemmy.site.get() except Exception as err: - logger.error(f"Error retrieving mastodon site info for {domain}: {err}") + logger.error(f"Error retrieving {software} site info for {domain}: {err}") raise err if not site: - logger.error(f"Error retrieving mastodon site info for {domain}") - raise Exception(f"Error retrieving mastodon site info for {domain}") + logger.error(f"Error retrieving {software} site info for {domain}") + raise Exception(f"Error retrieving {software} site info for {domain}") return [a["person"]["name"] for a in site["admins"]] -def get_mastodon_admins(domain): +def get_mastodon_admins(domain,software): site = None try: site = requests.get(f"https://{domain}/api/v2/instance") @@ -26,12 +26,65 @@ def get_mastodon_admins(domain): return [site_json["contact"]["account"]["username"]] except Exception as err: if site is not None: - logger.error(f"Error retrieving mastodon site info for {domain}: {err}. Request text: {site.text()}") + logger.error(f"Error retrieving {software} site info for {domain}: {err}. Request text: {site.text()}") else: - logger.error(f"Error retrieving mastodon site info for {domain}: {err}") - raise Exception(f"Error retrieving mastodon site info for {domain}: {err}") + logger.error(f"Error retrieving {software} site info for {domain}: {err}") + raise Exception(f"Error retrieving {software} site info for {domain}: {err}") -def get_unknown_admins(domain): +def get_misskey_admins(domain,software): + site = None + try: + site = requests.get(f"https://{domain}/api/v1/instance") + site_json = site.json() + if "contact_account" not in site_json or "username" not in site_json["contact_account"]: + logger.error(f"No admin contact is specified for {domain}.") + raise Exception(f"No admin contact is specified for {domain}.") + return [site_json["contact_account"]["username"]] + except Exception as err: + if site is not None: + logger.error(f"Error retrieving {software} site info for {domain}: {err}. Request text: {site.text()}") + else: + logger.error(f"Error retrieving {software} site info for {domain}: {err}") + raise Exception(f"Error retrieving {software} site info for {domain}: {err}") + +def get_pleroma_admins(domain,software): + site = None + try: + site = requests.get(f"https://{domain}/api/v1/instance") + site_json = site.json() + if "email" not in site_json or site_json["email"] is None or site_json["email"] == '': + logger.error(f"No admin contact is specified for {domain}.") + raise Exception(f"No admin contact is specified for {domain}.") + admin_username = site_json["email"].split('@',1)[0] + return [admin_username] + except Exception as err: + if site is not None: + logger.error(f"Error retrieving {software} site info for {domain}: {err}. Request text: {site.text()}") + else: + logger.error(f"Error retrieving {software} site info for {domain}: {err}") + raise Exception(f"Error retrieving {software} site info for {domain}: {err}") + +def discover_admins(domain,software): + site = None + try: + site = requests.get(f"https://{domain}/api/v1/instance") + site_json = site.json() + # Pleroma/Akkoma style + if "email" in site_json: + admin_username = site_json["email"].split('@',1)[0] + return [admin_username] + # Misskey/Firefish style + if "contact_account" in site_json: + return [site_json["contact_account"]["username"]] + # Mastodon style + if "contact" in site_json: + return [site_json["contact"]["account"]["username"]] + raise Exception(f"Site software '{software} does not match any of the known APIs") + except Exception as err: + logger.error(f"Error retrieving {software} site info for {domain}: {err}") + raise Exception(f"Error retrieving {software} site info for {domain}: {err}") + +def get_unknown_admins(domain,software): return [] def get_admin_for_software(software: str, domain: str): @@ -39,12 +92,17 @@ def get_admin_for_software(software: str, domain: str): "lemmy": get_lemmy_admins, "mastodon": get_mastodon_admins, "friendica": get_mastodon_admins, + "pleroma": get_pleroma_admins, + "akkoma": get_pleroma_admins, + "firefish": get_misskey_admins, + "iceshrimp": get_misskey_admins, + "misskey": get_misskey_admins, "unknown": get_unknown_admins, "wildcard": get_unknown_admins, } if software not in software_map: - return [] - return software_map[software](domain) + return discover_admins(domain,software) + return software_map[software](domain,software) def get_nodeinfo(domain): diff --git a/fediseer/messaging.py b/fediseer/messaging.py index 483e60c..b123565 100644 --- a/fediseer/messaging.py +++ b/fediseer/messaging.py @@ -11,13 +11,16 @@ import secrets import markdown import fediseer.exceptions as e from pythorhead import Lemmy +from mastodon import Mastodon from loguru import logger from fediseer.database import functions as database from fediseer.consts import SUPPORTED_SOFTWARE, FEDISEER_VERSION from fediseer.fediverse import get_admin_for_software +from fediseer import enums -class ActivityPubPM: +class ActivityPubPM: private_key = None + mastodon = None def __init__(self): with open('private.pem', 'rb') as file: private_key_data = file.read() @@ -34,6 +37,11 @@ class ActivityPubPM: }, } + self.mastodon = Mastodon( + access_token = 'pytooter_usercred.secret', + api_base_url = f"https://{os.environ['MASTODON_INSTANCE']}" + ) + def send_pm_to_right_software(self, message, username, domain, software): software_map = { "lemmy": self.send_lemmy_pm, @@ -41,7 +49,9 @@ class ActivityPubPM: "friendica": self.send_mastodon_pm, "fediseer": self.send_fediseer_pm, } - return software_map[software](message, username, domain) + if software in software_map: + return software_map[software](message, username, domain) + raise e.BadRequest("This software does not have direct PM implemented. Please retry using a MASTODON pm_proxy setting.") def send_fediseer_pm(self, message, username, domain): document = copy.deepcopy(self.document_core) @@ -115,56 +125,98 @@ class ActivityPubPM: response = requests.post(url, data=document, headers=headers) return response.ok - def pm_new_api_key(self, domain: str, username: str, software: str, requestor = None): + def pm_new_api_key(self, domain: str, username: str, software: str, requestor = None, proxy = None): api_key = secrets.token_urlsafe(16) if requestor: - pm_content = f"user '{requestor}' has initiated an API Key reset for your domain {domain} on the [Fediseer](https://fediseer.com)\n\nThe new API key is\n\n{api_key}\n\n**Please purge this message after storing the API key**" + pm_content = f"user '{requestor}' has initiated an API Key reset for your domain {domain} on the [Fediseer](https://fediseer.com)\n\nThe new API key is\n\n{api_key}\n\n**Please purge this message after storing the API key or use the Fediseer API to generate a new API key without PM**" else: - pm_content = f"Your API Key for domain {domain} is\n\n{api_key}\n\nUse this to perform operations on the [Fediseer](https://fediseer.com).\n\n**Please purge this message after storing the API key**" - if not self.send_pm_to_right_software( - message=pm_content, - username=username, - domain=domain, - software=software - ): - raise e.BadRequest("API Key PM failed") + pm_content = f"Your API Key for domain {domain} is\n\n{api_key}\n\nUse this to perform operations on the [Fediseer](https://fediseer.com).\n\n**Please purge this message after storing the API key or use the Fediseer API to generate a new API key without PM**" + if proxy == enums.PMProxy.MASTODON: + self.mastodon_proxy_pm(pm_content,username,domain) + else: + if not self.send_pm_to_right_software( + message=pm_content, + username=username, + domain=domain, + software=software + ): + raise e.BadRequest("API Key PM failed") return api_key - def pm_new_key_notification(self, domain: str, username: str, software: str, requestor: str): + def pm_new_key_notification(self, domain: str, username: str, software: str, requestor: str, proxy = None): api_key = secrets.token_urlsafe(16) pm_content = f"user '{requestor}' has initiated an API Key reset for your domain {domain} on the [Fediseer](https://fediseer.com)\n\nThe new API key was provided in the response already\n" logger.info(f"user '{requestor}' reset the API key for {username}@{domain} on the response.") - if not self.send_pm_to_right_software( - message=pm_content, - username=username, - domain=domain, - software=software - ): - raise e.BadRequest("API Key PM failed") + if proxy == enums.PMProxy.MASTODON: + self.mastodon_proxy_pm(pm_content,username,domain) + else: + if not self.send_pm_to_right_software( + message=pm_content, + username=username, + domain=domain, + software=software + ): + raise e.BadRequest("API Key PM failed") return api_key - + + def pm_new_proxy_switch(self, new_proxy: enums.PMProxy, old_proxy: enums.PMProxy, instance: str, requestor: str): + if new_proxy == enums.PMProxy.NONE: + pm_content = f"user '{requestor}' has switched the fediseer messaging for {instance.domain} to not use a proxy (was {old_proxy.name})." + else: + pm_content = f"user '{requestor}' has switched the fediseer messaging for {instance.domain} to use a {new_proxy.name} proxy (was {old_proxy.name})." + logger.info(f"user '{requestor}' changed instance pm_proxy setting from {old_proxy} to {new_proxy} for {instance.domain}.") + admins = [a.username for a in database.find_admins_by_instance(instance)] + for admin_username in admins: + if instance.domain == "lemmy.dbzer0.com" and admin_username != 'db0': # Debug + logger.debug(f"skipping admin {admin_username} for debug") + continue + for proxy in [new_proxy,old_proxy]: + if proxy == enums.PMProxy.MASTODON: + self.mastodon_proxy_pm(pm_content,admin_username,instance.domain) + else: + if not self.send_pm_to_right_software( + message=pm_content, + username=admin_username, + domain=instance.domain, + software=instance.software + ): + raise e.BadRequest("API Key PM failed") def pm_admins(self, message: str, domain: str, software: str, instance): - if software not in SUPPORTED_SOFTWARE: - return None + proxy = None admins = database.find_admins_by_instance(instance) if not admins: try: admins = get_admin_for_software(software, domain) except Exception as err: + if software not in SUPPORTED_SOFTWARE: + logger.warning(f"Failed to figure out admins from {software}: {domain}") raise e.BadRequest(f"Failed to retrieve admin list: {err}") else: admins = [a.username for a in admins] + proxy = instance.pm_proxy if not admins: raise e.BadRequest(f"Could not determine admins for {domain}") for admin_username in admins: - if not self.send_pm_to_right_software( - message=message, - username=admin_username, - domain=domain, - software=software - ): - raise e.BadRequest("Admin PM Failed") + if proxy == enums.PMProxy.MASTODON: + self.mastodon_proxy_pm(message,admin_username,domain) + else: + if not self.send_pm_to_right_software( + message=message, + username=admin_username, + domain=domain, + software=software + ): + raise e.BadRequest("Admin PM Failed") + def mastodon_proxy_pm(self, message, username, domain): + try: + self.mastodon.status_post( + status=f"@{username}@{domain} {message}", + visibility="direct", + ) + except Exception as err: + raise e.BadRequest(f"PM via Mastodon Proxy Failed: {err}") + activitypub_pm = ActivityPubPM() diff --git a/mastodon_proxy_login.py b/mastodon_proxy_login.py new file mode 100644 index 0000000..ca9386c --- /dev/null +++ b/mastodon_proxy_login.py @@ -0,0 +1,21 @@ +import os +from mastodon import Mastodon +from dotenv import load_dotenv + +load_dotenv() + +Mastodon.create_app( + 'fediseer', + api_base_url = f"https://{os.environ['MASTODON_INSTANCE']}", + to_file = 'pytooter_clientcred.secret' +) + +mastodon = Mastodon( + client_id = 'pytooter_clientcred.secret', + api_base_url = f"https://{os.environ['MASTODON_INSTANCE']}" +) +mastodon.log_in( + os.environ['MASTODON_EMAIL'], + os.environ['MASTODON_PASSWORD'], + to_file = 'pytooter_usercred.secret' +) diff --git a/requirements.txt b/requirements.txt index 3139ad8..a0d8c4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,4 +17,4 @@ pythorhead>=0.8.2 bleach boto3 pybadges - +mastodon.py diff --git a/sql_statements/0.14.0.txt b/sql_statements/0.14.0.txt new file mode 100644 index 0000000..4fabed5 --- /dev/null +++ b/sql_statements/0.14.0.txt @@ -0,0 +1,3 @@ +CREATE TYPE pmproxy AS ENUM ('MASTODON'); +ALTER TYPE pmproxy ADD VALUE 'NONE'; +ALTER TABLE instances ADD COLUMN pm_proxy pmproxy default 'NONE';