matrix-feedbot/twitbot.py

220 lines
8.0 KiB
Python

from matrix_client.client import MatrixClient
from jinja2 import Template
import twitter
import argparse
import yaml
import sys
import os
from sets import Set
import time
import re
IMAGE_MATCH_RE = re.compile(r'<img src="([^"]*)')
class Twitbot():
def __init__(self, config):
self.config = config
self.timelines = config['twitter']['timelines']
self.room_ids = Set([config['default_room']])
for timeline in self.timelines:
rooms = timeline.get('rooms') or [self.config['default_room']]
for room in rooms:
self.room_ids.add(room)
self.client = MatrixClient(config['homeserver_uri'])
self.client.api.validate_certificate(False)
self.rooms = {}
self.cooloff = None
self.watermarks_path = os.path.join(self.config['data_path'], 'watermarks.yaml')
if os.path.isfile(self.watermarks_path):
with open(self.watermarks_path) as f:
self.watermarks = yaml.safe_load(f.read())
else:
self.watermarks = {}
self.client.listeners.append(self.check_for_command)
with open(os.path.join(self.config['data_path'], 'twitter.j2')) as f:
self.template = Template(f.read())
self.t = twitter.Twitter(
auth=twitter.OAuth(config['twitter']['token'],
config['twitter']['token_key'],
config['twitter']['con_secret'],
config['twitter']['con_secret_key']))
def connect_and_join(self):
self.token = self.client.login_with_password(
username=self.config['username'],
password=self.config['password'])
for room_id in self.room_ids:
self.rooms[room_id] = self.client.join_room(room_id)
time.sleep(1)
print "Logged in, joined %s" % self.rooms
def loop(self):
while(True):
if self.cooloff and time.mktime(time.localtime()) < self.cooloff + 180:
self.poll()
continue
for timeline in self.timelines:
self.update_timeline(timeline)
self.poll()
self.cooloff = time.mktime(time.localtime())
def poll(self):
try:
self.client.listen_for_events(1000)
except:
pass
def check_for_command(self, event):
room_id = event.get('room_id')
if room_id and room_id in self.room_ids:
print event.get('event_id')
self.maybe_process_event(event)
def maybe_process_event(self, event):
content = event.get('content')
if not content:
return
sender = event.get('sender')
if sender and sender == self.config['username']:
return
body = content.get('body')
if not body:
return
reply_match = re.match(r're ([a-zA-Z:/\.0-9]+) (.*)', body)
rt_match = re.match(r'rt ([a-zA-Z:/\.0-9]+)', body)
if body.startswith('t: '):
room = self.rooms[event.get('room_id')]
if not room:
return
try:
self.t.statuses.update(status=body[3:])
room.send_text("Roger.")
except:
room.send_text("Something went wrong.")
elif reply_match:
url = reply_match.group(1)
user = url.split("/")[3]
tweet_id = url.split("/")[5]
room = self.rooms[event.get('room_id')]
try:
self.t.statuses.update(status="@%s %s" % (user, reply_match.group(2)))
room.send_text("Roger.")
except:
room.send_text("Something went wrong.")
elif rt_match:
url = reply_match.group(1)
user = url.split("/")[3]
tweet_id = url.split("/")[5]
room = self.rooms[event.get('room_id')]
try:
self.t.statuses.retweet(id=tweet_id)
room.send_text("Roger.")
except:
room.send_text("Something went wrong.")
def update_timeline(self, timeline):
timeline_spec = timeline['type'] + '_' + (timeline.get('target') or "")
state = None
since_id = self.watermarks.get(timeline_spec)
if timeline['type'] == 'user':
if since_id:
state = self.t.statuses.user_timeline(
screen_name=timeline['target'], since_id=since_id)
else:
state = self.t.statuses.user_timeline(
screen_name=timeline['target'])
elif timeline['type'] == 'mentions':
if since_id:
state = self.t.statuses.mentions_timeline(since_id=since_id)
else:
state = self.t.statuses.mentions_timeline()
elif timeline['type'] == 'home':
if since_id:
state = self.t.statuses.home_timeline(since_id=since_id)
else:
state = self.t.statuses.home_timeline()
elif timeline['type'] == 'list':
_split = timeline['target'].split('/')
if since_id:
state = self.t.lists.statuses(
slug=_split[1],
owner_screen_name=_split[0],
since_id=since_id)
else:
state = self.t.lists.statuses(
slug=_split[1], owner_screen_name=_split[0])
state.reverse() # start at the bottom so these send chronologically
for tweet in state:
rooms = timeline.get('rooms') or [self.config['default_room']]
for room_id in rooms:
self.send_to_room(timeline, room_id, tweet)
self.update_watermark(timeline_spec, tweet['id'])
def send_to_room(self, timeline, room_id, raw):
timeline_spec = timeline['type'] + ' ' + (timeline.get('target') or "")
if raw['id'] < (self.watermarks.get(timeline_spec) or 0):
return
in_reply_to = None
irt_displayname = None
if raw.get('in_reply_to_status_id'):
in_reply_to = "https://twitter.com/%s/status/%s" % (
raw['in_reply_to_screen_name'],
raw['in_reply_to_status_id_str']
)
irt_displayname = raw['in_reply_to_screen_name']
tweet_link = "https://twitter.com/%s/status/%s" % (
raw['user']['screen_name'],
raw['id_str']
)
if raw['text'].startswith('RT '):
return
string = self.template.render(spec=timeline_spec,
date=raw['created_at'],
tweet_link=tweet_link,
displayname=raw['user']['name'],
text=raw['text'],
in_reply_to=in_reply_to,
irt_displayname=irt_displayname)
self.client.api.send_message_event(room_id, 'm.room.message', {
"body": u"{dn} {raw} {tweet_link}".format(
dn=raw['user']['name'],
raw=raw['text'],
tweet_link=tweet_link
),
"format": "org.matrix.custom.html",
"formatted_body": string,
"msgtype": "m.text"
})
time.sleep(5)
def update_watermark(self, timeline_spec, id):
self.watermarks[timeline_spec] = id
with open(self.watermarks_path, 'w') as f:
f.write(yaml.dump(self.watermarks))
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Twitter to matrix")
parser.add_argument('--config_path', "-c")
parser.add_argument('--data_path', "-d")
args = parser.parse_args()
if not os.path.exists(args.config_path):
print("Configuration file doesn't exist, please create one")
sys.exit(1)
mcat = None
with open(args.config_path) as f:
conf = yaml.safe_load(f)
conf['data_path'] = args.data_path
twitbot = Twitbot(conf)
twitbot.connect_and_join()
twitbot.loop()