Compare commits
No commits in common. "843f384083d1f378271e9874df3942f2f2699b44" and "6c65eaa30180dfa1a5995cead10d8ce3f9b2af49" have entirely different histories.
843f384083
...
6c65eaa301
16 changed files with 434 additions and 822 deletions
66
README.md
66
README.md
|
@ -1,40 +1,10 @@
|
||||||
# masto-bridges (formerly masto-gitsocial-bridge)
|
# masto-gitsocial-bridge
|
||||||
|
|
||||||
Mirrored to https://github.com/sneakers-the-rat/masto-gitsocial-bridge if for some godforsaken reason you want to raise issues or pull requests or fork it or whatever
|
|
||||||
|
|
||||||
|
|
||||||
## CalDAV to Masto (and back again)
|
|
||||||
|
|
||||||
Post directly from your (CalDAV) calendar, and follow your feeds from your calendar!
|
|
||||||
|
|
||||||
See [config](#Config) below for how to set up for your calendar.
|
|
||||||
|
|
||||||
Compatible with mobile too!
|
|
||||||
|
|
||||||
![Post from your calendar, get your feed from your calendar](img/masto-caldav.png)
|
|
||||||
|
|
||||||
### From Calendar to Masto
|
|
||||||
|
|
||||||
Make a new calendar event with
|
|
||||||
- Event title/summary: your username `user@instance.com` or just `user`. Calendar entries that don't
|
|
||||||
match your username will not be posted (since they aren't from you!)
|
|
||||||
- Notes/Description: The content of your post!
|
|
||||||
- Time, date, duration, repetition: don't matter for now!
|
|
||||||
- The absence of a URL in the "location" field is used to make sure we don't echo posts we make from the real masto
|
|
||||||
|
|
||||||
### From Masto to Calendar
|
|
||||||
|
|
||||||
When running `masto_bridgebot` with `MASTOBRIDGE_ENABLE_CALDAV=true` and
|
|
||||||
`MASTOBRIDGE_STREAM_MODE="home"`, your home feed should already be posting to your calendar!
|
|
||||||
|
|
||||||
To make it so only your posts are added to your calendar, set the stream mode to `'list'`
|
|
||||||
|
|
||||||
## git-social
|
|
||||||
|
|
||||||
bridge between mastodon and bcrypt's [git-social](https://github.com/diracdeltas/tweets)
|
bridge between mastodon and bcrypt's [git-social](https://github.com/diracdeltas/tweets)
|
||||||
|
|
||||||
|
Mirrored to https://github.com/sneakers-the-rat/masto-gitsocial-bridge if for some godforsaken reason you want to raise issues or pull requests or fork it or whatever
|
||||||
|
|
||||||
### From git to masto
|
## From git to masto
|
||||||
|
|
||||||
Post on git-social...
|
Post on git-social...
|
||||||
|
|
||||||
|
@ -44,7 +14,7 @@ Bridge to mastodon
|
||||||
|
|
||||||
![Post on mastodon, same text but with link to the git commit](img/git-to-masto_1.png)
|
![Post on mastodon, same text but with link to the git commit](img/git-to-masto_1.png)
|
||||||
|
|
||||||
### From masto to git
|
## From masto to git
|
||||||
|
|
||||||
Post on mastodon...
|
Post on mastodon...
|
||||||
|
|
||||||
|
@ -101,25 +71,11 @@ from environment variables, for example, make an `.env` file in the cloned
|
||||||
repository directory like
|
repository directory like
|
||||||
|
|
||||||
```
|
```
|
||||||
MASTOBRIDGE_LOGDIR="/wherever/you/want/to/put/logs"
|
MASTOGIT_MASTO_URL="https://social.coop"
|
||||||
|
MASTOGIT_MASTO_TOKEN="<mastodon bot access token>"
|
||||||
MASTOBRIDGE_MASTO_URL="https://social.coop"
|
MASTOGIT_GIT_REPO="/path/to/your/git-social/tweets"
|
||||||
MASTOBRIDGE_MASTO_TOKEN="<mastodon bot access token>"
|
MASTOGIT_GIT_REMOTE_URL="https://git.jon-e.net/jonny/tweets"
|
||||||
|
MASTOGIT_LOGDIR="/wherever/you/want/to/put/logs"
|
||||||
MASTOBRIDGE_ENABLE_GIT=false
|
|
||||||
MASTOBRIDGE_GIT_REPO="/path/to/your/git-social/tweets"
|
|
||||||
MASTOBRIDGE_GIT_REMOTE_URL="https://git.jon-e.net/jonny/tweets"
|
|
||||||
|
|
||||||
MASTOBRIDGE_ENABLE_CALDAV=true
|
|
||||||
MASTOBRIDGE_CALDAV_URL="https://your.caldav.calendar/wherever"
|
|
||||||
MASTOBRIDGE_CALDAV_USER="<caldav username>"
|
|
||||||
MASTOBRIDGE_CALDAV_PASSWORD="<caldav password>"
|
|
||||||
MASTOBRIDGE_CALDAV_CALENDAR_NAME="<name of calendar to use for feeds and posts>"
|
|
||||||
|
|
||||||
# "home" (for streaming your home feed to bridges)
|
|
||||||
# or "list" for streaming a list with just your account in it to bridges
|
|
||||||
MASTOBRIDGE_STREAM_MODE="home"
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
This is assuming you don't need any sort of authentication/local password
|
This is assuming you don't need any sort of authentication/local password
|
||||||
|
@ -153,9 +109,9 @@ git-social (only if they are "public" or "unlisted").
|
||||||
poetry run masto_gitbot
|
poetry run masto_gitbot
|
||||||
# or
|
# or
|
||||||
# >>> poetry shell
|
# >>> poetry shell
|
||||||
# >>> masto_bridgebot
|
# >>> masto_gitbot
|
||||||
# or however else you run python entrypoint scripts
|
# or however else you run python entrypoint scripts
|
||||||
# hell you could do python -m masto_bridges.main:masto_gitbot
|
# hell you could do python -m masto_git_bridge.main:masto_gitbot
|
||||||
# i think?
|
# i think?
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 402 KiB |
|
@ -1,164 +0,0 @@
|
||||||
import pdb
|
|
||||||
from typing import Optional
|
|
||||||
from datetime import datetime
|
|
||||||
from masto_bridges.config import Config
|
|
||||||
from masto_bridges.models import Account, List
|
|
||||||
from masto_bridges.post import Post, Status
|
|
||||||
from masto_bridges.logger import init_logger
|
|
||||||
from masto_bridges.repo import Repo
|
|
||||||
from masto_bridges.caldav import CalDAV
|
|
||||||
|
|
||||||
from mastodon import Mastodon, StreamListener
|
|
||||||
|
|
||||||
class Listener(StreamListener):
|
|
||||||
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 = None
|
|
||||||
if self.config.ENABLE_GIT:
|
|
||||||
self.repo = Repo(path=config.GIT_REPO)
|
|
||||||
|
|
||||||
self.caldav = None
|
|
||||||
if self.config.ENABLE_CALDAV is not None:
|
|
||||||
self.caldav = CalDAV(config=self.config)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def on_update(self, status:dict):
|
|
||||||
try:
|
|
||||||
status = Status(**status)
|
|
||||||
except:
|
|
||||||
self.logger.warning(f'Unhandled event: {status}')
|
|
||||||
return
|
|
||||||
|
|
||||||
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 = False
|
|
||||||
if self.repo:
|
|
||||||
git_success = self.repo.post(post.format_commit())
|
|
||||||
else:
|
|
||||||
git_success = True
|
|
||||||
|
|
||||||
if self.caldav:
|
|
||||||
caldav_success = self.caldav.post(post.format_caldav())
|
|
||||||
else:
|
|
||||||
caldav_success = False
|
|
||||||
|
|
||||||
success = git_success and caldav_success
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self.logger.info('Posted to bridges!')
|
|
||||||
else:
|
|
||||||
self.logger.exception('Failed to post to bridges!')
|
|
||||||
|
|
||||||
def on_unknown_event(self, name, unknown_event = None):
|
|
||||||
"""An unknown mastodon API event has been received. The name contains the event-name and unknown_event
|
|
||||||
contains the content of the unknown event.
|
|
||||||
|
|
||||||
This function must be implemented, if unknown events should be handled without an error.
|
|
||||||
"""
|
|
||||||
self.logger.exception(f'Unknown event type: {name}')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
self.caldav = None
|
|
||||||
if self.config.ENABLE_CALDAV:
|
|
||||||
self.caldav = CalDAV(self.config)
|
|
||||||
|
|
||||||
def init_stream(self, run_async:bool=True):
|
|
||||||
# Listen to a stream consisting of just us.
|
|
||||||
listener = Listener(client=self.client, config=self.config)
|
|
||||||
self.logger.info('Initializing streaming')
|
|
||||||
if self.config.STREAM_MODE == 'list':
|
|
||||||
self.client.stream_list(
|
|
||||||
self.me_list.id,
|
|
||||||
listener = listener,
|
|
||||||
run_async=run_async
|
|
||||||
)
|
|
||||||
elif self.config.STREAM_MODE == 'home':
|
|
||||||
self.client.stream_user(
|
|
||||||
listener=listener,
|
|
||||||
run_async=run_async
|
|
||||||
)
|
|
||||||
|
|
||||||
def post(self, post:str):
|
|
||||||
# TODO: Split long posts
|
|
||||||
if len(post)>self.post_length:
|
|
||||||
raise NotImplementedError(f"Cant split long posts yet, got post of length {len(post)} when max length is {self.post_length}")
|
|
||||||
|
|
||||||
self.client.status_post(post)
|
|
||||||
self.logger.info(f"Posted:\n{post}")
|
|
||||||
|
|
||||||
def poll_caldav(self):
|
|
||||||
events = self.caldav.poll()
|
|
||||||
if len(events) > 0:
|
|
||||||
for event in events:
|
|
||||||
post = Post.from_vevent(event.vobject_instance.vevent)
|
|
||||||
if post.cal_user.split('@')[0] != self.me.acct and post.cal_user != self.me.acct:
|
|
||||||
self.logger.debug(f"Not posting calendar event not from us. Got cal user: {post.cal_user} and we are {self.me.acct}")
|
|
||||||
continue
|
|
||||||
if post.cal_url:
|
|
||||||
self.logger.debug("Not posting a post we made on masto back to masto")
|
|
||||||
continue
|
|
||||||
self.post(post.format_masto())
|
|
||||||
|
|
||||||
@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
|
|
|
@ -1,99 +0,0 @@
|
||||||
from typing import Optional
|
|
||||||
from caldav import DAVClient, Calendar
|
|
||||||
from caldav.objects import SynchronizableCalendarObjectCollection
|
|
||||||
# from caldav.lib.error import ResponseError
|
|
||||||
from pydantic import BaseModel, Field
|
|
||||||
from datetime import datetime
|
|
||||||
from icalendar import Event as iEvent
|
|
||||||
from uuid import uuid1
|
|
||||||
|
|
||||||
from masto_bridges.config import Config
|
|
||||||
|
|
||||||
class Event(BaseModel):
|
|
||||||
|
|
||||||
summary: str # the title of the event, use for username
|
|
||||||
dtstart: datetime
|
|
||||||
dtend: datetime
|
|
||||||
uid: str = Field(default_factory=uuid1)
|
|
||||||
url: str = ''
|
|
||||||
location:str = ''
|
|
||||||
description:str = '' # post body
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def to_ical(self) -> str:
|
|
||||||
evt = iEvent()
|
|
||||||
evt.add('dtstart', self.dtstart)
|
|
||||||
evt.add('dtend', self.dtend)
|
|
||||||
evt.add('uid', self.uid)
|
|
||||||
evt.add('location', self.location)
|
|
||||||
evt.add('summary', self.summary)
|
|
||||||
evt.add('description', self.description)
|
|
||||||
evt.add('url', self.url)
|
|
||||||
return evt.to_ical()
|
|
||||||
|
|
||||||
|
|
||||||
class CalDAV:
|
|
||||||
def __init__(self, config:Config):
|
|
||||||
self.config = config
|
|
||||||
for value in ('CALDAV_URL', 'CALDAV_USER', 'CALDAV_PASSWORD', 'CALDAV_CALENDAR_NAME'):
|
|
||||||
if getattr(self.config, value) is None:
|
|
||||||
raise ValueError('Need all CALDAV fields to be present in config! Check your .env file!')
|
|
||||||
self._sync_token = None
|
|
||||||
self._sync = None # type: Optional[SynchronizableCalendarObjectCollection]
|
|
||||||
|
|
||||||
|
|
||||||
def get_calendar(self, name:str) -> Optional[Calendar]:
|
|
||||||
"""
|
|
||||||
Get a calendar by its name, creating one if none exists.
|
|
||||||
"""
|
|
||||||
with self.client as client:
|
|
||||||
cals = client.principal().calendars()
|
|
||||||
# try and find
|
|
||||||
cal = [cal for cal in cals if cal.name == name]
|
|
||||||
if len(cal) == 0:
|
|
||||||
cal = client.principal().make_calendar(name=name)
|
|
||||||
else:
|
|
||||||
cal = cal[0]
|
|
||||||
|
|
||||||
return cal
|
|
||||||
|
|
||||||
def post(self, event:Event) -> bool:
|
|
||||||
with self.client as client:
|
|
||||||
calendar = self.get_calendar(self.config.CALDAV_CALENDAR_NAME)
|
|
||||||
calendar.save_event(
|
|
||||||
**event.dict()
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def poll(self) -> SynchronizableCalendarObjectCollection:
|
|
||||||
cal = self.get_calendar(self.config.CALDAV_CALENDAR_NAME)
|
|
||||||
if self._sync_token is None:
|
|
||||||
# we haven't polled yet. get and ignore previous entries.
|
|
||||||
# we don't want to post a bunch of junk every time lol
|
|
||||||
self._sync = cal.objects(load_objects=False)
|
|
||||||
|
|
||||||
self._sync_token = self._sync.sync_token
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._sync = cal.objects(sync_token=self._sync_token, load_objects=True)
|
|
||||||
self._sync_token = self._sync.sync_token
|
|
||||||
except:
|
|
||||||
# deleting calendar entries makes us choke!
|
|
||||||
self._sync = cal.objects(sync_token=self._sync_token, load_objects=False)
|
|
||||||
self._sync_token = self._sync.sync_token
|
|
||||||
|
|
||||||
return self._sync
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@property
|
|
||||||
def client(self) -> DAVClient:
|
|
||||||
return DAVClient(
|
|
||||||
url=self.config.CALDAV_URL,
|
|
||||||
username=self.config.CALDAV_USER,
|
|
||||||
password=self.config.CALDAV_PASSWORD
|
|
||||||
)
|
|
|
@ -1,24 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
from typing import Optional, Literal
|
|
||||||
from pydantic import BaseSettings, AnyHttpUrl
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
|
||||||
MASTO_URL:AnyHttpUrl
|
|
||||||
MASTO_TOKEN:str
|
|
||||||
LOGDIR:Path=Path().home() / '.mastobridge'
|
|
||||||
LOGLEVEL:Literal['DEBUG','INFO','WARNING','ERROR','EXCEPTION']='INFO'
|
|
||||||
STREAM_MODE:Literal['list', 'home'] = 'list'
|
|
||||||
ENABLE_GIT:bool=False
|
|
||||||
ENABLE_CALDAV:bool=False
|
|
||||||
GIT_REPO:Optional[Path]=None
|
|
||||||
GIT_REMOTE_URL:Optional[AnyHttpUrl]=None
|
|
||||||
CALDAV_URL:Optional[AnyHttpUrl] = None
|
|
||||||
CALDAV_USER:Optional[str]=None
|
|
||||||
CALDAV_PASSWORD:Optional[str]=None
|
|
||||||
CALDAV_CALENDAR_NAME:Optional[str]=None
|
|
||||||
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
env_file = '.env'
|
|
||||||
env_file_encoding = 'utf-8'
|
|
||||||
env_prefix = "MASTOBRIDGE_"
|
|
|
@ -1,116 +0,0 @@
|
||||||
from typing import Optional, Literal, TYPE_CHECKING
|
|
||||||
from datetime import datetime, timedelta
|
|
||||||
from pydantic import BaseModel
|
|
||||||
import re
|
|
||||||
from bs4 import BeautifulSoup
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from vobject.icalendar import RecurringComponent
|
|
||||||
|
|
||||||
from masto_bridges.repo import Commit
|
|
||||||
from masto_bridges.models import Account
|
|
||||||
from masto_bridges.caldav import Event
|
|
||||||
|
|
||||||
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
|
|
||||||
visibility: Literal['public', 'unlisted', 'private', 'direct']
|
|
||||||
created_at:datetime
|
|
||||||
in_reply_to_id: Optional[int] = None
|
|
||||||
in_reply_to_account_id: Optional[int] = None
|
|
||||||
spoiler_text: Optional[str] = None
|
|
||||||
reblog: Optional['Status'] = None
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
if kwargs.get('url', None) is None:
|
|
||||||
# try using the uri
|
|
||||||
kwargs['url'] = kwargs['uri']
|
|
||||||
super(Status, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
extra='ignore'
|
|
||||||
|
|
||||||
class Post(BaseModel):
|
|
||||||
#timestamp: Optional[datetime] = None
|
|
||||||
text:str
|
|
||||||
status:Optional[Status] = None
|
|
||||||
commit:Optional[Commit] = None
|
|
||||||
cal_user:Optional[str] = None
|
|
||||||
cal_url:Optional[str] = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_commit(cls, commit:Commit) -> 'Post':
|
|
||||||
text = '\n'.join([commit.subject, commit.body])
|
|
||||||
return Post(text=text, commit=commit)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_status(cls, status:Status) -> 'Post':
|
|
||||||
# if this is a boost, get the original status
|
|
||||||
if status.reblog:
|
|
||||||
date = status.created_at
|
|
||||||
status = status.reblog
|
|
||||||
status.created_at = date
|
|
||||||
|
|
||||||
soup = BeautifulSoup(status.content, 'lxml')
|
|
||||||
# replace with double line breaks
|
|
||||||
pars = [p.text for p in soup.find_all('p')]
|
|
||||||
text = '\n\n'.join(pars)
|
|
||||||
return Post(text=text, status=status)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_vevent(cls, event:'RecurringComponent'):
|
|
||||||
user=event.summary.value
|
|
||||||
text = event.description.value
|
|
||||||
try:
|
|
||||||
url = event.location.value
|
|
||||||
except AttributeError:
|
|
||||||
url = None
|
|
||||||
return Post(text=text, cal_user=user, cal_url=url)
|
|
||||||
|
|
||||||
|
|
||||||
def format_masto(self) -> str:
|
|
||||||
"""
|
|
||||||
Format a post to go from git -> masto.
|
|
||||||
|
|
||||||
Needs to have a :attr:`.commit` attribute!
|
|
||||||
|
|
||||||
Does not split the body text into multiple toots.
|
|
||||||
That should be handled in the posting action
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
git-social: https://{repo_url}/commits/{hash}
|
|
||||||
{subject line}
|
|
||||||
{body}
|
|
||||||
"""
|
|
||||||
if self.commit is not None:
|
|
||||||
return f"xpost from git-social: {self.commit.url}\n---\n{self.text}"""
|
|
||||||
elif self.cal_user is not None:
|
|
||||||
return f"xpost from a calendar\n---\n{self.text}"""
|
|
||||||
else:
|
|
||||||
return self.text
|
|
||||||
|
|
||||||
def format_commit(self) -> str:
|
|
||||||
"""
|
|
||||||
Add a link back to original masto post split by double lines
|
|
||||||
"""
|
|
||||||
return f"xpost from mastodon: {self.status.url}\n\n{self.text}"
|
|
||||||
|
|
||||||
def format_caldav(self) -> Event:
|
|
||||||
"""
|
|
||||||
Format a mastodon post for posting to CALDAV
|
|
||||||
"""
|
|
||||||
return Event(
|
|
||||||
dtstart=self.status.created_at,
|
|
||||||
dtend = self.status.created_at + timedelta(minutes=5),
|
|
||||||
location = self.status.url,
|
|
||||||
summary= self.status.account.acct,
|
|
||||||
description = self.text
|
|
||||||
)
|
|
106
masto_git_bridge/bot.py
Normal file
106
masto_git_bridge/bot.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import pdb
|
||||||
|
from typing import Optional
|
||||||
|
from datetime import datetime
|
||||||
|
from masto_git_bridge.config import Config
|
||||||
|
from masto_git_bridge.models import Account, List
|
||||||
|
from masto_git_bridge.post import Post, Status
|
||||||
|
from masto_git_bridge.logger import init_logger
|
||||||
|
from masto_git_bridge.repo import Repo
|
||||||
|
|
||||||
|
from mastodon import Mastodon, StreamListener
|
||||||
|
|
||||||
|
class Listener(StreamListener):
|
||||||
|
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 = Listener(client=self.client, config=self.config)
|
||||||
|
self.logger.info('Initializing streaming')
|
||||||
|
self.client.stream_list(
|
||||||
|
self.me_list.id,
|
||||||
|
listener = listener,
|
||||||
|
run_async=run_async
|
||||||
|
)
|
||||||
|
|
||||||
|
def post(self, post:str):
|
||||||
|
# TODO: Split long posts
|
||||||
|
if len(post)>self.post_length:
|
||||||
|
raise NotImplementedError(f"Cant split long posts yet, got post of length {len(post)} when max length is {self.post_length}")
|
||||||
|
|
||||||
|
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
|
15
masto_git_bridge/config.py
Normal file
15
masto_git_bridge/config.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from pathlib import Path
|
||||||
|
from pydantic import BaseSettings, AnyHttpUrl
|
||||||
|
|
||||||
|
class Config(BaseSettings):
|
||||||
|
MASTO_URL:AnyHttpUrl
|
||||||
|
MASTO_TOKEN:str
|
||||||
|
GIT_REPO:Path
|
||||||
|
GIT_REMOTE_URL:AnyHttpUrl
|
||||||
|
LOGDIR:Path=Path().home() / '.mastogit'
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
env_file = '.env'
|
||||||
|
env_file_encoding = 'utf-8'
|
||||||
|
env_prefix = "MASTOGIT_"
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import pdb
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from masto_bridges.config import Config
|
from masto_git_bridge.config import Config
|
||||||
from masto_bridges.repo import Repo
|
from masto_git_bridge.repo import Repo
|
||||||
from masto_bridges.bot import Bot
|
from masto_git_bridge.bot import Bot
|
||||||
from masto_bridges.post import Post
|
from masto_git_bridge.post import Post
|
||||||
from masto_bridges.logger import init_logger
|
from masto_git_bridge.logger import init_logger
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
def post_last_commit(config:Optional[Config]=None):
|
def post_last_commit(config:Optional[Config]=None):
|
||||||
|
@ -28,7 +27,7 @@ def post_last_commit(config:Optional[Config]=None):
|
||||||
bot = Bot(config=config)
|
bot = Bot(config=config)
|
||||||
bot.post(post.format_masto())
|
bot.post(post.format_masto())
|
||||||
|
|
||||||
def masto_bridgebot(config:Optional[Config]=None):
|
def masto_gitbot(config:Optional[Config]=None):
|
||||||
if config is None:
|
if config is None:
|
||||||
config = Config()
|
config = Config()
|
||||||
|
|
||||||
|
@ -36,17 +35,8 @@ def masto_bridgebot(config:Optional[Config]=None):
|
||||||
try:
|
try:
|
||||||
bot.init_stream()
|
bot.init_stream()
|
||||||
while True:
|
while True:
|
||||||
|
sleep(60*60)
|
||||||
sleep(1)
|
bot.logger.info('taking a breath')
|
||||||
if bot.caldav:
|
|
||||||
# poll for new events
|
|
||||||
bot.poll_caldav()
|
|
||||||
# pdb.set_trace()
|
|
||||||
|
|
||||||
# print(events.objects)
|
|
||||||
bot.logger.debug('taking a breath')
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
bot.logger.info('quitting!')
|
bot.logger.info('quitting!')
|
||||||
|
|
||||||
|
|
||||||
|
|
68
masto_git_bridge/post.py
Normal file
68
masto_git_bridge/post.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
from typing import Optional, Literal
|
||||||
|
from datetime import datetime
|
||||||
|
from pydantic import BaseModel
|
||||||
|
import re
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from masto_git_bridge.repo import Commit
|
||||||
|
from masto_git_bridge.models import Account
|
||||||
|
|
||||||
|
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
|
||||||
|
visibility: Literal['public', 'unlisted', 'private', 'direct']
|
||||||
|
in_reply_to_id: Optional[int] = None
|
||||||
|
in_reply_to_account_id: Optional[int] = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
extra='ignore'
|
||||||
|
|
||||||
|
class Post(BaseModel):
|
||||||
|
#timestamp: Optional[datetime] = None
|
||||||
|
text:str
|
||||||
|
status:Optional[Status] = None
|
||||||
|
commit:Optional[Commit] = None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_commit(cls, commit:Commit) -> 'Post':
|
||||||
|
text = '\n'.join([commit.subject, commit.body])
|
||||||
|
return Post(text=text, commit=commit)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_status(cls, status:Status) -> 'Post':
|
||||||
|
# split paragraphs using bs4
|
||||||
|
soup = BeautifulSoup(status.content, 'lxml')
|
||||||
|
# replace with double line breaks
|
||||||
|
pars = [p.text for p in soup.find_all('p')]
|
||||||
|
text = '\n\n'.join(pars)
|
||||||
|
return Post(text=text, status=status)
|
||||||
|
|
||||||
|
def format_masto(self) -> str:
|
||||||
|
"""
|
||||||
|
Format a post to go from git -> masto.
|
||||||
|
|
||||||
|
Needs to have a :attr:`.commit` attribute!
|
||||||
|
|
||||||
|
Does not split the body text into multiple toots.
|
||||||
|
That should be handled in the posting action
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
git-social: https://{repo_url}/commits/{hash}
|
||||||
|
{subject line}
|
||||||
|
{body}
|
||||||
|
"""
|
||||||
|
return f"xpost from git-social: {self.commit.url}\n---\n{self.text}"""
|
||||||
|
|
||||||
|
def format_commit(self) -> str:
|
||||||
|
"""
|
||||||
|
Add a link back to original masto post split by double lines
|
||||||
|
"""
|
||||||
|
return f"xpost from mastodon: {self.status.url}\n\n{self.text}"
|
560
poetry.lock
generated
560
poetry.lock
generated
|
@ -1,5 +1,3 @@
|
||||||
# This file is automatically @generated by Poetry and should not be changed by hand.
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "beautifulsoup4"
|
name = "beautifulsoup4"
|
||||||
version = "4.11.1"
|
version = "4.11.1"
|
||||||
|
@ -7,10 +5,6 @@ description = "Screen-scraping library"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6.0"
|
python-versions = ">=3.6.0"
|
||||||
files = [
|
|
||||||
{file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"},
|
|
||||||
{file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
soupsieve = ">1.2"
|
soupsieve = ">1.2"
|
||||||
|
@ -26,38 +20,10 @@ description = "Pure-Python implementation of the blurhash algorithm."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
|
||||||
{file = "blurhash-1.1.4-py2.py3-none-any.whl", hash = "sha256:7611c1bc41383d2349b6129208587b5d61e8792ce953893cb49c38beeb400d1d"},
|
|
||||||
{file = "blurhash-1.1.4.tar.gz", hash = "sha256:da56b163e5a816e4ad07172f5639287698e09d7f3dc38d18d9726d9c1dbc4cee"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["Pillow", "numpy", "pytest"]
|
test = ["Pillow", "numpy", "pytest"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "caldav"
|
|
||||||
version = "1.2.1"
|
|
||||||
description = "CalDAV (RFC4791) client library"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
files = [
|
|
||||||
{file = "caldav-1.2.1-py3-none-any.whl", hash = "sha256:6860abfab926e7a7290e94e2e179dd9ddb06ea6bc4eb60304b49ef4b14b85c6f"},
|
|
||||||
{file = "caldav-1.2.1.tar.gz", hash = "sha256:934d7169d6e51b6e6ed4beef579b3d4008e2e6edac326a70225a32400ead86c9"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
icalendar = "*"
|
|
||||||
lxml = "*"
|
|
||||||
pytz = "*"
|
|
||||||
recurring-ical-events = ">=2.0.0"
|
|
||||||
requests = "*"
|
|
||||||
tzlocal = "*"
|
|
||||||
vobject = "*"
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
test = ["coverage", "icalendar", "pytest", "pytest-coverage", "pytz", "radicale", "tzlocal", "xandikos"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2022.9.24"
|
version = "2022.9.24"
|
||||||
|
@ -65,10 +31,6 @@ description = "Python package for providing Mozilla's CA Bundle."
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
files = [
|
|
||||||
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
|
|
||||||
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "charset-normalizer"
|
name = "charset-normalizer"
|
||||||
|
@ -77,10 +39,6 @@ description = "The Real First Universal Charset Detector. Open, modern and activ
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6.0"
|
python-versions = ">=3.6.0"
|
||||||
files = [
|
|
||||||
{file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"},
|
|
||||||
{file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
unicode-backport = ["unicodedata2"]
|
unicode-backport = ["unicodedata2"]
|
||||||
|
@ -92,10 +50,6 @@ description = "Python parser for the CommonMark Markdown spec"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
|
||||||
{file = "commonmark-0.9.1-py2.py3-none-any.whl", hash = "sha256:da2f38c92590f83de410ba1a3cbceafbc74fee9def35f9251ba9a971d6d66fd9"},
|
|
||||||
{file = "commonmark-0.9.1.tar.gz", hash = "sha256:452f9dc859be7f06631ddcb328b6919c67984aca654e5fefb3914d54691aed60"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
|
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
|
||||||
|
@ -107,10 +61,6 @@ description = "Decorators for Humans"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
files = [
|
|
||||||
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
|
|
||||||
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dnspython"
|
name = "dnspython"
|
||||||
|
@ -119,10 +69,6 @@ description = "DNS toolkit"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6,<4.0"
|
python-versions = ">=3.6,<4.0"
|
||||||
files = [
|
|
||||||
{file = "dnspython-2.2.1-py3-none-any.whl", hash = "sha256:a851e51367fb93e9e1361732c1d60dab63eff98712e503ea7d92e6eccb109b4f"},
|
|
||||||
{file = "dnspython-2.2.1.tar.gz", hash = "sha256:0f7569a4a6ff151958b64304071d370daa3243d15941a7beedf0c9fe5105603e"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"]
|
curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"]
|
||||||
|
@ -139,31 +85,11 @@ description = "A robust email address syntax and deliverability validation libra
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||||
files = [
|
|
||||||
{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"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
dnspython = ">=1.15.0"
|
dnspython = ">=1.15.0"
|
||||||
idna = ">=2.0.0"
|
idna = ">=2.0.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "icalendar"
|
|
||||||
version = "5.0.7"
|
|
||||||
description = "iCalendar parser/generator"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "icalendar-5.0.7-py3-none-any.whl", hash = "sha256:18ad51f9d1741d33795ddaf5c886c59f6575f287d30e5a145c2d42ef72910c7f"},
|
|
||||||
{file = "icalendar-5.0.7.tar.gz", hash = "sha256:e306014a64dc4dcf638da0acb2487ee4ada57b871b03a62ed7b513dfc135655c"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
python-dateutil = "*"
|
|
||||||
pytz = "*"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "3.4"
|
version = "3.4"
|
||||||
|
@ -171,10 +97,6 @@ description = "Internationalized Domain Names in Applications (IDNA)"
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.5"
|
python-versions = ">=3.5"
|
||||||
files = [
|
|
||||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
|
||||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lxml"
|
name = "lxml"
|
||||||
|
@ -183,7 +105,214 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li
|
||||||
category = "main"
|
category = "main"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
|
||||||
files = [
|
|
||||||
|
[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 = "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 = "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)"]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
lock-version = "1.1"
|
||||||
|
python-versions = "^3.9"
|
||||||
|
content-hash = "c85c922dc9bcac039fd7b4b5f12d310934ae4faa2bd23c426c2a3beb62e86721"
|
||||||
|
|
||||||
|
[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-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_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-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"},
|
||||||
|
@ -255,47 +384,11 @@ files = [
|
||||||
{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-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"},
|
{file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"},
|
||||||
]
|
]
|
||||||
|
mastodon-py = [
|
||||||
[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 = "*"
|
|
||||||
files = [
|
|
||||||
{file = "Mastodon.py-1.5.2-py2.py3-none-any.whl", hash = "sha256:49afbf9f4347f355bee5638b71a5231b0e1164a58d253ccd9b4345d999c43369"},
|
{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"},
|
{file = "Mastodon.py-1.5.2.tar.gz", hash = "sha256:c98fd97b7450cd02262669b80be20f53657b5540c4888a47231df11856910918"},
|
||||||
]
|
]
|
||||||
|
pydantic = [
|
||||||
[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"
|
|
||||||
files = [
|
|
||||||
{file = "pydantic-1.10.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb6ad4489af1bac6955d38ebcb95079a836af31e4c4f74aba1ca05bb9f6027bd"},
|
{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-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_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:352aedb1d71b8b0736c6d56ad2bd34c6982720644b0624462059ab29bd6e5912"},
|
||||||
|
@ -333,258 +426,47 @@ files = [
|
||||||
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
|
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
|
||||||
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
|
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
|
||||||
]
|
]
|
||||||
|
pygments = [
|
||||||
[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"
|
|
||||||
files = [
|
|
||||||
{file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
|
{file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
|
||||||
{file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
|
{file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
|
||||||
]
|
]
|
||||||
|
python-dateutil = [
|
||||||
[package.extras]
|
|
||||||
plugins = ["importlib-metadata"]
|
|
||||||
|
|
||||||
[[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"
|
|
||||||
files = [
|
|
||||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
{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"},
|
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
|
||||||
]
|
]
|
||||||
|
python-dotenv = [
|
||||||
[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"
|
|
||||||
files = [
|
|
||||||
{file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
|
{file = "python-dotenv-0.21.0.tar.gz", hash = "sha256:b77d08274639e3d34145dfa6c7008e66df0f04b7be7a75fd0d5292c191d79045"},
|
||||||
{file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
|
{file = "python_dotenv-0.21.0-py3-none-any.whl", hash = "sha256:1684eb44636dd462b66c3ee016599815514527ad99965de77f43e0944634a7e5"},
|
||||||
]
|
]
|
||||||
|
python-magic = [
|
||||||
[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.*"
|
|
||||||
files = [
|
|
||||||
{file = "python-magic-0.4.27.tar.gz", hash = "sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b"},
|
{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"},
|
{file = "python_magic-0.4.27-py2.py3-none-any.whl", hash = "sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3"},
|
||||||
]
|
]
|
||||||
|
pytz = [
|
||||||
[[package]]
|
|
||||||
name = "pytz"
|
|
||||||
version = "2022.6"
|
|
||||||
description = "World timezone definitions, modern and historical"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
files = [
|
|
||||||
{file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"},
|
{file = "pytz-2022.6-py2.py3-none-any.whl", hash = "sha256:222439474e9c98fced559f1709d89e6c9cbf8d79c794ff3eb9f8800064291427"},
|
||||||
{file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"},
|
{file = "pytz-2022.6.tar.gz", hash = "sha256:e89512406b793ca39f5971bc999cc538ce125c0e51c27941bef4568b460095e2"},
|
||||||
]
|
]
|
||||||
|
requests = [
|
||||||
[[package]]
|
|
||||||
name = "recurring-ical-events"
|
|
||||||
version = "2.0.2"
|
|
||||||
description = "A Python module which repeats ICalendar events by RRULE, RDATE and EXDATE."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
files = [
|
|
||||||
{file = "recurring_ical_events-2.0.2-py3-none-any.whl", hash = "sha256:a618140129e2ff00afa6b7afc1a154c2cb2177166621a519941b7058d6f6c339"},
|
|
||||||
{file = "recurring_ical_events-2.0.2.tar.gz", hash = "sha256:c4bc7d0d26fcda0ac3b773c9706fd1e271b7b0e0230ad0ee02ccab55e274ea11"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
icalendar = "*"
|
|
||||||
python-dateutil = ">=2.8.1"
|
|
||||||
pytz = "*"
|
|
||||||
x-wr-timezone = ">=0.0.5,<1.0.0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "requests"
|
|
||||||
version = "2.28.1"
|
|
||||||
description = "Python HTTP for Humans."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7, <4"
|
|
||||||
files = [
|
|
||||||
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
|
||||||
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
|
||||||
]
|
]
|
||||||
|
rich = [
|
||||||
[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"
|
|
||||||
files = [
|
|
||||||
{file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
|
{file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
|
||||||
{file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
|
{file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
|
||||||
]
|
]
|
||||||
|
six = [
|
||||||
[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.*"
|
|
||||||
files = [
|
|
||||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
]
|
]
|
||||||
|
soupsieve = [
|
||||||
[[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"
|
|
||||||
files = [
|
|
||||||
{file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
|
{file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
|
||||||
{file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
|
{file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
|
||||||
]
|
]
|
||||||
|
typing-extensions = [
|
||||||
[[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"
|
|
||||||
files = [
|
|
||||||
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
|
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
|
||||||
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
|
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},
|
||||||
]
|
]
|
||||||
|
urllib3 = [
|
||||||
[[package]]
|
|
||||||
name = "tzdata"
|
|
||||||
version = "2023.3"
|
|
||||||
description = "Provider of IANA time zone data"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=2"
|
|
||||||
files = [
|
|
||||||
{file = "tzdata-2023.3-py2.py3-none-any.whl", hash = "sha256:7e65763eef3120314099b6939b5546db7adce1e7d6f2e179e3df563c70511eda"},
|
|
||||||
{file = "tzdata-2023.3.tar.gz", hash = "sha256:11ef1e08e54acb0d4f95bdb1be05da659673de4acbd21bf9c69e94cc5e907a3a"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tzlocal"
|
|
||||||
version = "5.0.1"
|
|
||||||
description = "tzinfo object for the local timezone"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "tzlocal-5.0.1-py3-none-any.whl", hash = "sha256:f3596e180296aaf2dbd97d124fe76ae3a0e3d32b258447de7b939b3fd4be992f"},
|
|
||||||
{file = "tzlocal-5.0.1.tar.gz", hash = "sha256:46eb99ad4bdb71f3f72b7d24f4267753e240944ecfc16f25d2719ba89827a803"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
tzdata = {version = "*", markers = "platform_system == \"Windows\""}
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
devenv = ["black", "check-manifest", "flake8", "pyroma", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"]
|
|
||||||
|
|
||||||
[[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"
|
|
||||||
files = [
|
|
||||||
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
|
{file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"},
|
||||||
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
{file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[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 = "vobject"
|
|
||||||
version = "0.9.6.1"
|
|
||||||
description = "A full-featured Python package for parsing and creating iCalendar and vCard files"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
files = [
|
|
||||||
{file = "vobject-0.9.6.1.tar.gz", hash = "sha256:96512aec74b90abb71f6b53898dd7fe47300cc940104c4f79148f0671f790101"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
python-dateutil = ">=2.4.0"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "x-wr-timezone"
|
|
||||||
version = "0.0.5"
|
|
||||||
description = "A Python module and program to convert calendars using X-WR-TIMEZONE to standard ones."
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = "*"
|
|
||||||
files = [
|
|
||||||
{file = "x_wr_timezone-0.0.5-py3-none-any.whl", hash = "sha256:e438b27b96635f5f712a4fb5dda4c82597a53a412fe834c9fe8409fddb3fc2b1"},
|
|
||||||
{file = "x_wr_timezone-0.0.5.tar.gz", hash = "sha256:c05cb34b9b58a4607a788db086dcae5766728e4b94e0672870dc5593a6e13fe6"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
icalendar = "*"
|
|
||||||
pytz = "*"
|
|
||||||
|
|
||||||
[metadata]
|
|
||||||
lock-version = "2.0"
|
|
||||||
python-versions = "^3.9"
|
|
||||||
content-hash = "c5dd090c8750013b4ea3a1c79ede4226cc28f5ea999544d29f6021344754e459"
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "masto-bridges"
|
name = "masto-gitsocial-bridge"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Crosspost between mastodon and git.social and also CalDAV for some reason"
|
description = "Crosspost between mastodon and git.social"
|
||||||
authors = ["sneakers-the-rat <JLSaunders987@gmail.com>"]
|
authors = ["sneakers-the-rat <JLSaunders987@gmail.com>"]
|
||||||
license = "AGPL-3.0"
|
license = "AGPL-3.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
packages = [{include = "masto_bridges"}]
|
packages = [{include = "masto_git_bridge"}]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.9"
|
python = "^3.9"
|
||||||
|
@ -14,12 +14,10 @@ pydantic = {extras = ["dotenv", "email"], version = "^1.10.2"}
|
||||||
rich = "^12.6.0"
|
rich = "^12.6.0"
|
||||||
beautifulsoup4 = "^4.11.1"
|
beautifulsoup4 = "^4.11.1"
|
||||||
lxml = "^4.9.1"
|
lxml = "^4.9.1"
|
||||||
caldav = "^1.2.1"
|
|
||||||
icalendar = "^5.0.7"
|
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
post_last_commit = 'masto_bridges.main:post_last_commit'
|
post_last_commit = 'masto_git_bridge.main:post_last_commit'
|
||||||
masto_bridgebot = 'masto_bridges.main:masto_bridgebot'
|
masto_gitbot = 'masto_git_bridge.main:masto_gitbot'
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
|
|
Loading…
Reference in a new issue