diff --git a/masto_ld/__init__.py b/masto_ld/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masto_ld/bots/__init__.py b/masto_ld/bots/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masto_ld/bots/mastodon.py b/masto_ld/bots/mastodon.py new file mode 100644 index 0000000..af182a7 --- /dev/null +++ b/masto_ld/bots/mastodon.py @@ -0,0 +1,184 @@ +import pdb +from typing import Optional +from urllib.parse import urlsplit +from masto_ld.config import Config +from masto_ld.models.models import Account, List +from masto_ld.models.post import Status +from masto_ld.logger import init_logger +from masto_ld.patterns.tag import Namespaced_Tag +from masto_ld.interfaces.smw import SMW +from masto_ld.interfaces.mediawiki import Wiki + +import pypandoc + +from mastodon import Mastodon, StreamListener + +class UserListener(StreamListener): + """ + Listener for all events that are in our feed + """ + def __init__(self, client:Mastodon, config:Config): + self.client = client + self.config = config + self.logger = init_logger(name="user-listener", basedir=config.LOGDIR) + + def on_update(self, status): + status = Status(**status) + tag = Namespaced_Tag.from_html(status.content) + if tag is not None: + self.handle_tagged_post(status, tag) + + pdb.set_trace() + + def on_notification(self, notification): + pdb.set_trace() + if notification.get('type', '') == 'follow': + self.logger.debug(f'following back {notification["account"]["url"]}') + self.follow_back(notification) + + + def follow_back(self, notification): + self.client.account_follow(notification['account']['id']) + self.logger.debug(f'account followed! {notification["account"]["url"]} with id {notification["account"]["id"]}') + + def handle_tagged_post(self, status:Status, tag:Namespaced_Tag): + # FIXME: This is the worst way of doing this + + # resolve the name of the tag + # find the right field + field = [f for f in status.account.fields if f.name == tag.tags[0]] + if len(field) == 0: + raise ValueError('No namespace found!') + else: + field = field[0] + + # get the tags from that field + smw = SMW(self.config.WIKI_URL) + pagename = urlsplit(field.url).path.lstrip('/') + wiki_tags = smw.tags(pagename) + + # find the long name of the tag + longname = [t for t in wiki_tags if t[0] == tag.tags[1]] + if len(longname) == 0: + raise ValueError('No matching tag found!') + else: + longname = longname[0] + + # format our freaking page IN PLACE LIKE AN ANIMAL + page_content = ( + "{{Thread\n", + f"|About={longname}\n", + f"|URL={status.url}\n", + "}}\n\n" + ) + + page_content += pypandoc.convert_text(status.content, 'mediawiki-raw_html', format='html') + + page_name = status.soup.find('h1') + if page_name is None: + raise ValueError('No Header found!') + page_ + + + wiki = Wiki(self.config, log_dir=self.config.LOGDIR) + + + + + + + +class ListListener(StreamListener): + """ + Listener for events from users on our follows list + """ + def __init__(self, client: Mastodon, config:Optional[Config]=None): + super(Listener, self).__init__() + self.client = client + + if config is None: + config = Config() + self.config = config + + self.logger = init_logger('mastogit_bot-stream', basedir=self.config.LOGDIR) + + self.repo = Repo(path=config.GIT_REPO) + + + + def on_update(self, status:dict): + status = Status(**status) + if status.visibility in ('private', 'direct'): + # not xposting dms + self.logger.info('Not xposting private messages') + return + + post = Post.from_status(status) + if post.text.startswith('xpost'): + self.logger.info('Not xposting an xpost') + return + + success = self.repo.post(post.format_commit()) + if success: + self.logger.info('Posted to git!') + else: + self.logger.exception('Failed to post to git!') + + + + + +class Bot: + def __init__(self, config:Optional[Config]=None, post_length=500): + self._me = None # type: Optional[Account] + # self._me_list = None # type: Optional[List] + + if config is None: + config = Config() + + self.config = config + self.config.LOGDIR.mkdir(exist_ok=True) + # self.post_length = post_length + self.logger = init_logger('mastogit_bot', basedir=self.config.LOGDIR) + + self.client = Mastodon( + access_token=self.config.MASTO_TOKEN, + api_base_url=self.config.MASTO_URL + ) + + def init_stream(self, run_async:bool=True): + # Listen to a stream consisting of just us. + listener = UserListener(self.client, self.config) + self.logger.info('Initializing streaming') + self.client.stream_user( + listener = listener, + run_async=run_async + ) + + def post(self, post:str): + + self.client.status_post(post) + self.logger.info(f"Posted:\n{post}") + + @property + def me(self) -> Account: + if self._me is None: + self._me = Account(**self.client.me()) + return self._me + + def _make_me_list(self) -> List: + me_list = List(**self.client.list_create('me')) + self.client.list_accounts_add(me_list.id, [self.me.id]) + self.logger.info('Created list with just me in it!') + return me_list + + # @property + # def me_list(self) -> List: + # if self._me_list is None: + # lists = self.client.lists() + # me_list = [l for l in lists if l.get('title', '') == 'me'] + # if len(me_list)>0: + # self._me_list = List(**me_list[0]) + # else: + # self._me_list = self._make_me_list() + # return self._me_list diff --git a/masto_ld/config.py b/masto_ld/config.py new file mode 100644 index 0000000..72f9cbc --- /dev/null +++ b/masto_ld/config.py @@ -0,0 +1,16 @@ +from pathlib import Path +from pydantic import BaseSettings, AnyHttpUrl + +class Config(BaseSettings): + MASTO_URL:AnyHttpUrl + MASTO_TOKEN:str + WIKI_URL:AnyHttpUrl + WIKI_USER:str + WIKI_PASSWORD:str + LOGDIR:Path=Path().home() / '.mastold' + + class Config: + env_file = '.env' + env_file_encoding = 'utf-8' + env_prefix = "MASTOLD_" + diff --git a/masto_ld/creds.py b/masto_ld/creds.py new file mode 100644 index 0000000..26da9ae --- /dev/null +++ b/masto_ld/creds.py @@ -0,0 +1,6 @@ +from dataclasses import dataclass + +@dataclass +class Mediawiki_Creds: + user:str + password:str diff --git a/masto_ld/formats/__init__.py b/masto_ld/formats/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masto_ld/formats/wiki.py b/masto_ld/formats/wiki.py new file mode 100644 index 0000000..139da82 --- /dev/null +++ b/masto_ld/formats/wiki.py @@ -0,0 +1,17 @@ +""" +Helper functions for dealing with wiki syntax as well as format +output posts triggered by :class:`.action.WikiLink` +""" +from dataclasses import dataclass +import wikitextparser as wtp + +@dataclass +class WikiPage: + title:str + source:str + content:wtp.WikiText + + @classmethod + def from_source(self, title, source) -> 'WikiPage': + content = wtp.parse(source) + return WikiPage(title, source, content) \ No newline at end of file diff --git a/masto_ld/interfaces/__init__.py b/masto_ld/interfaces/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masto_ld/interfaces/mediawiki.py b/masto_ld/interfaces/mediawiki.py new file mode 100644 index 0000000..ae41111 --- /dev/null +++ b/masto_ld/interfaces/mediawiki.py @@ -0,0 +1,150 @@ +from pathlib import Path +from typing import Optional +from urllib.parse import urljoin + +import requests +from masto_ld.creds import Mediawiki_Creds +from masto_ld.formats.wiki import WikiPage +from masto_ld.logger import init_logger +from masto_ld.config import Config + + +class Wiki: + def __init__(self, config:Config, + api_suffix:str="/api.php", index_page="Discord Messages", + log_dir:Path=Path('/var/www/wikibot')): + self.url = config.WIKI_URL + self.api_url = urljoin(self.url, api_suffix) + self.creds = Mediawiki_Creds(config.WIKI_USER, config.WIKI_PASSWORD) + self.sess = None + self.index_page = index_page + self.logger = init_logger('wiki_interface', basedir=log_dir) + self.login(self.creds) + + def login(self, creds:Mediawiki_Creds): + # get token to log in + sess = requests.Session() + + login_token = sess.get( + self.api_url, + params={ + "action":"query", + "meta":"tokens", + "type":"login", + "format":"json" + }, + verify=False + ).json()['query']['tokens']['logintoken'] + + + login_result = sess.post( + self.api_url, + data = { + "action":"login", + "lgname":creds.user, + "lgpassword":creds.password, + "lgtoken": login_token, + "format": "json" + }, + verify=False + ) + assert login_result.json()['login']['result'] == "Success" + self.sess = sess + + def get_page(self, page:str) -> Optional[WikiPage]: + + content = self.sess.get( + self.api_url, + params={ + 'action':'parse', + 'page': page, + 'prop': 'wikitext', + 'formatversion':'2', + 'format':'json' + } + ).json() + + if content.get('error', {}).get('code', '') == 'missingtitle': + # Page does not exist! + self.logger.debug("Page does not exist") + return None + + self.logger.debug(f"Got Page content:") + self.logger.debug(content) + return WikiPage.from_source(title=content['parse']['title'], source=content['parse']['wikitext']) + + def new_page(self, page, text): + token = self.sess.get( + self.api_url, + params={ + "action": "query", + "meta": "tokens", + "format": "json" + }, + verify=False + ).json()['query']['tokens']['csrftoken'] + + result = self.sess.post( + self.api_url, + data={ + "action": "edit", + "title": page, + "appendtext": text, + "format": "json", + "token": token + } + ) + + def insert_text(self, page, section, text): + + # TODO: Move finding section IDs into the page class! + page_text = self.get_page(page) + matching_section = -1 + if page_text is not None: + # find section number + sections = page_text.content.get_sections() + + for i, page_section in enumerate(sections): + if page_section.title is not None and page_section.title.strip().lower() == section.lower(): + matching_section = i + break + + + token = self.sess.get( + self.api_url, + params={ + "action": "query", + "meta": "tokens", + "format": "json" + }, + verify=False + ).json()['query']['tokens']['csrftoken'] + + if matching_section >= 0: + print(f'found matching section {matching_section}') + result = self.sess.post( + self.api_url, + data={ + "action":"edit", + "title":page, + "section":str(matching_section), + "appendtext":text, + "format":"json", + "token":token + } + ) + else: + self.logger.debug('making new section') + result = self.sess.post( + self.api_url, + data={ + "action":"edit", + "title":page, + "section":"new", + "sectiontitle":section, + "appendtext":text, + "format":"json", + "token":token + } + ) + return result \ No newline at end of file diff --git a/masto_ld/interfaces/smw.py b/masto_ld/interfaces/smw.py new file mode 100644 index 0000000..98f29d8 --- /dev/null +++ b/masto_ld/interfaces/smw.py @@ -0,0 +1,127 @@ +""" +Adapted from https://github.com/auto-pi-lot/autopilot/blob/main/autopilot/utils/wiki.py + +So sort of shit. + +""" + +import requests +from typing import List, Union +from urllib.parse import urljoin + +class SMW: + + def __init__(self, url:str, api_suffix="api.php"): + self.url = url + self.api_url = urljoin(self.url, api_suffix) + + def tags(self, page:str): + """SUPER MEGA HACK DO NOT READ""" + res = self.ask(filters="[["+page+"]]", properties='Tag') + tags = res[0]['Tag'] + clean_tags = [] + for tag in tags: + shortname = tag['ShortName']['item'][0] + longurl = tag['LongName']['item'][0]['fulltext'] + clean_tags = (shortname, longurl) + return clean_tags + + + def ask(self, + filters:Union[List[str],str], + properties:Union[None,List[str],str]=None, + clean:bool=True + ) -> List[dict]: + """ + + Args: + filters (list, str): A list of strings or a single string of semantic + mediawiki formatted property filters, eg ``"[[Category:Hardware]]"`` + or ``"[[Has Contributor::sneakers-the-rat]]"``. Refer to the + `semantic mediawiki documentation `_ + for more information on syntax + properties (None, list, str): Properties to return from filtered pages, + see the `available properties `_ + on the wiki and the `semantic mediawiki documentation `_ + for more information on syntax. If ``None`` (default), just return + the names of the pages + full_url (bool): If ``True`` (default), prepend ``f'{WIKI_URL}api.php?action=ask&query='`` + to the returned string to make it `ready for an API call `_ + + """ + query_str = self._make_ask_string(filters, properties, full_url=True) + result = requests.get(query_str) + if clean: + unnested = [] + for entry in result.json()['query']['results']: + entry_name = list(entry.keys())[0] + nested_entry = entry[entry_name] + unnest_entry = _clean_smw_result(nested_entry) + unnested.append(unnest_entry) + return unnested + else: + return result.json() + + + def _make_ask_string(self, + filters: Union[List[str], str], + properties: Union[None, List[str], str] = None, + full_url: bool = True) -> str: + """ + Create a query string to request semantic information from a semantic wiki + + Returns: + str: the formatted query string + """ + # combine the components, separated by pipes or pip question marks as the case may be + if isinstance(filters, str): + filters = [filters] + + if len(filters) == 0: + raise ValueError(f'You need to provide at least one filter! Cant get the whole wiki!') + + query_str = "|".join(filters) + + if isinstance(properties, str): + properties = [properties] + elif properties is None: + properties = [] + + if len(properties) > 0: + # double join with ?| so it goes between + # all the properties *and* between filters and + query_str = "|?".join(( + query_str, + "|?".join(properties) + )) + + # add api call boilerplate and URI-encode + query_str = requests.utils.quote(query_str) + "&format=json&api_version=3" + + if full_url: + return f"{self.api_url}?action=ask&query=" + query_str + else: + return query_str + +def _clean_smw_result(nested_entry:dict) -> dict: + # unnest entries that are [[Has type::page]] and thus have extra metadata + unnest_entry = {} + printouts = nested_entry.get('printouts', {}) + if len(printouts)>0: + for k, v in printouts.items(): + if isinstance(v, list) and len(v) > 1: + unnest_entry[k] = [] + for subv in v: + if isinstance(subv, dict) and 'fulltext' in subv.keys(): + subv = subv['fulltext'] + unnest_entry[k].append(subv) + elif isinstance(v, list) and len(v) == 1: + unnest_entry[k] = v[0] + if isinstance(unnest_entry[k], dict) and 'fulltext' in unnest_entry[k].keys(): + unnest_entry[k] = unnest_entry[k]['fulltext'] + else: + unnest_entry[k] = v + + unnest_entry['name'] = nested_entry['fulltext'] + unnest_entry['url'] = nested_entry['fullurl'] + return unnest_entry diff --git a/masto_ld/logger.py b/masto_ld/logger.py new file mode 100644 index 0000000..fb5ebb4 --- /dev/null +++ b/masto_ld/logger.py @@ -0,0 +1,56 @@ +import logging +from rich.logging import RichHandler +from pathlib import Path +import sys +import typing +from typing import Optional, Union, Tuple, List, Dict, Literal +from logging.handlers import RotatingFileHandler + +def init_logger( + name:Optional[str]=None, + basedir:Optional[Path]=None, + loglevel:str='DEBUG', + loglevel_disk:Optional[str]='DEBUG' + ): + if name is None: + name = 'masto_ld' + else: + if not name.startswith('masto_ld'): + name = '.'.join(['masto_ld', name]) + + if loglevel_disk is None: + loglevel_disk = loglevel + + logger = logging.getLogger(name) + logger.setLevel(loglevel) + + + if basedir is not None: + logger.addHandler(_file_handler(basedir, name, loglevel_disk)) + + logger.addHandler(_rich_handler()) + return logger + + +def _file_handler(basedir:Path, name:str, loglevel:str="DEBUG") -> RotatingFileHandler: + filename = Path(basedir) / '.'.join([name, 'log']) + basedir.mkdir(parents=True, exist_ok=True) + file_handler = RotatingFileHandler( + str(filename), + mode='a', + maxBytes=2 ** 24, + backupCount=5 + ) + file_formatter = logging.Formatter("[%(asctime)s] %(levelname)s [%(name)s]: %(message)s") + file_handler.setLevel(loglevel) + file_handler.setFormatter(file_formatter) + return file_handler + +def _rich_handler() -> RichHandler: + rich_handler = RichHandler(rich_tracebacks=True, markup=True) + rich_formatter = logging.Formatter( + "[bold green]\[%(name)s][/bold green] %(message)s", + datefmt='[%y-%m-%dT%H:%M:%S]' + ) + rich_handler.setFormatter(rich_formatter) + return rich_handler \ No newline at end of file diff --git a/masto_ld/main.py b/masto_ld/main.py new file mode 100644 index 0000000..182260c --- /dev/null +++ b/masto_ld/main.py @@ -0,0 +1,18 @@ +from typing import Optional +from masto_ld.config import Config +from masto_ld.bots.mastodon import Bot +from time import sleep + + +def masto_ld(config:Optional[Config]=None): + if config is None: + config = Config() + + bot = Bot(config=config) + try: + bot.init_stream() + while True: + sleep(60*60) + bot.logger.info('taking a breath') + except KeyboardInterrupt: + bot.logger.info('quitting!') \ No newline at end of file diff --git a/masto_ld/models/__init__.py b/masto_ld/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masto_ld/models/models.py b/masto_ld/models/models.py new file mode 100644 index 0000000..56d01d2 --- /dev/null +++ b/masto_ld/models/models.py @@ -0,0 +1,52 @@ +from pydantic import BaseModel, AnyHttpUrl +from pydantic import Field as _Field +from datetime import datetime +from typing import Optional, List +from bs4 import BeautifulSoup + + + +# class List(BaseModel): +# """A mastodon list!""" +# id: str +# title: str +# +# class Config: +# extra = 'ignore' + +class Field(BaseModel): + name: str + value: str + url: Optional[AnyHttpUrl] = None + + def __init__(self, name:str, value:str): + soup = BeautifulSoup(value, 'lxml') + a = soup.find('a') + if a is not None: + url = a.get('href') + else: + url = None + super().__init__(name=name, value=value, url=url) + + class Config: + extra = "ignore" + + +class Account(BaseModel): + """Not transcribing full model now, just using to check""" + acct: str + avatar: str + avatar_static: str + bot: bool + created_at:datetime + discoverable:bool + display_name:str + fields: List[Field] = _Field(default_factory=list) + followers_count:int + following_count:int + id: int + url: AnyHttpUrl + username: str + + class Config: + extra = 'ignore' \ No newline at end of file diff --git a/masto_ld/models/post.py b/masto_ld/models/post.py new file mode 100644 index 0000000..ffbee57 --- /dev/null +++ b/masto_ld/models/post.py @@ -0,0 +1,58 @@ +from typing import Optional, Literal, List +from datetime import datetime +from pydantic import BaseModel, Field +import re +from bs4 import BeautifulSoup +import pypandoc + +from masto_ld.models.models import Account +from masto_ld.patterns.wikilink import Wikilink + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from masto_ld.models.thread import Thread + +class Mention(BaseModel): + acct: str + id: int + url: str + username: str + +class Status(BaseModel): + """ + Model of a toot on mastodon + + See: https://mastodonpy.readthedocs.io/en/stable/#toot-dicts + """ + id: int + url: str + account: Account + content: str + created_at: datetime + visibility: Literal['public', 'unlisted', 'private', 'direct'] + in_reply_to_id: Optional[int] = None + in_reply_to_account_id: Optional[int] = None + mentions: Optional[List[Mention]] = None + + def format_content(self, format:str='commonmark-raw_html') -> str: + """Format the HTMl content of the post using pypandoc""" + output = pypandoc.convert_text(self.content, format, format='html') + return output + + @property + def soup(self) -> BeautifulSoup: + return BeautifulSoup(self.content) + + @property + def wikilinks(self) -> List[Wikilink]: + return Wikilink.parse(self.content) + + class Config: + extra='ignore' + +class Post(BaseModel): + #timestamp: Optional[datetime] = None + text:str + status:Optional[Status] = None + + diff --git a/masto_ld/models/thread.py b/masto_ld/models/thread.py new file mode 100644 index 0000000..59842e2 --- /dev/null +++ b/masto_ld/models/thread.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel +from typing import List, Optional, Union + +from masto_ld.models.post import Status + +class Thread(BaseModel): + """ + Container for multiple posts, allowing appending, resolving of the last subject, etc. + """ + posts: List[Status] + diff --git a/masto_ld/patterns/__init__.py b/masto_ld/patterns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/masto_ld/patterns/patterns.py b/masto_ld/patterns/patterns.py new file mode 100644 index 0000000..3849a4a --- /dev/null +++ b/masto_ld/patterns/patterns.py @@ -0,0 +1,11 @@ +""" +Basic regex patterns that are simply `re.compile`d +""" + +class Pattern: + """ + Metaclass for detecting patterns + + Not sure what should go here but making it just for the sake of structure + """ + diff --git a/masto_ld/patterns/tag.py b/masto_ld/patterns/tag.py new file mode 100644 index 0000000..b5ef324 --- /dev/null +++ b/masto_ld/patterns/tag.py @@ -0,0 +1,31 @@ +from typing import List, Optional +import pyparsing as pp +from bs4 import BeautifulSoup +from dataclasses import dataclass + +EXAMPLE = '

# My New Post

@jonny:wiki:scruffy

Here is some new post where I talk about being real scruffy

' + +@dataclass +class Namespaced_Tag: + username: str + tags: List[str] + + @classmethod + def from_html(cls, html:str) -> Optional['Namespaced_Tag']: + """ + # FIXME: Just finds the first one for now + """ + soup = BeautifulSoup(html, 'lxml') + mention = soup.find(class_='h-card') + if mention is None: + return None + username = mention.text + tags = mention.next_sibling.text.split(':') + # FIXME: super weak check, just check that the next text starts with a ':' + if tags[0] != '': + return None + tags = tags[1:] + return Namespaced_Tag(username=username, tags=tags) + + + diff --git a/masto_ld/patterns/wikilink.py b/masto_ld/patterns/wikilink.py new file mode 100644 index 0000000..1160887 --- /dev/null +++ b/masto_ld/patterns/wikilink.py @@ -0,0 +1,189 @@ +import re +from masto_ld.patterns.patterns import Pattern +from dataclasses import dataclass +from typing import Optional, Union, List +import pyparsing as pp +from pprint import pformat + + +WIKILINK = re.compile(r'\[\[(.*?)\]\]', re.IGNORECASE) +""" +Basic structure of wikilink, used to detect presence +""" + +class NBack: + FIELDS = ('wildcard', 'start', 'end') + + def __init__(self, start:Optional[int]=None, end:Optional[int]=None, + wildcard:Optional[Union[str,bool]]=None, + one:Optional[str]=None): + + if wildcard: + self.wildcard = True + self.start = None + self.end = None + return + else: + self.wildcard = False + + if one: + self.start = 1 + self.end = 1 + else: + if start is not None: + start = int(start) + if end is not None: + end = int(end) + self.start = start + self.end = end + + if self.start is not None and self.end is not None: + if self.start > self.end: + raise ValueError(f"Start value must be less than end value, got start:{self.start}, end:{self.end}") + + @classmethod + def make_parser(cls) -> pp.ParserElement: + # -------------------------------------------------- + # n-back links immediately follow the [[ and can be one of + # ^ + # ^* + # ^{n,m} + # ^{n,} + # ^{,m} + # ^{m} + + # make elements + caret = pp.Literal("^") + lcurly = pp.Literal('{').suppress() + rcurly = pp.Literal('}').suppress() + integer = pp.Word(pp.nums) + comma = pp.Literal(',').suppress() + nb_range = caret + lcurly + + # combine into matches + nb_wildcard = caret.suppress() + "*" + # start or end can be omitted if comma is present + nb_full = nb_range + pp.Optional(integer("start")) + comma + pp.Optional(integer("end")) + rcurly + # if no comma present, it's just an end + nb_end = nb_range + integer("end") + rcurly + + # combine into full nback parser + nback = pp.Group(nb_wildcard('wildcard') | nb_full | nb_end | caret("one")).set_results_name("nback") + return nback + + def __eq__(self, other:'NBack'): + return all([getattr(self, f) == getattr(other, f) for f in self.FIELDS]) + + def __repr__(self) -> str: + return pformat({f:getattr(self, f) for f in self.FIELDS}) + +class Wikilink(Pattern): + """ + Pattern for detecting wikilinks! + + This pattern implements an extended wikilink syntax that includes + + * **n-back links** - allows the user to specify messages in threads that are not the initiating message, and + * **Semantic wikilinks** - specify a triplet subject-predicate-object link + + In each of the following examples, `LINK` is a placeholder for the text of the wikilink to be made. + + # N-Back Links (see :class:`.NBack`) + + For all of these, whitespace in-between the n-back specifier and the link text will be ignored. So + `[[^LINK]]` and `[[^ LINK]]` are both valid. + + * **Preceding Message** - `[[^LINK]]` + * **Entire Preceding Thread** - `[[^*LINK]]` + * **Ranges** + ** **Fully specified** - `[[^{n,m}LINK]]` where `n` and `m` are the start and end of the range to be included, inclusive. + eg. `[[^{2,5}LINK]]` would specify four messages: the 2nd one above the initiating message through the 5th, and + `n == 0` indicates the initiating message. + ** **End specified** - `[[^{,m}LINK]]` OR `[[^{m}LINK]]` - include the initiating message and the `m` messages above it. + ** **Start specified** - `[[^{n,}LINK]]` - include all preceding messages in the thread before the `nth` message + + # Semantic Wikilinks + + Semantic wikilinks create a subject, predicate, object triplet. The subject will be the page that the + + Semantic wikilinks use `::` as a delimiter between terms, and a `::` indicates that a wikilink is semantic. + + `SUB`, `PRED`, and `OBJ` are placeholders for the parts of + a triplet in the following examples. + + * **Complete Triplet** - `[[SUB::PRED::OBJ]]` - create a semantic wikilink on the `SUB`ject page that links to the + `OBJ`ect page with the indicated predicate. + + eg. `[[Paper::Has DOI::https://doi.org/10.xxx/yyyy]]` + + * **Implicit Triplet** - `[[PRED::OBJ]]` after a `[[SUB]]` wikilink has been previously used in the message or thread. + A subject can also be declared with a complete triplet. + """ + FIELDS = ('link', 'nback', 'predicate', 'object', 'section') + + def __init__( + self, + link: str, + nback: Optional[Union[NBack, tuple, dict]] = None, + predicate: Optional[str] = None, + object: Optional[str] = None, + section: Optional[str] = None, + **kwargs): + super(Wikilink, self).__init__(**kwargs) + + self.link = link + if isinstance(nback, (tuple, list)): + nback = NBack(*nback) + elif isinstance(nback, dict): + nback = NBack(**nback) + elif isinstance(nback, pp.ParseResults): + nback = NBack(**dict(nback)) + + if isinstance(section, pp.ParseResults): + section = section[0] + + self.nback = nback + self.predicate = predicate + self.object = object + self.section = section + + @classmethod + def make_parser(cls) -> pp.ParserElement: + """ + Make the parser to detect wikilinks! + """ + # All wikilinks start with [[ and end with ]] + lbracket = pp.Literal('[[').suppress() + rbracket = pp.Literal(']]').suppress() + + #nback parser + nback = NBack.make_parser() + + # main wikilink subject text + link = pp.Word(pp.printables+ " ", excludeChars="#[]{}|") + + # optional page section + hash = pp.Literal("#").suppress() + section = hash + link + + # Combine all + parser = lbracket + pp.Optional(nback) + link("link") + pp.Optional(section("section")) + rbracket + return parser + + @classmethod + def parse(cls, string:str, return_parsed:bool=False) -> List['Wikilink']: + parser = cls.make_parser() + results = parser.search_string(string) + if return_parsed: + return results + else: + return [Wikilink(**dict(res.items())) for res in results] + + def __eq__(self, other:'Wikilink'): + return all(getattr(self, f) == getattr(other, f) for f in self.FIELDS) + + def __repr__(self) -> str: + return pformat({f:getattr(self, f) for f in self.FIELDS if getattr(self, f) is not None}) + + + diff --git a/masto_ld/templates/__init__.py b/masto_ld/templates/__init__.py new file mode 100644 index 0000000..1f1a74b --- /dev/null +++ b/masto_ld/templates/__init__.py @@ -0,0 +1,8 @@ +from pydantic import BaseModel + +class WikiPage(BaseModel): + """ + A wiki page that should be just straight up added to the wiki as is + """ + name: str + body: str \ No newline at end of file diff --git a/masto_ld/templates/categories.py b/masto_ld/templates/categories.py new file mode 100644 index 0000000..c367f4d --- /dev/null +++ b/masto_ld/templates/categories.py @@ -0,0 +1,12 @@ +from masto_ld.templates import WikiPage + +class Category(WikiPage): + """Trivial subclass to indicate these are categories!""" + pass + +Thread = Category( + name="Category:Thread", + body=""" +[[Imported from::schema:SocialMediaPosting]] +""" +) \ No newline at end of file diff --git a/masto_ld/templates/properties.py b/masto_ld/templates/properties.py new file mode 100644 index 0000000..dbc4dd2 --- /dev/null +++ b/masto_ld/templates/properties.py @@ -0,0 +1,48 @@ +from masto_ld.templates import WikiPage + +class Property(WikiPage): + """Trivial subclass to indicate that these are properties!""" + pass + +Tag = Property( + name="Property:Tag", + body=""" +[[Has type::Record]] +[[Has fields::ShortName;LongName]] +""" +) + +ShortName = Property( + name="Property:ShortName", + body=""" +A short name [[Used In::Property:Tag]] +[[Has type::Text]] +""" +) + +LongName = Property( + name="Property:LongName", + body=""" +A long name [[Used In::Property:Tag]] +[[Has type::Page]] +""" +) + +HasThread = Property( + name="Property:Has_Thread", + body=""" +To specify a thread page that is about this page! +[[Has type::Page]] +[[Imported from::schema:subjectOf]] +""" +) + +About = Property( + name="Property:About", + body=""" +To specify that something is about another thing! +[[Has type::Page]] +[[Imported from::schema:about]] +""" +) + diff --git a/masto_ld/templates/templates.py b/masto_ld/templates/templates.py new file mode 100644 index 0000000..e82be44 --- /dev/null +++ b/masto_ld/templates/templates.py @@ -0,0 +1,23 @@ +from masto_ld.templates import WikiPage +from typing import List + +class Template(WikiPage): + """Trivial subclass to indicate template!""" + params: List[str] + +Thread = Template( + name="Template:Thread", + params= ['About', 'URL'], + body=""" + +{|class="wikitable sortable" +! About +| [[About::{{{About|}}}]] +|- +! Thread URL +| [[Has URL::{{{URL|}}}]] +|} +[[Category:Thread]] + +""" +) \ No newline at end of file diff --git a/masto_ld/templates/thread.py b/masto_ld/templates/thread.py new file mode 100644 index 0000000..e69de29 diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..2e4c9f5 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,629 @@ +[[package]] +name = "beautifulsoup4" +version = "4.11.1" +description = "Screen-scraping library" +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "blurhash" +version = "1.1.4" +description = "Pure-Python implementation of the blurhash algorithm." +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["Pillow", "numpy", "pytest"] + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "commonmark" +version = "0.9.1" +description = "Python parser for the CommonMark Markdown spec" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "dnspython" +version = "2.2.1" +description = "DNS toolkit" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[package.extras] +curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"] +dnssec = ["cryptography (>=2.6,<37.0)"] +doh = ["h2 (>=4.1.0)", "httpx (>=0.21.1)", "requests (>=2.23.0,<3.0.0)", "requests-toolbelt (>=0.9.1,<0.10.0)"] +idna = ["idna (>=2.1,<4.0)"] +trio = ["trio (>=0.14,<0.20)"] +wmi = ["wmi (>=1.5.1,<2.0.0)"] + +[[package]] +name = "email-validator" +version = "1.3.0" +description = "A robust email address syntax and deliverability validation library." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[package.dependencies] +dnspython = ">=1.15.0" +idna = ">=2.0.0" + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "lxml" +version = "4.9.1" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "mastodon-py" +version = "1.5.2" +description = "Python wrapper for the Mastodon API" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +blurhash = ">=1.1.4" +decorator = ">=4.0.0" +python-dateutil = "*" +python-magic = "*" +pytz = "*" +requests = ">=2.4.2" +six = "*" + +[package.extras] +blurhash = ["blurhash (>=1.1.4)"] +test = ["blurhash (>=1.1.4)", "cryptography (>=1.6.0)", "http-ece (>=1.0.5)", "pytest", "pytest-cov", "pytest-mock", "pytest-runner", "pytest-vcr", "requests-mock", "vcrpy"] +webpush = ["cryptography (>=1.6.0)", "http-ece (>=1.0.5)"] + +[[package]] +name = "pydantic" +version = "1.10.2" +description = "Data validation and settings management using python type hints" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +email-validator = {version = ">=1.0.3", optional = true, markers = "extra == \"email\""} +python-dotenv = {version = ">=0.10.4", optional = true, markers = "extra == \"dotenv\""} +typing-extensions = ">=4.1.0" + +[package.extras] +dotenv = ["python-dotenv (>=0.10.4)"] +email = ["email-validator (>=1.0.3)"] + +[[package]] +name = "pygments" +version = "2.13.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pypandoc" +version = "1.10" +description = "Thin wrapper for pandoc." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-dotenv" +version = "0.21.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-magic" +version = "0.4.27" +description = "File type identification using libmagic" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pytz" +version = "2022.6" +description = "World timezone definitions, modern and historical" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "regex" +version = "2022.10.31" +description = "Alternative regular expression module, to replace re." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "12.6.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "main" +optional = false +python-versions = ">=3.6.3,<4.0.0" + +[package.dependencies] +commonmark = ">=0.9.0,<0.10.0" +pygments = ">=2.6.0,<3.0.0" + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<8.0.0)"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "wikitextparser" +version = "0.51.1" +description = "A simple parsing tool for MediaWiki's wikitext markup." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +regex = ">=2022.9.11" +wcwidth = "*" + +[package.extras] +dev = ["coverage", "path.py", "twine"] +tests = ["pytest"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.9" +content-hash = "d0b7c1d488d5cf14efa9dc53449d39e6b9a032c09a81fae39b9ce3a1dc5c3ba0" + +[metadata.files] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] +blurhash = [ + {file = "blurhash-1.1.4-py2.py3-none-any.whl", hash = "sha256:7611c1bc41383d2349b6129208587b5d61e8792ce953893cb49c38beeb400d1d"}, + {file = "blurhash-1.1.4.tar.gz", hash = "sha256:da56b163e5a816e4ad07172f5639287698e09d7f3dc38d18d9726d9c1dbc4cee"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +commonmark = [ + {file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"}, + {file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"}, +] +decorator = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] +dnspython = [ + {file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"}, + {file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"}, +] +email-validator = [ + {file = "email_validator-1.3.0-py2.py3-none-any.whl", hash = "sha256:816073f2a7cffef786b29928f58ec16cdac42710a53bb18aa94317e3e145ec5c"}, + {file = "email_validator-1.3.0.tar.gz", hash = "sha256:553a66f8be2ec2dea641ae1d3f29017ab89e9d603d4a25cdaac39eefa283d769"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +lxml = [ + {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"}, + {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"}, + {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"}, + {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"}, + {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"}, + {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"}, + {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"}, + {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"}, + {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"}, + {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"}, + {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"}, + {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"}, + {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"}, + {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"}, + {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"}, + {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"}, + {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"}, + {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"}, + {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"}, + {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"}, + {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"}, + {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"}, + {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"}, + {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"}, + {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"}, + {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"}, + {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"}, + {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"}, + {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"}, + {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"}, + {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"}, + {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"}, + {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"}, + {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"}, + {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"}, + {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"}, + {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"}, + {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"}, + {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"}, + {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"}, + {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"}, +] +mastodon-py = [ + {file = "Mastodon.py-1.5.2-py2.py3-none-any.whl", hash = "sha256:49afbf9f4347f355bee5638b71a5231b0e1164a58d253ccd9b4345d999c43369"}, + {file = "Mastodon.py-1.5.2.tar.gz", hash = "sha256:c98fd97b7450cd02262669b80be20f53657b5540c4888a47231df11856910918"}, +] +pydantic = [ + {file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"}, + {file = "pydantic-1.10.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a1f5a63a6dfe19d719b1b6e6106561869d2efaca6167f84f5ab9347887d78b98"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"}, + {file = "pydantic-1.10.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19b3b9ccf97af2b7519c42032441a891a5e05c68368f40865a90eb88833c2559"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e9069e1b01525a96e6ff49e25876d90d5a563bc31c658289a8772ae186552236"}, + {file = "pydantic-1.10.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:355639d9afc76bcb9b0c3000ddcd08472ae75318a6eb67a15866b87e2efa168c"}, + {file = "pydantic-1.10.2-cp310-cp310-win_amd64.whl", hash = "sha256:ae544c47bec47a86bc7d350f965d8b15540e27e5aa4f55170ac6a75e5f73b644"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a4c805731c33a8db4b6ace45ce440c4ef5336e712508b4d9e1aafa617dc9907f"}, + {file = "pydantic-1.10.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d49f3db871575e0426b12e2f32fdb25e579dea16486a26e5a0474af87cb1ab0a"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37c90345ec7dd2f1bcef82ce49b6235b40f282b94d3eec47e801baf864d15525"}, + {file = "pydantic-1.10.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b5ba54d026c2bd2cb769d3468885f23f43710f651688e91f5fb1edcf0ee9283"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:05e00dbebbe810b33c7a7362f231893183bcc4251f3f2ff991c31d5c08240c42"}, + {file = "pydantic-1.10.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:2d0567e60eb01bccda3a4df01df677adf6b437958d35c12a3ac3e0f078b0ee52"}, + {file = "pydantic-1.10.2-cp311-cp311-win_amd64.whl", hash = "sha256:c6f981882aea41e021f72779ce2a4e87267458cc4d39ea990729e21ef18f0f8c"}, + {file = "pydantic-1.10.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4aac8e7103bf598373208f6299fa9a5cfd1fc571f2d40bf1dd1955a63d6eeb5"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81a7b66c3f499108b448f3f004801fcd7d7165fb4200acb03f1c2402da73ce4c"}, + {file = "pydantic-1.10.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bedf309630209e78582ffacda64a21f96f3ed2e51fbf3962d4d488e503420254"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9300fcbebf85f6339a02c6994b2eb3ff1b9c8c14f502058b5bf349d42447dcf5"}, + {file = "pydantic-1.10.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:216f3bcbf19c726b1cc22b099dd409aa371f55c08800bcea4c44c8f74b73478d"}, + {file = "pydantic-1.10.2-cp37-cp37m-win_amd64.whl", hash = "sha256:dd3f9a40c16daf323cf913593083698caee97df2804aa36c4b3175d5ac1b92a2"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b97890e56a694486f772d36efd2ba31612739bc6f3caeee50e9e7e3ebd2fdd13"}, + {file = "pydantic-1.10.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9cabf4a7f05a776e7793e72793cd92cc865ea0e83a819f9ae4ecccb1b8aa6116"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06094d18dd5e6f2bbf93efa54991c3240964bb663b87729ac340eb5014310624"}, + {file = "pydantic-1.10.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc78cc83110d2f275ec1970e7a831f4e371ee92405332ebfe9860a715f8336e1"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ee433e274268a4b0c8fde7ad9d58ecba12b069a033ecc4645bb6303c062d2e9"}, + {file = "pydantic-1.10.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c2abc4393dea97a4ccbb4ec7d8658d4e22c4765b7b9b9445588f16c71ad9965"}, + {file = "pydantic-1.10.2-cp38-cp38-win_amd64.whl", hash = "sha256:0b959f4d8211fc964772b595ebb25f7652da3f22322c007b6fed26846a40685e"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c33602f93bfb67779f9c507e4d69451664524389546bacfe1bee13cae6dc7488"}, + {file = "pydantic-1.10.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5760e164b807a48a8f25f8aa1a6d857e6ce62e7ec83ea5d5c5a802eac81bad41"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6eb843dcc411b6a2237a694f5e1d649fc66c6064d02b204a7e9d194dff81eb4b"}, + {file = "pydantic-1.10.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b8795290deaae348c4eba0cebb196e1c6b98bdbe7f50b2d0d9a4a99716342fe"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e0bedafe4bc165ad0a56ac0bd7695df25c50f76961da29c050712596cf092d6d"}, + {file = "pydantic-1.10.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2e05aed07fa02231dbf03d0adb1be1d79cabb09025dd45aa094aa8b4e7b9dcda"}, + {file = "pydantic-1.10.2-cp39-cp39-win_amd64.whl", hash = "sha256:c1ba1afb396148bbc70e9eaa8c06c1716fdddabaf86e7027c5988bae2a829ab6"}, + {file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"}, + {file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"}, +] +pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pypandoc = [ + {file = "pypandoc-1.10-py3-none-any.whl", hash = "sha256:ff9658cda18865a822695349a7c707756ecc454b59b71f920b554579ec54aaa7"}, + {file = "pypandoc-1.10.tar.gz", hash = "sha256:101164d154f0b9957372cdbf285396153b74144fda47f531fb556de244efc86e"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +python-dotenv = [ + {file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"}, + {file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"}, +] +python-magic = [ + {file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"}, + {file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"}, +] +pytz = [ + {file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"}, + {file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"}, +] +regex = [ + {file = "regex-2022.10.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a8ff454ef0bb061e37df03557afda9d785c905dab15584860f982e88be73015f"}, + {file = "regex-2022.10.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1eba476b1b242620c266edf6325b443a2e22b633217a9835a52d8da2b5c051f9"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0e5af9a9effb88535a472e19169e09ce750c3d442fb222254a276d77808620b"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d03fe67b2325cb3f09be029fd5da8df9e6974f0cde2c2ac6a79d2634e791dd57"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9d0b68ac1743964755ae2d89772c7e6fb0118acd4d0b7464eaf3921c6b49dd4"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a45b6514861916c429e6059a55cf7db74670eaed2052a648e3e4d04f070e001"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b0886885f7323beea6f552c28bff62cbe0983b9fbb94126531693ea6c5ebb90"}, + {file = "regex-2022.10.31-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5aefb84a301327ad115e9d346c8e2760009131d9d4b4c6b213648d02e2abe144"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:702d8fc6f25bbf412ee706bd73019da5e44a8400861dfff7ff31eb5b4a1276dc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a3c1ebd4ed8e76e886507c9eddb1a891673686c813adf889b864a17fafcf6d66"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:50921c140561d3db2ab9f5b11c5184846cde686bb5a9dc64cae442926e86f3af"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:7db345956ecce0c99b97b042b4ca7326feeec6b75facd8390af73b18e2650ffc"}, + {file = "regex-2022.10.31-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:763b64853b0a8f4f9cfb41a76a4a85a9bcda7fdda5cb057016e7706fde928e66"}, + {file = "regex-2022.10.31-cp310-cp310-win32.whl", hash = "sha256:44136355e2f5e06bf6b23d337a75386371ba742ffa771440b85bed367c1318d1"}, + {file = "regex-2022.10.31-cp310-cp310-win_amd64.whl", hash = "sha256:bfff48c7bd23c6e2aec6454aaf6edc44444b229e94743b34bdcdda2e35126cf5"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4b4b1fe58cd102d75ef0552cf17242705ce0759f9695334a56644ad2d83903fe"}, + {file = "regex-2022.10.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:542e3e306d1669b25936b64917285cdffcd4f5c6f0247636fec037187bd93542"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c27cc1e4b197092e50ddbf0118c788d9977f3f8f35bfbbd3e76c1846a3443df7"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8e38472739028e5f2c3a4aded0ab7eadc447f0d84f310c7a8bb697ec417229e"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76c598ca73ec73a2f568e2a72ba46c3b6c8690ad9a07092b18e48ceb936e9f0c"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c28d3309ebd6d6b2cf82969b5179bed5fefe6142c70f354ece94324fa11bf6a1"}, + {file = "regex-2022.10.31-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9af69f6746120998cd9c355e9c3c6aec7dff70d47247188feb4f829502be8ab4"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a5f9505efd574d1e5b4a76ac9dd92a12acb2b309551e9aa874c13c11caefbe4f"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5ff525698de226c0ca743bfa71fc6b378cda2ddcf0d22d7c37b1cc925c9650a5"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:4fe7fda2fe7c8890d454f2cbc91d6c01baf206fbc96d89a80241a02985118c0c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2cdc55ca07b4e70dda898d2ab7150ecf17c990076d3acd7a5f3b25cb23a69f1c"}, + {file = "regex-2022.10.31-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:44a6c2f6374e0033873e9ed577a54a3602b4f609867794c1a3ebba65e4c93ee7"}, + {file = "regex-2022.10.31-cp311-cp311-win32.whl", hash = "sha256:d8716f82502997b3d0895d1c64c3b834181b1eaca28f3f6336a71777e437c2af"}, + {file = "regex-2022.10.31-cp311-cp311-win_amd64.whl", hash = "sha256:61edbca89aa3f5ef7ecac8c23d975fe7261c12665f1d90a6b1af527bba86ce61"}, + {file = "regex-2022.10.31-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:0a069c8483466806ab94ea9068c34b200b8bfc66b6762f45a831c4baaa9e8cdd"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d26166acf62f731f50bdd885b04b38828436d74e8e362bfcb8df221d868b5d9b"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac741bf78b9bb432e2d314439275235f41656e189856b11fb4e774d9f7246d81"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75f591b2055523fc02a4bbe598aa867df9e953255f0b7f7715d2a36a9c30065c"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b30bddd61d2a3261f025ad0f9ee2586988c6a00c780a2fb0a92cea2aa702c54"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef4163770525257876f10e8ece1cf25b71468316f61451ded1a6f44273eedeb5"}, + {file = "regex-2022.10.31-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7b280948d00bd3973c1998f92e22aa3ecb76682e3a4255f33e1020bd32adf443"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:d0213671691e341f6849bf33cd9fad21f7b1cb88b89e024f33370733fec58742"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:22e7ebc231d28393dfdc19b185d97e14a0f178bedd78e85aad660e93b646604e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:8ad241da7fac963d7573cc67a064c57c58766b62a9a20c452ca1f21050868dfa"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:586b36ebda81e6c1a9c5a5d0bfdc236399ba6595e1397842fd4a45648c30f35e"}, + {file = "regex-2022.10.31-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0653d012b3bf45f194e5e6a41df9258811ac8fc395579fa82958a8b76286bea4"}, + {file = "regex-2022.10.31-cp36-cp36m-win32.whl", hash = "sha256:144486e029793a733e43b2e37df16a16df4ceb62102636ff3db6033994711066"}, + {file = "regex-2022.10.31-cp36-cp36m-win_amd64.whl", hash = "sha256:c14b63c9d7bab795d17392c7c1f9aaabbffd4cf4387725a0ac69109fb3b550c6"}, + {file = "regex-2022.10.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4cac3405d8dda8bc6ed499557625585544dd5cbf32072dcc72b5a176cb1271c8"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23cbb932cc53a86ebde0fb72e7e645f9a5eec1a5af7aa9ce333e46286caef783"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74bcab50a13960f2a610cdcd066e25f1fd59e23b69637c92ad470784a51b1347"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78d680ef3e4d405f36f0d6d1ea54e740366f061645930072d39bca16a10d8c93"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6910b56b700bea7be82c54ddf2e0ed792a577dfaa4a76b9af07d550af435c6"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:659175b2144d199560d99a8d13b2228b85e6019b6e09e556209dfb8c37b78a11"}, + {file = "regex-2022.10.31-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1ddf14031a3882f684b8642cb74eea3af93a2be68893901b2b387c5fd92a03ec"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b683e5fd7f74fb66e89a1ed16076dbab3f8e9f34c18b1979ded614fe10cdc4d9"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2bde29cc44fa81c0a0c8686992c3080b37c488df167a371500b2a43ce9f026d1"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4919899577ba37f505aaebdf6e7dc812d55e8f097331312db7f1aab18767cce8"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:9c94f7cc91ab16b36ba5ce476f1904c91d6c92441f01cd61a8e2729442d6fcf5"}, + {file = "regex-2022.10.31-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae1e96785696b543394a4e3f15f3f225d44f3c55dafe3f206493031419fedf95"}, + {file = "regex-2022.10.31-cp37-cp37m-win32.whl", hash = "sha256:c670f4773f2f6f1957ff8a3962c7dd12e4be54d05839b216cb7fd70b5a1df394"}, + {file = "regex-2022.10.31-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0caeff18b96ea90fc0eb6e3bdb2b10ab5b01a95128dfeccb64a7238decf5f0"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:131d4be09bea7ce2577f9623e415cab287a3c8e0624f778c1d955ec7c281bd4d"}, + {file = "regex-2022.10.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e613a98ead2005c4ce037c7b061f2409a1a4e45099edb0ef3200ee26ed2a69a8"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:052b670fafbe30966bbe5d025e90b2a491f85dfe5b2583a163b5e60a85a321ad"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aa62a07ac93b7cb6b7d0389d8ef57ffc321d78f60c037b19dfa78d6b17c928ee"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5352bea8a8f84b89d45ccc503f390a6be77917932b1c98c4cdc3565137acc714"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20f61c9944f0be2dc2b75689ba409938c14876c19d02f7585af4460b6a21403e"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29c04741b9ae13d1e94cf93fca257730b97ce6ea64cfe1eba11cf9ac4e85afb6"}, + {file = "regex-2022.10.31-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:543883e3496c8b6d58bd036c99486c3c8387c2fc01f7a342b760c1ea3158a318"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7a8b43ee64ca8f4befa2bea4083f7c52c92864d8518244bfa6e88c751fa8fff"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6a9a19bea8495bb419dc5d38c4519567781cd8d571c72efc6aa959473d10221a"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:6ffd55b5aedc6f25fd8d9f905c9376ca44fcf768673ffb9d160dd6f409bfda73"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4bdd56ee719a8f751cf5a593476a441c4e56c9b64dc1f0f30902858c4ef8771d"}, + {file = "regex-2022.10.31-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ca88da1bd78990b536c4a7765f719803eb4f8f9971cc22d6ca965c10a7f2c4c"}, + {file = "regex-2022.10.31-cp38-cp38-win32.whl", hash = "sha256:5a260758454580f11dd8743fa98319bb046037dfab4f7828008909d0aa5292bc"}, + {file = "regex-2022.10.31-cp38-cp38-win_amd64.whl", hash = "sha256:5e6a5567078b3eaed93558842346c9d678e116ab0135e22eb72db8325e90b453"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5217c25229b6a85049416a5c1e6451e9060a1edcf988641e309dbe3ab26d3e49"}, + {file = "regex-2022.10.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4bf41b8b0a80708f7e0384519795e80dcb44d7199a35d52c15cc674d10b3081b"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf0da36a212978be2c2e2e2d04bdff46f850108fccc1851332bcae51c8907cc"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d403d781b0e06d2922435ce3b8d2376579f0c217ae491e273bab8d092727d244"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a37d51fa9a00d265cf73f3de3930fa9c41548177ba4f0faf76e61d512c774690"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4f781ffedd17b0b834c8731b75cce2639d5a8afe961c1e58ee7f1f20b3af185"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d243b36fbf3d73c25e48014961e83c19c9cc92530516ce3c43050ea6276a2ab7"}, + {file = "regex-2022.10.31-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:370f6e97d02bf2dd20d7468ce4f38e173a124e769762d00beadec3bc2f4b3bc4"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:597f899f4ed42a38df7b0e46714880fb4e19a25c2f66e5c908805466721760f5"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7dbdce0c534bbf52274b94768b3498abdf675a691fec5f751b6057b3030f34c1"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:22960019a842777a9fa5134c2364efaed5fbf9610ddc5c904bd3a400973b0eb8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7f5a3ffc731494f1a57bd91c47dc483a1e10048131ffb52d901bfe2beb6102e8"}, + {file = "regex-2022.10.31-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7ef6b5942e6bfc5706301a18a62300c60db9af7f6368042227ccb7eeb22d0892"}, + {file = "regex-2022.10.31-cp39-cp39-win32.whl", hash = "sha256:395161bbdbd04a8333b9ff9763a05e9ceb4fe210e3c7690f5e68cedd3d65d8e1"}, + {file = "regex-2022.10.31-cp39-cp39-win_amd64.whl", hash = "sha256:957403a978e10fb3ca42572a23e6f7badff39aa1ce2f4ade68ee452dc6807692"}, + {file = "regex-2022.10.31.tar.gz", hash = "sha256:a3a98921da9a1bf8457aeee6a551948a83601689e5ecdd736894ea9bbec77e83"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +rich = [ + {file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"}, + {file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +wikitextparser = [ + {file = "wikitextparser-0.51.1-py3-none-any.whl", hash = "sha256:f7da75a6607a5ff31f858dc1322bf204aec6b8a508ec777fbb2e722c2a53d5d9"}, + {file = "wikitextparser-0.51.1.tar.gz", hash = "sha256:d3eee1c18de0f2184d15f9ff1d9ad8dfd4f5b2ce50e7de4190409ca7bc848c11"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b3889c3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,28 @@ +[tool.poetry] +name = "masto-ld" +version = "0.1.0" +description = "A bot for prefixing and using linked data from Mastodon" +authors = ["sneakers-the-rat "] +license = "AGPL-3.0" +readme = "README.md" +packages = [{include = "masto_ld"}] + +[tool.poetry.dependencies] +python = "^3.9" +pydantic = {extras = ["dotenv", "email"], version = "^1.10.2"} +mastodon-py = "^1.5.2" +pyparsing = "^3.0.9" +rich = "^12.6.0" +beautifulsoup4 = "^4.11.1" +lxml = "^4.9.1" +pypandoc = "^1.10" +requests = "^2.28.1" +wikitextparser = "^0.51.1" + +[tool.poetry.scripts] +masto_ld = 'masto_ld.main:masto_ld' + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api"