2
votes

I'll start with I'm not a python programmer... I have modified a twisted bot for my IRC channel. I'm now trying to integrate a second thread so I can get twitter updates on the channel every 30 minutes without interrupting the normal IRC commands.

I have tested the twitter update code by itself and it works ok... but I'm not sure how to add it to the classes and functions in the bot and use twisted's framework to send the message.

This is the code block for the twitter updates:

# For Twitter feed
from twitter import *
from thread import start_new_thread
from datetime import datetime, timedelta
from twitter_app_credentials import *
twitter = Twitter(
auth=OAuth(access_token_key, access_token_secret, consumer_key, consumer_secret))

def twitterthread(self, channel):
    userlist = ['victorbrca', 'MississaugaLUG']
    keepthread = 1
    while keepthread > 0:
        for user in userlist:
            raw = twitter.statuses.user_timeline(screen_name=user,count=1)[0]
            create_date = raw['created_at'].encode('utf-8')
            date = re.sub(r'\+[0-9]{4}', 'UTC', create_date)
            cdate = datetime.strptime(date, '%a %b %d %H:%M:%S %Z %Y')
            current_time = datetime.now() - timedelta(minutes = 30)
            if cdate > current_time:
                tweet = ("@%s: %s" % (raw["user"]["screen_name"], raw["text"]))
                print tweet
                time.sleep(10)
            else:
                print "no new tweets"
            time.sleep(10)

I would like to initiate this either via a command or when the bot joins the channel, but without having multiple threads running at the same time.

Here's what the last commit of the bot looks like (with my attempts to get it to work) - Commit

And here's what the default twisted bot looks like:

# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
# See LICENSE for details.


"""
An example IRC log bot - logs a channel's events to a file.

If someone says the bot's name in the channel followed by a ':',
e.g.

  <foo> logbot: hello!

the bot will reply:

  <logbot> foo: I am a log bot

Run this script with two arguments, the channel name the bot should
connect to, and file to log to, e.g.:

  $ python ircLogBot.py test test.log

will log channel #test to the file 'test.log'.
"""


# twisted imports
from twisted.words.protocols import irc
from twisted.internet import reactor, protocol
from twisted.python import log

# system imports
import time, sys


class MessageLogger:
    """
    An independent logger class (because separation of application
    and protocol logic is a good thing).
    """
    def __init__(self, file):
        self.file = file

    def log(self, message):
        """Write a message to the file."""
        timestamp = time.strftime("[%H:%M:%S]", time.localtime(time.time()))
        self.file.write('%s %s\n' % (timestamp, message))
        self.file.flush()

    def close(self):
        self.file.close()


class LogBot(irc.IRCClient):
    """A logging IRC bot."""

    nickname = "twistedbot"

    def connectionMade(self):
        irc.IRCClient.connectionMade(self)
        self.logger = MessageLogger(open(self.factory.filename, "a"))
        self.logger.log("[connected at %s]" % 
                        time.asctime(time.localtime(time.time())))

    def connectionLost(self, reason):
        irc.IRCClient.connectionLost(self, reason)
        self.logger.log("[disconnected at %s]" % 
                        time.asctime(time.localtime(time.time())))
        self.logger.close()


    # callbacks for events

    def signedOn(self):
        """Called when bot has succesfully signed on to server."""
        self.join(self.factory.channel)

    def joined(self, channel):
        """This will get called when the bot joins the channel."""
        self.logger.log("[I have joined %s]" % channel)

    def privmsg(self, user, channel, msg):
        """This will get called when the bot receives a message."""
        user = user.split('!', 1)[0]
        self.logger.log("<%s> %s" % (user, msg))

        # Check to see if they're sending me a private message
        if channel == self.nickname:
            msg = "It isn't nice to whisper!  Play nice with the group."
            self.msg(user, msg)
            return

        # Otherwise check to see if it is a message directed at me
        if msg.startswith(self.nickname + ":"):
            msg = "%s: I am a log bot" % user
            self.msg(channel, msg)
            self.logger.log("<%s> %s" % (self.nickname, msg))

    def action(self, user, channel, msg):
        """This will get called when the bot sees someone do an action."""
        user = user.split('!', 1)[0]
        self.logger.log("* %s %s" % (user, msg))

    # irc callbacks

    def irc_NICK(self, prefix, params):
        """Called when an IRC user changes their nickname."""
        old_nick = prefix.split('!')[0]
        new_nick = params[0]
        self.logger.log("%s is now known as %s" % (old_nick, new_nick))


    # For fun, override the method that determines how a nickname is changed on
    # collisions. The default method appends an underscore.
    def alterCollidedNick(self, nickname):
        """
        Generate an altered version of a nickname that caused a collision in an
        effort to create an unused related name for subsequent registration.
        """
        return nickname + '^'



class LogBotFactory(protocol.ClientFactory):
    """A factory for LogBots.

    A new protocol instance will be created each time we connect to the server.
    """

    # the class of the protocol to build when new connection is made
    protocol = LogBot

    def __init__(self, channel, filename):
        self.channel = channel
        self.filename = filename

    def clientConnectionLost(self, connector, reason):
        """If we get disconnected, reconnect to server."""
        connector.connect()

    def clientConnectionFailed(self, connector, reason):
        print "connection failed:", reason
        reactor.stop()


if __name__ == '__main__':
    # initialize logging
    log.startLogging(sys.stdout)

    # create factory protocol and application
    f = LogBotFactory(sys.argv[1], sys.argv[2])

    # connect factory to this host and port
    reactor.connectTCP("irc.freenode.net", 6667, f)

    # run bot
    reactor.run()

Thanks.

2

2 Answers

0
votes

Don't use threads. Use https://github.com/dustin/twitty-twister and twisted.internet.task.LoopingCall.

0
votes

I figured out a way that works for what I need using Jean-Paul's idea of going with LoopingCall.

I defined a function that checks for the Twitter update:

def twitterFeed(self, channel):
        twitter_userlist = ['victorbrca', 'MississaugaLUG']
        for user in twitter_userlist:
            RawTweet = twitter.statuses.user_timeline(screen_name=user,count=1)[0]
            RawTweetDate = RawTweet['created_at']
            UTCRawTweetDate = re.sub(r'\+[0-9]{4}', 'UTC', RawTweetDate)
            TweetDateToTime = datetime.strptime(UTCRawTweetDate, '%a %b %d %H:%M:%S %Z %Y')
            TweetDate = TweetDateToTime.strftime('%Y-%b-%d %H:%M')
            GetFiveMinAgo = datetime.utcnow() - timedelta(minutes = 5)
            FiveMinAgo = GetFiveMinAgo.strftime('%Y-%b-%d %H:%M')
            if FiveMinAgo < TweetDate:
                CurrentTime = datetime.utcnow()
                diff = relativedelta(CurrentTime, TweetDateToTime)
                if diff.minutes < 1:
                    Ago = "%s seconds ago" % diff.seconds
                else:
                    Ago = "%s minutes ago" % diff.minutes
                tweet = ("@%s: %s (%s)" % (RawTweet["user"]["screen_name"], RawTweet["text"], Ago))
                msg = tweet.encode('utf-8')
                self.sendLine("PRIVMSG %s :%s" % (channel, msg))

And the function gets called with a command via LoopingCall (for now):

# Start Twitter feed
elif msg == "!starttwitter" and user == authorized_user:
    task = LoopingCall(self.twitterFeed, channel)
    task.start(300)

Thanks for all the replies.