Skip to content

Instantly share code, notes, and snippets.

@phikal
Created December 14, 2014 13:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phikal/903b3d1a74b891247372 to your computer and use it in GitHub Desktop.
Save phikal/903b3d1a74b891247372 to your computer and use it in GitHub Desktop.
A bot for Reddit.
#!/usr/bin/env python3
#
# sendto - Reddit bot v1.2
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# PRAW - PRAW: The Python Reddit Api Wrapper // https://praw.readthedocs.org/en/v2.1.16/
import praw
import re
from threading import Thread
from time import sleep
import traceback
import logging as log
import datetime
# Possible replies
res_sucesss = """
**Post has been sent to /r/{}.**
Link: {}
*^(Hi! I'm a bot. No need to reply. About me: http://redd.it/2ihemu)*
"""
res_nonexist = """
**Subreddit (/r/{}) does not exist.**
*^(Hi! I'm a bot. No need to reply. About me: http://redd.it/2ihemu)*
"""
res_alredy = """
**Link alredy submitted to /r/{}.**
*^(Hi! I'm a bot. No need to reply. About me: http://redd.it/2ihemu)*
"""
res_direct = """
**This link has been sent from /r/{} by /u/{}.**
Comment of origin: {}
*^(Hi! I'm a bot. No need to reply. About me: http://redd.it/2ihemu)*
"""
res_ignored = """
**This subreddit ({}) is ignored.***
*^(Hi! I'm a bot. No need to reply. About me: http://redd.it/2ihemu)*
"""
r = praw.Reddit(user_agent='/u/sendto bot 1.0 by /u/SurviAvi')
# Compile syntax matching RE
find = re.compile("/u/sendto(!)?\s+/r/(\w+)(?:\s+(.*))?$")
# All responses to be processed
queue = []
# All ignored subreddits
ignored = []
verbose = 1
# Processing thread
class Work(Thread):
def __init__(self):
Thread.__init__(self)
self.daemon = True
self.start()
def run(self):
while True:
# Check if there is anything to process
if len(queue) == 0:
sleep(30)
continue
# Get first item
work = queue.pop(0)
# Get comment and subreddit
sub = r.get_subreddit(work['sub'])
com = r.get_info(thing_id='t1_'+work['id'])
# Try posting and replying to comment
try:
resp = sub.submit(work['title'], url=work['link'])
com.reply(res_sucesss.format(work['sub'], resp.short_link))
if 'direct' in work:
resp.add_comment(res_direct.format(com.subreddit.display_name, com.author.name, com.permalink))
print(work['id']+': processed')
# Errors:
except praw.errors.InvalidSubreddit:
com.reply(res_nonexist.format(work['sub']))
log.info(work['id']+': posted on inv. subreddit')
except praw.errors.AlreadySubmitted:
com.reply(res_alredy.format(work['sub']))
log.warning(work['id']+': already submitted')
except (praw.errors.RateLimitExceeded, praw.errors.APIException):
queue.append(work)
log.warning(work['id']+': exceded rate limit or quota filled')
sleep(180)
except:
log.error('[PT] Other error:\n' + traceback.format_exc())
break
def main(user, passw):
# Login using api
r.login(user, passw)
# Start processing thread
Work()
# Start parsing incoming comments from /r/all/new
log.info('STARTED')
while True:
try:
i = 0
for comment in praw.helpers.comment_stream(r, 'all', limit=None, verbosity=verbose): # Get all incoming comments
# Should it be ignored?
if comment.subreddit.display_name in ignored:
continue
# Check if commeny body matches notify syntax
mat = find.match(comment.body)
if mat:
# Should it be ignored?
if mat.group(2) in ignored:
comment.reply(res_ignored.format(mat.group(2)))
continue
log.info('parsed comment: '+comment.id)
obj = {
'sub' : mat.group(2),
'title' : mat.group(3) or '"'+comment.submission.title+'"',
'id' : comment.id
}
# Direct link?
if mat.group(1):
obj['link'] = comment.submission.url
obj['direct'] = True
# Get link for submission ( is parent submis. or comment)
elif comment.is_root:
obj['link'] = comment.submission.permalink
else:
obj['link'] = r.get_info(thing_id=comment.parent_id).permalink
# Add to response list
queue.append(obj)
except KeyboardInterrupt:
log.info("Stopped by user.")
break
except:
log.error('[MT] Error:\n'+traceback.format_exc())
break
if __name__ == '__main__':
from json import load, dump
from os.path import isfile
import atexit
file_name = 'config.json'
queue_file = 'queue.json'
# Check if file exists
if not isfile(file_name):
log.warning('config.json missing')
exit()
# Get config.json data
with open(file_name) as file:
data = load(file)
# ignored subreddits
if 'ignore' in data:
ignored = data['ignore']
assert isinstance(ignored, list)
def onexit():
with open(queue_file, 'w') as outfile:
dump(queue, outfile)
atexit.register(onexit)
if isfile(queue_file):
with open(queue_file) as file:
queue = load(file)
assert isinstance(queue, list)
# stdout redirect
if 'stdout' in data:
log.basicConfig(filename=data['stdout'], filemode='w', level=log.INFO)
verbose = 0
# Start program
try:
main(data['user'], data['pass'])
except KeyboardInterrupt:
log.info("Stopped by user.")
except:
log.error('[MT] Error:\n'+traceback.format_exc())
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment