220 lines
8.0 KiB
Python
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()
|