├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── app.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jay Hennig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: python app.py 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a template for creating your own twitter bot using python and heroku. The only programming necessary is to update the function ```get_message()``` in ```app.py``` to create your bot's tweets. 2 | 3 | Also, if you'd like to randomly favorite other users' tweets by keyword search, uncoment the call to `random_favoriting()` in `main()`, and change the keywords to those you'd like to search for. 4 | 5 | Confused? Try reading [this](http://tinysubversions.com/2013/09/how-to-make-a-twitter-bot/). Or contact me [@jehosafet](https://twitter.com/jehosafet). 6 | 7 | Requirements 8 | -------- 9 | * __python__ 10 | * Install [Twython](https://github.com/ryanmcgrath/twython) (```pip install Twython```): _for generating/posting tweets in python app_ 11 | * __heroku__ 12 | * [account](https://www.heroku.com/) and [toolbelt](https://toolbelt.heroku.com/): _for hosting python app and keeping it running_ 13 | 14 | Instructions 15 | -------- 16 | 0. Fork and pull this repo. 17 | 18 | 1. In your local repo, create a new heroku app. 19 | * ```heroku create --stack cedar``` 20 | * ```heroku apps:rename YOUR_APP_NAME``` 21 | * Your heroku app will now keep the python script ```app.py``` running as often as possible. 22 | 23 | 2. Create a [new twitter account](https://twitter.com/). 24 | * Use your current email to create the account by adding [a tag](http://en.wikipedia.org/wiki/Email_address#Address_tags). 25 | - Ex: _email@gmail.com_ => _email+twitterbot@gmail.com_ 26 | * Confirm the email address associated with this new twitter account. 27 | 28 | 3. From your main twitter account (not the one you just created, unless this is your first twitter account!) create a [new twitter app](https://dev.twitter.com/apps). 29 | * Under _Settings_ / _Application Type_: 30 | - Enable _"Read and Write"_ 31 | - Check _"Allow this application to be used to Sign in with Twitter"_ 32 | * Under _Details_: 33 | - Click _"Create My Access Token"_ 34 | * Connect this app to your bot's twitter acount (help [here](http://dghubble.com/blog/posts/twitter-app-write-access-and-bots/)) 35 | - `$ gem install twurl` 36 | - `$ twurl authorize --consumer-key "REPLACE_THIS" --consumer-secret "REPLACE_THIS"` 37 | * When `twurl` asks you to go to a url to log-in, now use the bot account you just created. 38 | 39 | 4. Create an `.env` file containing your twitter keys. 40 | * `twurl` generated some keys for your new bot account. You can find these in `~/.twurlrc`. 41 | * In your local repo, create a file called ```.env``` that contains these twitter app keys, one per line: 42 | - ```TWITTER_CONSUMER_KEY=replace_this``` 43 | - ```TWITTER_CONSUMER_SECRET=replace_this``` 44 | - ```TWITTER_OAUTH_TOKEN=replace_this``` 45 | - ```TWITTER_OAUTH_TOKEN_SECRET=replace_this``` 46 | * For [heroku](https://devcenter.heroku.com/articles/config-vars), use ```heroku-config``` to copy contents of ```.env``` to your heroku app. 47 | - Install heroku-config: ```heroku plugins:install heroku-config```. 48 | - Now run ```heroku config:push```. 49 | - NOTE: To update heroku environment variables later, run ```heroku config:push --overwrite``` 50 | - Alternatively, add heroku environment variables manually using ```heroku config:set YOUR_ENV_VAR=replace_this``` 51 | 52 | __Okay, now here's the fun part:__ 53 | 54 | 5. Update the function ```get_message()``` in ```app.py``` to create your bot's tweets. 55 | * Read about [twitter bot etiquette](http://tinysubversions.com/2013/03/basic-twitter-bot-etiquette/) for bot guidelines. 56 | * Use the [wordnik api](https://github.com/wordnik/wordnik-python) for getting random parts of speech. 57 | 58 | 6. Test your bot locally. 59 | * Running ```foreman start``` should generate your tweets once every minute, or at whatever rate you set in ```app.py```. 60 | 61 | 7. Commit and push local changes to heroku and github. 62 | * ```git push heroku master``` pushes all commits to heroku and starts up your app. 63 | * Your bot should now tweet as long as your heroku app is running (via the worker dyno). 64 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | from random import random 4 | from twython import Twython 5 | 6 | CONSUMER_KEY = os.environ['TWITTER_CONSUMER_KEY'] 7 | CONSUMER_SECRET = os.environ['TWITTER_CONSUMER_SECRET'] 8 | OAUTH_TOKEN = os.environ['TWITTER_OAUTH_TOKEN'] 9 | OAUTH_TOKEN_SECRET = os.environ['TWITTER_OAUTH_TOKEN_SECRET'] 10 | TWEET_LENGTH = 140 11 | TWEET_URL_LENGTH = 21 12 | 13 | RUN_EVERY_N_SECONDS = 60*5 # e.g. 60*5 = tweets every five minutes 14 | 15 | USERS_TO_IGNORE = [] 16 | DO_NOT_FAVORITE_USERS_AGAIN = True 17 | 18 | def twitter_handle(): 19 | return Twython(CONSUMER_KEY, CONSUMER_SECRET, OAUTH_TOKEN, OAUTH_TOKEN_SECRET) 20 | 21 | def favorite_tweet(tweet, handle): 22 | handle.create_favorite(id=tweet['id']) 23 | 24 | def random_favoriting(keywords, handle, favorite_probability=0.2): 25 | """ 26 | keywords is a list, like ['bananas', 'apples', 'oranges'] 27 | 28 | n.b. if this function is called every N seconds 29 | then you can expect to favorite a tweet 30 | once every N/favorite_probability seconds 31 | """ 32 | for keyword in keywords: 33 | xs = handle.search(q=keyword) 34 | ts = [x for x in xs['statuses']] 35 | if DO_NOT_FAVORITE_USERS_AGAIN: 36 | ts = [x for x in ts if x['user']['id'] not in USERS_TO_IGNORE] 37 | if ts: 38 | if random() < favorite_probability: 39 | print 'Favoriting: ' + ts[0]['text'] 40 | favorite_tweet(ts[0], handle) 41 | if DO_NOT_FAVORITE_USERS_AGAIN: 42 | USERS_TO_IGNORE.append(ts[0]['user']['id']) 43 | return 44 | 45 | def get_urls_of_media_in_tweet(tweet): 46 | """ 47 | get the urls of media contained in a tweet 48 | """ 49 | if 'entities' not in tweet or 'media' not in tweet['entities']: 50 | return [] 51 | return [x['media_url'] for x in tweet['entities']['media']] 52 | 53 | def get_mentions(handle, include_entities=False): 54 | """ 55 | returns iterator of tweets mentioning us 56 | if you want to get media in tweets, include_entities must be True 57 | """ 58 | return handle.cursor(handle.get_mentions_timeline, 59 | include_entities=include_entities) 60 | 61 | def get_images_in_mentions(handle): 62 | """ 63 | check for tweets that mention you, and get the urls of media in those tweets 64 | 65 | e.g., this might be helpful if you make a twitter bot where users can mention you in tweets containing photos, and your bot replies with an altered version of that photo 66 | """ 67 | for tweet in get_mentions(handle, include_entities=True): 68 | urls = get_urls_of_media_in_tweet(tweet) 69 | yield urls 70 | 71 | def submit_tweet_with_media(message, mediafile, tweet_to_reply=None, handle=None): 72 | """ 73 | imfile is the path to an media 74 | tweet_to_reply is a tweet that you're replying to, if not None 75 | """ 76 | if not handle: 77 | handle = twitter_handle() 78 | media_ids = handle.upload_media(media=open(mediafile)) 79 | if tweet_to_reply is None: 80 | handle.update_status(status=message, 81 | media_ids=media_ids['media_id']) 82 | else: 83 | # must mention user's name for it to be a reply 84 | message += ' @' + tweet_to_reply['user']['screen_name'] 85 | handle.update_status(status=message, 86 | in_reply_to_status_id=tweet_to_reply['id'], 87 | media_ids=media_ids['media_id']) 88 | 89 | def submit_tweet(message, tweet_to_reply=None, handle=None): 90 | """ 91 | tweet_to_reply is a tweet that you're replying to, if not None 92 | """ 93 | if not handle: 94 | handle = twitter_handle() 95 | if tweet_to_reply is None: 96 | handle.update_status(status=message) 97 | else: 98 | # must mention user's name for it to be a reply 99 | message += ' @' + tweet_to_reply['user']['screen_name'] 100 | handle.update_status(status=message, 101 | in_reply_to_status_id=tweet_to_reply['id']) 102 | 103 | def get_message(handle): 104 | """ 105 | Your code goes here! 106 | """ 107 | message = 'I TWEET THIS.' 108 | assert len(message) <= TWEET_LENGTH 109 | return message 110 | 111 | def main(): 112 | handle = twitter_handle() 113 | USERS_TO_IGNORE.extend([x['user']['id'] for x in handle.get_favorites()]) 114 | while True: 115 | message = get_message(handle) 116 | print message 117 | submit_tweet(message, handle) 118 | # random_favoriting(['apples', 'oranges'], handle) 119 | time.sleep(RUN_EVERY_N_SECONDS) 120 | 121 | if __name__ == '__main__': 122 | main() 123 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | twython==3.0.0 2 | --------------------------------------------------------------------------------