start of project
This commit is contained in:
commit
284f6b2bd0
5 changed files with 226 additions and 0 deletions
22
LICENSE
Normal file
22
LICENSE
Normal file
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Morgan McMillian
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# feedbot
|
||||
|
||||
My quick and dirty cross posting script to take posts from Mastodon and cross post to pnut.io.
|
||||
|
||||
## Contributing
|
||||
|
||||
Report bugs, send patches to [~thrrgilag/public-inbox@lists.sr.ht](https://lists.sr.ht/~thrrgilag/public-inbox).
|
||||
|
||||
Discussion also in [thrrgilag's dev chat](https://thrrgilag.net/chat/dev).
|
||||
|
142
feedbot.py
Normal file
142
feedbot.py
Normal file
|
@ -0,0 +1,142 @@
|
|||
import feedparser
|
||||
import requests
|
||||
import logging
|
||||
import pnutpy
|
||||
import click
|
||||
import re
|
||||
import os
|
||||
|
||||
import models as zdb
|
||||
|
||||
from PIL import ImageFile
|
||||
|
||||
SNAP_USER_DATA = os.environ.get('SNAP_USER_DATA')
|
||||
|
||||
@click.group(invoke_without_command=True)
|
||||
@click.pass_context
|
||||
@click.option('--db')
|
||||
@click.option('--prime', is_flag=True)
|
||||
@click.option('--debug', is_flag=True)
|
||||
@click.version_option(version='0.1.0')
|
||||
def main(ctx, db, prime, debug):
|
||||
|
||||
if debug:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
if SNAP_USER_DATA is None and db is not None:
|
||||
db_file = db
|
||||
else:
|
||||
db_file = SNAP_USER_DATA + '/feeds.db'
|
||||
|
||||
zdb.db.init(db_file)
|
||||
zdb.create_tables()
|
||||
|
||||
if ctx.invoked_subcommand is None:
|
||||
for feed in zdb.Feeds.select():
|
||||
fetch(feed.url, feed.pnut_uid, feed.id, prime)
|
||||
|
||||
@main.command()
|
||||
@click.argument('username')
|
||||
@click.argument('token')
|
||||
def adduser(username, token):
|
||||
'''Add a pnut user'''
|
||||
pnutpy.api.add_authorization_token(token)
|
||||
pass
|
||||
try:
|
||||
pnut_user, meta = pnutpy.api.get_user('me')
|
||||
|
||||
user = zdb.User(pnut_uid=pnut_user.id, pnut_token=token, pnut_enabled=True)
|
||||
user.save()
|
||||
|
||||
except pnutpy.errors.PnutAuthAPIException:
|
||||
logging.error(f"pnut user token not valid")
|
||||
|
||||
@main.command()
|
||||
@click.argument('uid')
|
||||
@click.argument('url')
|
||||
def add(uid, url):
|
||||
'''Add a feed'''
|
||||
feed = zdb.Feeds(pnut_uid=uid, url=url)
|
||||
feed.save()
|
||||
|
||||
def fetch(url, pnut_uid, fid, prime):
|
||||
feed = feedparser.parse(url)
|
||||
try:
|
||||
user = zdb.User.get(pnut_uid=pnut_uid)
|
||||
|
||||
source = {
|
||||
'link': feed.feed.link,
|
||||
'username': feed.feed.title,
|
||||
'avatar': feed.feed.image.href
|
||||
}
|
||||
|
||||
for post in reversed(feed.entries):
|
||||
link = post.link
|
||||
|
||||
try:
|
||||
entry = zdb.Entries.get(feedid=fid, link=link)
|
||||
logging.debug(f"skipping {link}...")
|
||||
|
||||
except zdb.Entries.DoesNotExist:
|
||||
entry = zdb.Entries(feedid=fid, link=link)
|
||||
entry.save()
|
||||
if prime:
|
||||
logging.debug(f"saving {link}...")
|
||||
|
||||
else:
|
||||
logging.debug(f"posting {link}...")
|
||||
pnutpost(post, source, user.pnut_token)
|
||||
|
||||
except zdb.User.DoesNotExist:
|
||||
logging.error(f"user {pnut_uid} not found")
|
||||
|
||||
except Exception:
|
||||
logging.exception("bad stuff")
|
||||
|
||||
def pnutpost(entry, source, token):
|
||||
pnutpy.api.add_authorization_token(token)
|
||||
crosspost = {
|
||||
'type': "io.pnut.core.crosspost",
|
||||
'value': {
|
||||
'canonical_url': entry.link,
|
||||
#'source': {'url': source['link']},
|
||||
'user': {
|
||||
'username': source['username'],
|
||||
'avatar_image': source['avatar']
|
||||
}
|
||||
}
|
||||
}
|
||||
raw = [crosspost]
|
||||
for link in entry.links:
|
||||
if link.rel == "enclosure":
|
||||
if "image" in link.type:
|
||||
raw.append(embed_image(link))
|
||||
|
||||
try:
|
||||
rx = re.compile('<.*?>')
|
||||
text = re.sub(rx, '', entry.summary)
|
||||
p, meta = pnutpy.api.create_post(data={'text': text, 'raw': raw})
|
||||
|
||||
except Exception:
|
||||
logging.exception("bad stuff")
|
||||
|
||||
def embed_image(link):
|
||||
resume_header = {'Range': 'bytes=0-2000000'}
|
||||
r = requests.get(link.href, stream=True, headers=resume_header)
|
||||
|
||||
p = ImageFile.Parser()
|
||||
p.feed(r.content)
|
||||
if p.image:
|
||||
width, height = p.image.size
|
||||
embed = {
|
||||
'version': "1.0",
|
||||
'type': "photo",
|
||||
'width': width,
|
||||
'height': height,
|
||||
'url': link.href
|
||||
}
|
||||
return {'type': "io.pnut.core.oembed", 'value': embed}
|
||||
|
||||
else:
|
||||
return {}
|
||||
|
29
models.py
Normal file
29
models.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from peewee import *
|
||||
|
||||
db = SqliteDatabase(None)
|
||||
|
||||
class BaseModel(Model):
|
||||
class Meta:
|
||||
database = db
|
||||
|
||||
class Feeds(BaseModel):
|
||||
url = CharField()
|
||||
pnut_uid = CharField()
|
||||
|
||||
class Entries(BaseModel):
|
||||
feedid = IntegerField()
|
||||
link = CharField()
|
||||
|
||||
class User(BaseModel):
|
||||
pnut_uid = CharField(unique=True)
|
||||
pnut_token = CharField(null=True)
|
||||
pnut_enabled = BooleanField(default=False)
|
||||
|
||||
class System(BaseModel):
|
||||
key = CharField(unique=True)
|
||||
value = CharField()
|
||||
|
||||
def create_tables():
|
||||
with db:
|
||||
db.create_tables([Feeds, Entries, User, System])
|
||||
|
23
setup.py
Normal file
23
setup.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name='feedbot',
|
||||
version='0.1.0',
|
||||
py_modules=[
|
||||
'feedbot',
|
||||
],
|
||||
install_requires=[
|
||||
'requests',
|
||||
'pnutpy',
|
||||
'click',
|
||||
'feedparser',
|
||||
'peewee',
|
||||
'pillow',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'feedbot = feedbot:main',
|
||||
]
|
||||
},
|
||||
)
|
||||
|
Loading…
Reference in a new issue