Browse Source

Add CW hiding abilities and Add ability to parse RSS and cross post

bot_curator 20170628.2
KemoNine 4 years ago
parent
commit
d1b111fb2b
  1. 31
      README.md
  2. 1
      configs/example_reminder.ini
  3. 26
      configs/example_rss.ini
  4. 101
      toot_bot.py

31
README.md

@ -3,28 +3,47 @@ Welcome to the Mastodon Bot project page. This project aims to provide a Mastodo
This was designed with the admin in mind and functionality will grow over time.
# Features
- Ability to regularly post a toot + sub toots
- Ability to toot articles that are in an RSS feed
- Ability to hide toots behind CW text
# Compatibility
- Mastodon 1.4.x
# Requirements
- Python 3.x
- Mastodon.py
- feedparser
- Mastodon.py Python module
- feedparser Python Module
- argparse Python module (should be built in)
- configparser Python module (should be built in)
- sqlite3 Python module (should be built in)
# Installation
1. Install the dependencies 'pip3 install -r requirements.txt'
1. Run the bot: 'python .\toot_bot.py --config .\configs\reminder.ini [init|login|toot|rss]'
1. Install the dependencies: ```pip3 install -r requirements.txt```
1. Run the bot: ```python .\toot_bot.py --config .\configs\config.ini [init|login|toot|rss]```
# Tooting
## Reminder Toots
To send reminder toots take a look at 'configs/example_reminder.ini' and do the following
1. Run python .\toot_bot.py --config .\configs\reminder.ini init
1. Run python .\toot_bot.py --config .\configs\reminder.ini toot
1. Run python ```.\toot_bot.py --config .\configs\example_reminder.ini init```
1. Run python ```.\toot_bot.py --config .\configs\example_reminder.ini toot```
The above will initialize the configuration (including login, you'll be prompted for user/password) and then toot what has been setup in your config file.
You'll probably want to run the 2nd command on a schedule via cron for auto-tooting on a schedule
## RSS Toots
To cross post RSS articles take a look at 'configs/example_rss.ini' and do the following
1. Run python ```.\toot_bod.py --config .\configs\example_rss.ini init```
1. Run python ```.\toot_bot.py --config .\configs\example_rss.ini rss```
The above will initialize the configuration (including login, you'll be prompted for user/password) then toot the various articles from the RSS feed. *BE CAREFUL*, the cache will be empty to start and you might spam your instance if there are a large number of articles in the feed.
Note: the RSS cache will be initialized the first time you run the rss command
You'll probably want to run the 2nd command on a schedule via cron for cross posting on a schedule
# Contributing
Contributions to this project are welcome and appreciated. Please create a pull request with any changes you'd like to see included. The developers(s) will review the pull request and make any necessary comments/approvals after review.

1
configs/example_reminder.ini

@ -15,6 +15,7 @@ user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = This is a friendly reminder! awoooooooooo
main_toot = Reminder!
There is a blog with announcements at https://blog.instance.com
subtoot_1 = subtoot 1

26
configs/example_rss.ini

@ -0,0 +1,26 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = rss_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/rss_bot/rss_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/rss_bot/rss_bot_user_cred.secret
# This section is optional in RSS mode
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
cw_text = A new article is on the blog, check it out!
[rss]
# The main RSS feed
feed = http://blog.instance.com/rss
# The number of articles to fetch, must be between 1 and 150 (150 is a standard instance rate limit)
article_limit = 25
# File that holds the cache of RSS articles previously seen (sqlite3)
cache_file = /root/rss_bot/rss_cache.db

101
toot_bot.py

@ -1,8 +1,9 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse, sys, os, configparser, getpass
import argparse, sys, os, configparser, getpass, sqlite3
from mastodon import Mastodon
import feedparser
def init(api_base_url, app_name, client_cred_file, user_cred_file):
# Prompt user to find out if they want to continue if the client_cred_file exists already -- shouldn't happen twice per docs
@ -13,6 +14,7 @@ def init(api_base_url, app_name, client_cred_file, user_cred_file):
Mastodon.create_app(
app_name,
api_base_url = api_base_url,
scopes=['read', 'write'],
to_file = client_cred_file
)
@ -24,27 +26,68 @@ def login(api_base_url, client_cred_file, user_cred_file):
email = input('what is the e-mail that was used to setup the bot account on ' + api_base_url + '?')
password = getpass.getpass('what is the password for the account?')
# Setup Mastodon API
mastodon = Mastodon(
client_id = client_cred_file,
api_base_url = api_base_url
)
# Login and cache credential
mastodon.log_in(
email,
password,
scopes=['read', 'write'],
to_file = user_cred_file
)
def toot(api_base_url, client_cred_file, user_cred_file, toot, subtoots):
def toot(api_base_url, client_cred_file, user_cred_file, cw_text, toot, subtoots):
# Setup Mastodon API
mastodon = Mastodon(
client_id = client_cred_file,
access_token = user_cred_file,
api_base_url = api_base_url
)
parent_toot = mastodon.status_post(toot)
# Process toot
parent_toot = mastodon.status_post(toot, spoiler_text=cw_text)
parent_toot_id = parent_toot['id']
# Handle any sub-toots
if subtoots is not None:
for subtoot in subtoots:
mastodon.status_post(subtoot, in_reply_to_id=parent_toot_id, visibility='unlisted')
# Return the parent toot dict just in case it's needed elsewhere
return parent_toot
def rss(api_base_url, client_cred_file, user_cred_file, rss_feed, rss_article_limit, rss_cache_file, cw_text):
# Crash on reading the feed before doing any database operations or tooting
feed = feedparser.parse(rss_feed)
# Get access to cache
conn = sqlite3.connect(rss_cache_file)
c = conn.cursor()
# Ensure cache table has been created
c.execute('create table if not exists article_cache (id varchar(256) primary key);')
# Run through all articles in feed
for entry in feed['entries']:
# Check if article is in cache already and skip if found
c.execute('select count(1) as found from article_cache where id = ?', (entry.id,))
if c.fetchone()[0] > 0:
continue
# Toot article
toot_text = entry.title + '\n\n' + entry.link + '\n\n' + entry.published
toot(api_base_url, client_cred_file, user_cred_file, cw_text, toot_text, None)
# Cache article
c.execute('insert into article_cache values (?)', (entry.id,))
conn.commit()
# Cleanup connection to sqlite database for rss cache
conn.close()
if __name__ == '__main__':
# Global CLI arguments/options
@ -56,13 +99,16 @@ if __name__ == '__main__':
init_parser = subparsers.add_parser('init', help='initialize credentials')
login_parser = subparsers.add_parser('login', help='login to instance if credentials have expired')
toot_parser = subparsers.add_parser('toot', help='send configured toot')
rss_parser = subparsers.add_parser('rss', help='cross post articles from an rss feed')
# Parse CLI arguments
args = parser.parse_args()
# Make sure a command was specified
if args.command is None:
sys.exit('command must be specified')
# Make sure the config file specified exists
config_path = os.path.abspath(args.config)
if not os.path.exists(config_path):
sys.exit('invalid path to config file')
@ -76,14 +122,6 @@ if __name__ == '__main__':
config_api_base_url = config['config']['api_base_url']
config_client_cred_file = config['config']['client_cred_file']
config_user_cred_file = config['config']['user_cred_file']
config_toot = config['toots']['main_toot']
# Setup sub toots from config
config_sub_toots = dict(config.items('toots'))
config_sub_toots.pop('main_toot') # Remove the main toots from the dict of sub toots
if len(config_sub_toots.values()) > 0:
config_sub_toots = list(config_sub_toots.values()) # Throw away the keys, we really only care about the items in the list'
else:
config_sub_toots = None
# Ensure client_cred_file path is valid
config_client_cred_file = os.path.abspath(config_client_cred_file)
@ -100,13 +138,46 @@ if __name__ == '__main__':
if not os.path.exists(config_user_cred_file):
print('warning: user_cred file will be created')
# Ensure total toot length <= 500 characters
if len(config_toot) > 500:
sys.exit('toot length must be <= 500 characters')
# Pull out CW Text if present (valid for both toot and rss modes)
config_cw_text = None
if config.has_option('toots', 'cw_text'):
config_cw_text = config['toots']['cw_text']
# Deal with init command
if args.command == 'init':
init(config_api_base_url, config_app_name, config_client_cred_file, config_user_cred_file)
# Deal with login command
if args.command == 'login':
login(config_api_base_url, config_client_cred_file, config_user_cred_file)
# Deal with toot command
if args.command == 'toot':
toot(config_api_base_url, config_client_cred_file, config_user_cred_file, config_toot, config_sub_toots)
# Setup main toot text (applies to RSS as well -- is prefix for RSS toots
config_toot = config['toots']['main_toot']
# Ensure total toot length <= 500 characters
if len(config_toot) > 500:
sys.exit('toot length must be <= 500 characters')
# Setup sub toots from config
config_sub_toots = dict(config.items('toots'))
# Remove the main toots from the dict of sub toots
config_sub_toots.pop('main_toot')
# If CW text is specified, pop it from dict of sub toots
if config_cw_text is not None:
config_sub_toots.pop('cw_text')
if len(config_sub_toots.values()) > 0:
# Throw away the keys, we really only care about the items in the list'
config_sub_toots = list(config_sub_toots.values())
else:
config_sub_toots = None
toot(config_api_base_url, config_client_cred_file, config_user_cred_file, config_cw_text, config_toot, config_sub_toots)
# Deal with rss command
if args.command == 'rss':
config_rss_feed = config['rss']['feed']
config_rss_article_limit = int(config['rss']['article_limit'])
if config_rss_article_limit < 1 or config_rss_article_limit > 150:
sys.exit("rss article limit must be greater than 0 and less than 150")
config_rss_cache_file = config['rss']['cache_file']
if not os.path.exists(os.path.split(config_rss_cache_file)[0]):
sys.exit('rss_cache_file directory does not exist')
if not os.path.exists(config_rss_cache_file):
print('warning: rss_cache_file file will be created')
rss(config_api_base_url, config_client_cred_file, config_user_cred_file, config_rss_feed, config_rss_article_limit, config_rss_cache_file, config_cw_text)

Loading…
Cancel
Save