public version & README.md

This commit is contained in:
sneakers-the-rat 2022-11-07 22:13:05 -08:00
parent f6b401b527
commit 11a50ccdd9
14 changed files with 574 additions and 12 deletions

117
README.md
View file

@ -1,3 +1,118 @@
# masto-gitsocial-bridge
bridge between mastodon and git social
bridge between mastodon and bcrypt's [git-social](https://github.com/diracdeltas/tweets)
## From git to masto
Post on git-social...
![Post on git social: This is what we call infrastructure shitposting](img/git-to-masto_0.png)
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
Post on mastodon...
![Post on mastodon: This is the future of decentralized communication](img/masto-to-git_0.png)
Bridge to git-social
![Post on git social: same post, but with link to tweet](img/masto-to-git_1.png)
# Features
Everything in this package is a bug and not a feature.
# Setup
## Installation
I'm not going to uh, make this good or put it on pypi or anything.
So you should clone this and install it with poetry, (otherwise
you have to modify the below `post-commit` action to activate the
venv where it is installed correctly)
```shell
git clone https://git.jon-e.net/jonny/masto-gitsocial-bridge
cd masto-gitsocial-bridge
poetry install
```
## Make masto token
~ check with your instance's policies before doing something bad like this ~
From your masto instance's homepage...
- Preferences (in hamburger menu top right if page is narrow)
- Development tab
- New Application
The bot needs permissions (I'm honestly not sure you need to give all of `read`, but
`Mastodon.py`'s `me()` method seems to need a lot of them. idk.)
- `read`
- `write:lists` - the bot only streams your posts by making a list with just you on it
- `write:statuses` - to xpost, dummy!
Then copy the resulting access token for use in configuration...
## Config
See `masto_git_bridge.config.Config` for the required configuration values.
The config object uses Pydantic to load either from an `.env` file or
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"
```
This is assuming you don't need any sort of authentication/local password
on your local git repository in order to commit or push to it.
## `post-commit` action
In your git-social repository, make a `post-commit` action (`.git/hooks/post-commit`)
that looks something like this (see the [sample](post-commit.sample))
```bash
#!/bin/bash
# Assuming we have installed the package using poetry from a git repository
# lmao I did not say we handled virtual environments well in this package
cd <path/to/masto-gitsocial-bridge>
poetry run post_last_commit
# otherwise activate whatever venv you have installed the package in and
# call masto_git_bridge.main:post_last_commit, which is
# installed as an entrypoint script by poetry
```
# Usage
The post-commit action should run anytime you commit a post to git-social, but to post
from mastodon you'll have to run the bot, which listens for your posts and reposts them to
git-social (only if they are "public" or "unlisted").
```bash
poetry run masto_gitbot
# or
# >>> poetry shell
# >>> masto_gitbot
# or however else you run python entrypoint scripts
# hell you could do python -m masto_git_bridge.main:masto_gitbot
# i think?
```
# Warnings & Gotchas
If you ever enable this, you should promptly disable it

BIN
img/git-to-masto_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

BIN
img/git-to-masto_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
img/masto-to-git_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

BIN
img/masto-to-git_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

View file

@ -1,40 +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
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):
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

@ -0,0 +1,56 @@
import logging
from rich.logging import RichHandler
from pathlib import Path
import sys
import typing
from typing import Optional, Union, Tuple, List, Dict, Literal
from logging.handlers import RotatingFileHandler
def init_logger(
name:Optional[str]=None,
basedir:Optional[Path]=None,
loglevel:str='DEBUG',
loglevel_disk:Optional[str]='DEBUG'
):
if name is None:
name = 'wiki_postbot'
else:
if not name.startswith('wiki_postbot'):
name = '.'.join(['wiki_postbot', name])
if loglevel_disk is None:
loglevel_disk = loglevel
logger = logging.getLogger(name)
logger.setLevel(loglevel)
if basedir is not None:
logger.addHandler(_file_handler(basedir, name, loglevel_disk))
logger.addHandler(_rich_handler())
return logger
def _file_handler(basedir:Path, name:str, loglevel:str="DEBUG") -> RotatingFileHandler:
filename = Path(basedir) / '.'.join([name, 'log'])
basedir.mkdir(parents=True, exist_ok=True)
file_handler = RotatingFileHandler(
str(filename),
mode='a',
maxBytes=2 ** 24,
backupCount=5
)
file_formatter = logging.Formatter("[%(asctime)s] %(levelname)s [%(name)s]: %(message)s")
file_handler.setLevel(loglevel)
file_handler.setFormatter(file_formatter)
return file_handler
def _rich_handler() -> RichHandler:
rich_handler = RichHandler(rich_tracebacks=True, markup=True)
rich_formatter = logging.Formatter(
"[bold green]\[%(name)s][/bold green] %(message)s",
datefmt='[%y-%m-%dT%H:%M:%S]'
)
rich_handler.setFormatter(rich_formatter)
return rich_handler

View file

@ -1,10 +1,42 @@
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 time import sleep
def post_last_commit(config:Optional[Config]=None):
"""
Should be triggered as a commit hook because it doesn't validate
the last commit hasn't already been posted.
"""
if config is None:
config = Config()
logger = init_logger('post-git', basedir=config.LOGDIR)
repo = Repo(config.GIT_REPO)
last_commit = repo.last_commit
post = Post.from_commit(last_commit)
if post.text.startswith('xpost'):
logger.info('Not xposting an xpost')
return
bot = Bot(config=config)
bot.post(post.format_masto())
def masto_gitbot(config:Optional[Config]=None):
if config is None:
config = Config()
bot = Bot(config=config)
try:
bot.init_stream()
while True:
sleep(60*60)
bot.logger.info('taking a breath')
except KeyboardInterrupt:
bot.logger.info('quitting!')

View file

@ -1,5 +1,13 @@
from pydantic import BaseModel
class List(BaseModel):
"""A mastodon list!"""
id: str
title: str
class Config:
extra = 'ignore'
class Account(BaseModel):
"""Not transcribing full model now, just using to check"""
acct: str

View file

@ -1,8 +1,68 @@
from typing import Optional
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: datetime
hash: Optional[str]
#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}"

View file

@ -1,9 +1,10 @@
import pdb
from pathlib import Path
from subprocess import run
import json
from urllib.parse import urljoin
from pydantic import BaseModel, EmailStr
from pydantic import BaseModel, EmailStr, AnyHttpUrl
log_format = '{%n "commit": "%H",%n "abbreviated_commit": "%h",%n "tree": "%T",%n "abbreviated_tree": "%t",%n "parent": "%P",%n "abbreviated_parent": "%p",%n "refs": "%D",%n "encoding": "%e",%n "subject": "%s",%n "sanitized_subject_line": "%f",%n "body": "%b",%n "commit_notes": "%N",%n "verification_flag": "%G?",%n "signer": "%GS",%n "signer_key": "%GK",%n "author": {%n "name": "%aN",%n "email": "%aE",%n "date": "%aD"%n },%n "commiter": {%n "name": "%cN",%n "email": "%cE",%n "date": "%cD"%n }%n}'
"""Thanks https://gist.github.com/varemenos/e95c2e098e657c7688fd"""
@ -20,9 +21,11 @@ class Commit(BaseModel):
body:str
author: Author
commiter: Author
origin_url: AnyHttpUrl
def make_url(self, remote_url:str):
return urljoin(remote_url, f'commit/{self.abbreviated_commit}')
@property
def url(self) -> str:
return urljoin(self.origin_url + '/', f'commit/{self.abbreviated_commit}')
class Config:
extra='ignore'
@ -31,6 +34,33 @@ class Repo:
def __init__(self, path:Path):
self.path = Path(path)
def post(self, post:str) -> bool:
# make paragraphs by splitting \n\n
paras = []
for para in post.split('\n\n'):
paras.extend(('-m', para))
path = self.path
command = [
'git',
'-C', str(path),
'commit',
*paras,
'--allow-empty'
]
output = run(command, capture_output=True)
if output.returncode != 0:
return False
output = run([
'git',
'-C', str(path),
'push'
], capture_output=True)
return True
@property
def last_commit(self) -> Commit:
path = self.path
@ -44,4 +74,17 @@ class Repo:
capture_output=True
)
parse = json.loads(output.stdout, strict=False)
return Commit(**parse)
return Commit(origin_url=self.origin_url, **parse)
@property
def origin_url(self) -> str:
path = self.path
output = run([
"git",
'-C', str(path),
'remote',
'get-url',
'origin'
], capture_output=True)
url = output.stdout.decode('utf-8').strip().rstrip('.git')
return url

168
poetry.lock generated
View file

@ -1,3 +1,18 @@
[[package]]
name = "beautifulsoup4"
version = "4.11.1"
description = "Screen-scraping library"
category = "main"
optional = false
python-versions = ">=3.6.0"
[package.dependencies]
soupsieve = ">1.2"
[package.extras]
html5lib = ["html5lib"]
lxml = ["lxml"]
[[package]]
name = "blurhash"
version = "1.1.4"
@ -28,6 +43,17 @@ python-versions = ">=3.6.0"
[package.extras]
unicode-backport = ["unicodedata2"]
[[package]]
name = "commonmark"
version = "0.9.1"
description = "Python parser for the CommonMark Markdown spec"
category = "main"
optional = false
python-versions = "*"
[package.extras]
test = ["flake8 (==3.7.8)", "hypothesis (==3.55.3)"]
[[package]]
name = "decorator"
version = "5.1.1"
@ -72,6 +98,20 @@ category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "lxml"
version = "4.9.1"
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
[package.extras]
cssselect = ["cssselect (>=0.7)"]
html5 = ["html5lib"]
htmlsoup = ["BeautifulSoup4"]
source = ["Cython (>=0.29.7)"]
[[package]]
name = "mastodon-py"
version = "1.5.2"
@ -111,6 +151,17 @@ typing-extensions = ">=4.1.0"
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"
@ -167,6 +218,21 @@ urllib3 = ">=1.21.1,<1.27"
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"
@ -175,6 +241,14 @@ 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"
@ -199,9 +273,13 @@ socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "61c87bda2ae45e82c6ed4dca57ad766a01078f32838844c10832c9e5f5790ab3"
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"},
@ -214,6 +292,10 @@ 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"},
@ -230,6 +312,78 @@ idna = [
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
lxml = [
{file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"},
{file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"},
{file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"},
{file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"},
{file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"},
{file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"},
{file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"},
{file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"},
{file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"},
{file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"},
{file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"},
{file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"},
{file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"},
{file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"},
{file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"},
{file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"},
{file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"},
{file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"},
{file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"},
{file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"},
{file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"},
{file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"},
{file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"},
{file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"},
{file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"},
{file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"},
{file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"},
{file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"},
{file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"},
{file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"},
{file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"},
{file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"},
{file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"},
{file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"},
{file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"},
{file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"},
{file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"},
{file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"},
{file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"},
{file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"},
{file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"},
{file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"},
{file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"},
{file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"},
{file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"},
{file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"},
{file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"},
{file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"},
{file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"},
{file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"},
{file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"},
{file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"},
{file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"},
{file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"},
{file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"},
{file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"},
{file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"},
{file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"},
{file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"},
{file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"},
{file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"},
{file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"},
{file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"},
{file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"},
{file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"},
{file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"},
{file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"},
{file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"},
{file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"},
{file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"},
]
mastodon-py = [
{file = "Mastodon.py-1.5.2-py2.py3-none-any.whl", hash = "sha256:49afbf9f4347f355bee5638b71a5231b0e1164a58d253ccd9b4345d999c43369"},
{file = "Mastodon.py-1.5.2.tar.gz", hash = "sha256:c98fd97b7450cd02262669b80be20f53657b5540c4888a47231df11856910918"},
@ -272,6 +426,10 @@ pydantic = [
{file = "pydantic-1.10.2-py3-none-any.whl", hash = "sha256:1b6ee725bd6e83ec78b1aa32c5b1fa67a3a65badddde3976bca5fe4568f27709"},
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
]
pygments = [
{file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"},
{file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
@ -292,10 +450,18 @@ requests = [
{file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"},
{file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"},
]
rich = [
{file = "rich-12.6.0-py3-none-any.whl", hash = "sha256:a4eb26484f2c82589bd9a17c73d32a010b1e29d89f1604cd9bf3a2097b81bb5e"},
{file = "rich-12.6.0.tar.gz", hash = "sha256:ba3a3775974105c221d31141f2c116f4fd65c5ceb0698657a11e9f295ec93fd0"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
soupsieve = [
{file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"},
{file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"},
]
typing-extensions = [
{file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"},
{file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"},

10
post-commit.sample Normal file
View file

@ -0,0 +1,10 @@
#!/bin/bash
# Assuming we have installed the package using poetry from a git repository
# lmao I did not say we handled virtual environments well in this package
cd <path/to/masto-gitsocial-bridge>
poetry run post_last_commit
# otherwise activate whatever venv you have installed the package in and
# call masto_git_bridge.main:post_last_commit, which is
# installed as an entrypoint script by poetry

View file

@ -11,7 +11,13 @@ packages = [{include = "masto_git_bridge"}]
python = "^3.9"
"Mastodon.py" = "^1.5.2"
pydantic = {extras = ["dotenv", "email"], version = "^1.10.2"}
rich = "^12.6.0"
beautifulsoup4 = "^4.11.1"
lxml = "^4.9.1"
[tool.poetry.scripts]
post_last_commit = 'masto_git_bridge.main:post_last_commit'
masto_gitbot = 'masto_git_bridge.main:masto_gitbot'
[build-system]
requires = ["poetry-core"]