Browse Source

Initial implementation of streaming API, basic toot caching

bot_curator
KemoNine 4 years ago
parent
commit
d5130629f2
  1. 12
      configs/example_curation_config.yaml
  2. 67
      curation_bot.py
  3. 93
      toot_bot.py

12
configs/example_curation_config.yaml

@ -0,0 +1,12 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: curation bot local and federated
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
curation:
local: True
federated: True
cache_file: /home/mastodon/bot/curation_cache.db

67
curation_bot.py

@ -1,67 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import getpass, sys
import pprint
from mastodon import Mastodon, StreamListener
class CurationBot(StreamListener):
def on_update(self, status):
'''A new status has appeared! 'status' is the parsed JSON dictionary
describing the status.'''
pprint.pprint(status)
pass
def on_notification(self, notification):
'''A new notification. 'notification' is the parsed JSON dictionary
describing the notification.'''
pprint.pprint(notification)
pass
def on_delete(self, status_id):
'''A status has been deleted. status_id is the status' integer ID.'''
pprint.pprint('deleted: ' + str(status_id))
pass
def handle_heartbeat(self):
'''The server has sent us a keep-alive message. This callback may be
useful to carry out periodic housekeeping tasks, or just to confirm
that the connection is still open.'''
print('!!!!!!!!heartbeat!!!!!!!!')
pass
if __name__ == '__main__':
client_cred_file = 'client_cred_file.secret'
user_cred_file = 'user_cred_file.secret'
api_base_url = 'http://mastodon-dev-1.kemonine.info'
#email = input('what is the e-mail that was used to setup the bot account? ')
#password = getpass.getpass('what is the password for the account? ')
#Mastodon.create_app(
# 'streaming_initial_tests',
# api_base_url = api_base_url,
# scopes=['read', 'write', 'follow'],
# to_file = client_cred_file
#)
#mastodon = Mastodon(
# client_id = client_cred_file,
# api_base_url = api_base_url
#)
#mastodon.log_in(
# email,
# password,
# scopes=['read', 'write', 'follow'],
# to_file = user_cred_file
# )
mastodon = Mastodon(
client_id = client_cred_file,
access_token = user_cred_file,
api_base_url = api_base_url
)
mastodon.public_stream(CurationBot())

93
toot_bot.py

@ -12,10 +12,78 @@ import os
import getpass
import codecs
import sqlite3
from mastodon import Mastodon
from mastodon import Mastodon, StreamListener
import feedparser
from ruamel.yaml import YAML
class CurationBot(StreamListener):
''' Implementation of the Mastodon.py StreamListener class for curation bot purposes '''
def __init__(self, bot_config):
StreamListener.__init__(self)
self.bot_config = bot_config
# Get access to cache
self.conn = sqlite3.connect(self.bot_config['curation']['cache_file'])
self.cursor = self.conn.cursor()
# Ensure cache table has been created
self.cursor.execute('''create table if not exists toot_cache (
toot_id integer primary key,
federated bool,
username varchar(2048),
is_reply bool,
is_boost bool,
toot_timestamp timestamp,
favorites integer,
boosts integer
);'''
)
def __del__(self):
# Cleanup connection to sqlite database for rss cache
self.conn.close()
def on_update(self, status):
'''A new status has appeared! 'status' is the parsed JSON dictionary
describing the status.'''
if status['visibility'] == 'public':
toot_id = status['id']
federated = '@' in status['account']['acct']
username = status['account']['acct']
is_reply = status['in_reply_to_id'] is not None
is_boost = status['reblog'] is not None
timestamp = status['created_at']
favorites = status['favourites_count']
boosts = status['reblogs_count']
#tags = status['tags'] # TODO: Add support for tags? - Will need many to 1 relationship and a cross table
# Ensure a toot isn't cached twice for some odd reason
self.cursor.execute('select count(1) as found from toot_cache where toot_id = ?', (toot_id,))
if self.cursor.fetchone()[0] > 0:
pass
# Cache toot
self.cursor.execute('insert into toot_cache values (?, ?, ?, ?, ?, ?, ?, ?)', (toot_id, federated, username, is_reply, is_boost, timestamp, favorites, boosts))
self.conn.commit()
def on_notification(self, notification):
'''A new notification. 'notification' is the parsed JSON dictionary
describing the notification.'''
# We don't care if notifications come through our bot / curation account
# Leave handling notifications/folow up to the admins and e-mail notifications
pass
def on_delete(self, status_id):
'''A status has been deleted. status_id is the status' integer ID.'''
print('deleted: ' + str(status_id))
print(' this status should delete an entry from the cache')
def handle_heartbeat(self):
'''The server has sent us a keep-alive message. This callback may be
useful to carry out periodic housekeeping tasks, or just to confirm
that the connection is still open.'''
print('!!!!!!!!heartbeat!!!!!!!!')
print(' we should probably update statuses and whatnot here or at least do some housekeeping for old toots')
def init(config):
''' Initialize the Mastdon API app and cache the API key
Auto login if app creation succeeds '''
@ -157,6 +225,17 @@ def rss(config):
# Cleanup connection to sqlite database for rss cache
conn.close()
def curate(config):
''' Curate (fav/boost) popular posts on local and/or federated timelines '''
# Setup Mastodon API
mastodon = Mastodon(
client_id=config['config']['client_cred_file'],
access_token=config['config']['user_cred_file'],
api_base_url=config['config']['api_base_url']
)
mastodon.public_stream(CurationBot(config))
if __name__ == '__main__':
# Global CLI arguments/options
PARSER = argparse.ArgumentParser()
@ -174,6 +253,7 @@ if __name__ == '__main__':
action='store_true')
TOOT_PARSER = SUBPARSERS.add_parser('toot', help='send configured toot')
RSS_PARSER = SUBPARSERS.add_parser('rss', help='cross post articles from an rss feed')
CURATE_PARSER = SUBPARSERS.add_parser('curation', help='Run the curation bot (service)')
# Parse CLI arguments
ARGS = PARSER.parse_args()
@ -216,6 +296,15 @@ if __name__ == '__main__':
# Deal with login command
if ARGS.command == 'login':
login(CONFIG)
# Deal with curation command
if ARGS.command == 'curation':
# Verify toot cache file folder exists
if not os.path.exists(os.path.abspath(os.path.split(CONFIG['curation']['cache_file'])[0])):
sys.exit('curation cache_file directory does not exist')
# Warn if curation cache file doesn't exist
if not os.path.exists(os.path.abspath(CONFIG['curation']['cache_file'])):
print('warning: curation cache_file file will be created')
curate(CONFIG)
# Deal with toot command
if ARGS.command == 'toot':
# Ensure main toot is <= 500 characters
@ -244,6 +333,6 @@ if __name__ == '__main__':
if not os.path.exists(os.path.abspath(os.path.split(CONFIG['rss']['cache_file'])[0])):
sys.exit('rss cache_file directory does not exist')
# Warn if RSS cache file doesn't exist
if not os.path.exists(os.path.abspath(os.path.split(CONFIG['rss']['cache_file'])[0])):
if not os.path.exists(os.path.abspath(CONFIG['rss']['cache_file'])):
print('warning: rss_cache_file file will be created')
rss(CONFIG)

Loading…
Cancel
Save