├── passwords ├── supporting_docs ├── intent_schema └── sample_utterances ├── .gitignore ├── reddit_play.py ├── README.md └── py2_reddit_app.py /passwords: -------------------------------------------------------------------------------- 1 | CLIENT_ID='YOURIDGOESHERE' 2 | CLIENT_SECRET = 'YOURCLIENTSECRETGOESHERE' 3 | USER_AGENT='amazon-alexa:Alexa Jokes:v0.0.1 (by /u/yourredditusername)' 4 | -------------------------------------------------------------------------------- /supporting_docs/intent_schema: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "intents": [{ 4 | 5 | "intent": "AMAZON.HelpIntent" 6 | }, 7 | 8 | { 9 | "intent": "AMAZON.CancelIntent" 10 | }, 11 | 12 | { 13 | "intent": "AMAZON.StopIntent" 14 | }, 15 | 16 | { "intent": "JokeType", 17 | 18 | "slots": [{ 19 | 20 | "name": "joke_type", 21 | 22 | "type": "AMAZON.LITERAL" 23 | 24 | }] 25 | 26 | }] 27 | 28 | } -------------------------------------------------------------------------------- /supporting_docs/sample_utterances: -------------------------------------------------------------------------------- 1 | JokeType tell me a joke about {russia|joke_type} 2 | JokeType a {christmas |joke_type} joke 3 | JokeType a joke about {pizza party|joke_type} 4 | JokeType {stuff|joke_type} 5 | JokeType {stuff stuff|joke_type} 6 | JokeType tell me a {stuff stuff|joke_type} joke 7 | JokeType tell me a {stuff|joke_type} joke 8 | JokeType {stuff|joke_type} joke 9 | JokeType {stuff stuff|joke_type} joke 10 | JokeType I want a {stuff|joke_type} joke 11 | JokeType I want a {stuff stuff|joke_type} joke 12 | JokeType for a {stuff|joke_type} joke 13 | JokeType for a {stuff stuff|joke_type} joke 14 | JokeType to tell me a {stuff|joke_type} joke 15 | JokeType to tell me a {stuff stuff|joke_type} joke 16 | JokeType to tell me a joke about {stuff|joke_type} 17 | JokeType to tell me a joke about {stuff stuff|joke_type} 18 | JokeType tell a {stuff|joke_type} joke 19 | JokeType tell a {stuff stuff|joke_type} joke 20 | JokeType tell me a joke about {stuff|joke_type} 21 | JokeType tell me a joke about {stuff stuff|joke_type} 22 | JokeType I want a joke about {stuff|joke_type} 23 | JokeType I want a joke about {stuff stuff|joke_type} 24 | JokeType I want a joke with {stuff|joke_type} 25 | JokeType I want a joke with {stuff stuff|joke_type} 26 | JokeType tell me a joke with {stuff|joke_type} 27 | JokeType tell me a joke with {stuff stuff|joke_type} 28 | JokeType {stuff|joke_type} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | passwords.py 2 | reddit3 3 | templates 4 | reddit 5 | *.pyc 6 | 7 | # Byte-compiled / optimized / DLL files 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | 12 | # C extensions 13 | *.so 14 | 15 | # Distribution / packaging 16 | .Python 17 | env/ 18 | build/ 19 | develop-eggs/ 20 | dist/ 21 | downloads/ 22 | eggs/ 23 | .eggs/ 24 | lib/ 25 | lib64/ 26 | parts/ 27 | sdist/ 28 | var/ 29 | *.egg-info/ 30 | .installed.cfg 31 | *.egg 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *,cover 52 | .hypothesis/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # IPython Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | -------------------------------------------------------------------------------- /reddit_play.py: -------------------------------------------------------------------------------- 1 | import random 2 | import praw 3 | from passwords import CLIENT_ID, CLIENT_SECRET, USER_AGENT 4 | 5 | def get_joke(joke_type): 6 | try: 7 | reddit = connect_to_reddit() 8 | if joke_type in ['funny', 'hilarious', 'good', 'great', 'something funny', 'something hilarious']: 9 | joke_type = 'funny' 10 | joke_list = list(reddit.subreddit('jokes').top(limit=50)) 11 | 12 | elif joke_type in ['dirty', 'really dirty', 'something dirty']: 13 | joke_list = list(reddit.subreddit('dirtyjokes').top(limit=50)) 14 | joke_type = 'dirty' 15 | 16 | elif joke_type in ['dad', 'dad joke']: 17 | joke_list = list(reddit.subreddit('dadjokes').top(limit=50)) 18 | joke_type = 'dad' 19 | 20 | else: 21 | joke_list = list(reddit.subreddit('jokes').search(joke_type, limit=30)) 22 | 23 | 24 | if not joke_list: 25 | joke_type = "something funny because I couldn't find any jokes about {}.".format(joke_type) 26 | joke_list = list(reddit.subreddit('jokes').top(limit=40)) 27 | 28 | joke_id = random.choice(joke_list) 29 | joke = reddit.submission(id=joke_id) 30 | 31 | while len(joke.selftext) > 2500: 32 | joke_id = random.choice(joke_list) 33 | joke = reddit.submission(id=joke_id) 34 | 35 | joke_title = joke.title.replace(r'\n','').replace('.',' ') 36 | joke_text = joke.selftext.replace(r'\n','') 37 | 38 | 39 | if ' '.join(joke_title.split()[:-1]) in joke_text: 40 | joke_title = '' 41 | 42 | edits = ['edit:', 'edit1', 'edit 1:', 'edit 1 '] 43 | 44 | for edit in edits: 45 | if edit in joke_text.lower(): 46 | joke_text = joke_text.lower().split(edit)[0] 47 | 48 | return joke_title, joke_text, joke_type 49 | 50 | except Exception as e: 51 | return "Whoops, something went wrong. Try again real soon! ".format(e), "Goodbye!", "Oops" 52 | 53 | 54 | def connect_to_reddit(): 55 | reddit = praw.Reddit(client_id=CLIENT_ID, 56 | client_secret=CLIENT_SECRET, 57 | user_agent=USER_AGENT) 58 | 59 | return reddit 60 | 61 | if __name__ == '__main__': 62 | joke=get_joke('king of england') 63 | print(joke[0]) 64 | print(joke[1]) 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # alexa-reddit-joke 2 | 3 | Update: This skill is up on the Amazon skills list as Joke Fairy. https://www.amazon.com/Joke-Fairy-unofficial-Reddit-search/dp/B01N6IMCAP/ 4 | 5 | I wrote this using https://developer.amazon.com/blogs/post/Tx14R0IYYGH3SKT/Flask-Ask-A-New-Python-Framework-for-Rapid-Alexa-Skills-Kit-Development as a guide. 6 | 7 | The app lets you search reddit jokes for any search term, or you can specify a dirty joke or a dad joke. It's a lot of fun, and people seem to like it! 8 | as Amazon will be stopping the ability to use slot types of AMAZON.LITERAL, but after February you must use a custom slot. 9 | The custom slot still lets you accept any utterances from the users, but the format is just a little different. 10 | 11 | If you want to make your own version of this or use it as a guide to make your own skill, just follow the instructions on the above link to sign up as an Amazon developer and 12 | download ngrok etc. 13 | 14 | This should work in Python 3 and Python 2. 15 | 16 | You'll need to pip install flask, flask-ask, and praw, get a Reddit API key, and create a passwords.py file containing the reddit keys. 17 | 18 | ## For the reddit API wrapper you'll need to go on Reddit and sign up for their API. To do so: 19 | 20 | 1. Go to https://www.reddit.com/prefs/apps 21 | 2. Click "Are you a developer?" 22 | 3. Name your app, pick script, the third option, and put any URL you want into the redirect URI box. It's required but doesn't matter what's in there. 23 | 4. Grab the list of numbers/letters just below your app name and under "personal use script" - that goes in the client ID in passwords.py. 24 | 5. Grab the secret key, which goes in the secret key in passwords.py. 25 | 26 | ## Amazon App details 27 | 28 | * Invocation name, I used "reddit joke" but use whatever you like 29 | * Interaction model - copy and paste from the file "intent_schema" 30 | * Sample utterances - copy and paste from the file. The more you add, the better it will respond to variations. 31 | 32 | ## Hosting 33 | 34 | I am hosting this on pythonanywhere.com, on their $5 / month plan for now (but I might try using the free tier or switching to AWS Lambda). 35 | It's super easy to setup, just follow the instructions for the most part. 36 | I created a virtual environment to pip install the required libraries, then you can activate it on pythonanywhere.com. 37 | 38 | It should go something like this: 39 | User: "Alexa, ask [invocation name] to tell me a joke about XXXXXXX" 40 | Alexa: "Ok, get ready for a joke about XXXXXXX... {goes on to tell a random joke about XXXXXXX}" 41 | -------------------------------------------------------------------------------- /py2_reddit_app.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from flask import Flask 3 | from flask_ask import Ask, statement, question, session 4 | import reddit_play 5 | import random 6 | 7 | 8 | app = Flask(__name__) 9 | 10 | ask = Ask(app, "/joke_search_for_reddit") 11 | 12 | logging.getLogger("flask_ask").setLevel(logging.DEBUG) 13 | 14 | @ask.launch 15 | def ask_joke_type(): 16 | welcome = "Welcome! What kind of joke would you like to hear?" 17 | return question(welcome).reprompt('What kind of joke would you like?') 18 | 19 | 20 | @ask.intent("JokeType") 21 | def tell_joke(joke_type=None): 22 | try: 23 | if joke_type is None: 24 | joke_type = 'funny' 25 | jokehead, joke, joke_type = reddit_play.get_joke(joke_type) 26 | msg = "I couldn't understand you, so instead I'll tell you one of my favorite jokes. " 27 | 28 | 29 | else: 30 | jokehead, joke, joke_type = reddit_play.get_joke(joke_type) 31 | if joke_type in ['funny', 'dirty', 'dad']: 32 | msg = 'Oh boy, here comes one of my favorite {} jokes.... '.format(joke_type) 33 | 34 | else: 35 | msg1 = 'Ok, get ready for a joke about {}..... '.format(joke_type) 36 | msg2 = 'All right, here is one of my favorite jokes about {}..... '.format(joke_type) 37 | msg3 = 'Ready or not, here comes a joke about {}..... '.format(joke_type) 38 | 39 | msg = random.choice([msg1, msg2, msg3]) 40 | 41 | msg = msg + jokehead + '...' + joke 42 | msg2 = msg.lower().replace('..', '.. ')\ 43 | .replace('http://','')\ 44 | .replace('https://','')\ 45 | return statement(msg2).simple_card('Joke Fairy', msg) 46 | 47 | except Exception as e: 48 | joke_type = 'funny' 49 | jokehead, joke, joke_type = reddit_play.get_joke(joke_type) 50 | msg = "I couldn't understand you, so instead I'll tell you a random funny joke. " 51 | msg = msg + jokehead + '...' + joke 52 | return statement(msg).simple_card('Joke Fairy', msg) 53 | 54 | 55 | @ask.intent('AMAZON.HelpIntent') 56 | def help(): 57 | speech_text = 'Joke fairy is an unofficial Reddit joke search app that ' +\ 58 | 'will tell you a joke about whatever you like. ' +\ 59 | 'For example, you can ask Joke fairy for a joke about Russia, ' +\ 60 | 'or you can ask for a dirty joke, a funny joke, or a dad joke. What kind of joke would ' +\ 61 | 'you like to hear?' 62 | reprompt_text = 'What kind of joke would you like?' 63 | return question(speech_text).reprompt(reprompt_text) 64 | 65 | 66 | @ask.intent('AMAZON.StopIntent') 67 | def stop(): 68 | msg = 'Smell you later!' 69 | return statement(msg) 70 | 71 | 72 | @ask.intent('AMAZON.CancelIntent') 73 | def cancel(): 74 | msg = 'Smell you later!' 75 | return statement(msg) 76 | 77 | 78 | if __name__ == '__main__': 79 | # print(ask_joke_type().__dict__) 80 | print(tell_joke('dirty').__dict__) 81 | # app.run(debug=True) 82 | --------------------------------------------------------------------------------