Advertisement
bradenlance

mkb.py

Dec 7th, 2016
311
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 45.50 KB | None | 0 0
  1. # Bryan, Timon, and Braden made an edit to this file originally created
  2. # by Edwin Dalmaijer by replacing replacing line 318 with line 319
  3. # This lets us create our own tweet rather than using the random generated one
  4. # created by markovbot3
  5.  
  6. # -*- coding: utf-8 -*-
  7. #
  8. # For installation instructions and more information, please refer to:
  9. # http://www.pygaze.org/2016/03/tutorial-creating-a-twitterbot/
  10. # (This includes instructions to install the Twitter library used here)
  11. #
  12. # This file is part of markovbot, created by Edwin Dalmaijer
  13. # GitHub: https://github.com/esdalmaijer/markovbot
  14. #
  15. # Markovbot is free software: you can redistribute it and/or modify
  16. # it under the terms of the GNU General Public License as published by
  17. # the Free Software Foundation, either version 3 of the License, or
  18. # (at your option) any later version.
  19. # Markovbot is distributed in the hope that it will be useful,
  20. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  22. # GNU General Public License for more details.
  23. # You should have received a copy of the GNU General Public License
  24. # along with markovbot.  If not, see <http://www.gnu.org/licenses/>.
  25.  
  26. # native imports
  27. import os
  28. import sys
  29. import copy
  30. import time
  31. import pickle
  32. import random
  33. from threading import Thread, Lock
  34. from multiprocessing import Queue
  35.  
  36. # external imports
  37. # Twitter package: https://pypi.python.org/pypi/twitter
  38. # Homepage of Twitter package: http://mike.verdone.ca/twitter/
  39. try:
  40.     import twitter
  41.     IMPTWITTER = True
  42. except:
  43.     print("WARNING from Markovbot: Could not load the 'twitter' library, so Twitter functionality is not available.")
  44.     IMPTWITTER = False
  45.  
  46.  
  47. class MarkovBot():
  48.  
  49.     """Class to generate text with a Markov chain, with support to read and
  50.     post updates to Twitter accounts.
  51.     """
  52.  
  53.     def __init__(self):
  54.  
  55.         """Initialises the bot.
  56.         """
  57.  
  58.         # # # # #
  59.         # DATA
  60.  
  61.         # Create an empty dict for the data
  62.         self.data = {'default':{}}
  63.  
  64.  
  65.         # # # # #
  66.         # TWITTER
  67.  
  68.         # Starting value for the Twitter and TwitterStream instances
  69.         self._t = None
  70.         self._ts = None
  71.         # Create locks for these instances, so they won't be accessed at the
  72.         # same time by different threads.
  73.         self._tlock = Lock()
  74.         self._tslock = Lock()
  75.  
  76.         # Create a Boolean that indicates whether the bot is logged in, and
  77.         # a placeholder for the credentials of the user that is logged in
  78.         self._loggedin = False
  79.         self._credentials = None
  80.  
  81.         # Create variables to keep track of tweets that should not be
  82.         # replied to. The self._maxconvdepth value determines the maximum
  83.         # conversation lenght that this bot is allowed to participate in.
  84.         # Keep the number low to prevent the bot from being spammy.
  85.         self._nonotweets = []
  86.         self._maxconvdepth = None
  87.  
  88.         # Placeholders for debugging values of the last incoming and
  89.         # outgoing tweets
  90.         self._lasttweetin = None
  91.         self._lasttweetout = None
  92.  
  93.         # Start the autoreplying thread
  94.         self._autoreplying = False
  95.         self._autoreply_database = None
  96.         self._targetstring = None
  97.         self._keywords = None
  98.         self._tweetprefix = None
  99.         self._tweetsuffix = None
  100.         self._mindelay = 0.0
  101.         if IMPTWITTER:
  102.             self._autoreplythreadlives = True
  103.             self._autoreplythread = Thread(target=self._autoreply)
  104.             self._autoreplythread.daemon = True
  105.             self._autoreplythread.name = 'autoreplier'
  106.             self._autoreplythread.start()
  107.         else:
  108.             self._autoreplythreadlives = False
  109.  
  110.         # Start the tweeting thread
  111.         self._tweetingdatabase = None
  112.         self._autotweeting = False
  113.         self._tweetinginterval = None
  114.         self._tweetingjitter = None
  115.         self._tweetingkeywords = None
  116.         self._tweetingprefix = None
  117.         self._tweetingsuffix = None
  118.         if IMPTWITTER:
  119.             self._tweetingthreadlives = True
  120.             self._tweetingthread = Thread(target=self._autotweet)
  121.             self._tweetingthread.daemon = True
  122.             self._tweetingthread.name = 'autotweeter'
  123.             self._tweetingthread.start()
  124.         else:
  125.             self._tweetingthreadlives = False
  126.  
  127.  
  128.     def clear_data(self, database=None):
  129.  
  130.         """Clears the current internal data. NOTE: This does not remove
  131.         existing pickled data!
  132.  
  133.         Keyword Arguments
  134.  
  135.         database        -   A string that indicates the name of the
  136.                         specific database that you want to clear,
  137.                         or None to clear all data. (default = None)
  138.         """
  139.  
  140.         # Overwrite data
  141.         if database == None:
  142.             self.data = {'default':{}}
  143.         else:
  144.             try:
  145.                 self.data.pop(database)
  146.             except KeyError:
  147.                 self._error('clear_data', "There was no database named '%s'" % (database))
  148.  
  149.  
  150.     def generate_text(self, maxlength, seedword=None, database='default',
  151.         verbose=False, maxtries=100):
  152.  
  153.         """Generates random text based on the provided database.
  154.  
  155.         Arguments
  156.  
  157.         maxlength       -   An integer value indicating the amount of words
  158.                         that can maximally be produced. The actual
  159.                         number is determined by where interpunction
  160.                         occurred. Text will be cut off at a comma,
  161.                         full stop, and exclamation or question marks.
  162.  
  163.         Keyword Arguments
  164.  
  165.         seedword        -   A string that indicates what word should be in
  166.                         the sentence. If None is passed, or if the word
  167.                         is not in the database, a random word will be
  168.                         chosen. This value can also be a list of words,
  169.                         in which case the list will be processed
  170.                         one-by-one until a word is found that is in the
  171.                         database.
  172.  
  173.         database        -   A string that indicates the name of the
  174.                         specific database that you want to use to
  175.                         generate the text, or u'default' to use the
  176.                         default database. (default = 'default')
  177.  
  178.         verbose     -   Boolean that indicates whether this function
  179.                         should bother you with excessibe and unnecessary
  180.                         messages whenever it can't immeadiately produce
  181.                         a text (it will still raise an Exception after
  182.                         maxtries attempts).
  183.  
  184.         maxtries        -   Integer indicating how many attempts the function
  185.                         is allowed to construct some text (sometimes
  186.                         this fails, and I couldn't be bothered to do
  187.                         elaborate debugging)
  188.  
  189.         Returns
  190.  
  191.         sentence        -   A string that starts with a capital, and ends
  192.                         with a full stop.
  193.         """
  194.  
  195.         # Raise an Exception when no data exists
  196.         if self.data[database] == {}:
  197.             self._error('generate_text', "No data is available yet in database '%s'. Did you read any data yet?" % (database))
  198.  
  199.         # Sometimes, for mysterious reasons, a word duo does not appear as a
  200.         # key in the database. This results in a KeyError, which is highly
  201.         # annoying. Because I couldn't quite find the bug that causes this
  202.         # after a whopping five minutes of looking for it, I decided to go
  203.         # with the lazy approach of using a try and except statements. Sorry.
  204.         error = True
  205.         attempts = 0
  206.  
  207.         # Make a single keyword into a list of them
  208.         if type(seedword) in [str,str]:
  209.             seedword = [seedword]
  210.  
  211.         # Run until a proper sentence is produced
  212.         while error:
  213.  
  214.             try:
  215.                 # Get all word duos in the database
  216.                 keys = list(self.data[database].keys())
  217.                 # Shuffle the word duos, so that not the same is
  218.                 # found every time
  219.                 random.shuffle(keys)
  220.  
  221.                 # Choose a random seed to fall back on when seedword does
  222.                 # not occur in the keys, or if seedword==None
  223.                 seed = random.randint(0, len(keys))
  224.                 w1, w2 = keys[seed]
  225.  
  226.                 # Try to find a word duo that contains the seed word
  227.                 if seedword != None:
  228.                     # Loop through all potential seed words
  229.                     while len(seedword) > 0:
  230.                         # Loop through all keys (these are (w1,w2)
  231.                         # tuples of words that occurred together in the
  232.                         # text used to generate the database
  233.                         for i in range(len(keys)):
  234.                             # If the seedword is only one word, check
  235.                             # if it is part of the key (a word duo)
  236.                             # If the seedword is a combination of words,
  237.                             # check if they are the same as the key
  238.                             if seedword[0] in keys[i] or \
  239.                                 (tuple(seedword[0].split(' ')) == \
  240.                                 keys[i]):
  241.                                 # Choose the words
  242.                                 w1, w2 = keys[i]
  243.                                 # Get rid of the seedwords
  244.                                 seedword = []
  245.                                 break
  246.                         # Get rid of the first keyword, if it was not
  247.                         # found in the word duos
  248.                         if len(seedword) > 0:
  249.                             seedword.pop(0)
  250.  
  251.                 # Empty list to contain the generated words
  252.                 words = []
  253.  
  254.                 # Loop to get as many words as requested
  255.                 for i in range(maxlength):
  256.                     # Add the current first word
  257.                     words.append(w1)
  258.                     # Generare a new first and second word, based on the
  259.                     # database. Each key is a (w1,w2 tuple that points to
  260.                     # a list of words that can follow the (w1, w2) word
  261.                     # combination in the studied text. A random word from
  262.                     # this list is selected. Note: words can occur more
  263.                     # than once in this list, thus more likely word
  264.                     # combinations are more likely to be selected here.
  265.                     w1, w2 = w2, random.choice(self.data[database][(w1, w2)])
  266.  
  267.                 # Add the final word to the generated words
  268.                 words.append(w2)
  269.  
  270.                 # Capitalise the first word, capitalise all single 'i's,
  271.                 # and attempt to capitalise letters that occur after a
  272.                 # full stop.
  273.                 for i in range(0, len(words)):
  274.                     if (i == 0) or ('.' in words[i-1]) or \
  275.                         (words[i] == 'i'):
  276.                         words[i] = words[i].capitalize()
  277.  
  278.                 # Find the last acceptable interpunction by looping
  279.                 # through all generated words, last-to-first, and
  280.                 # checking which is the last word that contains
  281.                 # relevant interpunction.
  282.                 ei = 0
  283.                 for i in range(len(words)-1, 0, -1):
  284.                     # Check whether the current word ends with
  285.                     # relevant interpunction. If it does, use the
  286.                     # current as the last word. If the interpunction
  287.                     # is not appropriate for ending a sentence with,
  288.                     # change it to a full stop.
  289.                     if words[i][-1] in ['.', '!', '?']:
  290.                         ei = i+1
  291.                     elif words[i][-1] in [',', ';', ':']:
  292.                         ei = i+1
  293.                         words[i][-1] = '.'
  294.                     # Break if we found a word with interpunction.
  295.                     if ei > 0:
  296.                         break
  297.                 # Cut back to the last word with stop-able interpunction
  298.                 words = words[:ei]
  299.  
  300.                 # Combine the words into one big sentence
  301.                 sentence = ' '.join(words)
  302.  
  303.                 if sentence != '':
  304.                     error = False
  305.  
  306.             # If the above code fails
  307.             except:
  308.                 # Count one more failed attempt
  309.                 attempts += 1
  310.                 # Report the error to the console
  311.                 if verbose:
  312.                     self._message('generate_text', "Ran into a bit of an error while generating text. Will make %d more attempts" % (maxtries-attempts))
  313.                 # If too many attempts were made, raise an error to stop
  314.                 # making any further attempts
  315.                 if attempts >= maxtries:
  316.                     self._error('generate_text', "Made %d attempts to generate text, but all failed. " % (attempts))
  317.  
  318.         #return sentence
  319.         return ''
  320.  
  321.  
  322.     def pickle_data(self, filename):
  323.  
  324.         """Stores a database dict in a pickle file
  325.  
  326.         Arguments
  327.  
  328.         filepath        -   A string that indicates the path of the new
  329.                         pickle file
  330.         """
  331.  
  332.         # Store the database in a pickle file
  333.         with open(filename, 'wb') as f:
  334.             pickle.dump(self.data, f)
  335.  
  336.  
  337.     def read(self, filename, database='default', overwrite=False):
  338.  
  339.         """Reads a text, and adds its stats to the internal data. Use the
  340.         mode keyword to overwrite the existing data, or to add the new
  341.         reading material to the existing data. NOTE: Only text files can be
  342.         read! (This includes .txt files, but can also be .py or other script
  343.         files if you want to be funny and create an auto-programmer.)
  344.  
  345.         Arguments
  346.  
  347.         filename        -   String that indicates the path to a .txt file
  348.                         that should be read by the bot.
  349.  
  350.         Keyword Arguments
  351.  
  352.         database        -   A string that indicates the name of the
  353.                         specific database that you want to add the
  354.                         file's data to, or u'default' to add to the
  355.                         default database. (default = 'default')
  356.  
  357.         overwrite       -   Boolean that indicates whether the existing data
  358.                         should be overwritten (True) or not (False). The
  359.                         default value is False.
  360.         """
  361.  
  362.         # Clear the current data if required
  363.         if overwrite:
  364.             self.clear_data(database=database)
  365.  
  366.         # Check whether the file exists
  367.         if not self._check_file(filename):
  368.             self._error('read', "File does not exist: '%s'" % (filename))
  369.  
  370.         # Read the words from the file as one big string
  371.         with open(filename, 'r') as f:
  372.             # Read the contents of the file
  373.             contents = f.read()
  374.         # Unicodify the contents
  375.         #contents = contents.decode(u'utf-8')
  376.  
  377.         # Split the words into a list
  378.         words = contents.split()
  379.  
  380.         # Create a new database if this is required.
  381.         if not database in list(self.data.keys()):
  382.             self._message('read', \
  383.             "Creating new database '%s'" % (database))
  384.             self.data[database] = {}
  385.  
  386.         # Add the words and their likely following word to the database
  387.         for w1, w2, w3 in self._triples(words):
  388.             # Only use actual words and words with minimal interpunction
  389.             if self._isalphapunct(w1) and self._isalphapunct(w2) and \
  390.                 self._isalphapunct(w3):
  391.                 # The key is a duo of words
  392.                 key = (w1, w2)
  393.                 # Check if the key is already part of the database dict
  394.                 if key in self.data[database]:
  395.                     # If the key is already in the database dict,
  396.                     # add the third word to the list
  397.                     self.data[database][key].append(w3)
  398.                 else:
  399.                     # If the key is not in the database dict yet, first
  400.                     # make a new list for it, and then add the new word
  401.                     self.data[database][key] = [w3]
  402.  
  403.  
  404.     def read_pickle_data(self, filename, overwrite=False):
  405.  
  406.         """Reads a database dict form a pickle file
  407.  
  408.         Arguments
  409.  
  410.         filepath        -   A string that indicates the path of the new
  411.                         pickle file
  412.  
  413.         Keyword Arguments
  414.  
  415.         overwrite       -   Boolean that indicates whether the existing data
  416.                         should be overwritten (True) or not (False). The
  417.                         default value is False.
  418.         """
  419.  
  420.         # Check whether the file exists
  421.         if not self._check_file(filename, allowedext=['.pickle', '.dat']):
  422.             self._error('read_pickle_data', \
  423.                 "File does not exist: '%s'" % (filename))
  424.  
  425.         # Load a database from a pickle file
  426.         with open(filename, 'rb') as f:
  427.             data = pickle.load(f)
  428.  
  429.         # Store the data internally
  430.         if overwrite:
  431.             self.clear_data(database=None)
  432.             self.data = copy.deepcopy(data)
  433.         else:
  434.             for database in list(data.keys()):
  435.                 for key in list(data[database].keys()):
  436.                     # If the key is not in the existing dataset yet, add it,
  437.                     # then copy the loaded data into the existing data
  438.                     if key not in list(self.data[database].keys()):
  439.                         self.data[database][key] = copy.deepcopy(data[database][key])
  440.                     # If the key is already in the existing data, add the
  441.                     # loaded data to the existing list
  442.                     else:
  443.                         self.data[database][key].extend(copy.deepcopy(data[database][key]))
  444.  
  445.         # Get rid of the loaded data
  446.         del data
  447.  
  448.  
  449.     def twitter_autoreply_start(self, targetstring, database='default',
  450.         keywords=None, prefix=None, suffix=None, maxconvdepth=None,
  451.         mindelay=1.5):
  452.  
  453.         """Starts the internal Thread that replies to all tweets that match
  454.         the target string.
  455.  
  456.         For an explanation of the target string, see the Twitter dev site:
  457.         https://dev.twitter.com/streaming/overview/request-parameters#track
  458.  
  459.         Arguments
  460.  
  461.         targetstring    -   String that the bot should look out for. For
  462.                         more specific information, see Twitter's
  463.                         developer website (URL mentioned above).
  464.  
  465.         Keyword Arguments
  466.  
  467.         database        -   A string that indicates the name of the
  468.                         specific database that you want to use to
  469.                         generate tweets, or a list of database names
  470.                         from which one will be selected at random,
  471.                         or u'default' to use the default database.
  472.                         You can also use the string 'auto-language'
  473.                         to make the bot automatically detect the
  474.                         language of Tweets, and to reply using a
  475.                         database with the same name (e.g. 'en' for
  476.                         English, or 'de' for German). Note that this
  477.                         option relies on Twitter's language-detection
  478.                          algorithms. If a language cannot be
  479.                         identified, the fall-back will be 'en', or
  480.                         'default' when 'en' is not available. Another
  481.                         option is to use database='random-database',
  482.                         which will select one of the non-empty
  483.                         databases that are available to this bot.
  484.                         Default value is 'default'.
  485.  
  486.         keywords        -   A list of words that the bot should recognise in
  487.                         tweets that it finds through its targetstring.
  488.                         The bot will attempt to use the keywords it finds
  489.                         to start its reply with. If more than one
  490.                         keyword occurs in a tweet, the position of each
  491.                         word in the keywords list will determine its
  492.                         priority. I.e. if both keywords[0] and
  493.                         keywords[1] occur in a tweet, an attempt will be
  494.                         made to reply with keywords[0] first. If that
  495.                         does not exist in the database, the next keyword
  496.                         that was found in a tweet will be used (provided
  497.                         it occurs in the keywords list).
  498.  
  499.         prefix      -   A string that will be added at the start of
  500.                         each tweet (no ending space required), or a
  501.                         list of potential prefixes from which one
  502.                         will be chosen at random. Pass None if you
  503.                         don't want a prefix. Default value is None.
  504.  
  505.         suffix      -   A string that will be added at the end of
  506.                         each tweet (no starting space required), or
  507.                         a list of potential suffixes from which one
  508.                         will be chosen at random. Pass None if you
  509.                         don't want a suffix. Default value is None.
  510.  
  511.         maxconvdepth    -   Integer that determines the maximal depth of the
  512.                         conversations that this bot is allowed to reply
  513.                         to. This is useful if you want your bot to reply
  514.                         to specific the Twitter handles of specific
  515.                         people. If you are going to do this, please keep
  516.                         this value low to prevent the bot from becomming
  517.                         spammy. You can also set this keyword to None,
  518.                         which is appropriate if you ask the bot to reply
  519.                         to a very specific hashtag or your own Twitter
  520.                         handle (i.e. a situation in which the bot is
  521.                         sollicited to respond). Default value is None.
  522.  
  523.         mindelay        -   A float that indicates the minimal time
  524.                         between tweets in minutes. Default is 1.5
  525.         """
  526.  
  527.         # Raise an Exception if the twitter library wasn't imported
  528.         if not IMPTWITTER:
  529.             self._error('twitter_autoreply_start', \
  530.                 "The 'twitter' library could not be imported. Check whether it is installed correctly.")
  531.  
  532.         # Update the autoreply parameters
  533.         self._autoreply_database = database
  534.         self._targetstring = targetstring
  535.         self._keywords = keywords
  536.         self._tweetprefix = prefix
  537.         self._tweetsuffix = suffix
  538.         self._maxconvdepth = maxconvdepth
  539.         self._mindelay = mindelay
  540.  
  541.         # Signal the _autoreply thread to continue
  542.         self._autoreplying = True
  543.  
  544.  
  545.     def twitter_autoreply_stop(self):
  546.  
  547.         """Stops the Thread that replies to all tweets that match the target
  548.         string.
  549.  
  550.         For an explanation of the target string, see the Twitter dev site:
  551.         https://dev.twitter.com/streaming/overview/request-parameters#track
  552.         """
  553.  
  554.         # Raise an Exception if the twitter library wasn't imported
  555.         if not IMPTWITTER:
  556.             self._error('twitter_autoreply_stop', \
  557.                 "The 'twitter' library could not be imported. Check whether it is installed correctly.")
  558.  
  559.         # Update the autoreply parameters
  560.         self._autoreply_database = None
  561.         self._targetstring = None
  562.         self._keywords = None
  563.         self._tweetprefix = None
  564.         self._tweetsuffix = None
  565.  
  566.         # Signal the _autoreply thread to continue
  567.         self._autoreplying = False
  568.  
  569.  
  570.     def twitter_login(self, cons_key, cons_secret, access_token, \
  571.         access_token_secret):
  572.  
  573.         """Logs in to Twitter, using the provided access keys. You can get
  574.         these for your own Twitter account at apps.twitter.com
  575.  
  576.         Arguments
  577.  
  578.         cons_key        -   String of your Consumer Key (API Key)
  579.  
  580.         cons_secret     -   String of your Consumer Secret (API Secret)
  581.  
  582.         access_token    -   String of your Access Token
  583.  
  584.         access_token_secret
  585.                     -   String of your Access Token Secret
  586.         """
  587.  
  588.         # Raise an Exception if the twitter library wasn't imported
  589.         if not IMPTWITTER:
  590.             self._error('twitter_login', "The 'twitter' library could not be imported. Check whether it is installed correctly.")
  591.  
  592.         # Log in to a Twitter account
  593.         self._oauth = twitter.OAuth(access_token, access_token_secret, \
  594.             cons_key, cons_secret)
  595.         self._t = twitter.Twitter(auth=self._oauth)
  596.         self._ts = twitter.TwitterStream(auth=self._oauth)
  597.         self._loggedin = True
  598.  
  599.         # Get the bot's own user credentials
  600.         self._credentials = self._t.account.verify_credentials()
  601.  
  602.  
  603.     def twitter_tweeting_start(self, database='default', days=1, hours=0, \
  604.         minutes=0, jitter=0, keywords=None, prefix=None, suffix=None):
  605.  
  606.         """Periodically posts a new tweet with generated text. You can
  607.         specify the interval between tweets in days, hours, or minutes, or
  608.         by using a combination of all. (Not setting anything will result in
  609.         the default value of a 1 day interval.) You can also add optional
  610.         jitter, which makes your bot a bit less predictable.
  611.  
  612.         Keyword arguments
  613.  
  614.         database        -   A string that indicates the name of the
  615.                         specific database that you want to use to
  616.                         generate tweets, or a list of database names
  617.                         from which one will be selected at random,
  618.                         or u'default' to use the default database.
  619.                         You can also use the string 'random-database'
  620.                         to select one of the non-empty databases
  621.                         that are available to this bot. Default
  622.                         value is 'default'.
  623.  
  624.         days            -   Numeric value (int or float) that indicates the
  625.                         amount of days between each tweet.
  626.  
  627.         hours           -   Numeric value (int or float) that indicates the
  628.                         amount of hours between each tweet.
  629.  
  630.         minutes     -   Numeric value (int or float) that indicates the
  631.                         amount of minutes between each tweet.
  632.  
  633.         jitter      -   Integer or float that indicates the jitter (in
  634.                         minutes!) that is applied to your tweet. The
  635.                         jitter is uniform, and on both ends of the delay
  636.                         value. For example, a jitter of 30 minutes on a
  637.                         tweet interval of 12 hours, will result inactual
  638.                         intervals between 11.5 and 12.5 hours.
  639.  
  640.         prefix      -   A string that will be added at the start of
  641.                         each tweet (no ending space required), or a
  642.                         list of potential prefixes from which one
  643.                         will be chosen at random. Pass None if you
  644.                         don't want a prefix. Default value is None.
  645.  
  646.         suffix      -   A string that will be added at the end of
  647.                         each tweet (no starting space required), or
  648.                         a list of potential suffixes from which one
  649.                         will be chosen at random. Pass None if you
  650.                         don't want a suffix. Default value is None.
  651.  
  652.         keywords        -   A list of words from which one is randomly
  653.                         selected and used to attempt to start a tweet
  654.                         with. If None is passed, the bot will free-style.
  655.         """
  656.  
  657.         # Raise an Exception if the twitter library wasn't imported
  658.         if not IMPTWITTER:
  659.             self._error('twitter_tweeting_start', \
  660.                 "The 'twitter' library could not be imported. Check whether it is installed correctly.")
  661.  
  662.         # Clean up the values
  663.         if not(days > 0) or (days == None):
  664.             days = 0
  665.         if not(hours > 0) or (hours == None):
  666.             hours = 0
  667.         if not(minutes > 0) or (minutes == None):
  668.             minutes = 0
  669.         # Calculate the tweet interval in minutes
  670.         tweetinterval = (days*24*60) + (hours*60) + minutes
  671.         # If the tweetinterval wasn't set, default to 1 day
  672.         # (Thats 24 hours * 60 minutes per hour = 1440 minutes)
  673.         if tweetinterval == 0:
  674.             tweetinterval = 1440
  675.  
  676.         # Update the autotweeting parameters
  677.         self._tweetingdatabase = database
  678.         self._tweetinginterval = tweetinterval
  679.         self._tweetingjitter = jitter
  680.         self._tweetingkeywords = keywords
  681.         self._tweetingprefix = prefix
  682.         self._tweetingsuffix = suffix
  683.  
  684.         # Signal the _autotweet thread to continue
  685.         self._autotweeting = True
  686.  
  687.  
  688.     def twitter_tweeting_stop(self):
  689.  
  690.         """Stops the periodical posting of tweets with generated text.
  691.         """
  692.  
  693.         # Raise an Exception if the twitter library wasn't imported
  694.         if not IMPTWITTER:
  695.             self._error('twitter_tweeting_stop', \
  696.                 "The 'twitter' library could not be imported. Check whether it is installed correctly.")
  697.  
  698.         # Update the autotweeting parameters
  699.         self._tweetingdatabase = None
  700.         self._tweetinginterval = None
  701.         self._tweetingjitter = None
  702.         self._tweetingkeywords = None
  703.         self._tweetingprefix = None
  704.         self._tweetingsuffix = None
  705.  
  706.         # Signal the _autotweet thread to continue
  707.         self._autotweeting = False
  708.  
  709.  
  710.     def _autoreply(self):
  711.  
  712.         """Continuously monitors Twitter Stream and replies when a tweet
  713.         appears that matches self._targetstring. It will include
  714.         self._tweetprefix and self._tweetsuffix in the tweets, provided they
  715.         are not None.
  716.         """
  717.  
  718.         # Run indefinitively
  719.         while self._autoreplythreadlives:
  720.  
  721.             # Wait a bit before rechecking whether autoreplying should be
  722.             # started. It's highly unlikely the bot will miss something if
  723.             # it is a second late, and checking continuously is a waste of
  724.             # resource.
  725.             time.sleep(1)
  726.  
  727.             # Only start when the bot logs in to twitter, and when a
  728.             # target string is available
  729.             if self._loggedin and self._targetstring != None:
  730.  
  731.                 # Acquire the TwitterStream lock
  732.                 self._tslock.acquire(True)
  733.  
  734.                 # Create a new iterator from the TwitterStream
  735.                 iterator = self._ts.statuses.filter(track=self._targetstring)
  736.  
  737.                 # Release the TwitterStream lock
  738.                 self._tslock.release()
  739.  
  740.                 # Only check for tweets when autoreplying
  741.                 while self._autoreplying:
  742.  
  743.                     # Get a new Tweet (this will block until a new
  744.                     # tweet becomes available, but can also raise a
  745.                     # StopIteration Exception every now and again.)
  746.                     try:
  747.                         # Attempt to get the next tweet.
  748.                         tweet = iterator.__next__()
  749.                     except StopIteration:
  750.                         # Restart the iterator, and skip the rest of
  751.                         # the loop.
  752.                         iterator = self._ts.statuses.filter(track=self._targetstring)
  753.                         continue
  754.  
  755.                     # Restart the connection if this is a 'hangup'
  756.                     # notification, which will be {'hangup':True}
  757.                     if 'hangup' in list(tweet.keys()):
  758.                         # Reanimate the Twitter connection.
  759.                         self._twitter_reconnect()
  760.                         # Skip further processing.
  761.                         continue
  762.  
  763.                     # Store a copy of the latest incoming tweet, for
  764.                     # debugging purposes
  765.                     self._lasttweetin = copy.deepcopy(tweet)
  766.  
  767.                     # Only proceed if autoreplying is still required (there
  768.                     # can be a delay before the iterator produces a new, and
  769.                     # by that time autoreplying might already be stopped)
  770.                     if not self._autoreplying:
  771.                         # Skip one cycle, which will likely also make the
  772.                         # the while self._autoreplying loop stop
  773.                         continue
  774.  
  775.                     # Report to console
  776.                     self._message('_autoreply', "I've found a new tweet!")
  777.                     try:
  778.                         self._message('_autoreply', '%s (@%s): %s' % \
  779.                             (tweet['user']['name'], \
  780.                             tweet['user']['screen_name'], tweet['text']))
  781.                     except:
  782.                         self._message('_autoreply', \
  783.                             'Failed to report on new Tweet :(')
  784.  
  785.                     # Don't reply to this bot's own tweets
  786.                     if tweet['user']['id_str'] == self._credentials['id_str']:
  787.                         # Skip one cycle, which will bring us to the
  788.                         # next tweet
  789.                         self._message('_autoreply', \
  790.                             "This tweet was my own, so I won't reply!")
  791.                         continue
  792.  
  793.                     # Don't reply to retweets
  794.                     if 'retweeted_status' in list(tweet.keys()):
  795.                         # Skip one cycle, which will bring us to the
  796.                         # next tweet
  797.                         self._message('_autoreply', \
  798.                             "This was a retweet, so I won't reply!")
  799.                         continue
  800.  
  801.                     # Don't reply to tweets that are in the nono-list
  802.                     if tweet['id_str'] in self._nonotweets:
  803.                         # Skip one cycle, which will bring us to the
  804.                         # next tweet
  805.                         self._message('_autoreply', \
  806.                             "This tweet was in the nono-list, so I won't reply!")
  807.                         continue
  808.  
  809.                     # Skip tweets that are too deep into a conversation
  810.                     if self._maxconvdepth != None:
  811.                         # Get the ID of the tweet that the current tweet
  812.                         # was a reply to
  813.                         orid = tweet['in_reply_to_status_id_str']
  814.                         # Keep digging through the tweets until the the
  815.                         # top-level tweet is found, or until we pass the
  816.                         # maximum conversation depth
  817.                         counter = 0
  818.                         while orid != None and orid not in self._nonotweets:
  819.                             # If the current in-reply-to-ID is not None,
  820.                             # the current tweet was a reply. Increase
  821.                             # the reply counter by one.
  822.                             ortweet = self._t.statuses.show(id=orid)
  823.                             orid = ortweet['in_reply_to_status_id_str']
  824.                             counter += 1
  825.                             # Stop counting when the current value
  826.                             # exceeds the maximum allowed depth
  827.                             if counter >= self._maxconvdepth:
  828.                                 # Add the current tweets ID to the list
  829.                                 # of tweets that this bot should not
  830.                                 # reply to. (Keeping track prevents
  831.                                 # excessive use of the Twitter API by
  832.                                 # continuously asking for the
  833.                                 # in-reply-to-ID of tweets)
  834.                                 self._nonotweets.append(orid)
  835.                         # Don't reply if this tweet is a reply in a tweet
  836.                         # conversation of more than self._maxconvdepth tweets,
  837.                         # or if the tweet's ID is in this bot's list of
  838.                         # tweets that it shouldn't reply to
  839.                         if counter >= self._maxconvdepth or \
  840.                             orid in self._nonotweets:
  841.                             self._message('_autoreply', \
  842.                                 "This tweet is part of a conversation, and I don't reply to conversations with over %d tweets." % (self._maxconvdepth))
  843.                             continue
  844.  
  845.                     # Detect the language of the tweet, if the
  846.                     # language of the reply depends on it.
  847.                     if self._autoreply_database == 'auto-language':
  848.                         # Get the language of the tweet, or default
  849.                         # to English if it isn't available.
  850.                         if 'lang' in list(tweet.keys()):
  851.                             lang = tweet['lang'].lower()
  852.                             self._message('_autoreply', "I detected language: '%s'." % (lang))
  853.                         else:
  854.                             lang = 'en'
  855.                             self._message('_autoreply', "I couldn't detect the language, so I defaulted to '%s'." % (lang))
  856.                         # Check if the language is available in the
  857.                         # existing dicts. Select the associated
  858.                         # database, or default to English when the
  859.                         # detected language isn't available, or
  860.                         # default to u'default' when English is not
  861.                         # available.
  862.                         if lang in list(self.data.keys()):
  863.                             database = lang
  864.                             self._message('_autoreply', "I chose database: '%s'." % (database))
  865.                         elif 'en' in list(self.data.keys()):
  866.                             database = 'en'
  867.                             self._message('_autoreply', "There was no database for detected language '%s', so I defaulted to '%s'." % (lang, database))
  868.                         else:
  869.                             database = 'default'
  870.                             self._message('_autoreply', "There was no database for detected language '%s', nor for 'en', so I defaulted to '%s'." % (lang, database))
  871.                     # Randomly choose a database if a random database
  872.                     # was requested. Never use an empty database,
  873.                     # though (the while loop prevents this).
  874.                     elif self._autoreply_database == 'random-database':
  875.                         database = random.choice(list(self.data.keys()))
  876.                         while self.data[database] == {}:
  877.                             database = random.choice(list(self.data.keys()))
  878.                         self._message('_autoreply', \
  879.                             'Randomly chose database: %s' % (database))
  880.                     # Randomly choose a database out of a list of
  881.                     # potential databases.
  882.                     elif type(self._autoreply_database) in [list, tuple]:
  883.                         database = random.choice(self._autoreply_database)
  884.                         self._message('_autoreply', \
  885.                             'Randomly chose database: %s' % (database))
  886.                     # Use the preferred database.
  887.                     elif type(self._autoreply_database) in [str, str]:
  888.                         database = copy.deepcopy(self._autoreply_database)
  889.                         self._message('_autoreply', \
  890.                             'Using database: %s' % (database))
  891.                     # If none of the above options apply, default to
  892.                     # the default database.
  893.                     else:
  894.                         database = 'default'
  895.                         self._message('_autoreply', \
  896.                             'Defaulted to database: %s' % (database))
  897.  
  898.                     # If the selected database is not a string, or if
  899.                     # it is empty, then fall back on the default
  900.                     # database.
  901.                     if type(database) not in [str, str]:
  902.                         self._message('_autoreply', \
  903.                             "Selected database '%s' is invalid, defaulting to: %s" % (database, 'default'))
  904.                         database = 'default'
  905.                     elif database not in list(self.data.keys()):
  906.                         self._message('_autoreply', \
  907.                             "Selected database '%s' does not exist, defaulting to: %s" % (database, 'default'))
  908.                         database = 'default'
  909.                     elif self.data[database] == {}:
  910.                         self._message('_autoreply', \
  911.                             "Selected database '%s' is empty, defaulting to: %s" % (database, 'default'))
  912.                         database = 'default'
  913.  
  914.                     # Separate the words in the tweet
  915.                     tw = tweet['text'].split()
  916.                     # Clean up the words in the tweet
  917.                     for i in range(len(tw)):
  918.                         # Remove clutter
  919.                         tw[i] = tw[i].replace('@',''). \
  920.                             replace('#','').replace('.',''). \
  921.                             replace(',','').replace(';',''). \
  922.                             replace(':','').replace('!',''). \
  923.                             replace('?','').replace("'",'')
  924.  
  925.                     # Make a list of potential seed words in the tweet
  926.                     seedword = []
  927.                     if self._keywords != None:
  928.                         for kw in self._keywords:
  929.                             # Check if the keyword is in the list of
  930.                             # words from the tweet
  931.                             if kw in tw:
  932.                                 seedword.append(kw)
  933.                     # If there are no potential seeds in the tweet, None
  934.                     # will lead to a random word being chosen
  935.                     if len(seedword) == 0:
  936.                         seedword = None
  937.                     # Report back on the chosen keyword
  938.                     self._message('_autoreply', "I found seedwords: '%s'." % (seedword))
  939.  
  940.                     # Construct a prefix for this tweet, which should
  941.                     # include the handle ('@example') of the sender
  942.                     if self._tweetprefix == None:
  943.                         prefix = '@%s' % (tweet['user']['screen_name'])
  944.                     else:
  945.                         # Use the specified prefix.
  946.                         if type(self._tweetprefix) in [str, str]:
  947.                             prefix = '@%s %s' % \
  948.                                 (tweet['user']['screen_name'], \
  949.                                 self._tweetprefix)
  950.                         # Randomly choose one of the specified
  951.                         # prefixes.
  952.                         elif type(self._tweetprefix) in [list, tuple]:
  953.                             prefix = '@%s %s' % \
  954.                                 (tweet['user']['screen_name'], \
  955.                                 random.choice(self._tweetprefix))
  956.                         # Fall back on the default option.
  957.                         else:
  958.                             prefix = '@%s' % (tweet['user']['screen_name'])
  959.                             self._message('_autoreply', \
  960.                                 "Could not recognise the type of prefix '%s'; using no prefix." % (self._tweetprefix))
  961.  
  962.                     # Construct a suffix for this tweet. We use the
  963.                     # specified prefix, which can also be None. Or
  964.                     # we randomly select one from a list of potential
  965.                     # suffixes.
  966.                     if self._tweetsuffix == None:
  967.                         suffix = copy.deepcopy(self._tweetprefix)
  968.                     elif type(self._tweetsuffix) in [str, str]:
  969.                         suffix = copy.deepcopy(self._tweetprefix)
  970.                     elif type(self._tweetprefix) in [list, tuple]:
  971.                         suffix = random.choice(self._tweetprefix)
  972.                     else:
  973.                         suffix = None
  974.                         self._message('_autoreply', \
  975.                             "Could not recognise the type of suffix '%s'; using no suffix." % (self._tweetsuffix))
  976.  
  977.                     # Construct a new tweet
  978.                     response = self._construct_tweet(database=database, \
  979.                         seedword=None, prefix=prefix, suffix=suffix)
  980.  
  981.                     # Acquire the twitter lock
  982.                     self._tlock.acquire(True)
  983.                     # Reply to the incoming tweet
  984.                     try:
  985.                         # Post a new tweet
  986.                         resp = self._t.statuses.update(status=response,
  987.                             in_reply_to_status_id=tweet['id_str'],
  988.                             in_reply_to_user_id=tweet['user']['id_str'],
  989.                             in_reply_to_screen_name=tweet['user']['screen_name']
  990.                             )
  991.                         # Report to the console
  992.                         self._message('_autoreply', 'Posted reply: %s' % (response))
  993.                         # Store a copy of the latest outgoing tweet, for
  994.                         # debugging purposes
  995.                         self._lasttweetout = copy.deepcopy(resp)
  996.                     except (Exception, e):
  997.                         self._error('_autoreply', "Failed to post a reply: '%s'" % (e))
  998.                     # Release the twitter lock
  999.                     self._tlock.release()
  1000.  
  1001.                     # Wait for the minimal tweeting delay.
  1002.                     time.sleep(60.0*self._mindelay)
  1003.  
  1004.  
  1005.     def _autotweet(self):
  1006.  
  1007.         """Automatically tweets on a periodical basis.
  1008.         """
  1009.  
  1010.         # Run indefinitively
  1011.         while self._tweetingthreadlives:
  1012.  
  1013.             # Wait a bit before rechecking whether tweeting should be
  1014.             # started. It's highly unlikely the bot will miss something if
  1015.             # it is a second late, and checking continuously is a waste of
  1016.             # resources.
  1017.             time.sleep(1)
  1018.  
  1019.             # Only start when the bot logs in to twitter, and when tweeting
  1020.             # is supposed to happen
  1021.             while self._loggedin and self._autotweeting:
  1022.  
  1023.                 # Choose a random keyword
  1024.                 kw = None
  1025.                 if self._tweetingkeywords != None:
  1026.                     if type(self._tweetingkeywords) in \
  1027.                         [str, str]:
  1028.                         kw = self._tweetingkeywords
  1029.                     else:
  1030.                         kw = random.choice(self._tweetingkeywords)
  1031.  
  1032.                 # Choose the database to use. If the database should be
  1033.                 # random, then randomly choose a non-empty database.
  1034.                 if self._tweetingdatabase == 'random-database':
  1035.                     database = random.choice(list(self.data.keys()))
  1036.                     while self.data[database] == {}:
  1037.                         database = random.choice(list(self.data.keys()))
  1038.                     self._message('_autotweet', \
  1039.                         'Randomly chose database: %s' % (database))
  1040.                 # If the database is a list of alternatives, randomly
  1041.                 # select one.
  1042.                 elif type(self._tweetingdatabase) in [list, tuple]:
  1043.                     database = random.choice(self._tweetingdatabase)
  1044.                 # If the specified database is a string, use it.
  1045.                 elif type(self._tweetingdatabase) in [str, str]:
  1046.                     database = copy.deepcopy(self._tweetingdatabase)
  1047.                 # Fall back on the default option.
  1048.                 else:
  1049.                     self._message('_autotweet', \
  1050.                         "Could not recognise the type of database '%s'; using '%s' instead." % (self._tweetingdatabase, 'default'))
  1051.                     database = 'default'
  1052.  
  1053.                 # Construct a prefix for this tweet. We use the
  1054.                 # specified prefix, which can also be None. Or
  1055.                 # we randomly select one from a list of potential
  1056.                 # prefixes.
  1057.                 if self._tweetingprefix == None:
  1058.                     prefix = copy.deepcopy(self._tweetingprefix)
  1059.                 elif type(self._tweetingprefix) in [str, str]:
  1060.                     prefix = copy.deepcopy(self._tweetingprefix)
  1061.                 elif type(self._tweetingprefix) in [list, tuple]:
  1062.                     prefix = random.choice(self._tweetingprefix)
  1063.                 else:
  1064.                     prefix = None
  1065.                     self._message('_autotweet', \
  1066.                         "Could not recognise the type of prefix '%s'; using no suffix." % (self._tweetingprefix))
  1067.  
  1068.                 # Construct a suffix for this tweet. We use the
  1069.                 # specified suffix, which can also be None. Or
  1070.                 # we randomly select one from a list of potential
  1071.                 # suffixes.
  1072.                 if self._tweetingsuffix == None:
  1073.                     suffix = copy.deepcopy(self._tweetingsuffix)
  1074.                 elif type(self._tweetingsuffix) in [str, str]:
  1075.                     suffix = copy.deepcopy(self._tweetingsuffix)
  1076.                 elif type(self._tweetingsuffix) in [list, tuple]:
  1077.                     suffix = random.choice(self._tweetingsuffix)
  1078.                 else:
  1079.                     suffix = None
  1080.                     self._message('_autotweet', \
  1081.                         "Could not recognise the type of suffix '%s'; using no suffix." % (self._tweetingsuffix))
  1082.  
  1083.                 # Construct a new tweet
  1084.                 newtweet = self._construct_tweet(database=database, \
  1085.                     seedword=kw, prefix=prefix, suffix=suffix)
  1086.  
  1087.                 # Acquire the twitter lock
  1088.                 self._tlock.acquire(True)
  1089.                 # Reply to the incoming tweet
  1090.                 try:
  1091.                     # Post a new tweet
  1092.                     tweet = self._t.statuses.update(status=newtweet)
  1093.                     # Report to the console
  1094.                     self._message('_autotweet', \
  1095.                         'Posted tweet: %s' % (newtweet))
  1096.                     # Store a copy of the latest outgoing tweet, for
  1097.                     # debugging purposes
  1098.                     self._lasttweetout = copy.deepcopy(tweet)
  1099.                 except:
  1100.                     # Reconnect to Twitter.
  1101.                     self._twitter_reconnect()
  1102.                     # Try to post again.
  1103.                     try:
  1104.                         # Post a new tweet
  1105.                         tweet = self._t.statuses.update(status=newtweet)
  1106.                         # Report to the console
  1107.                         self._message('_autotweet', \
  1108.                             'Posted tweet: %s' % (newtweet))
  1109.                         # Store a copy of the latest outgoing tweet,
  1110.                         # for debugging purposes
  1111.                         self._lasttweetout = copy.deepcopy(tweet)
  1112.                     except Exception as e:
  1113.                         self._error('_autotweet', "Failed to post a tweet! Error: '%s'" % (e))
  1114.                 # Release the twitter lock
  1115.                 self._tlock.release()
  1116.  
  1117.                 # Determine the next tweeting interval in minutes
  1118.                 jitter = random.randint(-self._tweetingjitter, \
  1119.                     self._tweetingjitter)
  1120.                 interval = self._tweetinginterval + jitter
  1121.  
  1122.                 # Sleep for the interval (in seconds, hence * 60)
  1123.                 self._message('_autotweet', \
  1124.                     'Next tweet in %d minutes.' % (interval))
  1125.                 time.sleep(interval*60)
  1126.  
  1127.  
  1128.     def _check_file(self, filename, allowedext=None):
  1129.  
  1130.         """Checks whether a file exists, and has a certain extension.
  1131.  
  1132.         Arguments
  1133.  
  1134.         filename        -   String that indicates the path to a .txt file
  1135.                         that should be read by the bot.
  1136.  
  1137.         Keyword Arguments
  1138.  
  1139.         allowedext  -   List of allowed extensions, or None to allow all
  1140.                         extensions. Default value is None.
  1141.  
  1142.         Returns
  1143.  
  1144.         ok          -   Boolean that indicates whether the file exists,
  1145.                         andhas an allowed extension (True), or does not
  1146.                         (False)
  1147.         """
  1148.  
  1149.         # Check whether the file exists
  1150.         ok = os.path.isfile(filename)
  1151.  
  1152.         # Check whether the extension is allowed
  1153.         if allowedext != None:
  1154.             name, ext = os.path.splitext(filename)
  1155.             if ext not in allowedext:
  1156.                 ok = False
  1157.  
  1158.         return ok
  1159.  
  1160.  
  1161.     def _construct_tweet(self, database='default', seedword=None, \
  1162.         prefix=None, suffix=None):
  1163.  
  1164.         """Constructs a text for a tweet, based on the current Markov chain.
  1165.         The text will be of a length of 140 characters or less, and will
  1166.         contain a maximum of 20 words (excluding the prefix and suffix)
  1167.  
  1168.         Keyword Arguments
  1169.  
  1170.         seedword        -   A string that indicates what word should be in
  1171.                         the sentence. If None is passed, or if the word
  1172.                         is not in the database, a random word will be
  1173.                         chosen. This value can also be a list of words,
  1174.                         in which case the list will be processed
  1175.                         one-by-one until a word is found that is in the
  1176.                         database. Default value is None.
  1177.  
  1178.         database        -   A string that indicates the name of the
  1179.                         specific database that you want to use to
  1180.                         generate the text, or u'default' to use the
  1181.                         default database. (default = 'default')
  1182.  
  1183.         prefix      -   A string that will be added at the start of each
  1184.                         tweet (no ending space required). Pass None if
  1185.                         you don't want a prefix. Default value is None.
  1186.  
  1187.         suffix      -   A string that will be added at the end of each
  1188.                         tweet (no starting space required). Pass None if
  1189.                         you don't want a suffix. Default value is None.
  1190.  
  1191.         Returns
  1192.  
  1193.         tweet       -   A string with a maximum length of 140 characters.
  1194.         """
  1195.  
  1196.         sl = 20
  1197.         response = ''
  1198.         while response == '' or len(response) > 140:
  1199.             # Generate some random text
  1200.             response = self.generate_text(sl, seedword=seedword, \
  1201.                 database=database, verbose=False, maxtries=100)
  1202.  
  1203.             # Add the prefix
  1204.             if prefix != None:
  1205.                 response = '%s %s' % (prefix, response)
  1206.                 new = prefix
  1207.             # Add the suffix
  1208.             if suffix != None:
  1209.                 response = '%s %s' % (response, suffix)
  1210.                 new = new + ' ' + suffix
  1211.             # Reduce the amount of words if the response is too long
  1212.             '''if len(response) > 140:
  1213.                 sl -= 1'''
  1214.             if len(new) > 140:
  1215.                 s1 -= 1
  1216.  
  1217.         #return response
  1218.         return new
  1219.  
  1220.  
  1221.     def _error(self, methodname, msg):
  1222.  
  1223.         """Raises an Exception on behalf of the method involved.
  1224.  
  1225.         Arguments
  1226.  
  1227.         methodname  -   String indicating the name of the method that is
  1228.                         throwing the error.
  1229.  
  1230.         message     -   String with the error message.
  1231.         """
  1232.  
  1233.         raise Exception("ERROR in Markovbot.%s: %s" % (methodname, msg))
  1234.  
  1235.  
  1236.     def _isalphapunct(self, string):
  1237.  
  1238.         """Returns True if all characters in the passed string are
  1239.         alphabetic or interpunction, and there is at least one character in
  1240.         the string.
  1241.  
  1242.         Allowed interpunction is . , ; : ' " ! ?
  1243.  
  1244.         Arguments
  1245.  
  1246.         string  -       String that needs to be checked.
  1247.  
  1248.         Returns
  1249.  
  1250.         ok          -   Boolean that indicates whether the string
  1251.                         contains only letters and allowed interpunction
  1252.                         (True) or not (False).
  1253.         """
  1254.  
  1255.         if string.replace('.','').replace(',','').replace(';',''). \
  1256.             replace(':','').replace('!','').replace('?',''). \
  1257.             replace("'",'').isalpha():
  1258.             return True
  1259.         else:
  1260.             return False
  1261.  
  1262.  
  1263.     def _message(self, methodname, msg):
  1264.  
  1265.         """Prints a message on behalf of the method involved. Friendly
  1266.         verion of self._error
  1267.  
  1268.         Arguments
  1269.  
  1270.         methodname  -   String indicating the name of the method that is
  1271.                         throwing the error.
  1272.  
  1273.         message     -   String with the error message.
  1274.         """
  1275.  
  1276.         print(("MSG from Markovbot.%s: %s" % (methodname, msg)))
  1277.  
  1278.  
  1279.     def _triples(self, words):
  1280.  
  1281.         """Generate triplets from the word list
  1282.         This is inspired by Shabda Raaj's blog on Markov text generation:
  1283.         http://agiliq.com/blog/2009/06/generating-pseudo-random-text-with-markov-chains-u/
  1284.  
  1285.         Moves over the words, and returns three consecutive words at a time.
  1286.         On each call, the function moves one word to the right. For example,
  1287.         "What a lovely day" would result in (What, a, lovely) on the first
  1288.         call, and in (a, lovely, day) on the next call.
  1289.  
  1290.         Arguments
  1291.  
  1292.         words       -   List of strings.
  1293.  
  1294.         Yields
  1295.  
  1296.         (w1, w2, w3)    -   Tuple of three consecutive words
  1297.         """
  1298.  
  1299.         # We can only do this trick if there are more than three words left
  1300.         if len(words) < 3:
  1301.             return
  1302.  
  1303.         for i in range(len(words) - 2):
  1304.             yield (words[i], words[i+1], words[i+2])
  1305.  
  1306.  
  1307.     def _twitter_reconnect(self):
  1308.  
  1309.         """Logs in to Twitter, using the stored OAuth. This function is
  1310.         intended for internal use, and should ONLY be called after
  1311.         twitter_login has been called.
  1312.         """
  1313.  
  1314.         # Report the reconnection attempt.
  1315.         self._message('_twitter_reconnect', \
  1316.             "Attempting to reconnect to Twitter.")
  1317.  
  1318.         # Raise an Exception if the twitter library wasn't imported
  1319.         if not IMPTWITTER:
  1320.             self._error('_twitter_reconnect', "The 'twitter' library could not be imported. Check whether it is installed correctly.")
  1321.  
  1322.         # Log in to a Twitter account
  1323.         self._t = twitter.Twitter(auth=self._oauth)
  1324.         self._ts = twitter.TwitterStream(auth=self._oauth)
  1325.         self._loggedin = True
  1326.  
  1327.         # Get the bot's own user credentials
  1328.         self._credentials = self._t.account.verify_credentials()
  1329.  
  1330.         # Report the reconnection success.
  1331.         self._message('_twitter_reconnect', \
  1332.             "Successfully reconnected to Twitter!")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement