Merge branch 'twitterbot' into 'master'
Twitter: poll the bot's mentions feed, print to channel This uses the Twitter client's method for checking the mentions timeline in a new thread (which can be started/stopped via IRC) to print mentions to a specified IRC channel. See merge request !5
This commit is contained in:
commit
21941b3392
|
@ -1,9 +1,9 @@
|
||||||
"""Access to Twitter through bot commands."""
|
"""Access to Twitter through bot commands."""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import threading
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import requests
|
|
||||||
import twython
|
import twython
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
@ -29,6 +29,8 @@ class Twitter(Plugin):
|
||||||
self.temp_token = None
|
self.temp_token = None
|
||||||
self.temp_token_secret = None
|
self.temp_token_secret = None
|
||||||
|
|
||||||
|
self.poll_mentions = False
|
||||||
|
|
||||||
super(Twitter, self).__init__(bot, connection, event)
|
super(Twitter, self).__init__(bot, connection, event)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
@ -48,6 +50,10 @@ class Twitter(Plugin):
|
||||||
self.handle_auth, -20)
|
self.handle_auth, -20)
|
||||||
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!twitter\s+replyto\s+(\S+)\s+(.*)',
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!twitter\s+replyto\s+(\S+)\s+(.*)',
|
||||||
self.handle_replyto, -20)
|
self.handle_replyto, -20)
|
||||||
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!twitter\s+mentionpoll\s+start',
|
||||||
|
self.handle_start_mentionpoll, -20)
|
||||||
|
self.connection.reactor.add_global_regex_handler(['pubmsg', 'privmsg'], r'^!twitter\s+mentionpoll\s+stop',
|
||||||
|
self.handle_stop_mentionpoll, -20)
|
||||||
|
|
||||||
# try getting the stored auth tokens and logging in
|
# try getting the stored auth tokens and logging in
|
||||||
try:
|
try:
|
||||||
|
@ -79,9 +85,30 @@ class Twitter(Plugin):
|
||||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_gettoken)
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_gettoken)
|
||||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_auth)
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_auth)
|
||||||
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_replyto)
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_replyto)
|
||||||
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_start_mentionpoll)
|
||||||
|
self.connection.reactor.remove_global_regex_handler(['pubmsg', 'privmsg'], self.handle_stop_mentionpoll)
|
||||||
|
|
||||||
|
self.poll_mentions = False
|
||||||
|
|
||||||
super(Twitter, self).stop()
|
super(Twitter, self).stop()
|
||||||
|
|
||||||
|
def handle_start_mentionpoll(self, connection, event, match):
|
||||||
|
if not has_permission(event.source, 'twitter.manage_threads'):
|
||||||
|
return self.bot.reply(event, "You do not have permission to start/stop threads.")
|
||||||
|
|
||||||
|
self.poll_mentions = True
|
||||||
|
t = threading.Thread(target=self.thread_watch_mentions)
|
||||||
|
t.daemon = True
|
||||||
|
t.start()
|
||||||
|
self.bot.reply(event, "Now polling for mentions.")
|
||||||
|
|
||||||
|
def handle_stop_mentionpoll(self, connection, event, match):
|
||||||
|
if not has_permission(event.source, 'twitter.manage_threads'):
|
||||||
|
return self.bot.reply(event, "You do not have permission to start/stop threads.")
|
||||||
|
|
||||||
|
self.poll_mentions = False
|
||||||
|
self.bot.reply(event, "No longer polling for mentions.")
|
||||||
|
|
||||||
def handle_getstatus(self, connection, event, match):
|
def handle_getstatus(self, connection, event, match):
|
||||||
"""Get a status by tweet ID."""
|
"""Get a status by tweet ID."""
|
||||||
|
|
||||||
|
@ -94,7 +121,8 @@ class Twitter(Plugin):
|
||||||
status = match.group(3)
|
status = match.group(3)
|
||||||
try:
|
try:
|
||||||
tweet = self.twit.show_status(id=status)
|
tweet = self.twit.show_status(id=status)
|
||||||
return self._return_tweet_or_retweet_text(event, tweet=tweet, print_source=print_source, print_id=print_id)
|
return self._reply_with_tweet_or_retweet_text(event, tweet=tweet, print_source=print_source,
|
||||||
|
print_id=print_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("couldn't obtain status")
|
log.error("couldn't obtain status")
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
|
@ -130,8 +158,8 @@ class Twitter(Plugin):
|
||||||
tweets = self.twit.get_user_timeline(screen_name=user, count=count, include_rts=True)
|
tweets = self.twit.get_user_timeline(screen_name=user, count=count, include_rts=True)
|
||||||
if tweets:
|
if tweets:
|
||||||
tweet = tweets[-1*index]
|
tweet = tweets[-1*index]
|
||||||
return self._return_tweet_or_retweet_text(event, tweet=tweet, print_source=print_source,
|
return self._reply_with_tweet_or_retweet_text(event, tweet=tweet, print_source=print_source,
|
||||||
print_id=print_id)
|
print_id=print_id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error("couldn't obtain status")
|
log.error("couldn't obtain status")
|
||||||
log.exception(e)
|
log.exception(e)
|
||||||
|
@ -232,9 +260,60 @@ class Twitter(Plugin):
|
||||||
log.error("twitter settings object does not exist")
|
log.error("twitter settings object does not exist")
|
||||||
return self.bot.reply(event, "twitter module not configured")
|
return self.bot.reply(event, "twitter module not configured")
|
||||||
|
|
||||||
def _return_tweet_or_retweet_text(self, event, tweet, print_source=False, print_id=True):
|
def thread_watch_mentions(self, sleep_time=60):
|
||||||
|
"""Poll mentions from Twitter every sleep_time seconds.
|
||||||
|
|
||||||
|
:param sleep_time: second to sleep between checks
|
||||||
|
:type sleep_time: int
|
||||||
|
"""
|
||||||
|
|
||||||
|
while self.poll_mentions:
|
||||||
|
twittersettings = TwitterClient.objects.get(pk=1)
|
||||||
|
out_chan = twittersettings.mentions_output_channel.name
|
||||||
|
since_id = twittersettings.mentions_since_id
|
||||||
|
|
||||||
|
mentions = self.twit.get_mentions_timeline(since_id=since_id)
|
||||||
|
for mention in mentions:
|
||||||
|
reply = self._return_tweet_or_retweet_text(tweet=mention, print_source=True)
|
||||||
|
self.bot.privmsg(out_chan, reply)
|
||||||
|
since_id = mention['id'] if mention['id'] > since_id else since_id
|
||||||
|
|
||||||
|
twittersettings.mentions_since_id = since_id
|
||||||
|
twittersettings.save()
|
||||||
|
|
||||||
|
time.sleep(sleep_time)
|
||||||
|
|
||||||
|
def _reply_with_tweet_or_retweet_text(self, event, tweet, print_source=False, print_id=True):
|
||||||
|
"""Do a bot.reply() with the appropriate text representation of the given tweet.
|
||||||
|
|
||||||
|
See _return_tweet_or_retweet_text for details.
|
||||||
|
|
||||||
|
:param event: the irc event to use for the reply
|
||||||
|
:type event: Event
|
||||||
|
:param tweet: the tweet (from twython) to inspect and return a string for
|
||||||
|
:type tweet: dict
|
||||||
|
:param print_source: whether or not to print the tweet's author (default False)
|
||||||
|
:type print_source: bool
|
||||||
|
:param print_id: whether or not to print the tweet's ID (default True)
|
||||||
|
:type print_id: bool
|
||||||
|
:returns: tweet text suitable for printing
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.bot.reply(event, self._return_tweet_or_retweet_text(tweet, print_source, print_id))
|
||||||
|
|
||||||
|
def _return_tweet_or_retweet_text(self, tweet, print_source=False, print_id=True):
|
||||||
"""Return a string of the author and text body of a status, accounting for whether
|
"""Return a string of the author and text body of a status, accounting for whether
|
||||||
or not the fetched status is a retweet.
|
or not the fetched status is a retweet.
|
||||||
|
|
||||||
|
:param tweet: the tweet (from twython) to inspect and return a string for
|
||||||
|
:type tweet: dict
|
||||||
|
:param print_source: whether or not to print the tweet's author (default False)
|
||||||
|
:type print_source: bool
|
||||||
|
:param print_id: whether or not to print the tweet's ID (default True)
|
||||||
|
:type print_id: bool
|
||||||
|
:returns: tweet text suitable for printing
|
||||||
|
:rtype: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
retweet = getattr(tweet, 'retweeted_status', None)
|
retweet = getattr(tweet, 'retweeted_status', None)
|
||||||
|
@ -256,7 +335,7 @@ class Twitter(Plugin):
|
||||||
if print_id:
|
if print_id:
|
||||||
reply = reply + " [{0:d}]".format(tweet['id'])
|
reply = reply + " [{0:d}]".format(tweet['id'])
|
||||||
|
|
||||||
return self.bot.reply(event, reply)
|
return reply
|
||||||
|
|
||||||
|
|
||||||
plugin = Twitter
|
plugin = Twitter
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('ircbot', '0014_auto_20160116_1955'),
|
||||||
|
('twitter', '0003_auto_20150620_0951'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='twitterclient',
|
||||||
|
name='output_channel',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='twitterclient',
|
||||||
|
name='mentions_output_channel',
|
||||||
|
field=models.ForeignKey(blank=True, related_name='mentions_twitter_client', null=True, to='ircbot.IrcChannel', default=None),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('twitter', '0004_add_mentions_output_channel_to_config'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='twitterclient',
|
||||||
|
name='since_id',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='twitterclient',
|
||||||
|
name='mentions_since_id',
|
||||||
|
field=models.PositiveIntegerField(default=1, blank=True),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('twitter', '0005_replace_since_id_with_replies_specific_one'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterModelOptions(
|
||||||
|
name='twitterclient',
|
||||||
|
options={'permissions': (('send_tweets', 'Can send tweets via IRC'), ('manage_threads', 'Can start/stop polling threads via IRC'))},
|
||||||
|
),
|
||||||
|
]
|
|
@ -4,6 +4,8 @@ import logging
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
|
from ircbot.models import IrcChannel
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger('twitter.models')
|
log = logging.getLogger('twitter.models')
|
||||||
|
|
||||||
|
@ -12,12 +14,15 @@ class TwitterClient(models.Model):
|
||||||
|
|
||||||
"""Track twitter settings and similar."""
|
"""Track twitter settings and similar."""
|
||||||
|
|
||||||
since_id = models.PositiveIntegerField()
|
|
||||||
output_channel = models.CharField(max_length=200, default='', blank=True)
|
|
||||||
oauth_token = models.CharField(max_length=256, default='', blank=True)
|
oauth_token = models.CharField(max_length=256, default='', blank=True)
|
||||||
oauth_token_secret = models.CharField(max_length=256, default='', blank=True)
|
oauth_token_secret = models.CharField(max_length=256, default='', blank=True)
|
||||||
|
|
||||||
|
mentions_output_channel = models.ForeignKey(IrcChannel, related_name='mentions_twitter_client', default=None,
|
||||||
|
null=True, blank=True)
|
||||||
|
mentions_since_id = models.PositiveIntegerField(default=1, blank=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
permissions = (
|
permissions = (
|
||||||
('send_tweets', "Can send tweets via IRC"),
|
('send_tweets', "Can send tweets via IRC"),
|
||||||
|
('manage_threads', "Can start/stop polling threads via IRC"),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue