haha uh posting from calendar

renaming masto_bridges
This commit is contained in:
sneakers-the-rat 2023-06-14 00:51:06 -07:00
parent 11a50ccdd9
commit 671619526e
16 changed files with 819 additions and 433 deletions

View file

@ -1,8 +1,36 @@
# masto-gitsocial-bridge
# masto-bridges (formerly masto-gitsocial-bridge)
## 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)
## From git to masto
### From git to masto
Post on git-social...
@ -12,7 +40,7 @@ Bridge to mastodon
![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...
@ -69,11 +97,25 @@ from environment variables, for example, make an `.env` file in the cloned
repository directory like
```
MASTOGIT_MASTO_URL="https://social.coop"
MASTOGIT_MASTO_TOKEN="<mastodon bot access token>"
MASTOGIT_GIT_REPO="/path/to/your/git-social/tweets"
MASTOGIT_GIT_REMOTE_URL="https://git.jon-e.net/jonny/tweets"
MASTOGIT_LOGDIR="/wherever/you/want/to/put/logs"
MASTOBRIDGE_LOGDIR="/wherever/you/want/to/put/logs"
MASTOBRIDGE_MASTO_URL="https://social.coop"
MASTOBRIDGE_MASTO_TOKEN="<mastodon bot access token>"
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
@ -107,9 +149,9 @@ git-social (only if they are "public" or "unlisted").
poetry run masto_gitbot
# or
# >>> poetry shell
# >>> masto_gitbot
# >>> masto_bridgebot
# or however else you run python entrypoint scripts
# hell you could do python -m masto_git_bridge.main:masto_gitbot
# hell you could do python -m masto_bridges.main:masto_gitbot
# i think?
```

BIN
img/masto-caldav.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

164
masto_bridges/bot.py Normal file
View file

@ -0,0 +1,164 @@
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

99
masto_bridges/caldav.py Normal file
View file

@ -0,0 +1,99 @@
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
)

24
masto_bridges/config.py Normal file
View file

@ -0,0 +1,24 @@
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_"

View file

@ -1,9 +1,10 @@
import pdb
from typing import Optional
from masto_git_bridge.config import Config
from masto_git_bridge.repo import Repo
from masto_git_bridge.bot import Bot
from masto_git_bridge.post import Post
from masto_git_bridge.logger import init_logger
from masto_bridges.config import Config
from masto_bridges.repo import Repo
from masto_bridges.bot import Bot
from masto_bridges.post import Post
from masto_bridges.logger import init_logger
from time import sleep
def post_last_commit(config:Optional[Config]=None):
@ -27,7 +28,7 @@ def post_last_commit(config:Optional[Config]=None):
bot = Bot(config=config)
bot.post(post.format_masto())
def masto_gitbot(config:Optional[Config]=None):
def masto_bridgebot(config:Optional[Config]=None):
if config is None:
config = Config()
@ -35,8 +36,17 @@ def masto_gitbot(config:Optional[Config]=None):
try:
bot.init_stream()
while True:
sleep(60*60)
bot.logger.info('taking a breath')
sleep(1)
if bot.caldav:
# poll for new events
bot.poll_caldav()
# pdb.set_trace()
# print(events.objects)
bot.logger.debug('taking a breath')
except KeyboardInterrupt:
bot.logger.info('quitting!')

116
masto_bridges/post.py Normal file
View file

@ -0,0 +1,116 @@
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
)

View file

@ -1,106 +0,0 @@
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

View file

@ -1,15 +0,0 @@
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_"

View file

@ -1,68 +0,0 @@
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
View file

@ -1,3 +1,5 @@
# This file is automatically @generated by Poetry and should not be changed by hand.
[[package]]
name = "beautifulsoup4"
version = "4.11.1"
@ -5,6 +7,10 @@ description = "Screen-scraping library"
category = "main"
optional = false
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]
soupsieve = ">1.2"
@ -20,10 +26,38 @@ description = "Pure-Python implementation of the blurhash algorithm."
category = "main"
optional = false
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]
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]]
name = "certifi"
version = "2022.9.24"
@ -31,6 +65,10 @@ description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
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]]
name = "charset-normalizer"
@ -39,6 +77,10 @@ description = "The Real First Universal Charset Detector. Open, modern and activ
category = "main"
optional = false
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]
unicode-backport = ["unicodedata2"]
@ -50,6 +92,10 @@ description = "Python parser for the CommonMark Markdown spec"
category = "main"
optional = false
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]
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
@ -61,6 +107,10 @@ description = "Decorators for Humans"
category = "main"
optional = false
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]]
name = "dnspython"
@ -69,6 +119,10 @@ description = "DNS toolkit"
category = "main"
optional = false
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]
curio = ["curio (>=1.2,<2.0)", "sniffio (>=1.1,<2.0)"]
@ -85,11 +139,31 @@ description = "A robust email address syntax and deliverability validation libra
category = "main"
optional = false
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]
dnspython = ">=1.15.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]]
name = "idna"
version = "3.4"
@ -97,6 +171,10 @@ description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
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]]
name = "lxml"
@ -105,214 +183,7 @@ description = "Powerful and Pythonic XML processing library combining libxml2/li
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 = "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 = [
files = [
{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"},
@ -384,11 +255,47 @@ lxml = [
{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 = [
[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.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_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"},
@ -426,47 +333,258 @@ pydantic = [
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
{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.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-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-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-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.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.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.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.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.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.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.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"

View file

@ -1,11 +1,11 @@
[tool.poetry]
name = "masto-gitsocial-bridge"
name = "masto-bridges"
version = "0.1.0"
description = "Crosspost between mastodon and git.social"
description = "Crosspost between mastodon and git.social and also CalDAV for some reason"
authors = ["sneakers-the-rat <JLSaunders987@gmail.com>"]
license = "AGPL-3.0"
readme = "README.md"
packages = [{include = "masto_git_bridge"}]
packages = [{include = "masto_bridges"}]
[tool.poetry.dependencies]
python = "^3.9"
@ -14,10 +14,12 @@ pydantic = {extras = ["dotenv", "email"], version = "^1.10.2"}
rich = "^12.6.0"
beautifulsoup4 = "^4.11.1"
lxml = "^4.9.1"
caldav = "^1.2.1"
icalendar = "^5.0.7"
[tool.poetry.scripts]
post_last_commit = 'masto_git_bridge.main:post_last_commit'
masto_gitbot = 'masto_git_bridge.main:masto_gitbot'
post_last_commit = 'masto_bridges.main:post_last_commit'
masto_bridgebot = 'masto_bridges.main:masto_bridgebot'
[build-system]
requires = ["poetry-core"]