├── requirements.txt ├── screenshot.png ├── packages.py ├── config.json ├── serverless.yml ├── README.rst ├── LICENSE └── main.py /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.20.0 2 | praw==5.3.0 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/epsagon/reddit-slackbot/HEAD/screenshot.png -------------------------------------------------------------------------------- /packages.py: -------------------------------------------------------------------------------- 1 | """ 2 | Helper for importing packages 3 | """ 4 | 5 | import os 6 | import sys 7 | 8 | sys.path.append(os.path.join( 9 | os.path.dirname(os.path.realpath(__file__)), 10 | 'packages' 11 | )) 12 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "reddit": { 3 | "client-id": "[reddit-client-id]", 4 | "client-secret": "[reddit-client-secret]" 5 | }, 6 | "slack-hook-url": "[slack-hook-url]", 7 | "subreddits": "programming^lambda|Python^lambda" 8 | } 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /serverless.yml: -------------------------------------------------------------------------------- 1 | service: reddit-slackbot 2 | provider: 3 | name: aws 4 | runtime: python2.7 5 | stage: production 6 | region: us-east-1 7 | memorySize: 512 8 | timeout: 60 9 | iamRoleStatements: 10 | - Effect: "Allow" 11 | Action: 12 | - "lambda:UpdateFunctionConfiguration" 13 | 14 | # This parameter can be calculated according to the region and the account ID 15 | Resource: "arn:aws:lambda:us-east-1:[account-id]:function:reddit-slackbot-production-runner" 16 | 17 | functions: 18 | runner: 19 | handler: main.handler 20 | description: "Slackbot: Reddit Submissions" 21 | events: 22 | - schedule: rate(1 hour) 23 | environment: 24 | # Start time, e.g. the output of time.time() 25 | LAST_SUBMISSION: "1521756333.7" 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Serverless Reddit Slack Bot 2 | ===================== 3 | 4 | .. image:: https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg 5 | :target: https://saythanks.io/to/nshap 6 | 7 | .. image:: https://github.com/epsagon/reddit-slackbot/blob/master/screenshot.png 8 | :align: center 9 | 10 | Description 11 | ---------- 12 | - Serverless Reddit Slack Bot for tracking new submissions 13 | - Deployed as a Python AWS Lambda using the Serverless Framework 14 | - Can be used as a template for other Slack bots 15 | 16 | Setup 17 | ----- 18 | - Configure config.json with the Reddit client parameters, Slack webhook URL, and the subreddits to track 19 | - Update serverless.yml with your AWS account ID 20 | - Deploy the Lambda function 21 | 22 | .. code-block:: bash 23 | 24 | git clone https://github.com/epsagon/reddit-slackbot 25 | cd reddit-slackbot/ 26 | pip install -r requirements.txt -t packages/ 27 | serverless deploy 28 | 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Epsagon 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 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Reddit submissions Slack bot 3 | """ 4 | 5 | import os 6 | import time 7 | import json 8 | import boto3 9 | import packages 10 | import requests 11 | import praw 12 | 13 | # Constants 14 | TWO_HOURS = 7200 15 | TEN_HOURS = 36000 16 | 17 | config = json.load(open('config.json')) 18 | 19 | reddit = praw.Reddit( 20 | client_id=config['reddit']['client-id'], 21 | client_secret=config['reddit']['client-secret'], 22 | user_agent='bot' 23 | ) 24 | 25 | lambda_client = boto3.client('lambda') 26 | 27 | 28 | def get_submissions(): 29 | """ 30 | Listing submissions from subreddits 31 | :return: new submissions 32 | """ 33 | 34 | submissions = [] 35 | last_submission_time = float(os.environ['LAST_SUBMISSION']) 36 | print 'Last submission time: {}'.format(last_submission_time) 37 | 38 | for subreddit_data in config['subreddits'].split('|'): 39 | subreddit_name, query = subreddit_data.split('^') 40 | subreddit = reddit.subreddit(subreddit_name) 41 | 42 | # Get only updated submissions 43 | subs = subreddit.search(query, sort='new', limit=100) 44 | submissions.extend( 45 | [s for s in subs if s.created_utc > last_submission_time] 46 | ) 47 | 48 | return submissions 49 | 50 | 51 | def update_slack(submissions): 52 | """ 53 | Updating Slack with new submissions. 54 | :param submissions: new submissions 55 | """ 56 | 57 | for submission in submissions: 58 | submission_date = time.strftime( 59 | '%H:%M %d/%m', time.gmtime(int(submission.created_utc) + TWO_DAYS) 60 | ) 61 | 62 | message = '*<{}|{}>*\n*Subreddit*: {}\n*Date*: {}\n{}'.format( 63 | submission.url, 64 | submission.title, 65 | submission.subreddit.display_name, 66 | submission_date, 67 | '-' * 20, 68 | ) 69 | 70 | requests.post( 71 | config['slack-hook-url'], 72 | headers={'Content-type': 'application/json'}, 73 | data=json.dumps({'text': message}) 74 | ) 75 | 76 | 77 | def handler(_, context): 78 | """ 79 | Lambda handler 80 | :param _: event (unused) 81 | :param context: context 82 | :return: '' 83 | """ 84 | 85 | submissions = get_submissions() 86 | if submissions: 87 | update_slack(submissions) 88 | 89 | last_submission = str(max([submission.created_utc for submission in submissions])) 90 | print 'Updated last submission: {}'.format(last_submission) 91 | 92 | lambda_client.update_function_configuration( 93 | FunctionName=context.function_name, 94 | Environment={ 95 | 'Variables': { 96 | 'LAST_SUBMISSION': last_submission, 97 | } 98 | } 99 | ) 100 | 101 | return '' 102 | 103 | 104 | # For testing and running locally 105 | if __name__ == '__main__': 106 | os.environ['LAST_SUBMISSION'] = str(time.time() - TEN_DAYS) 107 | 108 | class Context(object): 109 | """ Mock context """ 110 | def __init__(self): 111 | self.function_name = 'test' 112 | 113 | handler(None, Context()) 114 | --------------------------------------------------------------------------------