From 284f6b2bd081cac77c21065aeb8b75b9434385eb Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 30 Apr 2022 07:05:18 -0700 Subject: [PATCH] start of project --- LICENSE | 22 +++++++++ README.md | 10 ++++ feedbot.py | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++++ models.py | 29 +++++++++++ setup.py | 23 +++++++++ 5 files changed, 226 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 feedbot.py create mode 100644 models.py create mode 100644 setup.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..295339f --- /dev/null +++ b/LICENSE @@ -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. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2dc8a5 --- /dev/null +++ b/README.md @@ -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). + diff --git a/feedbot.py b/feedbot.py new file mode 100644 index 0000000..c01a907 --- /dev/null +++ b/feedbot.py @@ -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 {} + diff --git a/models.py b/models.py new file mode 100644 index 0000000..870dcc5 --- /dev/null +++ b/models.py @@ -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]) + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..de43e3f --- /dev/null +++ b/setup.py @@ -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', + ] + }, +) +