├── .gitignore ├── credentials.py ├── mybot.py ├── mybot2.py ├── mashup_markov ├── markov.py ├── markovgen.py └── markovmaker.py ├── respondingbot.py ├── mashup_madlib.py ├── README.md └── put-your-bot-on-DreamHost.md /.gitignore: -------------------------------------------------------------------------------- 1 | credentials.pyc 2 | credentials_lacunybot.py 3 | -------------------------------------------------------------------------------- /credentials.py: -------------------------------------------------------------------------------- 1 | # Credentials for your Twitter bot account 2 | 3 | # 1. Sign into Twitter or create new account 4 | # 2. Make sure your mobile number is listed at twitter.com/settings/devices 5 | # 3. Head to apps.twitter.com and select Keys and Access Tokens 6 | 7 | CONSUMER_KEY = 'XXXXXXX' 8 | CONSUMER_SECRET = 'XXXXXXX' 9 | 10 | # Create a new Access Token 11 | ACCESS_TOKEN = 'XXXXXXX' 12 | ACCESS_SECRET = 'XXXXXXX' 13 | -------------------------------------------------------------------------------- /mybot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Twitter Bot Starter Kit: Bot 1 5 | 6 | # This bot tweets three times, waiting 15 seconds between tweets. 7 | 8 | # If you haven't changed credentials.py yet with your own Twitter 9 | # account settings, this script will tweet at twitter.com/lacunybot 10 | 11 | # Housekeeping: do not edit 12 | import tweepy, time 13 | from credentials import * 14 | auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) 15 | auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET) 16 | api = tweepy.API(auth) 17 | 18 | 19 | # What the bot will tweet 20 | 21 | tweetlist = ['Test tweet one!', 'Test tweet two!', 'Test tweet three!'] 22 | 23 | for line in tweetlist: 24 | api.update_status(line) 25 | print line 26 | print '...' 27 | time.sleep(15) # Sleep for 15 seconds 28 | 29 | print "All done!" 30 | 31 | 32 | -------------------------------------------------------------------------------- /mybot2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Twitter Bot Starter Kit: Bot 2 5 | 6 | # This bot tweets a text file line by line, waiting a 7 | # given period of time between tweets. 8 | 9 | # Download a Project Gutenberg "Plain Text UTF-8" file, 10 | # open it in Notepad, remove junk at beginning, 11 | # and replace all double-linebreaks with single linebreaks. 12 | 13 | 14 | # Housekeeping: do not edit 15 | import tweepy, time 16 | from credentials import * 17 | auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) 18 | auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET) 19 | api = tweepy.API(auth) 20 | 21 | 22 | # What the bot will tweet 23 | 24 | filename = open('twain.txt','r') 25 | tweettext = filename.readlines() 26 | filename.close() 27 | 28 | for line in tweettext[0:5]: #Will only write first 5 lines 29 | api.update_status(line) 30 | print line 31 | time.sleep(15) # Sleep for 15 seconds 32 | 33 | print "All done!" 34 | 35 | # To quit early: CTRL+C and wait a few seconds 36 | -------------------------------------------------------------------------------- /mashup_markov/markov.py: -------------------------------------------------------------------------------- 1 | # from http://agiliq.com/blog/2009/06/generating-pseudo-random-text-with-markov-chains-u/ 2 | # copied October 28, 2015 3 | 4 | import random 5 | 6 | class Markov(object): 7 | 8 | def __init__(self, open_file): 9 | self.cache = {} 10 | self.open_file = open_file 11 | self.words = self.file_to_words() 12 | self.word_size = len(self.words) 13 | self.database() 14 | 15 | 16 | def file_to_words(self): 17 | self.open_file.seek(0) 18 | data = self.open_file.read() 19 | words = data.split() 20 | return words 21 | 22 | 23 | def triples(self): 24 | """ Generates triples from the given data string. So if our string were 25 | "What a lovely day", we'd generate (What, a, lovely) and then 26 | (a, lovely, day). 27 | """ 28 | 29 | if len(self.words) < 3: 30 | return 31 | 32 | for i in range(len(self.words) - 2): 33 | yield (self.words[i], self.words[i+1], self.words[i+2]) 34 | 35 | def database(self): 36 | for w1, w2, w3 in self.triples(): 37 | key = (w1, w2) 38 | if key in self.cache: 39 | self.cache[key].append(w3) 40 | else: 41 | self.cache[key] = [w3] 42 | 43 | def generate_markov_text(self, size=25): 44 | seed = random.randint(0, self.word_size-3) 45 | seed_word, next_word = self.words[seed], self.words[seed+1] 46 | w1, w2 = seed_word, next_word 47 | gen_words = [] 48 | for i in xrange(size): 49 | gen_words.append(w1) 50 | w1, w2 = w2, random.choice(self.cache[(w1, w2)]) 51 | gen_words.append(w2) 52 | return ' '.join(gen_words) 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /mashup_markov/markovgen.py: -------------------------------------------------------------------------------- 1 | # from http://agiliq.com/blog/2009/06/generating-pseudo-random-text-with-markov-chains-u/ 2 | # copied October 28, 2015 3 | 4 | import random 5 | 6 | class Markov(object): 7 | 8 | def __init__(self, open_file): 9 | self.cache = {} 10 | self.open_file = open_file 11 | self.words = self.file_to_words() 12 | self.word_size = len(self.words) 13 | self.database() 14 | 15 | 16 | def file_to_words(self): 17 | self.open_file.seek(0) 18 | data = self.open_file.read() 19 | words = data.split() 20 | return words 21 | 22 | 23 | def triples(self): 24 | """ Generates triples from the given data string. So if our string were 25 | "What a lovely day", we'd generate (What, a, lovely) and then 26 | (a, lovely, day). 27 | """ 28 | 29 | if len(self.words) < 3: 30 | return 31 | 32 | for i in range(len(self.words) - 2): 33 | yield (self.words[i], self.words[i+1], self.words[i+2]) 34 | 35 | def database(self): 36 | for w1, w2, w3 in self.triples(): 37 | key = (w1, w2) 38 | if key in self.cache: 39 | self.cache[key].append(w3) 40 | else: 41 | self.cache[key] = [w3] 42 | 43 | def generate_markov_text(self): 44 | tweetsize = random.randint(4,20) 45 | seed = random.randint(0, self.word_size-3) 46 | seed_word, next_word = self.words[seed], self.words[seed+1] 47 | w1, w2 = seed_word, next_word 48 | gen_words = [] 49 | for i in xrange(tweetsize): 50 | gen_words.append(w1) 51 | w1, w2 = w2, random.choice(self.cache[(w1, w2)]) 52 | gen_words.append(w2) 53 | return ' '.join(gen_words) 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /mashup_markov/markovmaker.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Markov-chain text maker 5 | 6 | 7 | # This script takes a .txt file and makes a mashup of it 8 | # using a Markov chain: linking word phrases together 9 | # from different spots in the text. 10 | 11 | # For instance, if the text contained two lines, 12 | # "she has a dog" and "my dog has a tail," 13 | # this might generate "my dog has a dog" and "she has a tail." 14 | 15 | 16 | # Housekeeping 17 | import markovgen, re, string 18 | 19 | 20 | # Choose original file, new filename 21 | original = open('twain.txt') 22 | outfile = open('twain_markov.txt','w') 23 | 24 | 25 | # Repeatable Markov'd text generator 26 | newtext = [] 27 | mk = markovgen.Markov(original) 28 | 29 | counter = 0 30 | while counter < 10: # Change 10 to however many lines you want to generate 31 | line = '\n' + mk.generate_markov_text() 32 | 33 | #remove punctuation 34 | exclude = ['"','(',')',';'] 35 | line = ''.join(ch for ch in line if ch not in exclude) 36 | 37 | #make line lowercase, add period at end 38 | line = line.lower() + "." 39 | 40 | print line 41 | newtext.append(line) 42 | counter = counter + 1 43 | 44 | 45 | for aline in newtext: 46 | outfile.write(aline) #makes text file line by line 47 | 48 | 49 | # next steps if you want to tweet these lines: 50 | # move the newly-made file into the mybot folder 51 | # open mybot2.py 52 | # insert new filename 53 | 54 | 55 | outfile.close() 56 | original.close() 57 | 58 | 59 | 60 | # Script modified from http://agiliq.com/blog/2009/06/generating-pseudo-random-text-with-markov-chains-u/ 61 | # Original MarkovGen library from https://github.com/mattspitz/markovgen - modified by RobinCamille to spit out smaller chunks 62 | 63 | -------------------------------------------------------------------------------- /respondingbot.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Twitter Bot Starter Kit: Responding Bot 5 | 6 | # This bot listens to the account @ocertat, and when that account 7 | # tweets, it responds with a line of Twain 8 | 9 | # Download a Project Gutenberg "Plain Text UTF-8" file, 10 | # open it in Notepad, remove junk at beginning, 11 | # and replace all double-linebreaks with single linebreaks. 12 | 13 | 14 | # Housekeeping: do not edit 15 | import tweepy 16 | import time 17 | from credentials import * 18 | from random import randint 19 | auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) 20 | auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET) 21 | api = tweepy.API(auth) 22 | 23 | # initially, the script will assume that the last tweet was a null value 24 | lasttweet = None 25 | 26 | # What the bot will tweet 27 | filename = open('twain.txt', 'r') 28 | tweettext = filename.readlines() 29 | filename.close() 30 | 31 | 32 | # a function that picks a random line 33 | def linenum(): 34 | return randint(0, len(tweettext)) 35 | 36 | 37 | # this is the function that does most of the work of the bot 38 | def runTime(): 39 | 40 | # uses the global lasttweet variable, rather than the local one 41 | global lasttweet 42 | 43 | # gets the most recent tweet by @ocertat and prints its id 44 | mostrecenttweet = api.user_timeline('ocertat')[0] 45 | print(mostrecenttweet.id) 46 | 47 | # compares the two tweets, and tweets a line of Twain 48 | # if there is a new tweet from @ocertat 49 | if mostrecenttweet != lasttweet: 50 | line = tweettext[linenum()] 51 | api.update_status(status=line) 52 | print(line) 53 | 54 | # updates lasttweet to the most recent tweet 55 | lasttweet = mostrecenttweet 56 | 57 | # runs the main function every 5 seconds 58 | while True: 59 | runTime() 60 | print("sleeping") 61 | time.sleep(5) # Sleep for 5 seconds 62 | 63 | 64 | # To quit early: CTRL+C and wait a few seconds 65 | -------------------------------------------------------------------------------- /mashup_madlib.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Mad Lib Maker! 5 | 6 | # This script will generate mad-libs based off of a William Carlos 7 | # Williams poem, 'The Red Wheelbarrow.' Each poem will then be 8 | # tweeted by your bot account. 9 | 10 | # Housekeeping: do not edit 11 | import json, io, tweepy, time, urllib2 12 | from random import randint 13 | from credentials_lacunybot import * 14 | auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) 15 | auth.set_access_token(ACCESS_TOKEN, ACCESS_SECRET) 16 | api = tweepy.API(auth) 17 | 18 | 19 | # Housekeeping: opening JSON files 20 | # Find more item lists at https://github.com/dariusk/corpora/tree/master/data 21 | # Click "Raw" button, copy URL 22 | list1file = urllib2.urlopen('https://raw.githubusercontent.com/dariusk/corpora/master/data/objects/objects.json') 23 | list1read = list1file.read() 24 | 25 | list2file = urllib2.urlopen('https://raw.githubusercontent.com/dariusk/corpora/master/data/foods/menuItems.json') 26 | list2read = list2file.read() 27 | 28 | list3file = urllib2.urlopen('https://raw.githubusercontent.com/dariusk/corpora/master/data/humans/occupations.json') 29 | list3read = list3file.read() 30 | 31 | # Create Python-readable lists of items in JSON files 32 | list1 = json.loads(list1read)['objects'] # Change 'objects' to the title of the list 33 | list2 = json.loads(list2read)['menuItems'] 34 | list3 = json.loads(list3read)['occupations'] 35 | 36 | 37 | # Repeatable poem-generator 38 | poemlist = [] 39 | counter = 0 40 | 41 | while counter < 1: # Change 2 to however many poems you want to produce 42 | 43 | # Pick random numbers 44 | list1num = randint(0, len(list1) - 1) 45 | list2num = randint(0, len(list2) - 1) 46 | list3num = randint(0, len(list3) - 1) 47 | 48 | # Choose random items from each list using random numbers 49 | first = list1[list1num] # Syntax: list[number] 50 | second = list2[list2num].lower() 51 | third = list3[list3num].lower() + 's' 52 | 53 | # Fill in the blanks of the poem 54 | poem = 'so much depends\nupon\n\na\n%s\n\nglazed with\n%s\n\nbeside the\n%s\n\n' \ 55 | % (first, second, third) 56 | 57 | print poem 58 | poemlist.append(poem) #add to long list of poems 59 | counter = counter + 1 60 | 61 | 62 | # Line up tweets for bot 63 | 64 | for line in poemlist: 65 | api.update_status(line) 66 | #print line 67 | time.sleep(15) # Sleep for 15 seconds 68 | 69 | 70 | print '[All done!]' 71 | 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Twitter bot tutorial 2 | 3 | This tutorial and its materials were put together by Robin Davis (@robincamille) and Mark Eaton (github.com/MarkEEaton) for a December 15, 2015 workshop for librarians sponsored by the [LACUNY Emerging Technologies Committee](http://commons.gc.cuny.edu/groups/lacuny-emerging-technologies-committee/). You can use these materials for your own whimsical bot; the following instructions are for our workshop. 4 | 5 | See also: Davis, Robin, and Mark Eaton. [Make a Twitter Bot in Python: Iterative Code Examples](http://jitp.commons.gc.cuny.edu/make-a-twitter-bot-in-python-iterative-code-examples/). *Journal of Interactive Technology and Pedagogy* (Blueprints section). April 2016. (Verbose write-up featuring code in this repository.) 6 | 7 | 👉 **2018 update**: We updated our [bot tutorial for an ALA 2018 workshop](https://github.com/MarkEEaton/bot-tutorial-ala). It uses Python 3. 8 | 9 | --- 10 | 11 | **Python version:** 2.7 12 | 13 | **Required libraries:** tweepy, setuptools, json, urllib2 or urllib3 14 | 15 | ## Download the files 16 | 17 | See the "Download ZIP" button toward the upper right? Click it and save the folder to your desktop. 18 | 19 | ## Create a Twitter account for your bot 20 | 21 | 1. Go to http://twitter.com and sign up for a new account of your choosing. 22 | - Be sure to include your mobile number (required for using the API) 23 | - Email address must be unique to Twitter users; try adding random periods in your Gmail address, if you have one 24 | 25 | 2. Go to http://apps.twitter.com and create a new app 26 | - This info isn't public so it can be messy 27 | - Go to Keys and Access Tokens 28 | - Create new access token 29 | 30 | 3. Copy Consumer Key/Secret and Access Key/Secret to **credentials.py** 31 | 32 | ## Basic bot: mybot.py 33 | 34 | This script is a basic Twitter bot. It will tweet three things from a **list** inside the script. 35 | 36 | 1. Right-click on mybot.py and select Edit with IDLE 37 | 38 | 2. Take a look at the script; Robin and Mark will talk about what it's doing 39 | 40 | 3. Select Run > Run Module from the window's menu bar 41 | 42 | *Change it up!* 43 | - In **tweetlist**, add new things for your bot to tweet. 44 | - Increase/decrease time between tweets in **time.sleep(15)** (15 is the number of seconds). 45 | 46 | ## Intermediate bot: mybot2.py 47 | 48 | This script sends out five tweets from the first five lines of an external .txt file. 49 | 50 | 1. Right-click on mybot.py and select Edit with IDLE 51 | 52 | 2. Right-click on twain.txt and open it in Notepad 53 | 54 | 3. Take a look at both files; Robin and Mark will talk about what the script is doing 55 | 56 | 4. Select Run > Run Module from the window's menu bar for mybot2.py 57 | 58 | *Change it up!* 59 | - Go to http://gutenberg.org and choose a different text for your bot to tweet. 60 | - Download the file as "plain text" into the tutorial folder and open it in Notepad 61 | - Remove junk at the beginning of the file 62 | - Replace double linebreaks with single linebreaks with a find/replace 63 | - In mybot2.py, replace twain.txt with the name of the new text file 64 | - Make the bot send more or fewer tweets, or change which lines, by editing the numbers in **for line in tweettext[0:5]**. 65 | - [0:5] means from the first thing up to (but not including) the fifth thing 66 | 67 | ## Advanced bot: mashup_madlib.py 68 | 69 | This script treats *The Red Wheelbarrow* as a mad-lib, filling in three blanks from two data sources: JSON files from @dariusk's [collection of corpora](https://github.com/dariusk/corpora). 70 | 71 | ## Advanced bot: respondingbot.py 72 | 73 | This script from Mark tweets a random line from a .txt file whenever @jasonchowbot tweets. 74 | 75 | ## Advanced bot: mashup_markov 76 | 77 | This script uses a Markov chain to create new sentences from another text, and tweets them. 78 | 79 | ## Now what? 80 | 81 | Consider hosting your bot so it can tweet happily all by itself! If you happen to have DreamHost, I wrote up a step-by-step tutorial for hosting your bot: see **put-your-bot-on-DreamHost.md**. 82 | -------------------------------------------------------------------------------- /put-your-bot-on-DreamHost.md: -------------------------------------------------------------------------------- 1 | # Put your Twitter bot on DreamHost 2 | 3 | *Written June 2018* 4 | 5 | You don't have to run your bot from your desktop! You can, but it will shut down every time your computer does. This tutorial shows you how to host your Twitter bot with DreamHost. 6 | 7 | I use [DreamHost](https://www.dreamhost.com/), a paid hosting service, and I like them a lot. If you happen to have a DreamHost hosting account, these instructions will work for you. I have no idea if any of the below instructions work for other hosting services. ¯\\\_(ツ)\_/¯ 8 | 9 | This tutorial is specific to the [bot-tutorial](https://github.com/robincamille/bot-tutorial) it's housed in but can be adapted. It assumes you use the Tweepy library. 10 | 11 | Caveats: 12 | - **Intermediate and advanced users only!** You should already know how the command line works and what commands like `cd` and `chmod` mean. 13 | - Fair warning, these instructions might be wrong or out of date. You should only follow along if you know what you're doing. 14 | - These instructions are for DreamHost users only. 15 | 16 | ## Make the virtual environment & install Tweepy 17 | This is an essential step: it installs Python and makes it possible to use Python libraries, like Tweepy. By default, the below directions installs an isolated Python 2.7 for you. If you need Python 3, see Notes below. 18 | 19 | This may be the hardest part. You might run into errors. Google them and good luck. 20 | 21 | 1. [Use SSH to get to your DreamHost account directory](https://help.dreamhost.com/hc/en-us/articles/216041267-SSH-overview) 22 | 1. Get to your home directory (one level above the directories that contain your hosted websites) 23 | 2. Issue this command: `virtualenv mybots` 24 | - This creates a new folder, `mybots`. 25 | 1. `cd mybots` 26 | 1. `python -V` (just to check your Python version). 27 | - If it's a version you don't like, try to finagle a new version of Python or just give up and go with the flow. 28 | 3. Activate your virtual environment with `source bin/activate` 29 | - Your SSH prefix will now look like `(mybots)[servername]$` 30 | 4. `pip install tweepy` 31 | 1. Pip install any other libraries you require 32 | 5. `deactivate` 33 | 34 | ### Notes about this step 35 | - You shouldn't have to fool around with virtualenv after this step, except for updating or adding libraries. 36 | - I'm still using Python 2.7 but am getting an error message from one of my libraries every time a bot tweets, soooo I'll get around to changing up to Python 3 soonish 37 | - [Python2 virtualenv directions](https://help.dreamhost.com/hc/en-us/articles/215489338-Installing-and-using-virtualenv-with-Python-2) from DreamHost 38 | - [Python3 virtualenv directions](https://help.dreamhost.com/hc/en-us/articles/115000695551-Installing-and-using-Python-s-virtualenv-using-Python-3) from DreamHost 39 | 40 | ## Put your files in mybots/ 41 | 1. Using an FTP client (I like [Transmit](https://www.panic.com/transmit/) for Mac), navigate to your `mybots/` folder. 42 | 1. Upload these files: 43 | - Your bot's main `.py` script 44 | - Your bot's `credentials.py` script 45 | - Any data files (like `.txt` files the bots use). 46 | 47 | These files will reside on the same level as `bin/` and the other directories that were automatically added when you installed the virtual environment. 48 | 49 | ## Make a shell script 50 | 1. Make a new plaintext file that says this: 51 | > `#!/bin/bash` 52 | > 53 | > `cd /home/yourusername/mybots` 54 | > 55 | > `source bin/activate` 56 | > 57 | > `python tweetscript.py` 58 | 59 | 2. Change `yourusername`, `mybots`, and `tweetscript.py` accordingly. 60 | 1. Save this file as `make_bot_tweet.sh` or similar. Add it to `mybots/` alongside your script. 61 | 1. ⚠️ **Important!!** In your FTP client or through SSH, be sure that your `make_bot_tweet.sh` has User Execute permissions. (a.k.a., chmod 744.) 62 | 63 | ## Set up cron job 64 | A cron job is a task that is done periodically on a server or other operating system. You can it to run every hour, day, etc. 65 | 1. Log into the [DreamHost control panel](https://panel.dreamhost.com/). 66 | 1. On the left, under Goodies, find **Cron jobs**. 67 | 1. Add a new Cron Job. 68 | - **User**: the username you put your `twitterbots` directory under 69 | - **Title**: something descriptive that makes sense to you, like `poembot` 70 | - **Email output to**: add in your email address at first. Any error messages will get sent to you. You can remove it once you're sure your bot works. 71 | - **Command to run**: `/home/yourusername/mybots/make_bot_tweet.sh` 72 | - **When to run**: Up to you! I like using Custom and setting it to run at Selected Minutes (like every :25), and every hour, day, month, and day of week. 73 | - For testing purposes, choose a Selected Minutes that's like 10 minutes from now so you can see soon enough when it runs, but not so soon that any server settings won't take effect. 74 | 75 | ## Does your bot work? 76 | - At the moment the bot should run, take a look at your bot's Twitter page. Does a new tweet appear? 77 | - If not, did you get an error message by email? 78 | - You might get an error message even if the tweet is sent. For example, I get an `InsecurePlatformWarning` all the time because I'm still using Python 2.7. 79 | - At some point, your bot will stop working, likely because modules need an update or the Twitter API has changed. Make sure your email is in the cron job to get notified of error messages. 80 | 81 | ### Common error messages 82 | - `sh: /home/yourusernamehere/mybots/make_bot_tweet.sh: Permission denied` 83 | - You need to fix the permissions of your `.sh` script. 84 | - In an FTP client, make sure User has Execute permission. 85 | - Via SSH, run `chmod 744 make_bot_tweet.sh`. 86 | - `tweepy.error.TweepError: [{u'message': u'Bad Authentication data.', u'code': 215}]` 87 | - Check your credentials.py file again. Note that the access token has a hyphen in it, so you might’ve copied/pasted wrong from apps.twitter.com. 88 | - You can test your bot from your desktop to check your credentials. 89 | - `tweepy.error.TweepError: [{u'message': u'Status is a duplicate.', u'code': 187}]` 90 | - You can’t send the exact same tweet within a given time period. Just add another character to it or change it entirely. 91 | - `ImportError: No module named _io` (or similar module errors) 92 | - If this is an unusual library that you had to install yourself, activate the virtual environment again and pip install that library. 93 | - If it's a module that should be included by defalt, like `\_io`, rebuild the virtual environment. It's a pain (and you'll have to move your bot files to the new directory you make in the process) but necessary, as this is likely a problem with updates from the host’s side that aren’t reflected in your virtual environment. 94 | - Another Python warning not listed here? Fix your script! Run it on your desktop before uploading it to DreamHost and waiting for the cron job to run. 95 | 96 | ## Tips for running multiple bots 97 | - Each bot should have its own credentials, Python script, and `.sh` script. These 3 files should be consistently named for your own organization - e.g., give them all the same prefix. 98 | - Cron jobs are bot-specific. 99 | - Ensure that the bot's Twitter handle is in its Python script or credentials file so you can keep things straight. 100 | 101 | 102 | 103 | RCD • June 11, 2018 104 | --------------------------------------------------------------------------------