summaryrefslogtreecommitdiff
path: root/de_uvok/activitypub_fuse/providers.py
blob: 19cee6288fe123a9dce8b9a14760b075181d9962 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from typing import Optional

import logging
import requests
import urllib.parse

from .types import Status

logger = logging.Logger(__name__)

_api_url_ap_template = "https://{server}/users/{user}/outbox?page=true"
_api_url_m_lookup_template = "https://{server}/api/v1/accounts/lookup"
_api_url_m_status_template = "https://{server}/api/v1/accounts/{uid}/statuses"


class StatusProvider:
    def load_statuses(self, max_id="") -> tuple[list[Status], Optional[str]]:
        raise NotImplementedError

    def _fallback_not_found(self):
        return [Status("not-found", "User not found", "1970-01-01T00:00:00Z")]

    def _fallback_error(self, error_msg: str):
        return [Status("error", error_msg, "1970-01-01T00:00:00Z")]


class ActivityPubStatusProvider(StatusProvider):
    def __init__(self, server: str, user: str):
        self.server = server
        self.user = user

    def load_statuses(self, max_id="") -> tuple[list[Status], Optional[str]]:
        url = _api_url_ap_template.format(server=self.server, user=self.user)
        if max_id:
            url += "&" + urllib.parse.urlencode({"max_id": max_id})
        logger.debug("Get AP status from %s", url)
        res = requests.get(url)
        if res.status_code == 404:
            return self._fallback_not_found(), None

        try:
            res.raise_for_status()
        except requests.exceptions.RequestException as e:
            logger.error("Request error: %s", e)
            return self._fallback_error(getattr(e, "message", str(e))), None

        stats = res.json()
        status_items = stats.get("orderedItems", None)
        if not status_items:
            return (
                self._fallback_error("Malformed content in querying AP outbox."),
                None,
            )

        # consider reposts for getting max_id...
        ss = [
            Status(
                s["object"]["id"].split("/")[-1],
                s["object"]["content"],
                s["object"]["published"],
            )
            for s in status_items
            if s.get("type", None) == "Create"
            and "object" in s
            and all(key in s["object"] for key in ["id", "content", "published"])
        ]
        # ... but don't return it
        return [s for s in ss if len(s.content)], ss[-1].id


class MastodonStatusProvider(StatusProvider):
    def __init__(self, server: str, user: str):
        self.server = server
        self.user = user
        self.userid = 0

    def load_statuses(self, max_id="") -> tuple[list[Status], Optional[str]]:
        url = _api_url_m_lookup_template.format(server=self.server)
        url += "?" + urllib.parse.urlencode({"acct": self.user})
        res = requests.get(url)
        if res.status_code == 404:
            return self._fallback_not_found(), None
        try:
            res.raise_for_status()
        except requests.exceptions.RequestException as e:
            logger.error("Request error: %s", e)
            return self._fallback_error(getattr(e, "message", str(e))), None

        user = res.json()
        self.userid = user.get("id", None)
        if not self.userid:
            return self._fallback_error("Malformed content in querying user ID."), None

        url = _api_url_m_status_template.format(
            server=self.server, uid=urllib.parse.quote(self.userid)
        )

        if max_id:
            url += "?" + urllib.parse.urlencode({"max_id": max_id})
        logger.debug("Get Masto status from %s", url)

        res = requests.get(url)
        if res.status_code == 404:
            return self._fallback_not_found(), None
        try:
            res.raise_for_status()
        except requests.exceptions.RequestException as e:
            logger.error("Request error: %s", e)
            return self._fallback_error(getattr(e, "message", str(e))), None
        statuses = res.json()
        # consider reposts for getting max_id...
        ss = [
            Status(
                s["id"],
                s["content"],
                s["created_at"],
            )
            for s in statuses
            if all(key in s for key in ["id", "content", "created_at"])
        ]
        # ... but don't return it
        return [s for s in ss if len(s.content)], ss[-1].id