def parse_args(): """Return host and port, or print usage and exit.""" usage = "usage: %prog [options] host [port]" desc = ("Create a Minecraft proxy listening for a client connection," + "and forward that connection to <host>:<port>.") parser = OptionParser(usage=usage, description=desc) parser.add_option("-l", "--log-level", dest="loglvl", metavar="LEVEL", choices=["debug","info","warn","error"], help="Override logging.conf root log level") parser.add_option("--log-file", dest='logfile', metavar="FILE", default=None, help="logging configuration file (optional)") parser.add_option("-p", "--local-port", dest="locport", metavar="PORT", default="34343", type="int", help="Listen on this port") parser.add_option("-c", "--check-authenticity", dest="check_auth", action="store_true", default=False, help="Check authenticity of connecting clients") parser.add_option("-a", "--auto-authenticate", dest="authenticate", action="store_true", default=False, help="Authenticate with the credentials stored in the game client") parser.add_option("-u", "--user", dest="user", metavar="USERNAME", default=None, help="Authenticate with the given username and ask for the password") parser.add_option("-P", "--password-file", dest="password_file", metavar="FILE", default=None, help="Authenticate with the credentials stored in FILE" + "in the form \"username:password\"") parser.add_option("--plugin", dest="plugins", metavar="ID:PLUGIN(ARGS)", type="string", action="append", help="Configure a plugin", default=[]) parser.add_option("--profile", dest="perf_data", metavar="FILE", default=None, help="Enable profiling, save profiling data to FILE") (opts,args) = parser.parse_args() if not 1 <= len(args) <= 2: parser.error("Incorrect number of arguments.") # Calls sys.exit() host = args[0] port = 25565 if len(args) > 1: try: port = int(args[1]) except ValueError: parser.error("Invalid port %s" % args[1]) pcfg = PluginConfig() pregex = re.compile('((?P<id>\\w+):)?(?P<plugin_name>[\\w\\.\\d_]+)(\\((?P<argstr>.*)\\))?$') for pstr in opts.plugins: m = pregex.match(pstr) if not m: logger.error('Invalid --plugin option: %s' % pstr) sys.exit(1) else: parts = {'argstr': ''} parts.update(m.groupdict()) pcfg.add(**parts) return (host, port, opts, pcfg)
def __init__(self, bot): self.cfg = PluginConfig(self) self.image_filetypes = self.cfg.get("image_filetypes").split(",") self.db = FileBackend(self.cfg.get("main_db")) self.tumblr = pytumblr.TumblrRestClient( self.cfg.get("consumer_key"), self.cfg.get("consumer_secret"), self.cfg.get("oauth_token"), self.cfg.get("oauth_secret"), ) irc3.base.logging.log( irc3.base.logging.WARN, "Tumblr poster ready! Posting all URLs with: %s" % self.image_filetypes )
def __init__(self, bot): self.bot = bot self.cfg = PluginConfig(self) self.db = FileBackend(self.cfg.get('main_db')) mtt = MessageRetargeter(bot) self.msg = mtt.msg web = Flask(__name__, template_folder=tmpl_dir) mako = MakoTemplates() mako.init_app(web) # Add routes here web.add_url_rule('/edit_web/<args>', 'edit_web', self.edit_web, methods=['GET', 'POST']) _thread.start_new_thread(web.run, (), {'host': '0.0.0.0'})
class ImageToTumblr(object): def __init__(self, bot): self.cfg = PluginConfig(self) self.image_filetypes = self.cfg.get("image_filetypes").split(",") self.db = FileBackend(self.cfg.get("main_db")) self.tumblr = pytumblr.TumblrRestClient( self.cfg.get("consumer_key"), self.cfg.get("consumer_secret"), self.cfg.get("oauth_token"), self.cfg.get("oauth_secret"), ) irc3.base.logging.log( irc3.base.logging.WARN, "Tumblr poster ready! Posting all URLs with: %s" % self.image_filetypes ) def post_image(self, text, poster): # Strip everything but the address m = re.match(r".*(?P<url>http.*)", text) url = m.group("url") # Make sure we didn't do this one already try: self.db.get(PostedImage, {"url": url}) except PostedImage.DoesNotExist: try: # First we post it to tumblr p = self.tumblr.create_photo( "mmerpimages", state="published", source=str(url), caption="Found by %s" % poster ) irc3.base.logging.log(irc3.base.logging.WARN, "Posting image by %s: %s" % (poster, url)) # And then record the fact that we did. self.db.save(PostedImage({"url": url})) self.db.commit() except: irc3.base.logging.log(irc3.base.logging.WARN, "Could not post to tumblr: %s" % url) return else: irc3.base.logging.log(irc3.base.logging.WARN, "Not posting duplicate image: %s" % url) return @irc3.event(irc3.rfc.PRIVMSG) # Triggered on every message anywhere. def parse_image(self, target, mask, data, event): for extension in self.image_filetypes: if "." + extension.lower() in data: self.post_image(data, mask.nick)
class Profiles(object): def __init__(self, bot): self.bot = bot self.cfg = PluginConfig(self) self.db = FileBackend(self.cfg.get('main_db')) mtt = MessageRetargeter(bot) self.msg = mtt.msg web = Flask(__name__, template_folder=tmpl_dir) mako = MakoTemplates() mako.init_app(web) # Add routes here web.add_url_rule('/edit_web/<args>', 'edit_web', self.edit_web, methods=['GET', 'POST']) _thread.start_new_thread(web.run, (), {'host': '0.0.0.0'}) @command def learn(self, mask, target, args): """ Stores information allowing for later retrieval. Names are downcased for sanity. Usage: %%learn <name> <information>... """ name = args['<name>'].lower() info = ' '.join(args['<information>']) try: profile = self.db.get(Profile, {'name': name}) except Profile.DoesNotExist: profile = Profile( { 'name': name, 'owner': mask.nick.lower(), 'lines': [info], 'random': False, 'public': False } ) profile.save(self.db) self.db.commit() self.msg(mask, target, 'Your data "%s" has been stored.' % name) return except Profile.MultipleDocumentsReturned: self.msg(mask, target, "Found more than one %s. This is bad! Please notify the bot owner." % name) return if is_allowed_to(Action.edit, mask.nick, profile): lines_to_append = profile.lines lines_to_append.append(info) profile.save(self.db) self.db.commit() self.msg(mask, target, 'Your data "%s" has been updated.' % name) return else: self.msg(mask, target, 'You are not authorized to edit "%s". Ask %s instead.' % (name, profile.owner)) return @command def query(self, mask, target, args): """ Retrieve the information associated with <name>. If the item is marked random, then one random item will be returned. Usage: %%query <name> ?? <name> """ name = args['<name>'].lower() try: profile = self.db.get(Profile, {'name': name}) except Profile.DoesNotExist: self.msg(mask, target, 'I cannot find "%s" in the records.' % name) return if profile.random: self.msg(mask, target, get_flags(profile) + random.choice(profile.lines)) else: for line in profile.lines: self.msg(mask, target, get_flags(profile) + line) if len(profile.lines) >= int(self.cfg.get('throttle_max')): sleep(int(self.cfg.get('throttle_time'))) @command def forget(self, mask, target, args): """ Delete <name> from the records. Only the person who created the item can remove it. Usage: %%forget <name> """ name = args['<name>'].lower() try: profile = self.db.get(Profile, {'name': name}) except Profile.DoesNotExist: self.msg(mask, target, 'I cannot find "%s" in the records.' % name) return if is_allowed_to(Action.delete, mask.nick, profile): self.db.delete(profile) self.db.commit() self.msg(mask, target, "%s has been deleted." % name) else: self.msg(mask, target, 'You are not authorized to delete "%s". Ask %s instead.' % (name, profile.owner)) @command(permission='admin', show_in_help_list=False) def rmf(self, mask, target, args): """ Delete <name> from the records without checking permissions. Usage: %%rmf <name> """ name = args['<name>'].lower() try: profile = self.db.get(Profile, {'name': name}) except Profile.DoesNotExist: self.msg(mask, target, 'I cannot find "%s" in the records.' % name) return self.db.delete(profile) self.db.commit() self.msg(mask, target, "%s has been deleted." % name) @command(permission='admin', show_in_help_list=False) def chown(self, mask, target, args): """ Change the owner of <name> to <newowner>. Usage: %%chown <name> <newowner> """ name = args['<name>'].lower() newowner = args['<newowner>'].lower() try: profile = self.db.get(Profile, {'name': name}) except Profile.DoesNotExist: self.msg(mask, target, 'I cannot find "%s" in the records.' % name) return profile.owner = newowner self.db.save(profile) self.db.commit() self.msg(mask, target, "%s is now owned by %s." % (name, newowner)) @command def toggle_public(self, mask, target, args): """ Changes whether <name> is publicly editable or not Usage: %%toggle_public <name> """ profile = args['<name>'].lower() try: profile = self.db.get(Profile, {'name': profile}) except Profile.DoesNotExist: self.msg(mask, target, 'I cannot find "%s" in the records.' % profile) return if is_allowed_to(Action.edit, mask.nick, profile): if profile.public: profile.public = False self.msg(mask, target, '"%s" is no longer publicly editable.' % profile) else: profile.public = True self.msg(mask, target, '"%s" is now publicly editable.' % profile) self.db.save(profile) self.db.commit() return else: self.msg(mask, target, 'You are not authorized to edit "%s". Ask %s instead.' % (profile, profile.owner)) return @command def toggle_random(self, mask, target, args): """ Toggle the randomness of an item, so that it shows a single random line instead of all lines when queried. Usage: %%toggle_random <name> """ name = args['<name>'].lower() try: profile = self.db.get(Profile, {'name': name}) except Profile.DoesNotExist: self.msg(mask, target, 'I cannot find "%s" in the records.' % name) return if is_allowed_to(Action.edit, mask.nick, profile): profile.random = not profile.random self.msg(mask, target, 'Random mode for %s is set to: %s' % (profile.name, profile.random)) profile.save(self.db) self.db.commit() else: self.msg(mask, target, 'You are not authorized to edit "%s". Ask %s instead.' % (name, profile.owner)) irc3.base.logging.log(irc3.base.logging.WARN, "%s tried to edit %s, but can't since it's owned by %s" % (mask.nick, profile.name, profile.owner) ) @event("(@(?P<tags>\S+) )?:(?P<mask>\S+) PRIVMSG (?P<target>\S+) :\?\? (?P<data>.*)") def easy_query(self, mask, target, data): self.bot.get_plugin(Commands).on_command(cmd='query', mask=mask, target=target, data=data) #### # All web stuff below this point # @command def edit(self, mask, target, args): """ Sends you a webpage link to edit <name>. Great for longer profiles. Make sure to keep the URL you are given secure, as with it, anyone can edit your profiles. Usage: %%edit <name> """ # TODO: Clear any existing sessions the user has data = { 'id': str(uuid.uuid4()), 'name': mask.nick, 'profile': args['<name>'] } name = args['<name>'].lower() try: profile = self.db.get(Profile, {'name': name}) except Profile.DoesNotExist: self.msg(mask, target, 'I cannot find "%s" in the records.' % name) return if is_allowed_to(Action.fulledit, mask.nick, profile): newses = Session(data) self.db.save(newses) self.db.commit() self.bot.privmsg(mask.nick, "An editor has been set up for you at http://skaianet.tkware.us:5000/edit_web/%s" % str( data['id'])) self.bot.privmsg(mask.nick, "Be very careful not to expose this address - with it, anyone can edit your stuff") else: self.msg(mask, target, 'You are not authorized to webedit "%s". Ask %s instead.' % (name, profile.owner)) def edit_web(self, args): # Web endpoint: /edit_web/<args> if request.method == 'GET': # Does the session exist? try: edit_session = self.db.get(Session, {'id': args}) except Session.DoesNotExist: return render_template('youfail.html', bot=self.bot, failreason='Invalid Session', userfail=True) # Does the profile exist? name = edit_session.profile try: profile = self.db.get(Profile, {'name': name.lower()}) except Profile.DoesNotExist: return render_template('youfail.html', bot=self.bot, failreason='I cannot find "%s" in the records.' % name ) # Kick off to the edit page! return render_template('edit.html', bot=self.bot, profile=profile, username=edit_session.name, sessionid=edit_session.id ) elif request.method == 'POST': # We have to look up the session ID one more time. Something could have happened to the profile # since we created the session. try: edit_session = self.db.get(Session, {'id': request.form['ID']}) except Session.DoesNotExist: return render_template('youfail.html', bot=self.bot, failreason='Invalid Session', userfail=True) name = request.form['profile'] try: profile = self.db.get(Profile, {'name': request.form['profile']}) except Profile.DoesNotExist: return render_template('youfail.html', bot=self.bot, failreason='I cannot find "%s" in the records.' % name, userfail=True ) # Now with the profile in hand, blank the lines field and rebuild it from the form. # Here we grab all numeric items from the submission, sort it, and one by one refill the DB object. lines = [item for item in request.form if item.isdigit()] lines.sort() profile.lines = [] for item in lines: profile.lines.append(request.form[item]) self.db.save(profile) self.db.delete(edit_session) self.db.commit() return render_template('done.html', bot=self.bot, profile=profile.name )
def __init__(self, bot): self.bot = bot self.cfg = PluginConfig(self) self.db = FileBackend(self.cfg.get('main_db')) mtt = MessageRetargeter(bot) self.msg = mtt.msg
class Memos(object): def __init__(self, bot): self.bot = bot self.cfg = PluginConfig(self) self.db = FileBackend(self.cfg.get('main_db')) mtt = MessageRetargeter(bot) self.msg = mtt.msg @command def note(self, target, mask, args): """ Leaves a note for <name>, containing <text>. The next time I see <name> speak, I will deliver any notes they have waiting. Notes taken in private are delivered in private, and vice versa. Usage: %%note <name> <text>... """ if mask.is_channel: pubmsg = True else: pubmsg = False if args['<name>'] == self.bot.nick: self.msg(mask, target, "You can't leave notes for me, silly :)") return newmemo = Memo( { 'sender': target.nick.lower(), 'recipient': args['<name>'].lower(), 'public': pubmsg, 'timestamp': ctime(), 'text': ' '.join(args['<text>']) } ) newmemo.save(self.db) self.db.commit() confirmation_msg = "Your note for %s has been queued for delivery." % args['<name>'] self.msg(mask, target, confirmation_msg) @irc3.event(irc3.rfc.PRIVMSG) # Triggered on every message anywhere. def check_notes(self, target, mask, data, event): del data, event try: msgs = self.db.filter(Memo, {'recipient': mask.nick.lower()}) msgword = "message" if len(msgs) < 2 else "messages" # Fix: I have 1 messages for you! except Memo.DoesNotExist: return if len(msgs) == 0: return # Avoid telling people they have messages in public, if any of them are set public=False if contains_private_messages(msgs): self.msg(mask, mask.nick, "I have %s %s for you, %s!" % (len(msgs), msgword, mask.nick)) else: self.msg(mask, target, "I have %s %s for you, %s!" % (len(msgs), msgword, mask.nick)) # Actually deliver the memos for msg in msgs: # This looks ridiculous but we don't care about the timezone really, only the relative time # from the local system clock. now = datetime.datetime.strptime(ctime(), "%a %b %d %H:%M:%S %Y") reltime = humanize.naturaltime(now - datetime.datetime.strptime(msg.timestamp, "%a %b %d %H:%M:%S %Y")) message_text = "%s // %s // %s" % (msg.sender, reltime, msg.text) if msg.public: self.msg(mask, target, message_text) self.db.delete(msg) else: self.bot.privmsg(mask.nick, message_text) self.db.delete(msg) self.db.commit()