Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- From 7022da1f72428e8b90a861a6f4bcbeb16cb5e45c Mon Sep 17 00:00:00 2001
- From: S00ng <???>
- Date: Sun, 1 May 2016 01:48:56 +0200
- Subject: [PATCH 1/4] iniz machine learning ready
- ---
- sopel/modules/__init__.py | 2 -
- sopel/modules/admin.py | 229 ----------------------
- sopel/modules/adminchannel.py | 276 --------------------------
- sopel/modules/announce.py | 23 ---
- sopel/modules/bugzilla.py | 96 ----------
- sopel/modules/calc.py | 70 -------
- sopel/modules/clock.py | 277 ---------------------------
- sopel/modules/countdown.py | 39 ----
- sopel/modules/currency.py | 105 ----------
- sopel/modules/dice.py | 259 -------------------------
- sopel/modules/etymology.py | 97 ----------
- sopel/modules/find.py | 139 --------------
- sopel/modules/find_updates.py | 58 ------
- sopel/modules/help.py | 72 -------
- sopel/modules/ip.py | 139 --------------
- sopel/modules/ipython.py | 78 --------
- sopel/modules/isup.py | 36 ----
- sopel/modules/lmgtfy.py | 19 --
- sopel/modules/meetbot.py | 436 ------------------------------------------
- sopel/modules/movie.py | 48 -----
- sopel/modules/ping.py | 29 ---
- sopel/modules/rand.py | 49 -----
- sopel/modules/reddit.py | 191 ------------------
- sopel/modules/reload.py | 139 --------------
- sopel/modules/remind.py | 228 ----------------------
- sopel/modules/safety.py | 197 -------------------
- sopel/modules/search.py | 128 -------------
- sopel/modules/seen.py | 59 ------
- sopel/modules/spellcheck.py | 54 ------
- sopel/modules/tell.py | 183 ------------------
- sopel/modules/tld.py | 69 -------
- sopel/modules/translate.py | 203 --------------------
- sopel/modules/unicode_info.py | 49 -----
- sopel/modules/units.py | 186 ------------------
- sopel/modules/uptime.py | 27 ---
- sopel/modules/url.py | 237 -----------------------
- sopel/modules/version.py | 81 --------
- sopel/modules/weather.py | 183 ------------------
- sopel/modules/wikipedia.py | 133 -------------
- sopel/modules/wiktionary.py | 101 ----------
- sopel/modules/xkcd.py | 105 ----------
- sopel/trigger.py | 5 +-
- 42 files changed, 3 insertions(+), 5131 deletions(-)
- delete mode 100644 sopel/modules/__init__.py
- delete mode 100644 sopel/modules/admin.py
- delete mode 100644 sopel/modules/adminchannel.py
- delete mode 100644 sopel/modules/announce.py
- delete mode 100644 sopel/modules/bugzilla.py
- delete mode 100644 sopel/modules/calc.py
- delete mode 100644 sopel/modules/clock.py
- delete mode 100644 sopel/modules/countdown.py
- delete mode 100644 sopel/modules/currency.py
- delete mode 100644 sopel/modules/dice.py
- delete mode 100644 sopel/modules/etymology.py
- delete mode 100644 sopel/modules/find.py
- delete mode 100644 sopel/modules/find_updates.py
- delete mode 100644 sopel/modules/help.py
- delete mode 100644 sopel/modules/ip.py
- delete mode 100644 sopel/modules/ipython.py
- delete mode 100644 sopel/modules/isup.py
- delete mode 100644 sopel/modules/lmgtfy.py
- delete mode 100644 sopel/modules/meetbot.py
- delete mode 100644 sopel/modules/movie.py
- delete mode 100644 sopel/modules/ping.py
- delete mode 100644 sopel/modules/rand.py
- delete mode 100644 sopel/modules/reddit.py
- delete mode 100644 sopel/modules/reload.py
- delete mode 100644 sopel/modules/remind.py
- delete mode 100644 sopel/modules/safety.py
- delete mode 100644 sopel/modules/search.py
- delete mode 100644 sopel/modules/seen.py
- delete mode 100644 sopel/modules/spellcheck.py
- delete mode 100644 sopel/modules/tell.py
- delete mode 100644 sopel/modules/tld.py
- delete mode 100644 sopel/modules/translate.py
- delete mode 100644 sopel/modules/unicode_info.py
- delete mode 100644 sopel/modules/units.py
- delete mode 100644 sopel/modules/uptime.py
- delete mode 100644 sopel/modules/url.py
- delete mode 100644 sopel/modules/version.py
- delete mode 100644 sopel/modules/weather.py
- delete mode 100644 sopel/modules/wikipedia.py
- delete mode 100644 sopel/modules/wiktionary.py
- delete mode 100644 sopel/modules/xkcd.py
- diff --git a/sopel/modules/__init__.py b/sopel/modules/__init__.py
- deleted file mode 100644
- index b6f85b6..0000000
- --- a/sopel/modules/__init__.py
- +++ /dev/null
- @@ -1,2 +0,0 @@
- -# coding=utf-8
- -from __future__ import unicode_literals, absolute_import, print_function, division
- diff --git a/sopel/modules/admin.py b/sopel/modules/admin.py
- deleted file mode 100644
- index 14e84aa..0000000
- --- a/sopel/modules/admin.py
- +++ /dev/null
- @@ -1,229 +0,0 @@
- -# coding=utf-8
- -"""
- -admin.py - Sopel Admin Module
- -Copyright 2010-2011, Sean B. Palmer (inamidst.com) and Michael Yanovich
- -(yanovich.net)
- -Copyright © 2012, Elad Alfassa, <elad@fedoraproject.org>
- -Copyright 2013, Ari Koivula <ari@koivu.la>
- -
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel.config.types import (
- - StaticSection, ValidatedAttribute, FilenameAttribute
- -)
- -import sopel.module
- -
- -
- -class AdminSection(StaticSection):
- - hold_ground = ValidatedAttribute('hold_ground', bool, default=False)
- - """Auto re-join on kick"""
- - auto_accept_invite = ValidatedAttribute('auto_accept_invite', bool,
- - default=True)
- -
- -
- -def configure(config):
- - config.define_section('admin', AdminSection)
- - config.admin.configure_setting('hold_ground',
- - "Automatically re-join after being kicked?")
- - config.admin.configure_setting('auto_accept_invite',
- - 'Automatically join channels when invited?')
- -
- -
- -def setup(bot):
- - bot.config.define_section('admin', AdminSection)
- -
- -
- -@sopel.module.require_privmsg
- -@sopel.module.require_admin
- -@sopel.module.commands('join')
- -@sopel.module.priority('low')
- -@sopel.module.example('.join #example or .join #example key')
- -def join(bot, trigger):
- - """Join the specified channel. This is an admin-only command."""
- - channel, key = trigger.group(3), trigger.group(4)
- - if not channel:
- - return
- - elif not key:
- - bot.join(channel)
- - else:
- - bot.join(channel, key)
- -
- -
- -@sopel.module.require_privmsg
- -@sopel.module.require_admin
- -@sopel.module.commands('part')
- -@sopel.module.priority('low')
- -@sopel.module.example('.part #example')
- -def part(bot, trigger):
- - """Part the specified channel. This is an admin-only command."""
- - channel, _sep, part_msg = trigger.group(2).partition(' ')
- - if part_msg:
- - bot.part(channel, part_msg)
- - else:
- - bot.part(channel)
- -
- -
- -@sopel.module.require_privmsg
- -@sopel.module.require_owner
- -@sopel.module.commands('quit')
- -@sopel.module.priority('low')
- -def quit(bot, trigger):
- - """Quit from the server. This is an owner-only command."""
- - quit_message = trigger.group(2)
- - if not quit_message:
- - quit_message = 'Quitting on command from %s' % trigger.nick
- -
- - bot.quit(quit_message)
- -
- -
- -@sopel.module.require_privmsg
- -@sopel.module.require_admin
- -@sopel.module.commands('msg')
- -@sopel.module.priority('low')
- -@sopel.module.example('.msg #YourPants Does anyone else smell neurotoxin?')
- -def msg(bot, trigger):
- - """
- - Send a message to a given channel or nick. Can only be done in privmsg by an
- - admin.
- - """
- - if trigger.group(2) is None:
- - return
- -
- - channel, _sep, message = trigger.group(2).partition(' ')
- - message = message.strip()
- - if not channel or not message:
- - return
- -
- - bot.msg(channel, message)
- -
- -
- -@sopel.module.require_privmsg
- -@sopel.module.require_admin
- -@sopel.module.commands('me')
- -@sopel.module.priority('low')
- -def me(bot, trigger):
- - """
- - Send an ACTION (/me) to a given channel or nick. Can only be done in privmsg
- - by an admin.
- - """
- - if trigger.group(2) is None:
- - return
- -
- - channel, _sep, action = trigger.group(2).partition(' ')
- - action = action.strip()
- - if not channel or not action:
- - return
- -
- - msg = '\x01ACTION %s\x01' % action
- - bot.msg(channel, msg)
- -
- -
- -@sopel.module.event('INVITE')
- -@sopel.module.rule('.*')
- -@sopel.module.priority('low')
- -def invite_join(bot, trigger):
- - """
- - Join a channel sopel is invited to, if the inviter is an admin.
- - """
- - if trigger.admin or bot.config.admin.auto_accept_invite:
- - bot.join(trigger.args[1])
- - return
- -
- -
- -@sopel.module.event('KICK')
- -@sopel.module.rule(r'.*')
- -@sopel.module.priority('low')
- -def hold_ground(bot, trigger):
- - """
- - This function monitors all kicks across all channels sopel is in. If it
- - detects that it is the one kicked it'll automatically join that channel.
- -
- - WARNING: This may not be needed and could cause problems if sopel becomes
- - annoying. Please use this with caution.
- - """
- - if bot.config.admin.hold_ground:
- - channel = trigger.sender
- - if trigger.args[1] == bot.nick:
- - bot.join(channel)
- -
- -
- -@sopel.module.require_privmsg
- -@sopel.module.require_admin
- -@sopel.module.commands('mode')
- -@sopel.module.priority('low')
- -def mode(bot, trigger):
- - """Set a user mode on Sopel. Can only be done in privmsg by an admin."""
- - mode = trigger.group(3)
- - bot.write(('MODE ', bot.nick + ' ' + mode))
- -
- -
- -@sopel.module.require_privmsg("This command only works as a private message.")
- -@sopel.module.require_admin("This command requires admin privileges.")
- -@sopel.module.commands('set')
- -@sopel.module.example('.set core.owner Me')
- -def set_config(bot, trigger):
- - """See and modify values of sopels config object.
- -
- - Trigger args:
- - arg1 - section and option, in the form "section.option"
- - arg2 - value
- -
- - If there is no section, section will default to "core".
- - If value is None, the option will be deleted.
- - """
- - # Get section and option from first argument.
- - arg1 = trigger.group(3).split('.')
- - if len(arg1) == 1:
- - section_name, option = "core", arg1[0]
- - elif len(arg1) == 2:
- - section_name, option = arg1
- - else:
- - bot.reply("Usage: .set section.option value")
- - return
- - section = getattr(bot.config, section_name)
- - static_sec = isinstance(section, StaticSection)
- -
- - if static_sec and not hasattr(section, option):
- - bot.say('[{}] section has no option {}.'.format(section_name, option))
- - return
- -
- - # Display current value if no value is given.
- - value = trigger.group(4)
- - if not value:
- - if not static_sec and bot.config.parser.has_option(section, option):
- - bot.reply("Option %s.%s does not exist." % (section_name, option))
- - return
- - # Except if the option looks like a password. Censor those to stop them
- - # from being put on log files.
- - if option.endswith("password") or option.endswith("pass"):
- - value = "(password censored)"
- - else:
- - value = getattr(section, option)
- - bot.reply("%s.%s = %s" % (section_name, option, value))
- - return
- -
- - # Otherwise, set the value to one given as argument 2.
- - if static_sec:
- - descriptor = getattr(section.__class__, option)
- - try:
- - if isinstance(descriptor, FilenameAttribute):
- - value = descriptor.parse(bot.config, descriptor, value)
- - else:
- - value = descriptor.parse(value)
- - except ValueError as exc:
- - bot.say("Can't set attribute: " + str(exc))
- - return
- - setattr(section, option, value)
- -
- -
- -@sopel.module.require_privmsg
- -@sopel.module.require_admin
- -@sopel.module.commands('save')
- -@sopel.module.example('.save')
- -def save_config(bot, trigger):
- - """Save state of sopels config object to the configuration file."""
- - bot.config.save()
- diff --git a/sopel/modules/adminchannel.py b/sopel/modules/adminchannel.py
- deleted file mode 100644
- index e3fe10f..0000000
- --- a/sopel/modules/adminchannel.py
- +++ /dev/null
- @@ -1,276 +0,0 @@
- -# coding=utf-8
- -# Copyright 2010-2011, Michael Yanovich, Alek Rollyson, and Elsie Powell
- -# Copyright © 2012, Elad Alfassa <elad@fedoraproject.org>
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -from sopel import formatting
- -from sopel.module import commands, priority, OP, HALFOP, require_privilege, require_chanmsg
- -from sopel.tools import Identifier
- -
- -
- -def default_mask(trigger):
- - welcome = formatting.color('Welcome to:', formatting.colors.PURPLE)
- - chan = formatting.color(trigger.sender, formatting.colors.TEAL)
- - topic_ = formatting.bold('Topic:')
- - topic_ = formatting.color('| ' + topic_, formatting.colors.PURPLE)
- - arg = formatting.color('{}', formatting.colors.GREEN)
- - return '{} {} {} {}'.format(welcome, chan, topic_, arg)
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('kick')
- -@priority('high')
- -def kick(bot, trigger):
- - """
- - Kick a user from the channel.
- - """
- - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
- - return bot.reply("I'm not a channel operator!")
- - text = trigger.group().split()
- - argc = len(text)
- - if argc < 2:
- - return
- - opt = Identifier(text[1])
- - nick = opt
- - channel = trigger.sender
- - reasonidx = 2
- - if not opt.is_nick():
- - if argc < 3:
- - return
- - nick = text[2]
- - channel = opt
- - reasonidx = 3
- - reason = ' '.join(text[reasonidx:])
- - if nick != bot.config.core.nick:
- - bot.write(['KICK', channel, nick], reason)
- -
- -
- -def configureHostMask(mask):
- - if mask == '*!*@*':
- - return mask
- - if re.match('^[^.@!/]+$', mask) is not None:
- - return '%s!*@*' % mask
- - if re.match('^[^@!]+$', mask) is not None:
- - return '*!*@%s' % mask
- -
- - m = re.match('^([^!@]+)@$', mask)
- - if m is not None:
- - return '*!%s@*' % m.group(1)
- -
- - m = re.match('^([^!@]+)@([^@!]+)$', mask)
- - if m is not None:
- - return '*!%s@%s' % (m.group(1), m.group(2))
- -
- - m = re.match('^([^!@]+)!(^[!@]+)@?$', mask)
- - if m is not None:
- - return '%s!%s@*' % (m.group(1), m.group(2))
- - return ''
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('ban')
- -@priority('high')
- -def ban(bot, trigger):
- - """
- - This give admins the ability to ban a user.
- - The bot must be a Channel Operator for this command to work.
- - """
- - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
- - return bot.reply("I'm not a channel operator!")
- - text = trigger.group().split()
- - argc = len(text)
- - if argc < 2:
- - return
- - opt = Identifier(text[1])
- - banmask = opt
- - channel = trigger.sender
- - if not opt.is_nick():
- - if argc < 3:
- - return
- - channel = opt
- - banmask = text[2]
- - banmask = configureHostMask(banmask)
- - if banmask == '':
- - return
- - bot.write(['MODE', channel, '+b', banmask])
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('unban')
- -def unban(bot, trigger):
- - """
- - This give admins the ability to unban a user.
- - The bot must be a Channel Operator for this command to work.
- - """
- - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
- - return bot.reply("I'm not a channel operator!")
- - text = trigger.group().split()
- - argc = len(text)
- - if argc < 2:
- - return
- - opt = Identifier(text[1])
- - banmask = opt
- - channel = trigger.sender
- - if not opt.is_nick():
- - if argc < 3:
- - return
- - channel = opt
- - banmask = text[2]
- - banmask = configureHostMask(banmask)
- - if banmask == '':
- - return
- - bot.write(['MODE', channel, '-b', banmask])
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('quiet')
- -def quiet(bot, trigger):
- - """
- - This gives admins the ability to quiet a user.
- - The bot must be a Channel Operator for this command to work.
- - """
- - if bot.privileges[trigger.sender][bot.nick] < OP:
- - return bot.reply("I'm not a channel operator!")
- - text = trigger.group().split()
- - argc = len(text)
- - if argc < 2:
- - return
- - opt = Identifier(text[1])
- - quietmask = opt
- - channel = trigger.sender
- - if not opt.is_nick():
- - if argc < 3:
- - return
- - quietmask = text[2]
- - channel = opt
- - quietmask = configureHostMask(quietmask)
- - if quietmask == '':
- - return
- - bot.write(['MODE', channel, '+q', quietmask])
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('unquiet')
- -def unquiet(bot, trigger):
- - """
- - This gives admins the ability to unquiet a user.
- - The bot must be a Channel Operator for this command to work.
- - """
- - if bot.privileges[trigger.sender][bot.nick] < OP:
- - return bot.reply("I'm not a channel operator!")
- - text = trigger.group().split()
- - argc = len(text)
- - if argc < 2:
- - return
- - opt = Identifier(text[1])
- - quietmask = opt
- - channel = trigger.sender
- - if not opt.is_nick():
- - if argc < 3:
- - return
- - quietmask = text[2]
- - channel = opt
- - quietmask = configureHostMask(quietmask)
- - if quietmask == '':
- - return
- - bot.write(['MODE', channel, '-q', quietmask])
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('kickban', 'kb')
- -@priority('high')
- -def kickban(bot, trigger):
- - """
- - This gives admins the ability to kickban a user.
- - The bot must be a Channel Operator for this command to work.
- - .kickban [#chan] user1 user!*@* get out of here
- - """
- - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
- - return bot.reply("I'm not a channel operator!")
- - text = trigger.group().split()
- - argc = len(text)
- - if argc < 4:
- - return
- - opt = Identifier(text[1])
- - nick = opt
- - mask = text[2]
- - channel = trigger.sender
- - reasonidx = 3
- - if not opt.is_nick():
- - if argc < 5:
- - return
- - channel = opt
- - nick = text[2]
- - mask = text[3]
- - reasonidx = 4
- - reason = ' '.join(text[reasonidx:])
- - mask = configureHostMask(mask)
- - if mask == '':
- - return
- - bot.write(['MODE', channel, '+b', mask])
- - bot.write(['KICK', channel, nick], reason)
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('topic')
- -def topic(bot, trigger):
- - """
- - This gives ops the ability to change the topic.
- - The bot must be a Channel Operator for this command to work.
- - """
- - if bot.privileges[trigger.sender][bot.nick] < HALFOP:
- - return bot.reply("I'm not a channel operator!")
- - if not trigger.group(2):
- - return
- - channel = trigger.sender.lower()
- -
- - narg = 1
- - mask = None
- - mask = bot.db.get_channel_value(channel, 'topic_mask')
- - mask = mask or default_mask(trigger)
- - mask = mask.replace('%s', '{}')
- - narg = len(re.findall('{}', mask))
- -
- - top = trigger.group(2)
- - args = []
- - if top:
- - args = top.split('~', narg)
- -
- - if len(args) != narg:
- - message = "Not enough arguments. You gave {}, it requires {}.".format(
- - len(args), narg)
- - return bot.say(message)
- - topic = mask.format(*args)
- -
- - bot.write(('TOPIC', channel + ' :' + topic))
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('tmask')
- -def set_mask(bot, trigger):
- - """
- - Set the mask to use for .topic in the current channel. {} is used to allow
- - substituting in chunks of text.
- - """
- - bot.db.set_channel_value(trigger.sender, 'topic_mask', trigger.group(2))
- - bot.say("Gotcha, " + trigger.nick)
- -
- -
- -@require_chanmsg
- -@require_privilege(OP, 'You are not a channel operator.')
- -@commands('showmask')
- -def show_mask(bot, trigger):
- - """Show the topic mask for the current channel."""
- - mask = bot.db.get_channel_value(trigger.sender, 'topic_mask')
- - mask = mask or default_mask(trigger)
- - bot.say(mask)
- diff --git a/sopel/modules/announce.py b/sopel/modules/announce.py
- deleted file mode 100644
- index 2b3df4b..0000000
- --- a/sopel/modules/announce.py
- +++ /dev/null
- @@ -1,23 +0,0 @@
- -# coding=utf-8
- -"""
- -announce.py - Send a message to all channels
- -Copyright © 2013, Elad Alfassa, <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel.module import commands, example
- -
- -
- -@commands('announce')
- -@example('.announce Some important message here')
- -def announce(bot, trigger):
- - """
- - Send an announcement to all channels the bot is in
- - """
- - if not trigger.admin:
- - bot.reply('Sorry, I can\'t let you do that')
- - return
- - for channel in bot.channels:
- - bot.msg(channel, '[ANNOUNCEMENT] %s' % trigger.group(2))
- diff --git a/sopel/modules/bugzilla.py b/sopel/modules/bugzilla.py
- deleted file mode 100644
- index 9167128..0000000
- --- a/sopel/modules/bugzilla.py
- +++ /dev/null
- @@ -1,96 +0,0 @@
- -# coding=utf-8
- -"""Bugzilla issue reporting module
- -
- -Copyright 2013-2015, Embolalia, embolalia.com
- -Licensed under the Eiffel Forum License 2.
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -
- -import xmltodict
- -
- -from sopel import web, tools
- -from sopel.config.types import StaticSection, ListAttribute
- -from sopel.logger import get_logger
- -from sopel.module import rule
- -
- -
- -regex = None
- -LOGGER = get_logger(__name__)
- -
- -
- -class BugzillaSection(StaticSection):
- - domains = ListAttribute('domains')
- - """The domains of the Bugzilla instances from which to get information."""
- -
- -
- -def configure(config):
- - config.define_section('bugzilla', BugzillaSection)
- - config.bugzilla.configure_setting(
- - 'domains',
- - 'Enter the domains of the Bugzillas you want extra information '
- - 'from (e.g. bugzilla.gnome.org)'
- - )
- -
- -
- -def setup(bot):
- - global regex
- - bot.config.define_section('bugzilla', BugzillaSection)
- -
- - if not bot.config.bugzilla.domains:
- - return
- - if not bot.memory.contains('url_callbacks'):
- - bot.memory['url_callbacks'] = tools.SopelMemory()
- -
- - domains = '|'.join(bot.config.bugzilla.domains)
- - regex = re.compile((r'https?://(%s)'
- - '(/show_bug.cgi\?\S*?)'
- - '(id=\d+)')
- - % domains)
- - bot.memory['url_callbacks'][regex] = show_bug
- -
- -
- -def shutdown(bot):
- - del bot.memory['url_callbacks'][regex]
- -
- -
- -@rule(r'.*https?://(\S+?)'
- - '(/show_bug.cgi\?\S*?)'
- - '(id=\d+).*')
- -def show_bug(bot, trigger, match=None):
- - """Show information about a Bugzilla bug."""
- - match = match or trigger
- - domain = match.group(1)
- - if domain not in bot.config.bugzilla.domains:
- - return
- - url = 'https://%s%sctype=xml&%s' % match.groups()
- - data = web.get(url, dont_decode=True)
- - bug = xmltodict.parse(data).get('bugzilla').get('bug')
- - error = bug.get('@error', None) # error="NotPermitted"
- -
- - if error:
- - LOGGER.warning('Bugzilla error: %s', error)
- - return
- -
- - message = ('[BUGZILLA] %s | Product: %s | Component: %s | Version: %s | ' +
- - 'Importance: %s | Status: %s | Assigned to: %s | ' +
- - 'Reported: %s | Modified: %s')
- -
- - resolution = bug.get('resolution')
- - if resolution is not None:
- - status = bug.get('bug_status') + ' ' + resolution
- - else:
- - status = bug.get('bug_status')
- -
- - assigned_to = bug.get('assigned_to')
- - if isinstance(assigned_to, dict):
- - assigned_to = assigned_to.get('@name')
- -
- - message = message % (
- - bug.get('short_desc'), bug.get('product'),
- - bug.get('component'), bug.get('version'),
- - (bug.get('priority') + ' ' + bug.get('bug_severity')),
- - status, assigned_to, bug.get('creation_ts'),
- - bug.get('delta_ts'))
- - bot.say(message)
- diff --git a/sopel/modules/calc.py b/sopel/modules/calc.py
- deleted file mode 100644
- index 6497b74..0000000
- --- a/sopel/modules/calc.py
- +++ /dev/null
- @@ -1,70 +0,0 @@
- -# coding=utf-8
- -"""
- -calc.py - Sopel Calculator Module
- -Copyright 2008, Sean B. Palmer, inamidst.com
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -from sopel import web
- -from sopel.module import commands, example
- -from sopel.tools.calculation import eval_equation
- -from socket import timeout
- -import sys
- -if sys.version_info.major < 3:
- - import HTMLParser
- -else:
- - unichr = chr
- - import html.parser as HTMLParser
- -
- -
- -BASE_TUMBOLIA_URI = 'https://tumbolia-two.appspot.com/'
- -
- -
- -@commands('c', 'calc')
- -@example('.c 5 + 3', '8')
- -@example('.c 0.9*10', '9')
- -@example('.c 10*0.9', '9')
- -@example('.c 2*(1+2)*3', '18')
- -@example('.c 2**10', '1024')
- -@example('.c 5 // 2', '2')
- -@example('.c 5 / 2', '2.5')
- -def c(bot, trigger):
- - """Evaluate some calculation."""
- - if not trigger.group(2):
- - return bot.reply("Nothing to calculate.")
- - # Account for the silly non-Anglophones and their silly radix point.
- - eqn = trigger.group(2).replace(',', '.')
- - try:
- - result = eval_equation(eqn)
- - result = "{:.10g}".format(result)
- - except ZeroDivisionError:
- - result = "Division by zero is not supported in this universe."
- - except Exception as e:
- - result = "{error}: {msg}".format(error=type(e), msg=e)
- - bot.reply(result)
- -
- -
- -@commands('py')
- -@example('.py len([1,2,3])', '3')
- -def py(bot, trigger):
- - """Evaluate a Python expression."""
- - if not trigger.group(2):
- - return bot.say("Need an expression to evaluate")
- -
- - query = trigger.group(2)
- - uri = BASE_TUMBOLIA_URI + 'py/'
- - answer = web.get(uri + web.quote(query))
- - if answer:
- - #bot.say can potentially lead to 3rd party commands triggering.
- - bot.reply(answer)
- - else:
- - bot.reply('Sorry, no result.')
- -
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/clock.py b/sopel/modules/clock.py
- deleted file mode 100644
- index d7fa8bb..0000000
- --- a/sopel/modules/clock.py
- +++ /dev/null
- @@ -1,277 +0,0 @@
- -# coding=utf-8
- -# Copyright 2008-9, Sean B. Palmer, inamidst.com
- -# Copyright 2012, Elsie Powell, embolalia.com
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -try:
- - import pytz
- -except ImportError:
- - pytz = None
- -
- -from sopel.module import commands, example, OP
- -from sopel.tools.time import (
- - get_timezone, format_time, validate_format, validate_timezone
- -)
- -from sopel.config.types import StaticSection, ValidatedAttribute
- -
- -
- -class TimeSection(StaticSection):
- - tz = ValidatedAttribute(
- - 'tz',
- - parse=validate_timezone,
- - serialize=validate_timezone,
- - default='UTC'
- - )
- - """Default time zone (see http://sopel.chat/tz)"""
- - time_format = ValidatedAttribute(
- - 'time_format',
- - parse=validate_format,
- - default='%Y-%m-%d - %T%Z'
- - )
- - """Default time format (see http://strftime.net)"""
- -
- -
- -def configure(config):
- - config.define_section('clock', TimeSection)
- - config.clock.configure_setting(
- - 'tz', 'Preferred time zone (http://sopel.chat/tz)')
- - config.clock.configure_setting(
- - 'time_format', 'Preferred time format (http://strftime.net)')
- -
- -
- -def setup(bot):
- - bot.config.define_section('clock', TimeSection)
- -
- -
- -@commands('t', 'time')
- -@example('.t America/New_York')
- -def f_time(bot, trigger):
- - """Returns the current time."""
- - if trigger.group(2):
- - zone = get_timezone(bot.db, bot.config, trigger.group(2).strip(), None, None)
- - if not zone:
- - bot.say('Could not find timezone %s.' % trigger.group(2).strip())
- - return
- - else:
- - zone = get_timezone(bot.db, bot.config, None, trigger.nick,
- - trigger.sender)
- - time = format_time(bot.db, bot.config, zone, trigger.nick, trigger.sender)
- - bot.say(time)
- -
- -
- -@commands('settz', 'settimezone')
- -@example('.settz America/New_York')
- -def update_user(bot, trigger):
- - """
- - Set your preferred time zone. Most timezones will work, but it's best to
- - use one from http://sopel.chat/tz
- - """
- - if not pytz:
- - bot.reply("Sorry, I don't have timezone support installed.")
- - else:
- - tz = trigger.group(2)
- - if not tz:
- - bot.reply("What timezone do you want to set? Try one from "
- - "http://sopel.chat/tz")
- - return
- - if tz not in pytz.all_timezones:
- - bot.reply("I don't know that time zone. Try one from "
- - "http://sopel.chat/tz")
- - return
- -
- - bot.db.set_nick_value(trigger.nick, 'timezone', tz)
- - if len(tz) < 7:
- - bot.say("Okay, {}, but you should use one from http://sopel.chat/tz "
- - "if you use DST.".format(trigger.nick))
- - else:
- - bot.reply('I now have you in the %s time zone.' % tz)
- -
- -
- -@commands('gettz', 'gettimezone')
- -@example('.gettz [nick]')
- -def get_user_tz(bot, trigger):
- - """
- - Gets a user's preferred time zone, will show yours if no user specified
- - """
- - if not pytz:
- - bot.reply("Sorry, I don't have timezone support installed.")
- - else:
- - nick = trigger.group(2)
- - if not nick:
- - nick = trigger.nick
- -
- - nick = nick.strip()
- -
- - tz = bot.db.get_nick_value(nick, 'timezone')
- - if tz:
- - bot.say('%s\'s time zone is %s.' % (nick, tz))
- - else:
- - bot.say('%s has not set their time zone' % nick)
- -
- -
- -@commands('settimeformat', 'settf')
- -@example('.settf %Y-%m-%dT%T%z')
- -def update_user_format(bot, trigger):
- - """
- - Sets your preferred format for time. Uses the standard strftime format. You
- - can use http://strftime.net or your favorite search engine to learn more.
- - """
- - tformat = trigger.group(2)
- - if not tformat:
- - bot.reply("What format do you want me to use? Try using"
- - " http://strftime.net to make one.")
- - return
- -
- - tz = get_timezone(bot.db, bot.config, None, trigger.nick, trigger.sender)
- -
- - # Get old format as back-up
- - old_format = bot.db.get_nick_value(trigger.nick, 'time_format')
- -
- - # Save the new format in the database so we can test it.
- - bot.db.set_nick_value(trigger.nick, 'time_format', tformat)
- -
- - try:
- - timef = format_time(db=bot.db, zone=tz, nick=trigger.nick)
- - except:
- - bot.reply("That format doesn't work. Try using"
- - " http://strftime.net to make one.")
- - # New format doesn't work. Revert save in database.
- - bot.db.set_nick_value(trigger.nick, 'time_format', old_format)
- - return
- - bot.reply("Got it. Your time will now appear as %s. (If the "
- - "timezone is wrong, you might try the settz command)"
- - % timef)
- -
- -
- -@commands('gettimeformat', 'gettf')
- -@example('.gettf [nick]')
- -def get_user_format(bot, trigger):
- - """
- - Gets a user's preferred time format, will show yours if no user specified
- - """
- - nick = trigger.group(2)
- - if not nick:
- - nick = trigger.nick
- -
- - nick = nick.strip()
- -
- - # Get old format as back-up
- - format = bot.db.get_nick_value(nick, 'time_format')
- -
- - if format:
- - bot.say("%s's time format: %s." % (nick, format))
- - else:
- - bot.say("%s hasn't set a custom time format" % nick)
- -
- -
- -@commands('setchanneltz', 'setctz')
- -@example('.setctz America/New_York')
- -def update_channel(bot, trigger):
- - """
- - Set the preferred time zone for the channel.
- - """
- - if bot.privileges[trigger.sender][trigger.nick] < OP:
- - return
- - elif not pytz:
- - bot.reply("Sorry, I don't have timezone support installed.")
- - else:
- - tz = trigger.group(2)
- - if not tz:
- - bot.reply("What timezone do you want to set? Try one from "
- - "http://sopel.chat/tz")
- - return
- - if tz not in pytz.all_timezones:
- - bot.reply("I don't know that time zone. Try one from "
- - "http://sopel.chat/tz")
- - return
- -
- - bot.db.set_channel_value(trigger.sender, 'timezone', tz)
- - if len(tz) < 7:
- - bot.say("Okay, {}, but you should use one from http://sopel.chat/tz "
- - "if you use DST.".format(trigger.nick))
- - else:
- - bot.reply(
- - 'I now have {} in the {} time zone.'.format(trigger.sender, tz))
- -
- -
- -@commands('getchanneltz', 'getctz')
- -@example('.getctz [channel]')
- -def get_channel_tz(bot, trigger):
- - """
- - Gets the preferred channel timezone, or the current channel timezone if no
- - channel given.
- - """
- - if not pytz:
- - bot.reply("Sorry, I don't have timezone support installed.")
- - else:
- - channel = trigger.group(2)
- - if not channel:
- - channel = trigger.sender
- -
- - channel = channel.strip()
- -
- - timezone = bot.db.get_channel_value(channel, 'timezone')
- - if timezone:
- - bot.say('%s\'s timezone: %s' % (channel, timezone))
- - else:
- - bot.say('%s has no preferred timezone' % channel)
- -
- -
- -@commands('setchanneltimeformat', 'setctf')
- -@example('.setctf %Y-%m-%dT%T%z')
- -def update_channel_format(bot, trigger):
- - """
- - Sets your preferred format for time. Uses the standard strftime format. You
- - can use http://strftime.net or your favorite search engine to learn more.
- - """
- - if bot.privileges[trigger.sender][trigger.nick] < OP:
- - return
- -
- - tformat = trigger.group(2)
- - if not tformat:
- - bot.reply("What format do you want me to use? Try using"
- - " http://strftime.net to make one.")
- -
- - tz = get_timezone(bot.db, bot.config, None, None, trigger.sender)
- -
- - # Get old format as back-up
- - old_format = bot.db.get_channel_value(trigger.sender, 'time_format')
- -
- - # Save the new format in the database so we can test it.
- - bot.db.set_channel_value(trigger.sender, 'time_format', tformat)
- -
- - try:
- - timef = format_time(db=bot.db, zone=tz, channel=trigger.sender)
- - except:
- - bot.reply("That format doesn't work. Try using"
- - " http://strftime.net to make one.")
- - # New format doesn't work. Revert save in database.
- - bot.db.set_channel_value(trigger.sender, 'time_format', old_format)
- - return
- - bot.db.set_channel_value(trigger.sender, 'time_format', tformat)
- - bot.reply("Got it. Times in this channel will now appear as %s "
- - "unless a user has their own format set. (If the timezone"
- - " is wrong, you might try the settz and channeltz "
- - "commands)" % timef)
- -
- -
- -@commands('getchanneltimeformat', 'getctf')
- -@example('.getctf [channel]')
- -def get_channel_format(bot, trigger):
- - """
- - Gets the channel's preferred time format, will return current channel's if
- - no channel name is given
- - """
- -
- - channel = trigger.group(2)
- - if not channel:
- - channel = trigger.sender
- -
- - channel = channel.strip()
- -
- - tformat = bot.db.get_channel_value(channel, 'time_format')
- - if tformat:
- - bot.say('%s\'s time format: %s' % (channel, tformat))
- - else:
- - bot.say('%s has no preferred time format' % channel)
- diff --git a/sopel/modules/countdown.py b/sopel/modules/countdown.py
- deleted file mode 100644
- index 23a6df8..0000000
- --- a/sopel/modules/countdown.py
- +++ /dev/null
- @@ -1,39 +0,0 @@
- -# coding=utf-8
- -"""
- -countdown.py - Sopel Countdown Module
- -Copyright 2011, Michael Yanovich, yanovich.net
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -from sopel.module import commands, NOLIMIT
- -import datetime
- -
- -
- -@commands('countdown')
- -def generic_countdown(bot, trigger):
- - """
- - .countdown <year> <month> <day> - displays a countdown to a given date.
- - """
- - text = trigger.group(2)
- - if not text:
- - bot.say("Please use correct format: .countdown 2012 12 21")
- - return NOLIMIT
- - text = trigger.group(2).split()
- - if text and (len(text) == 3 and text[0].isdigit() and text[1].isdigit()
- - and text[2].isdigit()):
- - try:
- - diff = (datetime.datetime(int(text[0]), int(text[1]), int(text[2]))
- - - datetime.datetime.today())
- - except:
- - bot.say("Please use correct format: .countdown 2012 12 21")
- - return NOLIMIT
- - bot.say(str(diff.days) + " days, " + str(diff.seconds // 3600)
- - + " hours and "
- - + str(diff.seconds % 3600 // 60)
- - + " minutes until "
- - + text[0] + " " + text[1] + " " + text[2])
- - else:
- - bot.say("Please use correct format: .countdown 2012 12 21")
- - return NOLIMIT
- diff --git a/sopel/modules/currency.py b/sopel/modules/currency.py
- deleted file mode 100644
- index 3bf0183..0000000
- --- a/sopel/modules/currency.py
- +++ /dev/null
- @@ -1,105 +0,0 @@
- -# coding=utf-8
- -# Copyright 2013 Elsie Powell, embolalia.com
- -# Licensed under the Eiffel Forum License 2
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import json
- -import xmltodict
- -import re
- -
- -from sopel import web
- -from sopel.module import commands, example, NOLIMIT
- -
- -# The Canadian central bank has better exchange rate data than the Fed, the
- -# Bank of England, or the European Central Bank. Who knew?
- -base_url = 'http://www.bankofcanada.ca/stats/assets/rates_rss/noon/en_{}.xml'
- -regex = re.compile(r'''
- - (\d+(?:\.\d+)?) # Decimal number
- - \s*([a-zA-Z]{3}) # 3-letter currency code
- - \s+(?:in|as|of|to)\s+ # preposition
- - ([a-zA-Z]{3}) # 3-letter currency code
- - ''', re.VERBOSE)
- -
- -
- -def get_rate(code):
- - code = code.upper()
- - if code == 'CAD':
- - return 1, 'Canadian Dollar'
- - elif code == 'BTC':
- - rates = json.loads(web.get('https://api.bitcoinaverage.com/ticker/all'))
- - return 1 / rates['CAD']['24h_avg'], 'Bitcoin—24hr average'
- -
- - data, headers = web.get(base_url.format(code), dont_decode=True, return_headers=True)
- - if headers['_http_status'] == 404:
- - return False, False
- - namespaces = {
- - 'http://www.cbwiki.net/wiki/index.php/Specification_1.1': 'cb',
- - 'http://purl.org/rss/1.0/': None,
- - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#': 'rdf'
- - }
- - xml = xmltodict.parse(data, process_namespaces=True, namespaces=namespaces).get('rdf:RDF')
- - namestring = xml.get('channel').get('title').get('#text')
- - name = namestring[len('Bank of Canada noon rate: '):]
- - name = re.sub(r'\s*\(noon\)\s*', '', name)
- - rate = xml.get('item').get('cb:statistics').get('cb:exchangeRate').get('cb:value').get('#text')
- - return float(rate), name
- -
- -
- -@commands('cur', 'currency', 'exchange')
- -@example('.cur 20 EUR in USD')
- -def exchange(bot, trigger):
- - """Show the exchange rate between two currencies"""
- - if not trigger.group(2):
- - return bot.reply("No search term. An example: .cur 20 EUR in USD")
- - match = regex.match(trigger.group(2))
- - if not match:
- - # It's apologetic, because it's using Canadian data.
- - bot.reply("Sorry, I didn't understand the input.")
- - return NOLIMIT
- -
- - amount, of, to = match.groups()
- - try:
- - amount = float(amount)
- - except:
- - bot.reply("Sorry, I didn't understand the input.")
- - display(bot, amount, of, to)
- -
- -
- -def display(bot, amount, of, to):
- - if not amount:
- - bot.reply("Zero is zero, no matter what country you're in.")
- - try:
- - of_rate, of_name = get_rate(of)
- - if not of_name:
- - bot.reply("Unknown currency: %s" % of)
- - return
- - to_rate, to_name = get_rate(to)
- - if not to_name:
- - bot.reply("Unknown currency: %s" % to)
- - return
- - except Exception:
- - bot.reply("Something went wrong while I was getting the exchange rate.")
- - return NOLIMIT
- -
- - result = amount / of_rate * to_rate
- - bot.say("{} {} ({}) = {} {} ({})".format(amount, of.upper(), of_name,
- - result, to.upper(), to_name))
- -
- -
- -@commands('btc', 'bitcoin')
- -@example('.btc 20 EUR')
- -def bitcoin(bot, trigger):
- - #if 2 args, 1st is number and 2nd is currency. If 1 arg, it's either the number or the currency.
- - to = trigger.group(4)
- - amount = trigger.group(3)
- - if not to:
- - to = trigger.group(3) or 'USD'
- - amount = 1
- -
- - try:
- - amount = float(amount)
- - except:
- - bot.reply("Sorry, I didn't understand the input.")
- - return NOLIMIT
- -
- - display(bot, amount, 'BTC', to)
- diff --git a/sopel/modules/dice.py b/sopel/modules/dice.py
- deleted file mode 100644
- index 2ac0d06..0000000
- --- a/sopel/modules/dice.py
- +++ /dev/null
- @@ -1,259 +0,0 @@
- -# coding=utf-8
- -"""
- -dice.py - Dice Module
- -Copyright 2010-2013, Dimitri "Tyrope" Molenaars, TyRope.nl
- -Copyright 2013, Ari Koivula, <ari@koivu.la>
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat/
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -import random
- -import re
- -import operator
- -
- -import sopel.module
- -from sopel.tools.calculation import eval_equation
- -
- -
- -class DicePouch:
- - def __init__(self, num_of_die, type_of_die, addition):
- - """Initialize dice pouch and roll the dice.
- -
- - Args:
- - num_of_die: number of dice in the pouch.
- - type_of_die: how many faces the dice have.
- - addition: how much is added to the result of the dice.
- - """
- - self.num = num_of_die
- - self.type = type_of_die
- - self.addition = addition
- -
- - self.dice = {}
- - self.dropped = {}
- -
- - self.roll_dice()
- -
- - def roll_dice(self):
- - """Roll all the dice in the pouch."""
- - self.dice = {}
- - self.dropped = {}
- - for __ in range(self.num):
- - number = random.randint(1, self.type)
- - count = self.dice.setdefault(number, 0)
- - self.dice[number] = count + 1
- -
- - def drop_lowest(self, n):
- - """Drop n lowest dice from the result.
- -
- - Args:
- - n: the number of dice to drop.
- - """
- -
- - sorted_x = sorted(self.dice.items(), key=operator.itemgetter(0))
- -
- - for i, count in sorted_x:
- - count = self.dice[i]
- - if n == 0:
- - break
- - elif n < count:
- - self.dice[i] = count - n
- - self.dropped[i] = n
- - break
- - else:
- - self.dice[i] = 0
- - self.dropped[i] = count
- - n = n - count
- -
- - for i, count in self.dropped.items():
- - if self.dice[i] == 0:
- - del self.dice[i]
- -
- - def get_simple_string(self):
- - """Return the values of the dice like (2+2+2[+1+1])+1."""
- - dice = self.dice.items()
- - faces = ("+".join([str(face)] * times) for face, times in dice)
- - dice_str = "+".join(faces)
- -
- - dropped_str = ""
- - if self.dropped:
- - dropped = self.dropped.items()
- - dfaces = ("+".join([str(face)] * times) for face, times in dropped)
- - dropped_str = "[+%s]" % ("+".join(dfaces),)
- -
- - plus_str = ""
- - if self.addition:
- - plus_str = "{:+d}".format(self.addition)
- -
- - return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
- -
- - def get_compressed_string(self):
- - """Return the values of the dice like (3x2[+2x1])+1."""
- - dice = self.dice.items()
- - faces = ("%dx%d" % (times, face) for face, times in dice)
- - dice_str = "+".join(faces)
- -
- - dropped_str = ""
- - if self.dropped:
- - dropped = self.dropped.items()
- - dfaces = ("%dx%d" % (times, face) for face, times in dropped)
- - dropped_str = "[+%s]" % ("+".join(dfaces),)
- -
- - plus_str = ""
- - if self.addition:
- - plus_str = "{:+d}".format(self.addition)
- -
- - return "(%s%s)%s" % (dice_str, dropped_str, plus_str)
- -
- - def get_sum(self):
- - """Get the sum of non-dropped dice and the addition."""
- - result = self.addition
- - for face, times in self.dice.items():
- - result += face * times
- - return result
- -
- - def get_number_of_faces(self):
- - """Returns sum of different faces for dropped and not dropped dice
- -
- - This can be used to estimate, whether the result can be shown in
- - compressed form in a reasonable amount of space.
- - """
- - return len(self.dice) + len(self.dropped)
- -
- -
- -def _roll_dice(bot, dice_expression):
- - result = re.search(
- - r"""
- - (?P<dice_num>-?\d*)
- - d
- - (?P<dice_type>-?\d+)
- - (v(?P<drop_lowest>-?\d+))?
- - $""",
- - dice_expression,
- - re.IGNORECASE | re.VERBOSE)
- -
- - dice_num = int(result.group('dice_num') or 1)
- - dice_type = int(result.group('dice_type'))
- -
- - # Dice can't have zero or a negative number of sides.
- - if dice_type <= 0:
- - bot.reply("I don't have any dice with %d sides. =(" % dice_type)
- - return None # Signal there was a problem
- -
- - # Can't roll a negative number of dice.
- - if dice_num < 0:
- - bot.reply("I'd rather not roll a negative amount of dice. =(")
- - return None # Signal there was a problem
- -
- - # Upper limit for dice should be at most a million. Creating a dict with
- - # more than a million elements already takes a noticeable amount of time
- - # on a fast computer and ~55kB of memory.
- - if dice_num > 1000:
- - bot.reply('I only have 1000 dice. =(')
- - return None # Signal there was a problem
- -
- - dice = DicePouch(dice_num, dice_type, 0)
- -
- - if result.group('drop_lowest'):
- - drop = int(result.group('drop_lowest'))
- - if drop >= 0:
- - dice.drop_lowest(drop)
- - else:
- - bot.reply("I can't drop the lowest %d dice. =(" % drop)
- -
- - return dice
- -
- -
- -@sopel.module.commands("roll")
- -@sopel.module.commands("dice")
- -@sopel.module.commands("d")
- -@sopel.module.priority("medium")
- -@sopel.module.example(".roll 3d1+1", 'You roll 3d1+1: (1+1+1)+1 = 4')
- -@sopel.module.example(".roll 3d1v2+1", 'You roll 3d1v2+1: (1[+1+1])+1 = 2')
- -@sopel.module.example(".roll 2d4", 'You roll 2d4: \(\d\+\d\) = \d', re=True)
- -@sopel.module.example(".roll 100d1", '[^:]*: \(100x1\) = 100', re=True)
- -@sopel.module.example(".roll 1001d1", 'I only have 1000 dice. =(')
- -@sopel.module.example(".roll 1d1 + 1d1", 'You roll 1d1 + 1d1: (1) + (1) = 2')
- -@sopel.module.example(".roll 1d1+1d1", 'You roll 1d1+1d1: (1)+(1) = 2')
- -def roll(bot, trigger):
- - """.dice XdY[vZ][+N], rolls dice and reports the result.
- -
- - X is the number of dice. Y is the number of faces in the dice. Z is the
- - number of lowest dice to be dropped from the result. N is the constant to
- - be applied to the end result.
- - """
- - # This regexp is only allowed to have one captured group, because having
- - # more would alter the output of re.findall.
- - dice_regexp = r"-?\d*[dD]-?\d+(?:[vV]-?\d+)?"
- -
- - # Get a list of all dice expressions, evaluate them and then replace the
- - # expressions in the original string with the results. Replacing is done
- - # using string formatting, so %-characters must be escaped.
- - if not trigger.group(2):
- - return bot.reply("No dice to roll.")
- - arg_str = trigger.group(2)
- - dice_expressions = re.findall(dice_regexp, arg_str)
- - arg_str = arg_str.replace("%", "%%")
- - arg_str = re.sub(dice_regexp, "%s", arg_str)
- -
- - f = lambda dice_expr: _roll_dice(bot, dice_expr)
- - dice = list(map(f, dice_expressions))
- -
- - if None in dice:
- - # Stop computing roll if there was a problem rolling dice.
- - return
- -
- - def _get_eval_str(dice):
- - return "(%d)" % (dice.get_sum(),)
- -
- - def _get_pretty_str(dice):
- - if dice.num <= 10:
- - return dice.get_simple_string()
- - elif dice.get_number_of_faces() <= 10:
- - return dice.get_compressed_string()
- - else:
- - return "(...)"
- -
- - eval_str = arg_str % (tuple(map(_get_eval_str, dice)))
- - pretty_str = arg_str % (tuple(map(_get_pretty_str, dice)))
- -
- - # Showing the actual error will hopefully give a better hint of what is
- - # wrong with the syntax than a generic error message.
- - try:
- - result = eval_equation(eval_str)
- - except Exception as e:
- - bot.reply("SyntaxError, eval(%s), %s" % (eval_str, e))
- - return
- -
- - bot.reply("You roll %s: %s = %d" % (
- - trigger.group(2), pretty_str, result))
- -
- -
- -@sopel.module.commands("choice")
- -@sopel.module.commands("ch")
- -@sopel.module.commands("choose")
- -@sopel.module.priority("medium")
- -def choose(bot, trigger):
- - """
- - .choice option1|option2|option3 - Makes a difficult choice easy.
- - """
- - if not trigger.group(2):
- - return bot.reply('I\'d choose an option, but you didn\'t give me any.')
- - choices = [trigger.group(2)]
- - for delim in '|\\/,':
- - choices = trigger.group(2).split(delim)
- - if len(choices) > 1:
- - break
- - # Use a different delimiter in the output, to prevent ambiguity.
- - for show_delim in ',|/\\':
- - if show_delim not in trigger.group(2):
- - show_delim += ' '
- - break
- -
- - pick = random.choice(choices)
- - return bot.reply('Your options: %s. My choice: %s' % (show_delim.join(choices), pick))
- -
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/etymology.py b/sopel/modules/etymology.py
- deleted file mode 100644
- index e0663c8..0000000
- --- a/sopel/modules/etymology.py
- +++ /dev/null
- @@ -1,97 +0,0 @@
- -# coding=utf-8
- -"""
- -etymology.py - Sopel Etymology Module
- -Copyright 2007-9, Sean B. Palmer, inamidst.com
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -from sopel import web
- -from sopel.module import commands, example, NOLIMIT
- -
- -etyuri = 'http://etymonline.com/?term=%s'
- -etysearch = 'http://etymonline.com/?search=%s'
- -
- -r_definition = re.compile(r'(?ims)<dd[^>]*>.*?</dd>')
- -r_tag = re.compile(r'<(?!!)[^>]+>')
- -r_whitespace = re.compile(r'[\t\r\n ]+')
- -
- -abbrs = [
- - 'cf', 'lit', 'etc', 'Ger', 'Du', 'Skt', 'Rus', 'Eng', 'Amer.Eng', 'Sp',
- - 'Fr', 'N', 'E', 'S', 'W', 'L', 'Gen', 'J.C', 'dial', 'Gk',
- - '19c', '18c', '17c', '16c', 'St', 'Capt', 'obs', 'Jan', 'Feb', 'Mar',
- - 'Apr', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', 'c', 'tr', 'e', 'g'
- -]
- -t_sentence = r'^.*?(?<!%s)(?:\.(?= [A-Z0-9]|\Z)|\Z)'
- -r_sentence = re.compile(t_sentence % ')(?<!'.join(abbrs))
- -
- -
- -def unescape(s):
- - s = s.replace('>', '>')
- - s = s.replace('<', '<')
- - s = s.replace('&', '&')
- - return s
- -
- -
- -def text(html):
- - html = r_tag.sub('', html)
- - html = r_whitespace.sub(' ', html)
- - return unescape(html).strip()
- -
- -
- -def etymology(word):
- - # @@ <nsh> sbp, would it be possible to have a flag for .ety to get 2nd/etc
- - # entries? - http://swhack.com/logs/2006-07-19#T15-05-29
- -
- - if len(word) > 25:
- - raise ValueError("Word too long: %s[...]" % word[:10])
- - word = {'axe': 'ax/axe'}.get(word, word)
- -
- - bytes = web.get(etyuri % word)
- - definitions = r_definition.findall(bytes)
- -
- - if not definitions:
- - return None
- -
- - defn = text(definitions[0])
- - m = r_sentence.match(defn)
- - if not m:
- - return None
- - sentence = m.group(0)
- -
- - maxlength = 275
- - if len(sentence) > maxlength:
- - sentence = sentence[:maxlength]
- - words = sentence[:-5].split(' ')
- - words.pop()
- - sentence = ' '.join(words) + ' [...]'
- -
- - sentence = '"' + sentence.replace('"', "'") + '"'
- - return sentence + ' - ' + (etyuri % word)
- -
- -
- -@commands('ety')
- -@example('.ety word')
- -def f_etymology(bot, trigger):
- - """Look up the etymology of a word"""
- - word = trigger.group(2)
- -
- - try:
- - result = etymology(word)
- - except IOError:
- - msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
- - bot.msg(trigger.sender, msg)
- - return NOLIMIT
- - except (AttributeError, TypeError):
- - result = None
- -
- - if result is not None:
- - bot.msg(trigger.sender, result)
- - else:
- - uri = etysearch % word
- - msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri)
- - bot.msg(trigger.sender, msg)
- - return NOLIMIT
- diff --git a/sopel/modules/find.py b/sopel/modules/find.py
- deleted file mode 100644
- index 8a1f58a..0000000
- --- a/sopel/modules/find.py
- +++ /dev/null
- @@ -1,139 +0,0 @@
- -# coding=utf-8
- -"""Sopel Spelling correction module
- -
- -This module will fix spelling errors if someone corrects them
- -using the sed notation (s///) commonly found in vi/vim.
- -"""
- -# Copyright 2011, Michael Yanovich, yanovich.net
- -# Copyright 2013, Elsie Powell, embolalia.com
- -# Licensed under the Eiffel Forum License 2.
- -# Contributions from: Matt Meinwald and Morgan Goose
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -from sopel.tools import Identifier, SopelMemory
- -from sopel.module import rule, priority
- -from sopel.formatting import bold
- -
- -
- -def setup(bot):
- - bot.memory['find_lines'] = SopelMemory()
- -
- -
- -@rule('.*')
- -@priority('low')
- -def collectlines(bot, trigger):
- - """Create a temporary log of what people say"""
- -
- - # Don't log things in PM
- - if trigger.is_privmsg:
- - return
- -
- - # Add a log for the channel and nick, if there isn't already one
- - if trigger.sender not in bot.memory['find_lines']:
- - bot.memory['find_lines'][trigger.sender] = SopelMemory()
- - if Identifier(trigger.nick) not in bot.memory['find_lines'][trigger.sender]:
- - bot.memory['find_lines'][trigger.sender][Identifier(trigger.nick)] = list()
- -
- - # Create a temporary list of the user's lines in a channel
- - templist = bot.memory['find_lines'][trigger.sender][Identifier(trigger.nick)]
- - line = trigger.group()
- - if line.startswith("s/"): # Don't remember substitutions
- - return
- - elif line.startswith("\x01ACTION"): # For /me messages
- - line = line[:-1]
- - templist.append(line)
- - else:
- - templist.append(line)
- -
- - del templist[:-10] # Keep the log to 10 lines per person
- -
- - bot.memory['find_lines'][trigger.sender][Identifier(trigger.nick)] = templist
- -
- -
- -#Match nick, s/find/replace/flags. Flags and nick are optional, nick can be
- -#followed by comma or colon, anything after the first space after the third
- -#slash is ignored, you can escape slashes with backslashes, and if you want to
- -#search for an actual backslash followed by an actual slash, you're shit out of
- -#luck because this is the fucking regex of death as it is.
- -@rule(r"""(?:
- - (\S+) # Catch a nick in group 1
- - [:,]\s+)? # Followed by colon/comma and whitespace, if given
- - s/ # The literal s/
- - ( # Group 2 is the thing to find
- - (?:\\/ | [^/])+ # One or more non-slashes or escaped slashes
- - )/( # Group 3 is what to replace with
- - (?:\\/ | [^/])* # One or more non-slashes or escaped slashes
- - )
- - (?:/(\S+))? # Optional slash, followed by group 4 (flags)
- - """)
- -@priority('high')
- -def findandreplace(bot, trigger):
- - # Don't bother in PM
- - if trigger.is_privmsg:
- - return
- -
- - # Correcting other person vs self.
- - rnick = Identifier(trigger.group(1) or trigger.nick)
- -
- - search_dict = bot.memory['find_lines']
- - # only do something if there is conversation to work with
- - if trigger.sender not in search_dict:
- - return
- - if Identifier(rnick) not in search_dict[trigger.sender]:
- - return
- -
- - #TODO rest[0] is find, rest[1] is replace. These should be made variables of
- - #their own at some point.
- - rest = [trigger.group(2), trigger.group(3)]
- - rest[0] = rest[0].replace(r'\/', '/')
- - rest[1] = rest[1].replace(r'\/', '/')
- - me = False # /me command
- - flags = (trigger.group(4) or '')
- -
- - # If g flag is given, replace all. Otherwise, replace once.
- - if 'g' in flags:
- - count = -1
- - else:
- - count = 1
- -
- - # repl is a lambda function which performs the substitution. i flag turns
- - # off case sensitivity. re.U turns on unicode replacement.
- - if 'i' in flags:
- - regex = re.compile(re.escape(rest[0]), re.U | re.I)
- - repl = lambda s: re.sub(regex, rest[1], s, count == 1)
- - else:
- - repl = lambda s: s.replace(rest[0], rest[1], count)
- -
- - # Look back through the user's lines in the channel until you find a line
- - # where the replacement works
- - new_phrase = None
- - for line in reversed(search_dict[trigger.sender][rnick]):
- - if line.startswith("\x01ACTION"):
- - me = True # /me command
- - line = line[8:]
- - else:
- - me = False
- - new_phrase = repl(line)
- - if new_phrase != line: # we are done
- - break
- -
- - if not new_phrase or new_phrase == line:
- - return # Didn't find anything
- -
- - # Save the new "edited" message.
- - action = (me and '\x01ACTION ') or '' # If /me message, prepend \x01ACTION
- - templist = search_dict[trigger.sender][rnick]
- - templist.append(action + new_phrase)
- - search_dict[trigger.sender][rnick] = templist
- - bot.memory['find_lines'] = search_dict
- -
- - # output
- - if not me:
- - new_phrase = '%s to say: %s' % (bold('meant'), new_phrase)
- - if trigger.group(1):
- - phrase = '%s thinks %s %s' % (trigger.nick, rnick, new_phrase)
- - else:
- - phrase = '%s %s' % (trigger.nick, new_phrase)
- -
- - bot.say(phrase)
- diff --git a/sopel/modules/find_updates.py b/sopel/modules/find_updates.py
- deleted file mode 100644
- index b411447..0000000
- --- a/sopel/modules/find_updates.py
- +++ /dev/null
- @@ -1,58 +0,0 @@
- -# coding=utf-8
- -"""Update checking module for Sopel.
- -
- -This is separated from version.py, so that it can be easily overridden by
- -distribution packagers, and they can check their repositories rather than the
- -Sopel website.
- -"""
- -# Copyright 2014, Elsie Powell, embolalia.com
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import json
- -
- -import sopel
- -import sopel.module
- -import requests
- -import sopel.tools
- -
- -wait_time = 24 * 60 * 60 # check once per day
- -startup_check_run = False
- -version_url = 'http://sopel.chat/latest.json'
- -message = (
- - 'A new Sopel version, {}, is available. I am running {}. Please update '
- - 'me. Full release notes at {}'
- -)
- -unstable_message = (
- - 'A new pre-release version, {}, is available. I am running {}. Please '
- - 'update me. {}'
- -)
- -
- -
- -@sopel.module.event(sopel.tools.events.RPL_LUSERCLIENT)
- -@sopel.module.rule('.*')
- -def startup_version_check(bot, trigger):
- - global startup_check_run
- - if not startup_check_run:
- - startup_check_run = True
- - check_version(bot)
- -
- -
- -@sopel.module.interval(wait_time)
- -def check_version(bot):
- - version = sopel.version_info
- -
- - info = requests.get(version_url).json()
- - if version.releaselevel == 'final':
- - latest = info['version']
- - notes = info['release_notes']
- - else:
- - latest = info['unstable']
- - notes = info.get('unstable_notes', '')
- - if notes:
- - notes = 'Full release notes at ' + notes
- - latest_version = sopel._version_info(latest)
- - msg = message.format(latest, sopel.__version__, notes)
- -
- - if version < latest_version:
- - bot.msg(bot.config.core.owner, msg)
- diff --git a/sopel/modules/help.py b/sopel/modules/help.py
- deleted file mode 100644
- index 1dadde4..0000000
- --- a/sopel/modules/help.py
- +++ /dev/null
- @@ -1,72 +0,0 @@
- -# coding=utf-8
- -"""
- -help.py - Sopel Help Module
- -Copyright 2008, Sean B. Palmer, inamidst.com
- -Copyright © 2013, Elad Alfassa, <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import textwrap
- -import collections
- -
- -from sopel.formatting import bold
- -from sopel.module import commands, rule, example, priority
- -
- -
- -@rule('$nick' '(?i)(help|doc) +([A-Za-z]+)(?:\?+)?$')
- -@example('.help tell')
- -@commands('help', 'commands')
- -@priority('low')
- -def help(bot, trigger):
- - """Shows a command's documentation, and possibly an example."""
- - if trigger.group(2):
- - name = trigger.group(2)
- - name = name.lower()
- -
- - # number of lines of help to show
- - threshold = 3
- -
- - if name in bot.doc:
- - if len(bot.doc[name][0]) + (1 if bot.doc[name][1] else 0) > threshold:
- - if trigger.nick != trigger.sender: # don't say that if asked in private
- - bot.reply('The documentation for this command is too long; I\'m sending it to you in a private message.')
- - msgfun = lambda l: bot.msg(trigger.nick, l)
- - else:
- - msgfun = bot.reply
- -
- - for line in bot.doc[name][0]:
- - msgfun(line)
- - if bot.doc[name][1]:
- - msgfun('e.g. ' + bot.doc[name][1])
- - else:
- - if not trigger.is_privmsg:
- - bot.reply("I'm sending you a list of my commands in a private message!")
- - bot.say(
- - 'You can see more info about any of these commands by doing .help '
- - '<command> (e.g. .help time)',
- - trigger.nick
- - )
- -
- - name_length = max(6, max(len(k) for k in bot.command_groups.keys()))
- - for category, cmds in collections.OrderedDict(sorted(bot.command_groups.items())).items():
- - category = category.upper().ljust(name_length)
- - cmds = ' '.join(cmds)
- - msg = bold(category) + ' ' + cmds
- - indent = ' ' * (name_length + 2)
- - msg = textwrap.wrap(msg, subsequent_indent=indent)
- - for line in msg:
- - bot.say(line, trigger.nick)
- -
- -
- -@rule('$nick' r'(?i)help(?:[?!]+)?$')
- -@priority('low')
- -def help2(bot, trigger):
- - response = (
- - 'Hi, I\'m a bot. Say ".commands" to me in private for a list ' +
- - 'of my commands, or see http://sopel.chat for more ' +
- - 'general details. My owner is %s.'
- - ) % bot.config.core.owner
- - bot.reply(response)
- diff --git a/sopel/modules/ip.py b/sopel/modules/ip.py
- deleted file mode 100644
- index b2e2804..0000000
- --- a/sopel/modules/ip.py
- +++ /dev/null
- @@ -1,139 +0,0 @@
- -# coding=utf-8
- -"""GeoIP lookup module"""
- -# Copyright 2011, Dimitri Molenaars, TyRope.nl,
- -# Copyright © 2013, Elad Alfassa <elad@fedoraproject.org>
- -# Licensed under the Eiffel Forum License 2.
- -
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import pygeoip
- -import socket
- -import os
- -import gzip
- -
- -urlretrieve = None
- -try:
- - from urllib import urlretrieve
- -except ImportError:
- - try:
- - # urlretrieve has been put under urllib.request in Python 3.
- - # It's also deprecated so this should probably be replaced with
- - # urllib2.
- - from urllib.request import urlretrieve
- - except ImportError:
- - pass
- -
- -from sopel.config.types import StaticSection, FilenameAttribute
- -from sopel.module import commands, example
- -from sopel.logger import get_logger
- -
- -LOGGER = get_logger(__name__)
- -
- -
- -class GeoipSection(StaticSection):
- - GeoIP_db_path = FilenameAttribute('GeoIP_db_path', directory=True)
- - """Path of the directory containing the GeoIP db files."""
- -
- -
- -def configure(config):
- - config.define_section('ip', GeoipSection)
- - config.ip.configure_setting('GeoIP_db_path',
- - 'Path of the GeoIP db files')
- -
- -
- -def setup(bot=None):
- - if not bot:
- - return # Because of some weird pytest thing?
- -
- - bot.config.define_section('ip', GeoipSection)
- -
- -
- -def _decompress(source, target, delete_after_decompression=True):
- - """ Decompress a GZip file """
- - f_in = gzip.open(source, 'rb')
- - f_out = open(target, 'wb')
- - f_out.writelines(f_in)
- - f_out.close()
- - f_in.close()
- - if delete_after_decompression:
- - os.remove(source)
- -
- -
- -def _find_geoip_db(bot):
- - """ Find the GeoIP database """
- - config = bot.config
- - if config.ip.GeoIP_db_path:
- - cities_db = os.path.join(config.ip.GeoIP_db_path, 'GeoLiteCity.dat')
- - ipasnum_db = os.path.join(config.ip.GeoIP_db_path, 'GeoIPASNum.dat')
- - if os.path.isfile(cities_db) and os.path.isfile(ipasnum_db):
- - return config.ip.GeoIP_db_path
- - else:
- - LOGGER.warning(
- - 'GeoIP path configured but DB not found in configured path'
- - )
- - if (os.path.isfile(os.path.join(bot.config.core.homedir, 'GeoLiteCity.dat')) and
- - os.path.isfile(os.path.join(bot.config.core.homedir, 'GeoIPASNum.dat'))):
- - return bot.config.core.homedir
- - elif (os.path.isfile(os.path.join('/usr/share/GeoIP', 'GeoLiteCity.dat')) and
- - os.path.isfile(os.path.join('/usr/share/GeoIP', 'GeoIPASNum.dat'))):
- - return '/usr/share/GeoIP'
- - elif urlretrieve:
- - LOGGER.warning('Downloading GeoIP database')
- - bot.say('Downloading GeoIP database, please wait...')
- - geolite_city_url = 'http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz'
- - geolite_ASN_url = 'http://download.maxmind.com/download/geoip/database/asnum/GeoIPASNum.dat.gz'
- - geolite_city_filepath = os.path.join(bot.config.core.homedir, 'GeoLiteCity.dat.gz')
- - geolite_ASN_filepath = os.path.join(bot.config.core.homedir, 'GeoIPASNum.dat.gz')
- - urlretrieve(geolite_city_url, geolite_city_filepath)
- - urlretrieve(geolite_ASN_url, geolite_ASN_filepath)
- - _decompress(geolite_city_filepath, geolite_city_filepath[:-3])
- - _decompress(geolite_ASN_filepath, geolite_ASN_filepath[:-3])
- - return bot.config.core.homedir
- - else:
- - return False
- -
- -
- -@commands('iplookup', 'ip')
- -@example('.ip 8.8.8.8',
- - r'[IP/Host Lookup] Hostname: google-public-dns-a.google.com | Location: United States | Region: CA | ISP: AS15169 Google Inc.',
- - re=True,
- - ignore='Downloading GeoIP database, please wait...')
- -def ip(bot, trigger):
- - """IP Lookup tool"""
- - if not trigger.group(2):
- - return bot.reply("No search term.")
- - query = trigger.group(2)
- - db_path = _find_geoip_db(bot)
- - if db_path is False:
- - LOGGER.error('Can\'t find (or download) usable GeoIP database')
- - bot.say('Sorry, I don\'t have a GeoIP database to use for this lookup')
- - return False
- - geolite_city_filepath = os.path.join(_find_geoip_db(bot), 'GeoLiteCity.dat')
- - geolite_ASN_filepath = os.path.join(_find_geoip_db(bot), 'GeoIPASNum.dat')
- - gi_city = pygeoip.GeoIP(geolite_city_filepath)
- - gi_org = pygeoip.GeoIP(geolite_ASN_filepath)
- - host = socket.getfqdn(query)
- - response = "[IP/Host Lookup] Hostname: %s" % host
- - try:
- - response += " | Location: %s" % gi_city.country_name_by_name(query)
- - except AttributeError:
- - response += ' | Location: Unknown'
- - except socket.gaierror:
- - return bot.say('[IP/Host Lookup] Unable to resolve IP/Hostname')
- -
- - region_data = gi_city.region_by_name(query)
- - try:
- - region = region_data['region_code'] # pygeoip >= 0.3.0
- - except KeyError:
- - region = region_data['region_name'] # pygeoip < 0.3.0
- - if region:
- - response += " | Region: %s" % region
- -
- - isp = gi_org.org_by_name(query)
- - response += " | ISP: %s" % isp
- - bot.say(response)
- -
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/ipython.py b/sopel/modules/ipython.py
- deleted file mode 100644
- index 6df6112..0000000
- --- a/sopel/modules/ipython.py
- +++ /dev/null
- @@ -1,78 +0,0 @@
- -# coding=utf-8
- -"""
- -ipython.py - sopel ipython console!
- -Copyright © 2014, Elad Alfassa <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -Sopel: http://sopel.chat/
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -import sopel
- -import sys
- -if sys.version_info.major >= 3:
- - # Backup stderr/stdout wrappers
- - old_stdout = sys.stdout
- - old_stderr = sys.stderr
- -
- - # IPython wants actual stderr and stdout. In Python 2, it only needed that
- - # when actually starting the console, but in Python 3 it seems to need that
- - # on import as well
- - sys.stdout = sys.__stdout__
- - sys.stderr = sys.__stderr__
- -try:
- - import IPython
- - if hasattr(IPython, 'terminal'):
- - from IPython.terminal.embed import InteractiveShellEmbed
- - else:
- - from IPython.frontend.terminal.embed import InteractiveShellEmbed
- -finally:
- - if sys.version_info.major >= 3:
- - # Restore stderr/stdout wrappers
- - sys.stdout = old_stdout
- - sys.stderr = old_stderr
- -
- -console = None
- -
- -
- -@sopel.module.commands('console')
- -def interactive_shell(bot, trigger):
- - """
- - Starts an interactive IPython console
- - """
- - global console
- - if not trigger.admin:
- - bot.say('Only admins can start the interactive console')
- - return
- - if 'iconsole_running' in bot.memory and bot.memory['iconsole_running']:
- - bot.say('Console already running')
- - return
- - if not sys.__stdout__.isatty():
- - bot.say('A tty is required to start the console')
- - return
- - if bot._daemon:
- - bot.say('Can\'t start console when running as a daemon')
- - return
- -
- - # Backup stderr/stdout wrappers
- - old_stdout = sys.stdout
- - old_stderr = sys.stderr
- -
- - # IPython wants actual stderr and stdout
- - sys.stdout = sys.__stdout__
- - sys.stderr = sys.__stderr__
- -
- - banner1 = 'Sopel interactive shell (embedded IPython)'
- - banner2 = '`bot` and `trigger` are available. To exit, type exit'
- - exitmsg = 'Interactive shell closed'
- -
- - console = InteractiveShellEmbed(banner1=banner1, banner2=banner2,
- - exit_msg=exitmsg)
- -
- - bot.memory['iconsole_running'] = True
- - bot.say('console started')
- - console()
- - bot.memory['iconsole_running'] = False
- -
- - # Restore stderr/stdout wrappers
- - sys.stdout = old_stdout
- - sys.stderr = old_stderr
- diff --git a/sopel/modules/isup.py b/sopel/modules/isup.py
- deleted file mode 100644
- index d0c939d..0000000
- --- a/sopel/modules/isup.py
- +++ /dev/null
- @@ -1,36 +0,0 @@
- -# coding=utf-8
- -"""Simple website status check with isup.me"""
- -# Author: Elsie Powell http://embolalia.com
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel import web
- -from sopel.module import commands
- -
- -
- -@commands('isup')
- -def isup(bot, trigger):
- - """isup.me website status checker"""
- - site = trigger.group(2)
- - if not site:
- - return bot.reply("What site do you want to check?")
- -
- - if site[:7] != 'http://' and site[:8] != 'https://':
- - if '://' in site:
- - protocol = site.split('://')[0] + '://'
- - return bot.reply("Try it again without the %s" % protocol)
- - else:
- - site = 'http://' + site
- -
- - if not '.' in site:
- - site += ".com"
- -
- - try:
- - response = web.get(site)
- - except Exception:
- - bot.say(site + ' looks down from here.')
- - return
- -
- - if response:
- - bot.say(site + ' looks fine to me.')
- - else:
- - bot.say(site + ' is down from here.')
- diff --git a/sopel/modules/lmgtfy.py b/sopel/modules/lmgtfy.py
- deleted file mode 100644
- index 7d0caeb..0000000
- --- a/sopel/modules/lmgtfy.py
- +++ /dev/null
- @@ -1,19 +0,0 @@
- -# coding=utf-8
- -"""
- -lmgtfy.py - Sopel Let me Google that for you module
- -Copyright 2013, Dimitri Molenaars http://tyrope.nl/
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat/
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -from sopel.module import commands
- -
- -
- -@commands('lmgtfy', 'lmgify', 'gify', 'gtfy')
- -def googleit(bot, trigger):
- - """Let me just... google that for you."""
- - #No input
- - if not trigger.group(2):
- - return bot.say('http://google.com/')
- - bot.say('http://lmgtfy.com/?q=' + trigger.group(2).replace(' ', '+'))
- diff --git a/sopel/modules/meetbot.py b/sopel/modules/meetbot.py
- deleted file mode 100644
- index f0c007f..0000000
- --- a/sopel/modules/meetbot.py
- +++ /dev/null
- @@ -1,436 +0,0 @@
- -# coding=utf-8
- -"""
- -meetbot.py - Sopel meeting logger module
- -Copyright © 2012, Elad Alfassa, <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -This module is an attempt to implement at least some of the functionallity of Debian's meetbot
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -import time
- -import os
- -from sopel.config.types import (
- - StaticSection, FilenameAttribute, ValidatedAttribute
- -)
- -from sopel.web import quote
- -from sopel.modules.url import find_title
- -from sopel.module import example, commands, rule, priority
- -from sopel.tools import Ddict, Identifier
- -import codecs
- -
- -
- -class MeetbotSection(StaticSection):
- - meeting_log_path = FilenameAttribute('meeting_log_path', directory=True,
- - default='~/www/meetings')
- - """Path to meeting logs storage directory
- -
- - This should be an absolute path, accessible on a webserver."""
- - meeting_log_baseurl = ValidatedAttribute(
- - 'meeting_log_baseurl',
- - default='http://localhost/~sopel/meetings'
- - )
- - """Base URL for the meeting logs directory"""
- -
- -
- -def configure(config):
- - config.define_section('meetbot', MeetbotSection)
- - config.meetbot.configure_setting(
- - 'meeting_log_path',
- - 'Enter the directory to store logs in.'
- - )
- - config.meetbot.configure_setting(
- - 'meeting_log_baseurl',
- - 'Enter the base URL for the meeting logs.',
- - )
- -
- -
- -def setup(bot):
- - bot.config.define_section('meetbot', MeetbotSection)
- -
- -
- -meetings_dict = Ddict(dict) # Saves metadata about currently running meetings
- -"""
- -meetings_dict is a 2D dict.
- -
- -Each meeting should have:
- -channel
- -time of start
- -head (can stop the meeting, plus all abilities of chairs)
- -chairs (can add infolines to the logs)
- -title
- -current subject
- -comments (what people who aren't voiced want to add)
- -
- -Using channel as the meeting ID as there can't be more than one meeting in a channel at the same time.
- -"""
- -meeting_log_path = '' # To be defined on meeting start as part of sanity checks, used by logging functions so we don't have to pass them bot
- -meeting_log_baseurl = '' # To be defined on meeting start as part of sanity checks, used by logging functions so we don't have to pass them bot
- -meeting_actions = {} # A dict of channels to the actions that have been created in them. This way we can have .listactions spit them back out later on.
- -
- -
- -#Get the logfile name for the meeting in the requested channel
- -#Used by all logging functions
- -def figure_logfile_name(channel):
- - if meetings_dict[channel]['title'] is 'Untitled meeting':
- - name = 'untitled'
- - else:
- - name = meetings_dict[channel]['title']
- - # Real simple sluggifying. This bunch of characters isn't exhaustive, but
- - # whatever. It's close enough for most situations, I think.
- - for c in ' ./\\:*?"<>|&*`':
- - name = name.replace(c, '-')
- - timestring = time.strftime('%Y-%m-%d-%H:%M', time.gmtime(meetings_dict[channel]['start']))
- - filename = timestring + '_' + name
- - return filename
- -
- -
- -#Start HTML log
- -def logHTML_start(channel):
- - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.html', 'a', encoding='utf-8')
- - timestring = time.strftime('%Y-%m-%d %H:%M', time.gmtime(meetings_dict[channel]['start']))
- - title = '%s at %s, %s' % (meetings_dict[channel]['title'], channel, timestring)
- - logfile.write('<!doctype html>\n<html>\n<head>\n<meta charset="utf-8">\n<title>%TITLE%</title>\n</head>\n<body>\n<h1>%TITLE%</h1>\n'.replace('%TITLE%', title))
- - logfile.write('<h4>Meeting started by %s</h4><ul>\n' % meetings_dict[channel]['head'])
- - logfile.close()
- -
- -
- -#Write a list item in the HTML log
- -def logHTML_listitem(item, channel):
- - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.html', 'a', encoding='utf-8')
- - logfile.write('<li>' + item + '</li>\n')
- - logfile.close()
- -
- -
- -#End the HTML log
- -def logHTML_end(channel):
- - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.html', 'a', encoding='utf-8')
- - current_time = time.strftime('%H:%M:%S', time.gmtime())
- - logfile.write('</ul>\n<h4>Meeting ended at %s UTC</h4>\n' % current_time)
- - plainlog_url = meeting_log_baseurl + quote(channel + '/' + figure_logfile_name(channel) + '.log')
- - logfile.write('<a href="%s">Full log</a>' % plainlog_url)
- - logfile.write('\n</body>\n</html>')
- - logfile.close()
- -
- -
- -#Write a string to the plain text log
- -def logplain(item, channel):
- - current_time = time.strftime('%H:%M:%S', time.gmtime())
- - logfile = codecs.open(meeting_log_path + channel + '/' + figure_logfile_name(channel) + '.log', 'a', encoding='utf-8')
- - logfile.write('[' + current_time + '] ' + item + '\r\n')
- - logfile.close()
- -
- -
- -#Check if a meeting is currently running
- -def ismeetingrunning(channel):
- - try:
- - if meetings_dict[channel]['running']:
- - return True
- - else:
- - return False
- - except:
- - return False
- -
- -
- -#Check if nick is a chair or head of the meeting
- -def ischair(nick, channel):
- - try:
- - if nick.lower() == meetings_dict[channel]['head'] or nick.lower() in meetings_dict[channel]['chairs']:
- - return True
- - else:
- - return False
- - except:
- - return False
- -
- -
- -#Start meeting (also preforms all required sanity checks)
- -@commands('startmeeting')
- -@example('.startmeeting title or .startmeeting')
- -def startmeeting(bot, trigger):
- - """
- - Start a meeting.
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, there is already a meeting in progress here!')
- - return
- - if trigger.is_privmsg:
- - bot.say('Can only start meetings in channels')
- - return
- - #Start the meeting
- - meetings_dict[trigger.sender]['start'] = time.time()
- - if not trigger.group(2):
- - meetings_dict[trigger.sender]['title'] = 'Untitled meeting'
- - else:
- - meetings_dict[trigger.sender]['title'] = trigger.group(2)
- - meetings_dict[trigger.sender]['head'] = trigger.nick.lower()
- - meetings_dict[trigger.sender]['running'] = True
- - meetings_dict[trigger.sender]['comments'] = []
- -
- - global meeting_log_path
- - meeting_log_path = bot.config.meetbot.meeting_log_path
- - if not meeting_log_path.endswith('/'):
- - meeting_log_path = meeting_log_path + '/'
- - global meeting_log_baseurl
- - meeting_log_baseurl = bot.config.meetbot.meeting_log_baseurl
- - if not meeting_log_baseurl.endswith('/'):
- - meeting_log_baseurl = meeting_log_baseurl + '/'
- - if not os.path.isdir(meeting_log_path + trigger.sender):
- - try:
- - os.makedirs(meeting_log_path + trigger.sender)
- - except Exception:
- - bot.say("Can't create log directory for this channel, meeting not started!")
- - meetings_dict[trigger.sender] = Ddict(dict)
- - raise
- - return
- - #Okay, meeting started!
- - logplain('Meeting started by ' + trigger.nick.lower(), trigger.sender)
- - logHTML_start(trigger.sender)
- - meeting_actions[trigger.sender] = []
- - bot.say('Meeting started! use .action, .agreed, .info, .chairs, .subject and .comments to control the meeting. to end the meeting, type .endmeeting')
- - bot.say('Users without speaking permission can use .comment ' +
- - trigger.sender + ' followed by their comment in a PM with me to '
- - 'vocalize themselves.')
- -
- -
- -#Change the current subject (will appear as <h3> in the HTML log)
- -@commands('subject')
- -@example('.subject roll call')
- -def meetingsubject(bot, trigger):
- - """
- - Change the meeting subject.
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - if not trigger.group(2):
- - bot.say('what is the subject?')
- - return
- - if not ischair(trigger.nick, trigger.sender):
- - bot.say('Only meeting head or chairs can do that')
- - return
- - meetings_dict[trigger.sender]['current_subject'] = trigger.group(2)
- - logfile = codecs.open(meeting_log_path + trigger.sender + '/' + figure_logfile_name(trigger.sender) + '.html', 'a', encoding='utf-8')
- - logfile.write('</ul><h3>' + trigger.group(2) + '</h3><ul>')
- - logfile.close()
- - logplain('Current subject: ' + trigger.group(2) + ', (set by ' + trigger.nick + ')', trigger.sender)
- - bot.say('Current subject: ' + trigger.group(2))
- -
- -
- -#End the meeting
- -@commands('endmeeting')
- -@example('.endmeeting')
- -def endmeeting(bot, trigger):
- - """
- - End a meeting.
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - if not ischair(trigger.nick, trigger.sender):
- - bot.say('Only meeting head or chairs can do that')
- - return
- - meeting_length = time.time() - meetings_dict[trigger.sender]['start']
- - #TODO: Humanize time output
- - bot.say("Meeting ended! total meeting length %d seconds" % meeting_length)
- - logHTML_end(trigger.sender)
- - htmllog_url = meeting_log_baseurl + quote(trigger.sender + '/' + figure_logfile_name(trigger.sender) + '.html')
- - logplain('Meeting ended by %s, total meeting length %d seconds' % (trigger.nick, meeting_length), trigger.sender)
- - bot.say('Meeting minutes: ' + htmllog_url)
- - meetings_dict[trigger.sender] = Ddict(dict)
- - del meeting_actions[trigger.sender]
- -
- -
- -#Set meeting chairs (people who can control the meeting)
- -@commands('chairs')
- -@example('.chairs Tyrope Jason elad')
- -def chairs(bot, trigger):
- - """
- - Set the meeting chairs.
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - if not trigger.group(2):
- - bot.say('Who are the chairs?')
- - return
- - if trigger.nick.lower() == meetings_dict[trigger.sender]['head']:
- - meetings_dict[trigger.sender]['chairs'] = trigger.group(2).lower().split(' ')
- - chairs_readable = trigger.group(2).lower().replace(' ', ', ')
- - logplain('Meeting chairs are: ' + chairs_readable, trigger.sender)
- - logHTML_listitem('<span style="font-weight: bold">Meeting chairs are: </span>' + chairs_readable, trigger.sender)
- - bot.say('Meeting chairs are: ' + chairs_readable)
- - else:
- - bot.say("Only meeting head can set chairs")
- -
- -
- -#Log action item in the HTML log
- -@commands('action')
- -@example('.action elad will develop a meetbot')
- -def meetingaction(bot, trigger):
- - """
- - Log an action in the meeting log
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - if not trigger.group(2):
- - bot.say('try .action someone will do something')
- - return
- - if not ischair(trigger.nick, trigger.sender):
- - bot.say('Only meeting head or chairs can do that')
- - return
- - logplain('ACTION: ' + trigger.group(2), trigger.sender)
- - logHTML_listitem('<span style="font-weight: bold">Action: </span>' + trigger.group(2), trigger.sender)
- - meeting_actions[trigger.sender].append(trigger.group(2))
- - bot.say('ACTION: ' + trigger.group(2))
- -
- -
- -@commands('listactions')
- -@example('.listactions')
- -def listactions(bot, trigger):
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - for action in meeting_actions[trigger.sender]:
- - bot.say('ACTION: ' + action)
- -
- -
- -#Log agreed item in the HTML log
- -@commands('agreed')
- -@example('.agreed Bowties are cool')
- -def meetingagreed(bot, trigger):
- - """
- - Log an agreement in the meeting log.
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - if not trigger.group(2):
- - bot.say('try .action someone will do something')
- - return
- - if not ischair(trigger.nick, trigger.sender):
- - bot.say('Only meeting head or chairs can do that')
- - return
- - logplain('AGREED: ' + trigger.group(2), trigger.sender)
- - logHTML_listitem('<span style="font-weight: bold">Agreed: </span>' + trigger.group(2), trigger.sender)
- - bot.say('AGREED: ' + trigger.group(2))
- -
- -
- -#Log link item in the HTML log
- -@commands('link')
- -@example('.link http://example.com')
- -def meetinglink(bot, trigger):
- - """
- - Log a link in the meeing log.
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - if not trigger.group(2):
- - bot.say('try .action someone will do something')
- - return
- - if not ischair(trigger.nick, trigger.sender):
- - bot.say('Only meeting head or chairs can do that')
- - return
- - link = trigger.group(2)
- - if not link.startswith("http"):
- - link = "http://" + link
- - try:
- - title = find_title(link)
- - except:
- - title = ''
- - logplain('LINK: %s [%s]' % (link, title), trigger.sender)
- - logHTML_listitem('<a href="%s">%s</a>' % (link, title), trigger.sender)
- - bot.say('LINK: ' + link)
- -
- -
- -#Log informational item in the HTML log
- -@commands('info')
- -@example('.info all board members present')
- -def meetinginfo(bot, trigger):
- - """
- - Log an informational item in the meeting log
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - bot.say('Can\'t do that, start meeting first')
- - return
- - if not trigger.group(2):
- - bot.say('try .info some informative thing')
- - return
- - if not ischair(trigger.nick, trigger.sender):
- - bot.say('Only meeting head or chairs can do that')
- - return
- - logplain('INFO: ' + trigger.group(2), trigger.sender)
- - logHTML_listitem(trigger.group(2), trigger.sender)
- - bot.say('INFO: ' + trigger.group(2))
- -
- -
- -#called for every single message
- -#Will log to plain text only
- -@rule('(.*)')
- -@priority('low')
- -def log_meeting(bot, trigger):
- - if not ismeetingrunning(trigger.sender):
- - return
- - if trigger.startswith('.endmeeting') or trigger.startswith('.chairs') or trigger.startswith('.action') or trigger.startswith('.info') or trigger.startswith('.startmeeting') or trigger.startswith('.agreed') or trigger.startswith('.link') or trigger.startswith('.subject'):
- - return
- - logplain('<' + trigger.nick + '> ' + trigger, trigger.sender)
- -
- -
- -@commands('comment')
- -def take_comment(bot, trigger):
- - """
- - Log a comment, to be shown with other comments when a chair uses .comments.
- - Intended to allow commentary from those outside the primary group of people
- - in the meeting.
- -
- - Used in private message only, as `.comment <#channel> <comment to add>`
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not trigger.sender.is_nick():
- - return
- - if not trigger.group(4): # <2 arguements were given
- - bot.say('Usage: .comment <#channel> <comment to add>')
- - return
- -
- - target, message = trigger.group(2).split(None, 1)
- - target = Identifier(target)
- - if not ismeetingrunning(target):
- - bot.say("There's not currently a meeting in that channel.")
- - else:
- - meetings_dict[trigger.group(3)]['comments'].append((trigger.nick, message))
- - bot.say("Your comment has been recorded. It will be shown when the"
- - " chairs tell me to show the comments.")
- - bot.msg(meetings_dict[trigger.group(3)]['head'], "A new comment has been recorded.")
- -
- -
- -@commands('comments')
- -def show_comments(bot, trigger):
- - """
- - Show the comments that have been logged for this meeting with .comment.
- - https://github.com/sopel-irc/sopel/wiki/Using-the-meetbot-module
- - """
- - if not ismeetingrunning(trigger.sender):
- - return
- - if not ischair(trigger.nick, trigger.sender):
- - bot.say('Only meeting head or chairs can do that')
- - return
- - comments = meetings_dict[trigger.sender]['comments']
- - if comments:
- - msg = 'The following comments were made:'
- - bot.say(msg)
- - logplain('<%s> %s' % (bot.nick, msg), trigger.sender)
- - for comment in comments:
- - msg = '<%s> %s' % comment
- - bot.say(msg)
- - logplain('<%s> %s' % (bot.nick, msg), trigger.sender)
- - meetings_dict[trigger.sender]['comments'] = []
- - else:
- - bot.say('No comments have been logged.')
- diff --git a/sopel/modules/movie.py b/sopel/modules/movie.py
- deleted file mode 100644
- index 8469f53..0000000
- --- a/sopel/modules/movie.py
- +++ /dev/null
- @@ -1,48 +0,0 @@
- -# coding=utf-8
- -"""
- -imdb.py - Sopel Movie Information Module
- -Copyright © 2012-2013, Elad Alfassa, <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -This module relies on omdbapi.com
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -import requests
- -import sopel.module
- -from sopel.logger import get_logger
- -
- -LOGGER = get_logger(__name__)
- -
- -
- -@sopel.module.commands('movie', 'imdb')
- -@sopel.module.example('.movie ThisTitleDoesNotExist', '[MOVIE] Movie not found!')
- -@sopel.module.example('.movie Citizen Kane', '[MOVIE] Title: Citizen Kane | Year: 1941 | Rating: 8.4 | Genre: Drama, Mystery | IMDB Link: http://imdb.com/title/tt0033467')
- -def movie(bot, trigger):
- - """
- - Returns some information about a movie, like Title, Year, Rating, Genre and IMDB Link.
- - """
- - if not trigger.group(2):
- - return
- - word = trigger.group(2).rstrip()
- - uri = "http://www.omdbapi.com/"
- - data = requests.get(uri, params={'t': word}, timeout=30).json()
- - if data['Response'] == 'False':
- - if 'Error' in data:
- - message = '[MOVIE] %s' % data['Error']
- - else:
- - LOGGER.warning(
- - 'Got an error from the OMDb api, search phrase was %s; data was %s',
- - word, str(data))
- - message = '[MOVIE] Got an error from OMDbapi'
- - else:
- - message = '[MOVIE] Title: ' + data['Title'] + \
- - ' | Year: ' + data['Year'] + \
- - ' | Rating: ' + data['imdbRating'] + \
- - ' | Genre: ' + data['Genre'] + \
- - ' | IMDB Link: http://imdb.com/title/' + data['imdbID']
- - bot.say(message)
- -
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/ping.py b/sopel/modules/ping.py
- deleted file mode 100644
- index ee9c3d2..0000000
- --- a/sopel/modules/ping.py
- +++ /dev/null
- @@ -1,29 +0,0 @@
- -# coding=utf-8
- -"""
- -ping.py - Sopel Ping Module
- -Author: Sean B. Palmer, inamidst.com
- -About: http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import random
- -from sopel.module import rule, priority, thread
- -
- -
- -@rule(r'(?i)(hi|hello|hey),? $nickname[ \t]*$')
- -def hello(bot, trigger):
- - greeting = random.choice(('Hi', 'Hey', 'Hello'))
- - punctuation = random.choice(('', '!'))
- - bot.say(greeting + ' ' + trigger.nick + punctuation)
- -
- -
- -@rule(r'(?i)(Fuck|Screw) you,? $nickname[ \t]*$')
- -def rude(bot, trigger):
- - bot.say('Watch your mouth, ' + trigger.nick + ', or I\'ll tell your mother!')
- -
- -
- -@rule('$nickname!')
- -@priority('high')
- -@thread(False)
- -def interjection(bot, trigger):
- - bot.say(trigger.nick + '!')
- diff --git a/sopel/modules/rand.py b/sopel/modules/rand.py
- deleted file mode 100644
- index b9b0a6e..0000000
- --- a/sopel/modules/rand.py
- +++ /dev/null
- @@ -1,49 +0,0 @@
- -# coding=utf-8
- -"""
- -rand.py - Rand Module
- -Copyright 2013, Ari Koivula, <ari@koivu.la>
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel.module import commands, example
- -import random
- -import sys
- -
- -
- -@commands('rand')
- -@example('.rand 2', r'random\(0, 2\) = (0|1|2)', re=True, repeat=10)
- -@example('.rand -1 -1', 'random(-1, -1) = -1')
- -@example('.rand', r'random\(0, \d+\) = \d+', re=True)
- -@example('.rand 99 10', r'random\(10, 99\) = \d\d', re=True, repeat=10)
- -@example('.rand 10 99', r'random\(10, 99\) = \d\d', re=True, repeat=10)
- -def rand(bot, trigger):
- - """Replies with a random number between first and second argument."""
- - arg1 = trigger.group(3)
- - arg2 = trigger.group(4)
- -
- - try:
- - if arg2 is not None:
- - low = int(arg1)
- - high = int(arg2)
- - elif arg1 is not None:
- - low = 0
- - high = int(arg1)
- - else:
- - low = 0
- - high = sys.maxsize
- - except (ValueError, TypeError):
- - return bot.reply("Arguments must be of integer type")
- -
- - if low > high:
- - low, high = high, low
- -
- - number = random.randint(low, high)
- - bot.reply("random(%d, %d) = %d" % (low, high, number))
- -
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/reddit.py b/sopel/modules/reddit.py
- deleted file mode 100644
- index f5f79e0..0000000
- --- a/sopel/modules/reddit.py
- +++ /dev/null
- @@ -1,191 +0,0 @@
- -# coding=utf-8
- -# Author: Elsie Powell, embolalia.com
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel.module import commands, rule, example, require_chanmsg, NOLIMIT, OP
- -from sopel.formatting import bold, color, colors
- -from sopel.web import USER_AGENT
- -from sopel.tools import SopelMemory, time
- -import datetime as dt
- -import praw
- -import re
- -import sys
- -if sys.version_info.major >= 3:
- - unicode = str
- - if sys.version_info.minor >= 4:
- - from html import unescape
- - else:
- - from html.parser import HTMLParser
- - unescape = HTMLParser().unescape
- -else:
- - from HTMLParser import HTMLParser
- - unescape = HTMLParser().unescape
- -
- -
- -domain = r'https?://(?:www\.|np\.)?reddit\.com'
- -post_url = '%s/r/.*?/comments/([\w-]+)' % domain
- -user_url = '%s/u(ser)?/([\w-]+)' % domain
- -post_regex = re.compile(post_url)
- -user_regex = re.compile(user_url)
- -
- -
- -def setup(bot):
- - if not bot.memory.contains('url_callbacks'):
- - bot.memory['url_callbacks'] = SopelMemory()
- - bot.memory['url_callbacks'][post_regex] = rpost_info
- - bot.memory['url_callbacks'][user_regex] = redditor_info
- -
- -
- -def shutdown(bot):
- - del bot.memory['url_callbacks'][post_regex]
- - del bot.memory['url_callbacks'][user_regex]
- -
- -
- -@rule('.*%s.*' % post_url)
- -def rpost_info(bot, trigger, match=None):
- - r = praw.Reddit(user_agent=USER_AGENT)
- - match = match or trigger
- - s = r.get_submission(submission_id=match.group(1))
- -
- - message = ('[REDDIT] {title} {link}{nsfw} | {points} points ({percent}) | '
- - '{comments} comments | Posted by {author} | '
- - 'Created at {created}')
- -
- - if s.is_self:
- - link = '(self.{})'.format(s.subreddit.display_name)
- - else:
- - link = '({}) to r/{}'.format(s.url, s.subreddit.display_name)
- -
- - if s.over_18:
- - nsfw = bold(color(' [NSFW]', colors.RED))
- - sfw = bot.db.get_channel_value(trigger.sender, 'sfw')
- - if sfw:
- - link = '(link hidden)'
- - bot.write(['KICK', trigger.sender, trigger.nick,
- - 'Linking to NSFW content in a SFW channel.'])
- - else:
- - nsfw = ''
- -
- - if s.author:
- - author = s.author.name
- - else:
- - author = '[deleted]'
- -
- - tz = time.get_timezone(bot.db, bot.config, None, trigger.nick,
- - trigger.sender)
- - time_created = dt.datetime.utcfromtimestamp(s.created_utc)
- - created = time.format_time(bot.db, bot.config, tz, trigger.nick,
- - trigger.sender, time_created)
- -
- - if s.score > 0:
- - point_color = colors.GREEN
- - else:
- - point_color = colors.RED
- -
- - percent = color(unicode(s.upvote_ratio * 100) + '%', point_color)
- -
- - title = unescape(s.title)
- - message = message.format(
- - title=title, link=link, nsfw=nsfw, points=s.score, percent=percent,
- - comments=s.num_comments, author=author, created=created)
- -
- - bot.say(message)
- -
- -
- -# If you change this, you'll have to change some other things...
- -@commands('redditor')
- -@example('.redditor poem_for_your_sprog')
- -def redditor_info(bot, trigger, match=None):
- - """Show information about the given Redditor"""
- - commanded = re.match(bot.config.core.prefix + 'redditor', trigger)
- - r = praw.Reddit(user_agent=USER_AGENT)
- - match = match or trigger
- - try:
- - u = r.get_redditor(match.group(2))
- - except:
- - if commanded:
- - bot.say('No such Redditor.')
- - return NOLIMIT
- - else:
- - return
- - # Fail silently if it wasn't an explicit command.
- -
- - message = '[REDDITOR] ' + u.name
- - now = dt.datetime.utcnow()
- - cakeday_start = dt.datetime.utcfromtimestamp(u.created_utc)
- - cakeday_start = cakeday_start.replace(year=now.year)
- - day = dt.timedelta(days=1)
- - year_div_by_400 = now.year % 400 == 0
- - year_div_by_100 = now.year % 100 == 0
- - year_div_by_4 = now.year % 4 == 0
- - is_leap = year_div_by_400 or ((not year_div_by_100) and year_div_by_4)
- - if (not is_leap) and ((cakeday_start.month, cakeday_start.day) == (2, 29)):
- - # If cake day is 2/29 and it's not a leap year, cake day is 1/3.
- - # Cake day begins at exact account creation time.
- - is_cakeday = cakeday_start + day <= now <= cakeday_start + (2 * day)
- - else:
- - is_cakeday = cakeday_start <= now <= cakeday_start + day
- -
- - if is_cakeday:
- - message = message + ' | 13Cake day'
- - if commanded:
- - message = message + ' | http://reddit.com/u/' + u.name
- - if u.is_gold:
- - message = message + ' | 08Gold'
- - if u.is_mod:
- - message = message + ' | 05Mod'
- - message = message + (' | Link: ' + str(u.link_karma) + ' | Comment: '
- - + str(u.comment_karma))
- -
- - bot.say(message)
- -
- -
- -# If you change the groups here, you'll have to change some things above.
- -@rule('.*%s.*' % user_url)
- -def auto_redditor_info(bot, trigger):
- - redditor_info(bot, trigger)
- -
- -
- -@require_chanmsg('.setsfw is only permitted in channels')
- -@commands('setsafeforwork', 'setsfw')
- -@example('.setsfw true')
- -@example('.setsfw false')
- -def update_channel(bot, trigger):
- - """
- - Sets the Safe for Work status (true or false) for the current
- - channel. Defaults to false.
- - """
- - if bot.privileges[trigger.sender][trigger.nick] < OP:
- - return
- - else:
- - param = 'true'
- - if trigger.group(2) and trigger.group(3):
- - param = trigger.group(3).strip().lower()
- - sfw = param == 'true'
- - bot.db.set_channel_value(trigger.sender, 'sfw', sfw)
- - if sfw:
- - bot.reply('Got it. %s is now flagged as SFW.' % trigger.sender)
- - else:
- - bot.reply('Got it. %s is now flagged as NSFW.' % trigger.sender)
- -
- -
- -@commands('getsafeforwork', 'getsfw')
- -@example('.getsfw [channel]')
- -def get_channel_sfw(bot, trigger):
- - """
- - Gets the preferred channel's Safe for Work status, or the current
- - channel's status if no channel given.
- - """
- - channel = trigger.group(2)
- - if not channel:
- - channel = trigger.sender
- - if channel.is_nick():
- - return bot.say('.getsfw with no channel param is only permitted in channels')
- -
- - channel = channel.strip()
- -
- - sfw = bot.db.get_channel_value(channel, 'sfw')
- - if sfw:
- - bot.say('%s is flagged as SFW' % channel)
- - else:
- - bot.say('%s is flagged as NSFW' % channel)
- diff --git a/sopel/modules/reload.py b/sopel/modules/reload.py
- deleted file mode 100644
- index 53bd720..0000000
- --- a/sopel/modules/reload.py
- +++ /dev/null
- @@ -1,139 +0,0 @@
- -# coding=utf-8
- -"""
- -reload.py - Sopel Module Reloader Module
- -Copyright 2008, Sean B. Palmer, inamidst.com
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import collections
- -import sys
- -import time
- -from sopel.tools import iteritems
- -import sopel.loader
- -import sopel.module
- -import subprocess
- -
- -
- -@sopel.module.nickname_commands("reload")
- -@sopel.module.priority("low")
- -@sopel.module.thread(False)
- -def f_reload(bot, trigger):
- - """Reloads a module, for use by admins only."""
- - if not trigger.admin:
- - return
- -
- - name = trigger.group(2)
- -
- - if not name or name == '*' or name.upper() == 'ALL THE THINGS':
- - bot._callables = {
- - 'high': collections.defaultdict(list),
- - 'medium': collections.defaultdict(list),
- - 'low': collections.defaultdict(list)
- - }
- - bot._command_groups = collections.defaultdict(list)
- - bot.setup()
- - return bot.reply('done')
- -
- - if name not in sys.modules:
- - return bot.reply('%s: not loaded, try the `load` command' % name)
- -
- - old_module = sys.modules[name]
- -
- - old_callables = {}
- - for obj_name, obj in iteritems(vars(old_module)):
- - bot.unregister(obj)
- -
- - # Also remove all references to sopel callables from top level of the
- - # module, so that they will not get loaded again if reloading the
- - # module does not override them.
- - for obj_name in old_callables.keys():
- - delattr(old_module, obj_name)
- -
- - # Also delete the setup function
- - if hasattr(old_module, "setup"):
- - delattr(old_module, "setup")
- -
- - modules = sopel.loader.enumerate_modules(bot.config)
- - path, type_ = modules[name]
- - load_module(bot, name, path, type_)
- -
- -
- -def load_module(bot, name, path, type_):
- - module, mtime = sopel.loader.load_module(name, path, type_)
- - relevant_parts = sopel.loader.clean_module(module, bot.config)
- -
- - bot.register(*relevant_parts)
- -
- - # TODO sys.modules[name] = module
- - if hasattr(module, 'setup'):
- - module.setup(bot)
- -
- - modified = time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(mtime))
- -
- - bot.reply('%r (version: %s)' % (module, modified))
- -
- -
- -@sopel.module.nickname_commands('update')
- -def f_update(bot, trigger):
- - if not trigger.admin:
- - return
- -
- - """Pulls the latest versions of all modules from Git"""
- - proc = subprocess.Popen('/usr/bin/git pull',
- - stdout=subprocess.PIPE,
- - stderr=subprocess.PIPE, shell=True)
- - bot.reply(proc.communicate()[0])
- -
- - f_reload(bot, trigger)
- -
- -
- -@sopel.module.nickname_commands("load")
- -@sopel.module.priority("low")
- -@sopel.module.thread(False)
- -def f_load(bot, trigger):
- - """Loads a module, for use by admins only."""
- - if not trigger.admin:
- - return
- -
- - name = trigger.group(2)
- - path = ''
- - if not name:
- - return bot.reply('Load what?')
- -
- - if name in sys.modules:
- - return bot.reply('Module already loaded, use reload')
- -
- - mods = sopel.loader.enumerate_modules(bot.config)
- - if name not in mods:
- - return bot.reply('Module %s not found' % name)
- - path, type_ = mods[name]
- - load_module(bot, name, path, type_)
- -
- -
- -# Catch PM based messages
- -@sopel.module.commands("reload")
- -@sopel.module.priority("low")
- -@sopel.module.thread(False)
- -def pm_f_reload(bot, trigger):
- - """Wrapper for allowing delivery of .reload command via PM"""
- - if trigger.is_privmsg:
- - f_reload(bot, trigger)
- -
- -
- -@sopel.module.commands('update')
- -def pm_f_update(bot, trigger):
- - """Wrapper for allowing delivery of .update command via PM"""
- - if trigger.is_privmsg:
- - f_update(bot, trigger)
- -
- -
- -@sopel.module.commands("load")
- -@sopel.module.priority("low")
- -@sopel.module.thread(False)
- -def pm_f_load(bot, trigger):
- - """Wrapper for allowing delivery of .load command via PM"""
- - if trigger.is_privmsg:
- - f_load(bot, trigger)
- diff --git a/sopel/modules/remind.py b/sopel/modules/remind.py
- deleted file mode 100644
- index 7f59a57..0000000
- --- a/sopel/modules/remind.py
- +++ /dev/null
- @@ -1,228 +0,0 @@
- -# coding=utf-8
- -"""
- -remind.py - Sopel Reminder Module
- -Copyright 2011, Sean B. Palmer, inamidst.com
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import os
- -import re
- -import time
- -import threading
- -import collections
- -import codecs
- -from datetime import datetime
- -from sopel.module import commands, example, NOLIMIT
- -import sopel.tools
- -from sopel.tools.time import get_timezone, format_time
- -
- -try:
- - import pytz
- -except:
- - pytz = None
- -
- -
- -def filename(self):
- - name = self.nick + '-' + self.config.core.host + '.reminders.db'
- - return os.path.join(self.config.core.homedir, name)
- -
- -
- -def load_database(name):
- - data = {}
- - if os.path.isfile(name):
- - f = codecs.open(name, 'r', encoding='utf-8')
- - for line in f:
- - unixtime, channel, nick, message = line.split('\t')
- - message = message.rstrip('\n')
- - t = int(float(unixtime)) # WTFs going on here?
- - reminder = (channel, nick, message)
- - try:
- - data[t].append(reminder)
- - except KeyError:
- - data[t] = [reminder]
- - f.close()
- - return data
- -
- -
- -def dump_database(name, data):
- - f = codecs.open(name, 'w', encoding='utf-8')
- - for unixtime, reminders in sopel.tools.iteritems(data):
- - for channel, nick, message in reminders:
- - f.write('%s\t%s\t%s\t%s\n' % (unixtime, channel, nick, message))
- - f.close()
- -
- -
- -def setup(bot):
- - bot.rfn = filename(bot)
- - bot.rdb = load_database(bot.rfn)
- -
- - def monitor(bot):
- - time.sleep(5)
- - while True:
- - now = int(time.time())
- - unixtimes = [int(key) for key in bot.rdb]
- - oldtimes = [t for t in unixtimes if t <= now]
- - if oldtimes:
- - for oldtime in oldtimes:
- - for (channel, nick, message) in bot.rdb[oldtime]:
- - if message:
- - bot.msg(channel, nick + ': ' + message)
- - else:
- - bot.msg(channel, nick + '!')
- - del bot.rdb[oldtime]
- - dump_database(bot.rfn, bot.rdb)
- - time.sleep(2.5)
- -
- - targs = (bot,)
- - t = threading.Thread(target=monitor, args=targs)
- - t.start()
- -
- -scaling = collections.OrderedDict([
- - ('years', 365.25 * 24 * 3600),
- - ('year', 365.25 * 24 * 3600),
- - ('yrs', 365.25 * 24 * 3600),
- - ('y', 365.25 * 24 * 3600),
- -
- - ('months', 29.53059 * 24 * 3600),
- - ('month', 29.53059 * 24 * 3600),
- - ('mo', 29.53059 * 24 * 3600),
- -
- - ('weeks', 7 * 24 * 3600),
- - ('week', 7 * 24 * 3600),
- - ('wks', 7 * 24 * 3600),
- - ('wk', 7 * 24 * 3600),
- - ('w', 7 * 24 * 3600),
- -
- - ('days', 24 * 3600),
- - ('day', 24 * 3600),
- - ('d', 24 * 3600),
- -
- - ('hours', 3600),
- - ('hour', 3600),
- - ('hrs', 3600),
- - ('hr', 3600),
- - ('h', 3600),
- -
- - ('minutes', 60),
- - ('minute', 60),
- - ('mins', 60),
- - ('min', 60),
- - ('m', 60),
- -
- - ('seconds', 1),
- - ('second', 1),
- - ('secs', 1),
- - ('sec', 1),
- - ('s', 1),
- -])
- -
- -periods = '|'.join(scaling.keys())
- -
- -
- -@commands('in')
- -@example('.in 3h45m Go to class')
- -def remind(bot, trigger):
- - """Gives you a reminder in the given amount of time."""
- - if not trigger.group(2):
- - bot.say("Missing arguments for reminder command.")
- - return NOLIMIT
- - if trigger.group(3) and not trigger.group(4):
- - bot.say("No message given for reminder.")
- - return NOLIMIT
- - duration = 0
- - message = filter(None, re.split('(\d+(?:\.\d+)? ?(?:(?i)' + periods + ')) ?',
- - trigger.group(2))[1:])
- - reminder = ''
- - stop = False
- - for piece in message:
- - grp = re.match('(\d+(?:\.\d+)?) ?(.*) ?', piece)
- - if grp and not stop:
- - length = float(grp.group(1))
- - factor = scaling.get(grp.group(2).lower(), 60)
- - duration += length * factor
- - else:
- - reminder = reminder + piece
- - stop = True
- - if duration == 0:
- - return bot.reply("Sorry, didn't understand the input.")
- -
- - if duration % 1:
- - duration = int(duration) + 1
- - else:
- - duration = int(duration)
- - timezone = get_timezone(
- - bot.db, bot.config, None, trigger.nick, trigger.sender)
- - create_reminder(bot, trigger, duration, reminder, timezone)
- -
- -
- -@commands('at')
- -@example('.at 13:47 Do your homework!')
- -def at(bot, trigger):
- - """
- - Gives you a reminder at the given time. Takes hh:mm:ssTimezone
- - message. Timezone is any timezone Sopel takes elsewhere; the best choices
- - are those from the tzdb; a list of valid options is available at
- - http://sopel.chat/tz . The seconds and timezone are optional.
- - """
- - if not trigger.group(2):
- - bot.say("No arguments given for reminder command.")
- - return NOLIMIT
- - if trigger.group(3) and not trigger.group(4):
- - bot.say("No message given for reminder.")
- - return NOLIMIT
- - regex = re.compile(r'(\d+):(\d+)(?::(\d+))?([^\s\d]+)? (.*)')
- - match = regex.match(trigger.group(2))
- - if not match:
- - bot.reply("Sorry, but I didn't understand your input.")
- - return NOLIMIT
- - hour, minute, second, tz, message = match.groups()
- - if not second:
- - second = '0'
- -
- - if pytz:
- - timezone = get_timezone(bot.db, bot.config, tz,
- - trigger.nick, trigger.sender)
- - if not timezone:
- - timezone = 'UTC'
- - now = datetime.now(pytz.timezone(timezone))
- - at_time = datetime(now.year, now.month, now.day,
- - int(hour), int(minute), int(second),
- - tzinfo=now.tzinfo)
- - timediff = at_time - now
- - else:
- - if tz and tz.upper() != 'UTC':
- - bot.reply("I don't have timzeone support installed.")
- - return NOLIMIT
- - now = datetime.now()
- - at_time = datetime(now.year, now.month, now.day,
- - int(hour), int(minute), int(second))
- - timediff = at_time - now
- -
- - duration = timediff.seconds
- -
- - if duration < 0:
- - duration += 86400
- - create_reminder(bot, trigger, duration, message, 'UTC')
- -
- -
- -def create_reminder(bot, trigger, duration, message, tz):
- - t = int(time.time()) + duration
- - reminder = (trigger.sender, trigger.nick, message)
- - try:
- - bot.rdb[t].append(reminder)
- - except KeyError:
- - bot.rdb[t] = [reminder]
- -
- - dump_database(bot.rfn, bot.rdb)
- -
- - if duration >= 60:
- - remind_at = datetime.utcfromtimestamp(t)
- - timef = format_time(bot.db, bot.config, tz, trigger.nick,
- - trigger.sender, remind_at)
- -
- - bot.reply('Okay, will remind at %s' % timef)
- - else:
- - bot.reply('Okay, will remind in %s secs' % duration)
- diff --git a/sopel/modules/safety.py b/sopel/modules/safety.py
- deleted file mode 100644
- index 17c850d..0000000
- --- a/sopel/modules/safety.py
- +++ /dev/null
- @@ -1,197 +0,0 @@
- -# coding=utf-8
- -"""
- -safety.py - Alerts about malicious URLs
- -Copyright © 2014, Elad Alfassa, <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -This module uses virustotal.com
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import sopel.web as web
- -from sopel.config.types import StaticSection, ValidatedAttribute, ListAttribute
- -from sopel.formatting import color, bold
- -from sopel.logger import get_logger
- -from sopel.module import OP
- -import sopel.tools
- -import sys
- -import json
- -import time
- -import os.path
- -import re
- -
- -if sys.version_info.major > 2:
- - unicode = str
- - from urllib.request import urlretrieve
- - from urllib.parse import urlparse
- -else:
- - from urllib import urlretrieve
- - from urlparse import urlparse
- -
- -LOGGER = get_logger(__name__)
- -
- -vt_base_api_url = 'https://www.virustotal.com/vtapi/v2/url/'
- -malware_domains = set()
- -known_good = []
- -
- -
- -class SafetySection(StaticSection):
- - enabled_by_default = ValidatedAttribute('enabled_by_default', bool, default=True)
- - """Enable URL safety in all channels where it isn't explicitly disabled."""
- - known_good = ListAttribute('known_good')
- - """List of "known good" domains to ignore."""
- - vt_api_key = ValidatedAttribute('vt_api_key')
- - """Optional VirusTotal API key."""
- -
- -
- -def configure(config):
- - config.define_section('safety', SafetySection)
- - config.safety.configure_setting(
- - 'enabled_by_default',
- - "Enable URL safety in channels that don't specifically disable it?",
- - )
- - config.safety.configure_setting(
- - 'known_good',
- - 'Enter any domains to whitelist',
- - )
- - config.safety.configure_setting(
- - 'vt_api_key',
- - "Optionally, enter a VirusTotal API key to improve malicious URL "
- - "protection.\nOtherwise, only the Malwarebytes DB will be used."
- - )
- -
- -
- -def setup(bot):
- - bot.config.define_section('safety', SafetySection)
- -
- - bot.memory['safety_cache'] = sopel.tools.SopelMemory()
- - for item in bot.config.safety.known_good:
- - known_good.append(re.compile(item, re.I))
- -
- - loc = os.path.join(bot.config.homedir, 'malwaredomains.txt')
- - if os.path.isfile(loc):
- - if os.path.getmtime(loc) < time.time() - 24 * 60 * 60 * 7:
- - # File exists but older than one week, update
- - _download_malwaredomains_db(loc)
- - else:
- - _download_malwaredomains_db(loc)
- - with open(loc, 'r') as f:
- - for line in f:
- - clean_line = unicode(line).strip().lower()
- - if clean_line != '':
- - malware_domains.add(clean_line)
- -
- -
- -def _download_malwaredomains_db(path):
- - print('Downloading malwaredomains db...')
- - urlretrieve('http://mirror1.malwaredomains.com/files/justdomains', path)
- -
- -
- -@sopel.module.rule('(?u).*(https?://\S+).*')
- -@sopel.module.priority('high')
- -def url_handler(bot, trigger):
- - """ Check for malicious URLs """
- - check = True # Enable URL checking
- - strict = False # Strict mode: kick on malicious URL
- - positives = 0 # Number of engines saying it's malicious
- - total = 0 # Number of total engines
- - use_vt = True # Use VirusTotal
- - check = bot.config.safety.enabled_by_default
- - if check is None:
- - # If not set, assume default
- - check = True
- - # DB overrides config:
- - setting = bot.db.get_channel_value(trigger.sender, 'safety')
- - if setting is not None:
- - if setting == 'off':
- - return # Not checking
- - elif setting in ['on', 'strict', 'local', 'local strict']:
- - check = True
- - if setting == 'strict' or setting == 'local strict':
- - strict = True
- - if setting == 'local' or setting == 'local strict':
- - use_vt = False
- -
- - if not check:
- - return # Not overriden by DB, configured default off
- -
- - netloc = urlparse(trigger.group(1)).netloc
- - if any(regex.search(netloc) for regex in known_good):
- - return # Whitelisted
- -
- - apikey = bot.config.safety.vt_api_key
- - try:
- - if apikey is not None and use_vt:
- - payload = {'resource': unicode(trigger),
- - 'apikey': apikey,
- - 'scan': '1'}
- -
- - if trigger not in bot.memory['safety_cache']:
- - result = web.post(vt_base_api_url + 'report', payload)
- - if sys.version_info.major > 2:
- - result = result.decode('utf-8')
- - result = json.loads(result)
- - age = time.time()
- - data = {'positives': result['positives'],
- - 'total': result['total'],
- - 'age': age}
- - bot.memory['safety_cache'][trigger] = data
- - if len(bot.memory['safety_cache']) > 1024:
- - _clean_cache(bot)
- - else:
- - print('using cache')
- - result = bot.memory['safety_cache'][trigger]
- - positives = result['positives']
- - total = result['total']
- - except Exception:
- - LOGGER.debug('Error from checking URL with VT.', exc_info=True)
- - pass # Ignoring exceptions with VT so MalwareDomains will always work
- -
- - if unicode(netloc).lower() in malware_domains:
- - # malwaredomains is more trustworthy than some VT engines
- - # therefor it gets a weight of 10 engines when calculating confidence
- - positives += 10
- - total += 10
- -
- - if positives > 1:
- - # Possibly malicious URL detected!
- - confidence = '{}%'.format(round((positives / total) * 100))
- - msg = 'link posted by %s is possibly malicious ' % bold(trigger.nick)
- - msg += '(confidence %s - %s/%s)' % (confidence, positives, total)
- - bot.say('[' + bold(color('WARNING', 'red')) + '] ' + msg)
- - if strict:
- - bot.write(['KICK', trigger.sender, trigger.nick,
- - 'Posted a malicious link'])
- -
- -
- -@sopel.module.commands('safety')
- -def toggle_safety(bot, trigger):
- - """ Set safety setting for channel """
- - if not trigger.admin and bot.privileges[trigger.sender][trigger.nick] < OP:
- - bot.reply('Only channel operators can change safety settings')
- - return
- - allowed_states = ['strict', 'on', 'off', 'local', 'local strict']
- - if not trigger.group(2) or trigger.group(2).lower() not in allowed_states:
- - options = ' / '.join(allowed_states)
- - bot.reply('Available options: %s' % options)
- - return
- -
- - channel = trigger.sender.lower()
- - bot.db.set_channel_value(channel, 'safety', trigger.group(2).lower())
- - bot.reply('Safety is now set to "%s" on this channel' % trigger.group(2))
- -
- -
- -# Clean the cache every day, also when > 1024 entries
- -@sopel.module.interval(24 * 60 * 60)
- -def _clean_cache(bot):
- - """ Cleanup old entries in URL cache """
- - # TODO probably should be using locks here, to make sure stuff doesn't
- - # explode
- - oldest_key_age = 0
- - oldest_key = ''
- - for key, data in sopel.tools.iteritems(bot.memory['safety_cache']):
- - if data['age'] > oldest_key_age:
- - oldest_key_age = data['age']
- - oldest_key = key
- - if oldest_key in bot.memory['safety_cache']:
- - del bot.memory['safety_cache'][oldest_key]
- diff --git a/sopel/modules/search.py b/sopel/modules/search.py
- deleted file mode 100644
- index e77d5db..0000000
- --- a/sopel/modules/search.py
- +++ /dev/null
- @@ -1,128 +0,0 @@
- -# coding=utf-8
- -# Copyright 2008-9, Sean B. Palmer, inamidst.com
- -# Copyright 2012, Elsie Powell, embolalia.com
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -from sopel import web
- -from sopel.module import commands, example
- -import json
- -import sys
- -
- -if sys.version_info.major < 3:
- - from urllib import quote_plus
- -else:
- - from urllib.parse import quote_plus
- -
- -
- -def formatnumber(n):
- - """Format a number with beautiful commas."""
- - parts = list(str(n))
- - for i in range((len(parts) - 3), 0, -3):
- - parts.insert(i, ',')
- - return ''.join(parts)
- -
- -r_bing = re.compile(r'<h3><a href="([^"]+)"')
- -
- -
- -def bing_search(query, lang='en-GB'):
- - base = 'http://www.bing.com/search?mkt=%s&q=' % lang
- - bytes = web.get(base + query)
- - m = r_bing.search(bytes)
- - if m:
- - return m.group(1)
- -
- -r_duck = re.compile(r'nofollow" class="[^"]+" href="(.*?)">')
- -
- -
- -def duck_search(query):
- - query = query.replace('!', '')
- - uri = 'http://duckduckgo.com/html/?q=%s&kl=uk-en' % query
- - bytes = web.get(uri)
- - if 'web-result' in bytes: # filter out the adds on top of the page
- - bytes = bytes.split('web-result')[1]
- - m = r_duck.search(bytes)
- - if m:
- - return web.decode(m.group(1))
- -
- -# Alias google_search to duck_search
- -google_search = duck_search
- -
- -
- -def duck_api(query):
- - if '!bang' in query.lower():
- - return 'https://duckduckgo.com/bang.html'
- -
- - # This fixes issue #885 (https://github.com/sopel-irc/sopel/issues/885)
- - # It seems that duckduckgo api redirects to its Instant answer API html page
- - # if the query constains special charactares that aren't urlencoded.
- - # So in order to always get a JSON response back the query is urlencoded
- - query = quote_plus(query)
- - uri = 'http://api.duckduckgo.com/?q=%s&format=json&no_html=1&no_redirect=1' % query
- - results = json.loads(web.get(uri))
- - if results['Redirect']:
- - return results['Redirect']
- - else:
- - return None
- -
- -
- -@commands('duck', 'ddg', 'g')
- -@example('.duck privacy or .duck !mcwiki obsidian')
- -def duck(bot, trigger):
- - """Queries Duck Duck Go for the specified input."""
- - query = trigger.group(2)
- - if not query:
- - return bot.reply('.ddg what?')
- -
- - # If the API gives us something, say it and stop
- - result = duck_api(query)
- - if result:
- - bot.reply(result)
- - return
- -
- - # Otherwise, look it up on the HTMl version
- - uri = duck_search(query)
- -
- - if uri:
- - bot.reply(uri)
- - if 'last_seen_url' in bot.memory:
- - bot.memory['last_seen_url'][trigger.sender] = uri
- - else:
- - bot.reply("No results found for '%s'." % query)
- -
- -
- -@commands('search')
- -@example('.search nerdfighter')
- -def search(bot, trigger):
- - """Searches Bing and Duck Duck Go."""
- - if not trigger.group(2):
- - return bot.reply('.search for what?')
- - query = trigger.group(2)
- - bu = bing_search(query) or '-'
- - du = duck_search(query) or '-'
- -
- - if bu == du:
- - result = '%s (b, d)' % bu
- - else:
- - if len(bu) > 150:
- - bu = '(extremely long link)'
- - if len(du) > 150:
- - du = '(extremely long link)'
- - result = '%s (b), %s (d)' % (bu, du)
- -
- - bot.reply(result)
- -
- -
- -@commands('suggest')
- -def suggest(bot, trigger):
- - """Suggest terms starting with given input"""
- - if not trigger.group(2):
- - return bot.reply("No query term.")
- - query = trigger.group(2)
- - uri = 'http://websitedev.de/temp-bin/suggest.pl?q='
- - answer = web.get(uri + query.replace('+', '%2B'))
- - if answer:
- - bot.say(answer)
- - else:
- - bot.reply('Sorry, no result.')
- diff --git a/sopel/modules/seen.py b/sopel/modules/seen.py
- deleted file mode 100644
- index 262059c..0000000
- --- a/sopel/modules/seen.py
- +++ /dev/null
- @@ -1,59 +0,0 @@
- -# coding=utf-8
- -"""
- -seen.py - Sopel Seen Module
- -Copyright 2008, Sean B. Palmer, inamidst.com
- -Copyright © 2012, Elad Alfassa <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import time
- -import datetime
- -from sopel.tools import Identifier
- -from sopel.tools.time import get_timezone, format_time
- -from sopel.module import commands, rule, priority, thread
- -
- -
- -@commands('seen')
- -def seen(bot, trigger):
- - """Reports when and where the user was last seen."""
- - if not trigger.group(2):
- - bot.say(".seen <nick> - Reports when <nick> was last seen.")
- - return
- - nick = trigger.group(2).strip()
- - timestamp = bot.db.get_nick_value(nick, 'seen_timestamp')
- - if timestamp:
- - channel = bot.db.get_nick_value(nick, 'seen_channel')
- - message = bot.db.get_nick_value(nick, 'seen_message')
- - action = bot.db.get_nick_value(nick, 'seen_action')
- -
- - tz = get_timezone(bot.db, bot.config, None, trigger.nick,
- - trigger.sender)
- - saw = datetime.datetime.utcfromtimestamp(timestamp)
- - timestamp = format_time(bot.db, bot.config, tz, trigger.nick,
- - trigger.sender, saw)
- -
- - msg = "I last saw {} at {}".format(nick, timestamp)
- - if Identifier(channel) == trigger.sender:
- - if action:
- - msg = msg + " in here, doing " + nick + " " + message
- - else:
- - msg = msg + " in here, saying " + message
- - else:
- - msg += " in another channel."
- - bot.say(str(trigger.nick) + ': ' + msg)
- - else:
- - bot.say("Sorry, I haven't seen {} around.".format(nick))
- -
- -
- -@thread(False)
- -@rule('(.*)')
- -@priority('low')
- -def note(bot, trigger):
- - if not trigger.is_privmsg:
- - bot.db.set_nick_value(trigger.nick, 'seen_timestamp', time.time())
- - bot.db.set_nick_value(trigger.nick, 'seen_channel', trigger.sender)
- - bot.db.set_nick_value(trigger.nick, 'seen_message', trigger)
- - bot.db.set_nick_value(trigger.nick, 'seen_action', 'intent' in trigger.tags)
- diff --git a/sopel/modules/spellcheck.py b/sopel/modules/spellcheck.py
- deleted file mode 100644
- index f722595..0000000
- --- a/sopel/modules/spellcheck.py
- +++ /dev/null
- @@ -1,54 +0,0 @@
- -# coding=utf-8
- -"""
- -spellcheck.py - Sopel spell check Module
- -Copyright © 2012, Elad Alfassa, <elad@fedoraproject.org>
- -Copyright © 2012, Lior Ramati
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -
- -This module relies on pyenchant, on Fedora and Red Hat based system, it can be found in the package python-enchant
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -try:
- - import enchant
- -except ImportError:
- - enchant = None
- -from sopel.module import commands, example
- -
- -
- -@commands('spellcheck', 'spell')
- -@example('.spellcheck stuff')
- -def spellcheck(bot, trigger):
- - """
- - Says whether the given word is spelled correctly, and gives suggestions if
- - it's not.
- - """
- - if not enchant:
- - bot.say("Missing pyenchant module.")
- - if not trigger.group(2):
- - return
- - word = trigger.group(2).rstrip()
- - if " " in word:
- - bot.say("One word at a time, please")
- - return
- - dictionary = enchant.Dict("en_US")
- - dictionary_uk = enchant.Dict("en_GB")
- - # I don't want to make anyone angry, so I check both American and British English.
- - if dictionary_uk.check(word):
- - if dictionary.check(word):
- - bot.say(word + " is spelled correctly")
- - else:
- - bot.say(word + " is spelled correctly (British)")
- - elif dictionary.check(word):
- - bot.say(word + " is spelled correctly (American)")
- - else:
- - msg = word + " is not spelled correctly. Maybe you want one of these spellings:"
- - sugWords = []
- - for suggested_word in dictionary.suggest(word):
- - sugWords.append(suggested_word)
- - for suggested_word in dictionary_uk.suggest(word):
- - sugWords.append(suggested_word)
- - for suggested_word in sorted(set(sugWords)): # removes duplicates
- - msg = msg + " '" + suggested_word + "',"
- - bot.say(msg)
- diff --git a/sopel/modules/tell.py b/sopel/modules/tell.py
- deleted file mode 100644
- index ab7c397..0000000
- --- a/sopel/modules/tell.py
- +++ /dev/null
- @@ -1,183 +0,0 @@
- -# coding=utf-8
- -"""
- -tell.py - Sopel Tell and Ask Module
- -Copyright 2008, Sean B. Palmer, inamidst.com
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import os
- -import time
- -import threading
- -import sys
- -from sopel.tools import Identifier, iterkeys
- -from sopel.tools.time import get_timezone, format_time
- -from sopel.module import commands, nickname_commands, rule, priority, example
- -
- -maximum = 4
- -
- -
- -def loadReminders(fn, lock):
- - lock.acquire()
- - try:
- - result = {}
- - f = open(fn)
- - for line in f:
- - line = line.strip()
- - if sys.version_info.major < 3:
- - line = line.decode('utf-8')
- - if line:
- - try:
- - tellee, teller, verb, timenow, msg = line.split('\t', 4)
- - except ValueError:
- - continue # @@ hmm
- - result.setdefault(tellee, []).append((teller, verb, timenow, msg))
- - f.close()
- - finally:
- - lock.release()
- - return result
- -
- -
- -def dumpReminders(fn, data, lock):
- - lock.acquire()
- - try:
- - f = open(fn, 'w')
- - for tellee in iterkeys(data):
- - for remindon in data[tellee]:
- - line = '\t'.join((tellee,) + remindon)
- - try:
- - to_write = line + '\n'
- - if sys.version_info.major < 3:
- - to_write = to_write.encode('utf-8')
- - f.write(to_write)
- - except IOError:
- - break
- - try:
- - f.close()
- - except IOError:
- - pass
- - finally:
- - lock.release()
- - return True
- -
- -
- -def setup(self):
- - fn = self.nick + '-' + self.config.core.host + '.tell.db'
- - self.tell_filename = os.path.join(self.config.core.homedir, fn)
- - if not os.path.exists(self.tell_filename):
- - try:
- - f = open(self.tell_filename, 'w')
- - except OSError:
- - pass
- - else:
- - f.write('')
- - f.close()
- - self.memory['tell_lock'] = threading.Lock()
- - self.memory['reminders'] = loadReminders(self.tell_filename, self.memory['tell_lock'])
- -
- -
- -@commands('tell', 'ask')
- -@nickname_commands('tell', 'ask')
- -@example('$nickname, tell Embolalia he broke something again.')
- -def f_remind(bot, trigger):
- - """Give someone a message the next time they're seen"""
- - teller = trigger.nick
- - verb = trigger.group(1)
- -
- - if not trigger.group(3):
- - bot.reply("%s whom?" % verb)
- - return
- -
- - tellee = trigger.group(3).rstrip('.,:;')
- - msg = trigger.group(2).lstrip(tellee).lstrip()
- -
- - if not msg:
- - bot.reply("%s %s what?" % (verb, tellee))
- - return
- -
- - tellee = Identifier(tellee)
- -
- - if not os.path.exists(bot.tell_filename):
- - return
- -
- - if len(tellee) > 20:
- - return bot.reply('That nickname is too long.')
- - if tellee == bot.nick:
- - return bot.reply("I'm here now, you can tell me whatever you want!")
- -
- - if not tellee in (Identifier(teller), bot.nick, 'me'):
- - tz = get_timezone(bot.db, bot.config, None, tellee)
- - timenow = format_time(bot.db, bot.config, tz, tellee)
- - bot.memory['tell_lock'].acquire()
- - try:
- - if not tellee in bot.memory['reminders']:
- - bot.memory['reminders'][tellee] = [(teller, verb, timenow, msg)]
- - else:
- - bot.memory['reminders'][tellee].append((teller, verb, timenow, msg))
- - finally:
- - bot.memory['tell_lock'].release()
- -
- - response = "I'll pass that on when %s is around." % tellee
- -
- - bot.reply(response)
- - elif Identifier(teller) == tellee:
- - bot.say('You can %s yourself that.' % verb)
- - else:
- - bot.say("Hey, I'm not as stupid as Monty you know!")
- -
- - dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell
- -
- -
- -def getReminders(bot, channel, key, tellee):
- - lines = []
- - template = "%s: %s <%s> %s %s %s"
- - today = time.strftime('%d %b', time.gmtime())
- -
- - bot.memory['tell_lock'].acquire()
- - try:
- - for (teller, verb, datetime, msg) in bot.memory['reminders'][key]:
- - if datetime.startswith(today):
- - datetime = datetime[len(today) + 1:]
- - lines.append(template % (tellee, datetime, teller, verb, tellee, msg))
- -
- - try:
- - del bot.memory['reminders'][key]
- - except KeyError:
- - bot.msg(channel, 'Er...')
- - finally:
- - bot.memory['tell_lock'].release()
- - return lines
- -
- -
- -@rule('(.*)')
- -@priority('low')
- -def message(bot, trigger):
- -
- - tellee = trigger.nick
- - channel = trigger.sender
- -
- - if not os.path.exists(bot.tell_filename):
- - return
- -
- - reminders = []
- - remkeys = list(reversed(sorted(bot.memory['reminders'].keys())))
- -
- - for remkey in remkeys:
- - if not remkey.endswith('*') or remkey.endswith(':'):
- - if tellee == remkey:
- - reminders.extend(getReminders(bot, channel, remkey, tellee))
- - elif tellee.startswith(remkey.rstrip('*:')):
- - reminders.extend(getReminders(bot, channel, remkey, tellee))
- -
- - for line in reminders[:maximum]:
- - bot.say(line)
- -
- - if reminders[maximum:]:
- - bot.say('Further messages sent privately')
- - for line in reminders[maximum:]:
- - bot.msg(tellee, line)
- -
- - if len(bot.memory['reminders'].keys()) != remkeys:
- - dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell
- diff --git a/sopel/modules/tld.py b/sopel/modules/tld.py
- deleted file mode 100644
- index 62284b5..0000000
- --- a/sopel/modules/tld.py
- +++ /dev/null
- @@ -1,69 +0,0 @@
- -# coding=utf-8
- -"""
- -tld.py - Sopel TLD Module
- -Copyright 2009-10, Michael Yanovich, yanovich.net
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel import web
- -from sopel.module import commands, example
- -import re
- -import sys
- -if sys.version_info.major >= 3:
- - unicode = str
- -
- -uri = 'https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains'
- -r_tag = re.compile(r'<(?!!)[^>]+>')
- -
- -
- -@commands('tld')
- -@example('.tld ru')
- -def gettld(bot, trigger):
- - """Show information about the given Top Level Domain."""
- - page = web.get(uri)
- - tld = trigger.group(2)
- - if tld[0] == '.':
- - tld = tld[1:]
- - search = r'(?i)<td><a href="\S+" title="\S+">\.{0}</a></td>\n(<td><a href=".*</a></td>\n)?<td>([A-Za-z0-9].*?)</td>\n<td>(.*)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n'
- - search = search.format(tld)
- - re_country = re.compile(search)
- - matches = re_country.findall(page)
- - if not matches:
- - search = r'(?i)<td><a href="\S+" title="(\S+)">\.{0}</a></td>\n<td><a href=".*">(.*)</a></td>\n<td>([A-Za-z0-9].*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n'
- - search = search.format(tld)
- - re_country = re.compile(search)
- - matches = re_country.findall(page)
- - if matches:
- - matches = list(matches[0])
- - i = 0
- - while i < len(matches):
- - matches[i] = r_tag.sub("", matches[i])
- - i += 1
- - desc = matches[2]
- - if len(desc) > 400:
- - desc = desc[:400] + "..."
- - reply = "%s -- %s. IDN: %s, DNSSEC: %s" % (matches[1], desc,
- - matches[3], matches[4])
- - bot.reply(reply)
- - else:
- - search = r'<td><a href="\S+" title="\S+">.{0}</a></td>\n<td><span class="flagicon"><img.*?\">(.*?)</a></td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n<td[^>]*>(.*?)</td>\n'
- - search = search.format(unicode(tld))
- - re_country = re.compile(search)
- - matches = re_country.findall(page)
- - if matches:
- - matches = matches[0]
- - dict_val = dict()
- - dict_val["country"], dict_val["expl"], dict_val["notes"], dict_val["idn"], dict_val["dnssec"], dict_val["sld"] = matches
- - for key in dict_val:
- - if dict_val[key] == " ":
- - dict_val[key] = "N/A"
- - dict_val[key] = r_tag.sub('', dict_val[key])
- - if len(dict_val["notes"]) > 400:
- - dict_val["notes"] = dict_val["notes"][:400] + "..."
- - reply = "%s (%s, %s). IDN: %s, DNSSEC: %s, SLD: %s" % (dict_val["country"], dict_val["expl"], dict_val["notes"], dict_val["idn"], dict_val["dnssec"], dict_val["sld"])
- - else:
- - reply = "No matches found for TLD: {0}".format(unicode(tld))
- - bot.reply(reply)
- diff --git a/sopel/modules/translate.py b/sopel/modules/translate.py
- deleted file mode 100644
- index ef9281f..0000000
- --- a/sopel/modules/translate.py
- +++ /dev/null
- @@ -1,203 +0,0 @@
- -# coding=utf-8
- -"""
- -translate.py - Sopel Translation Module
- -Copyright 2008, Sean B. Palmer, inamidst.com
- -Copyright © 2013-2014, Elad Alfassa <elad@fedoraproject.org>
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -from sopel import web
- -from sopel.module import rule, commands, priority, example
- -import json
- -import sys
- -import random
- -import requests
- -mangle_lines = {}
- -if sys.version_info.major >= 3:
- - unicode = str
- -
- -
- -def translate(text, in_lang='auto', out_lang='en'):
- - raw = False
- - if unicode(out_lang).endswith('-raw'):
- - out_lang = out_lang[:-4]
- - raw = True
- -
- - headers = {
- - 'User-Agent': 'Mozilla/5.0' +
- - '(X11; U; Linux i686)' +
- - 'Gecko/20071127 Firefox/2.0.0.11'
- - }
- -
- - query = {
- - "client": "gtx",
- - "sl": in_lang,
- - "tl": out_lang,
- - "dt": "t",
- - "q": text,
- - }
- - url = "http://translate.googleapis.com/translate_a/single"
- - result = requests.get(url, params=query, timeout=40, headers=headers).text
- -
- - if result == '[,,""]':
- - return None, in_lang
- -
- - while ',,' in result:
- - result = result.replace(',,', ',null,')
- - result = result.replace('[,', '[null,')
- -
- - data = json.loads(result)
- -
- - if raw:
- - return str(data), 'en-raw'
- -
- - try:
- - language = data[2] # -2][0][0]
- - except:
- - language = '?'
- -
- - return ''.join(x[0] for x in data[0]), language
- -
- -
- -@rule(u'$nickname[,:]\s+(?:([a-z]{2}) +)?(?:([a-z]{2}|en-raw) +)?["“](.+?)["”]\? *$')
- -@example('$nickname: "mon chien"? or $nickname: fr "mon chien"?')
- -@priority('low')
- -def tr(bot, trigger):
- - """Translates a phrase, with an optional language hint."""
- - in_lang, out_lang, phrase = trigger.groups()
- -
- - if (len(phrase) > 350) and (not trigger.admin):
- - return bot.reply('Phrase must be under 350 characters.')
- -
- - if phrase.strip() == '':
- - return bot.reply('You need to specify a string for me to translate!')
- -
- - in_lang = in_lang or 'auto'
- - out_lang = out_lang or 'en'
- -
- - if in_lang != out_lang:
- - msg, in_lang = translate(phrase, in_lang, out_lang)
- - if sys.version_info.major < 3 and isinstance(msg, str):
- - msg = msg.decode('utf-8')
- - if msg:
- - msg = web.decode(msg) # msg.replace(''', "'")
- - msg = '"%s" (%s to %s, translate.google.com)' % (msg, in_lang, out_lang)
- - else:
- - msg = 'The %s to %s translation failed, are you sure you specified valid language abbreviations?' % (in_lang, out_lang)
- -
- - bot.reply(msg)
- - else:
- - bot.reply('Language guessing failed, so try suggesting one!')
- -
- -
- -@commands('translate', 'tr')
- -@example('.tr :en :fr my dog', '"mon chien" (en to fr, translate.google.com)')
- -@example('.tr היי', '"Hey" (iw to en, translate.google.com)')
- -@example('.tr mon chien', '"my dog" (fr to en, translate.google.com)')
- -def tr2(bot, trigger):
- - """Translates a phrase, with an optional language hint."""
- - command = trigger.group(2)
- -
- - if not command:
- - return bot.reply('You did not give me anything to translate')
- -
- - def langcode(p):
- - return p.startswith(':') and (2 < len(p) < 10) and p[1:].isalpha()
- -
- - args = ['auto', 'en']
- -
- - for i in range(2):
- - if ' ' not in command:
- - break
- - prefix, cmd = command.split(' ', 1)
- - if langcode(prefix):
- - args[i] = prefix[1:]
- - command = cmd
- - phrase = command
- -
- - if (len(phrase) > 350) and (not trigger.admin):
- - return bot.reply('Phrase must be under 350 characters.')
- -
- - if phrase.strip() == '':
- - return bot.reply('You need to specify a string for me to translate!')
- -
- - src, dest = args
- - if src != dest:
- - msg, src = translate(phrase, src, dest)
- - if sys.version_info.major < 3 and isinstance(msg, str):
- - msg = msg.decode('utf-8')
- - if msg:
- - msg = web.decode(msg) # msg.replace(''', "'")
- - msg = '"%s" (%s to %s, translate.google.com)' % (msg, src, dest)
- - else:
- - msg = 'The %s to %s translation failed, are you sure you specified valid language abbreviations?' % (src, dest)
- -
- - bot.reply(msg)
- - else:
- - bot.reply('Language guessing failed, so try suggesting one!')
- -
- -
- -def get_random_lang(long_list, short_list):
- - random_index = random.randint(0, len(long_list) - 1)
- - random_lang = long_list[random_index]
- - if random_lang not in short_list:
- - short_list.append(random_lang)
- - else:
- - return get_random_lang(long_list, short_list)
- - return short_list
- -
- -
- -@commands('mangle', 'mangle2')
- -def mangle(bot, trigger):
- - """Repeatedly translate the input until it makes absolutely no sense."""
- - global mangle_lines
- - long_lang_list = ['fr', 'de', 'es', 'it', 'no', 'he', 'la', 'ja', 'cy', 'ar', 'yi', 'zh', 'nl', 'ru', 'fi', 'hi', 'af', 'jw', 'mr', 'ceb', 'cs', 'ga', 'sv', 'eo', 'el', 'ms', 'lv']
- - lang_list = []
- - for __ in range(0, 8):
- - lang_list = get_random_lang(long_lang_list, lang_list)
- - random.shuffle(lang_list)
- - if trigger.group(2) is None:
- - try:
- - phrase = (mangle_lines[trigger.sender.lower()], '')
- - except:
- - bot.reply("What do you want me to mangle?")
- - return
- - else:
- - phrase = (trigger.group(2).strip(), '')
- - if phrase[0] == '':
- - bot.reply("What do you want me to mangle?")
- - return
- - for lang in lang_list:
- - backup = phrase
- - try:
- - phrase = translate(phrase[0], 'en', lang)
- - except:
- - phrase = False
- - if not phrase:
- - phrase = backup
- - break
- -
- - try:
- - phrase = translate(phrase[0], lang, 'en')
- - except:
- - phrase = backup
- - continue
- -
- - if not phrase:
- - phrase = backup
- - break
- - bot.reply(phrase[0])
- -
- -
- -@rule('(.*)')
- -@priority('low')
- -def collect_mangle_lines(bot, trigger):
- - global mangle_lines
- - mangle_lines[trigger.sender.lower()] = "%s said '%s'" % (trigger.nick, (trigger.group(0).strip()))
- -
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/unicode_info.py b/sopel/modules/unicode_info.py
- deleted file mode 100644
- index be5f44d..0000000
- --- a/sopel/modules/unicode_info.py
- +++ /dev/null
- @@ -1,49 +0,0 @@
- -# coding=utf-8
- -"""Codepoints Module"""
- -# Copyright 2013, Elsie Powell, embolalia.com
- -# Copyright 2008, Sean B. Palmer, inamidst.com
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -import unicodedata
- -import sys
- -from sopel.module import commands, example, NOLIMIT
- -
- -if sys.version_info.major >= 3:
- - unichr = chr
- -
- -
- -@commands('u')
- -@example('.u ‽', 'U+203D INTERROBANG (‽)')
- -@example('.u 203D', 'U+203D INTERROBANG (‽)')
- -def codepoint(bot, trigger):
- - arg = trigger.group(2).strip()
- - if len(arg) == 0:
- - bot.reply('What code point do you want me to look up?')
- - return NOLIMIT
- - elif len(arg) > 1:
- - if arg.startswith('U+'):
- - arg = arg[2:]
- - try:
- - arg = unichr(int(arg, 16))
- - except:
- - bot.reply("That's not a valid code point.")
- - return NOLIMIT
- -
- - # Get the hex value for the code point, and drop the 0x from the front
- - point = str(hex(ord(u'' + arg)))[2:]
- - # Make the hex 4 characters long with preceding 0s, and all upper case
- - point = point.rjust(4, str('0')).upper()
- - try:
- - name = unicodedata.name(arg)
- - except ValueError:
- - return 'U+%s (No name found)' % point
- -
- - if not unicodedata.combining(arg):
- - template = 'U+%s %s (%s)'
- - else:
- - template = 'U+%s %s (\xe2\x97\x8c%s)'
- - bot.say(template % (point, name, arg))
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/units.py b/sopel/modules/units.py
- deleted file mode 100644
- index 5355948..0000000
- --- a/sopel/modules/units.py
- +++ /dev/null
- @@ -1,186 +0,0 @@
- -# coding=utf-8
- -"""
- -units.py - Unit conversion module for Sopel
- -Copyright © 2013, Elad Alfassa, <elad@fedoraproject.org>
- -Copyright © 2013, Dimitri Molenaars, <tyrope@tyrope.nl>
- -Licensed under the Eiffel Forum License 2.
- -
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -from sopel.module import commands, example, NOLIMIT
- -import re
- -
- -find_temp = re.compile('(-?[0-9]*\.?[0-9]*)[ °]*(K|C|F)', re.IGNORECASE)
- -find_length = re.compile('([0-9]*\.?[0-9]*)[ ]*(mile[s]?|mi|inch|in|foot|feet|ft|yard[s]?|yd|(?:milli|centi|kilo|)meter[s]?|[mkc]?m|ly|light-year[s]?|au|astronomical unit[s]?|parsec[s]?|pc)', re.IGNORECASE)
- -find_mass = re.compile('([0-9]*\.?[0-9]*)[ ]*(lb|lbm|pound[s]?|ounce|oz|(?:kilo|)gram(?:me|)[s]?|[k]?g)', re.IGNORECASE)
- -
- -
- -def f_to_c(temp):
- - return (float(temp) - 32) * 5 / 9
- -
- -
- -def c_to_k(temp):
- - return temp + 273.15
- -
- -
- -def c_to_f(temp):
- - return (9.0 / 5.0 * temp + 32)
- -
- -
- -def k_to_c(temp):
- - return temp - 273.15
- -
- -
- -@commands('temp')
- -@example('.temp 100F', '37.78°C = 100.00°F = 310.93K')
- -@example('.temp 100C', '100.00°C = 212.00°F = 373.15K')
- -@example('.temp 100K', '-173.15°C = -279.67°F = 100.00K')
- -def temperature(bot, trigger):
- - """
- - Convert temperatures
- - """
- - try:
- - source = find_temp.match(trigger.group(2)).groups()
- - except (AttributeError, TypeError):
- - bot.reply("That's not a valid temperature.")
- - return NOLIMIT
- - unit = source[1].upper()
- - numeric = float(source[0])
- - celsius = 0
- - if unit == 'C':
- - celsius = numeric
- - elif unit == 'F':
- - celsius = f_to_c(numeric)
- - elif unit == 'K':
- - celsius = k_to_c(numeric)
- -
- - kelvin = c_to_k(celsius)
- - fahrenheit = c_to_f(celsius)
- - bot.reply("{:.2f}°C = {:.2f}°F = {:.2f}K".format(celsius, fahrenheit, kelvin))
- -
- -
- -@commands('length', 'distance')
- -@example('.distance 3m', '3.00m = 9 feet, 10.11 inches')
- -@example('.distance 3km', '3.00km = 1.86 miles')
- -@example('.distance 3 miles', '4.83km = 3.00 miles')
- -@example('.distance 3 inch', '7.62cm = 3.00 inches')
- -@example('.distance 3 feet', '91.44cm = 3 feet, 0.00 inches')
- -@example('.distance 3 yards', '2.74m = 9 feet, 0.00 inches')
- -@example('.distance 155cm', '1.55m = 5 feet, 1.02 inches')
- -@example('.length 3 ly', '28382191417742.40km = 17635876112814.77 miles')
- -@example('.length 3 au', '448793612.10km = 278867421.71 miles')
- -@example('.length 3 parsec', '92570329129020.20km = 57520535754731.61 miles')
- -def distance(bot, trigger):
- - """
- - Convert distances
- - """
- - try:
- - source = find_length.match(trigger.group(2)).groups()
- - except (AttributeError, TypeError):
- - bot.reply("That's not a valid length unit.")
- - return NOLIMIT
- - unit = source[1].lower()
- - numeric = float(source[0])
- - meter = 0
- - if unit in ("meters", "meter", "m"):
- - meter = numeric
- - elif unit in ("millimeters", "millimeter", "mm"):
- - meter = numeric / 1000
- - elif unit in ("kilometers", "kilometer", "km"):
- - meter = numeric * 1000
- - elif unit in ("miles", "mile", "mi"):
- - meter = numeric / 0.00062137
- - elif unit in ("inch", "in"):
- - meter = numeric / 39.370
- - elif unit in ("centimeters", "centimeter", "cm"):
- - meter = numeric / 100
- - elif unit in ("feet", "foot", "ft"):
- - meter = numeric / 3.2808
- - elif unit in ("yards", "yard", "yd"):
- - meter = numeric / (3.2808 / 3)
- - elif unit in ("light-year", "light-years", "ly"):
- - meter = numeric * 9460730472580800
- - elif unit in ("astronomical unit", "astronomical units", "au"):
- - meter = numeric * 149597870700
- - elif unit in ("parsec", "parsecs", "pc"):
- - meter = numeric * 30856776376340068
- -
- - if meter >= 1000:
- - metric_part = '{:.2f}km'.format(meter / 1000)
- - elif meter < 0.01:
- - metric_part = '{:.2f}mm'.format(meter * 1000)
- - elif meter < 1:
- - metric_part = '{:.2f}cm'.format(meter * 100)
- - else:
- - metric_part = '{:.2f}m'.format(meter)
- -
- - # Shit like this makes me hate being an American.
- - inch = meter * 39.37
- - foot = int(inch) // 12
- - inch = inch - (foot * 12)
- - yard = foot // 3
- - mile = meter * 0.000621371192
- -
- - if yard > 500:
- - stupid_part = '{:.2f} miles'.format(mile)
- - else:
- - parts = []
- - if yard >= 100:
- - parts.append('{} yards'.format(yard))
- - foot -= (yard * 3)
- -
- - if foot == 1:
- - parts.append('1 foot')
- - elif foot != 0:
- - parts.append('{:.0f} feet'.format(foot))
- -
- - parts.append('{:.2f} inches'.format(inch))
- -
- - stupid_part = ', '.join(parts)
- -
- - bot.reply('{} = {}'.format(metric_part, stupid_part))
- -
- -
- -@commands('weight', 'mass')
- -def mass(bot, trigger):
- - """
- - Convert mass
- - """
- - try:
- - source = find_mass.match(trigger.group(2)).groups()
- - except (AttributeError, TypeError):
- - bot.reply("That's not a valid mass unit.")
- - return NOLIMIT
- - unit = source[1].lower()
- - numeric = float(source[0])
- - metric = 0
- - if unit in ("gram", "grams", "gramme", "grammes", "g"):
- - metric = numeric
- - elif unit in ("kilogram", "kilograms", "kilogramme", "kilogrammes", "kg"):
- - metric = numeric * 1000
- - elif unit in ("lb", "lbm", "pound", "pounds"):
- - metric = numeric * 453.59237
- - elif unit in ("oz", "ounce"):
- - metric = numeric * 28.35
- -
- - if metric >= 1000:
- - metric_part = '{:.2f}kg'.format(metric / 1000)
- - else:
- - metric_part = '{:.2f}g'.format(metric)
- -
- - ounce = metric * .035274
- - pound = int(ounce) // 16
- - ounce = ounce - (pound * 16)
- -
- - if pound > 1:
- - stupid_part = '{} pounds'.format(pound)
- - if ounce > 0.01:
- - stupid_part += ' {:.2f} ounces'.format(ounce)
- - else:
- - stupid_part = '{:.2f} oz'.format(ounce)
- -
- - bot.reply('{} = {}'.format(metric_part, stupid_part))
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/uptime.py b/sopel/modules/uptime.py
- deleted file mode 100644
- index 7e3fe1d..0000000
- --- a/sopel/modules/uptime.py
- +++ /dev/null
- @@ -1,27 +0,0 @@
- -# coding=utf-8
- -"""
- -uptime.py - Uptime module
- -Copyright 2014, Fabian Neundorf
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel.module import commands
- -import datetime
- -
- -
- -def setup(bot):
- - if "uptime" not in bot.memory:
- - bot.memory["uptime"] = datetime.datetime.utcnow()
- -
- -
- -@commands('uptime')
- -def uptime(bot, trigger):
- - """.uptime - Returns the uptime of Sopel."""
- - delta = datetime.timedelta(seconds=round((datetime.datetime.utcnow() -
- - bot.memory["uptime"])
- - .total_seconds()))
- - bot.say("I've been sitting here for {} and I keep "
- - "going!".format(delta))
- diff --git a/sopel/modules/url.py b/sopel/modules/url.py
- deleted file mode 100644
- index d2a83df..0000000
- --- a/sopel/modules/url.py
- +++ /dev/null
- @@ -1,237 +0,0 @@
- -# coding=utf-8
- -"""URL title module"""
- -# Copyright 2010-2011, Michael Yanovich, yanovich.net, Kenneth Sham
- -# Copyright 2012-2013 Elsie Powell
- -# Copyright 2013 Lior Ramati (firerogue517@gmail.com)
- -# Copyright © 2014 Elad Alfassa <elad@fedoraproject.org>
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -from contextlib import closing
- -from sopel import web, tools
- -from sopel.module import commands, rule, example
- -from sopel.config.types import ValidatedAttribute, ListAttribute, StaticSection
- -
- -import requests
- -
- -url_finder = None
- -# These are used to clean up the title tag before actually parsing it. Not the
- -# world's best way to do this, but it'll do for now.
- -title_tag_data = re.compile('<(/?)title( [^>]+)?>', re.IGNORECASE)
- -quoted_title = re.compile('[\'"]<title>[\'"]', re.IGNORECASE)
- -# This is another regex that presumably does something important.
- -re_dcc = re.compile(r'(?i)dcc\ssend')
- -# This sets the maximum number of bytes that should be read in order to find
- -# the title. We don't want it too high, or a link to a big file/stream will
- -# just keep downloading until there's no more memory. 640k ought to be enough
- -# for anybody.
- -max_bytes = 655360
- -
- -
- -class UrlSection(StaticSection):
- - # TODO some validation rules maybe?
- - exclude = ListAttribute('exclude')
- - exclusion_char = ValidatedAttribute('exclusion_char', default='!')
- -
- -
- -def configure(config):
- - config.define_section('url', UrlSection)
- - config.url.configure_setting(
- - 'exclude',
- - 'Enter regular expressions for each URL you would like to exclude.'
- - )
- - config.url.configure_setting(
- - 'exclusion_char',
- - 'Enter a character which can be prefixed to suppress URL titling'
- - )
- -
- -
- -def setup(bot=None):
- - global url_finder
- -
- - # TODO figure out why this is needed, and get rid of it, because really?
- - if not bot:
- - return
- - bot.config.define_section('url', UrlSection)
- -
- - if bot.config.url.exclude:
- - regexes = [re.compile(s) for s in bot.config.url.exclude]
- - else:
- - regexes = []
- -
- - # We're keeping these in their own list, rather than putting then in the
- - # callbacks list because 1, it's easier to deal with modules that are still
- - # using this list, and not the newer callbacks list and 2, having a lambda
- - # just to pass is kinda ugly.
- - if not bot.memory.contains('url_exclude'):
- - bot.memory['url_exclude'] = regexes
- - else:
- - exclude = bot.memory['url_exclude']
- - if regexes:
- - exclude.extend(regexes)
- - bot.memory['url_exclude'] = exclude
- -
- - # Ensure that url_callbacks and last_seen_url are in memory
- - if not bot.memory.contains('url_callbacks'):
- - bot.memory['url_callbacks'] = tools.SopelMemory()
- - if not bot.memory.contains('last_seen_url'):
- - bot.memory['last_seen_url'] = tools.SopelMemory()
- -
- - url_finder = re.compile(r'(?u)(%s?(?:http|https|ftp)(?:://\S+))' %
- - (bot.config.url.exclusion_char), re.IGNORECASE)
- -
- -
- -@commands('title')
- -@example('.title http://google.com', '[ Google ] - google.com')
- -def title_command(bot, trigger):
- - """
- - Show the title or URL information for the given URL, or the last URL seen
- - in this channel.
- - """
- - if not trigger.group(2):
- - if trigger.sender not in bot.memory['last_seen_url']:
- - return
- - matched = check_callbacks(bot, trigger,
- - bot.memory['last_seen_url'][trigger.sender],
- - True)
- - if matched:
- - return
- - else:
- - urls = [bot.memory['last_seen_url'][trigger.sender]]
- - else:
- - urls = re.findall(url_finder, trigger)
- -
- - results = process_urls(bot, trigger, urls)
- - for title, domain in results[:4]:
- - bot.reply('[ %s ] - %s' % (title, domain))
- -
- -
- -@rule('(?u).*(https?://\S+).*')
- -def title_auto(bot, trigger):
- - """
- - Automatically show titles for URLs. For shortened URLs/redirects, find
- - where the URL redirects to and show the title for that (or call a function
- - from another module to give more information).
- - """
- - if re.match(bot.config.core.prefix + 'title', trigger):
- - return
- -
- - # Avoid fetching known malicious links
- - if 'safety_cache' in bot.memory and trigger in bot.memory['safety_cache']:
- - if bot.memory['safety_cache'][trigger]['positives'] > 1:
- - return
- -
- - urls = re.findall(url_finder, trigger)
- - if len(urls) == 0:
- - return
- -
- - results = process_urls(bot, trigger, urls)
- - bot.memory['last_seen_url'][trigger.sender] = urls[-1]
- -
- - for title, domain in results[:4]:
- - message = '[ %s ] - %s' % (title, domain)
- - # Guard against responding to other instances of this bot.
- - if message != trigger:
- - bot.say(message)
- -
- -
- -def process_urls(bot, trigger, urls):
- - """
- - For each URL in the list, ensure that it isn't handled by another module.
- - If not, find where it redirects to, if anywhere. If that redirected URL
- - should be handled by another module, dispatch the callback for it.
- - Return a list of (title, hostname) tuples for each URL which is not handled by
- - another module.
- - """
- -
- - results = []
- - for url in urls:
- - if not url.startswith(bot.config.url.exclusion_char):
- - # Magic stuff to account for international domain names
- - try:
- - url = web.iri_to_uri(url)
- - except:
- - pass
- - # First, check that the URL we got doesn't match
- - matched = check_callbacks(bot, trigger, url, False)
- - if matched:
- - continue
- - # Finally, actually show the URL
- - title = find_title(url)
- - if title:
- - results.append((title, get_hostname(url)))
- - return results
- -
- -
- -def check_callbacks(bot, trigger, url, run=True):
- - """
- - Check the given URL against the callbacks list. If it matches, and ``run``
- - is given as ``True``, run the callback function, otherwise pass. Returns
- - ``True`` if the url matched anything in the callbacks list.
- - """
- - # Check if it matches the exclusion list first
- - matched = any(regex.search(url) for regex in bot.memory['url_exclude'])
- - # Then, check if there's anything in the callback list
- - for regex, function in tools.iteritems(bot.memory['url_callbacks']):
- - match = regex.search(url)
- - if match:
- - if run:
- - function(bot, trigger, match)
- - matched = True
- - return matched
- -
- -
- -def find_title(url):
- - """Return the title for the given URL."""
- - response = requests.get(url, stream=True)
- - try:
- - content = ''
- - for byte in response.iter_content(chunk_size=512, decode_unicode=True):
- - if not isinstance(byte, bytes):
- - content += byte
- - else:
- - break
- - if '</title>' in content or len(content) > max_bytes:
- - break
- - except UnicodeDecodeError:
- - return # Fail silently when data can't be decoded
- - finally:
- - # need to close the connexion because we have not read all the data
- - response.close()
- -
- - # Some cleanup that I don't really grok, but was in the original, so
- - # we'll keep it (with the compiled regexes made global) for now.
- - content = title_tag_data.sub(r'<\1title>', content)
- - content = quoted_title.sub('', content)
- -
- - start = content.find('<title>')
- - end = content.find('</title>')
- - if start == -1 or end == -1:
- - return
- - title = web.decode(content[start + 7:end])
- - title = title.strip()[:200]
- -
- - title = ' '.join(title.split()) # cleanly remove multiple spaces
- -
- - # More cryptic regex substitutions. This one looks to be myano's invention.
- - title = re_dcc.sub('', title)
- -
- - return title or None
- -
- -
- -def get_hostname(url):
- - idx = 7
- - if url.startswith('https://'):
- - idx = 8
- - elif url.startswith('ftp://'):
- - idx = 6
- - hostname = url[idx:]
- - slash = hostname.find('/')
- - if slash != -1:
- - hostname = hostname[:slash]
- - return hostname
- -
- -if __name__ == "__main__":
- - from sopel.test_tools import run_example_tests
- - run_example_tests(__file__)
- diff --git a/sopel/modules/version.py b/sopel/modules/version.py
- deleted file mode 100644
- index a1b4219..0000000
- --- a/sopel/modules/version.py
- +++ /dev/null
- @@ -1,81 +0,0 @@
- -# coding=utf-8
- -"""
- -version.py - Sopel Version Module
- -Copyright 2009, Silas Baronda
- -Copyright 2014, Dimitri Molenaars <tyrope@tyrope.nl>
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from datetime import datetime
- -import sopel
- -import re
- -from os import path
- -
- -log_line = re.compile('\S+ (\S+) (.*? <.*?>) (\d+) (\S+)\tcommit[^:]*: (.+)')
- -
- -
- -def git_info():
- - repo = path.join(path.dirname(path.dirname(path.dirname(__file__))), '.git')
- - head = path.join(repo, 'HEAD')
- - if path.isfile(head):
- - with open(head) as h:
- - head_loc = h.readline()[5:-1] # strip ref: and \n
- - head_file = path.join(repo, head_loc)
- - if path.isfile(head_file):
- - with open(head_file) as h:
- - sha = h.readline()
- - if sha:
- - return sha
- -
- -
- -@sopel.module.commands('version')
- -def version(bot, trigger):
- - """Display the latest commit version, if Sopel is running in a git repo."""
- - release = sopel.__version__
- - sha = git_info()
- - if not sha:
- - msg = 'Sopel v. ' + release
- - if release[-4:] == '-git':
- - msg += ' at unknown commit.'
- - bot.reply(msg)
- - return
- -
- - bot.reply("Sopel v. {} at commit: {}".format(sopel.__version__, sha))
- -
- -
- -@sopel.module.intent('VERSION')
- -@sopel.module.rate(20)
- -@sopel.module.rule('.*')
- -def ctcp_version(bot, trigger):
- - print('wat')
- - bot.write(('NOTICE', trigger.nick),
- - '\x01VERSION Sopel IRC Bot version %s\x01' % sopel.__version__)
- -
- -
- -@sopel.module.rule('\x01SOURCE\x01')
- -@sopel.module.rate(20)
- -def ctcp_source(bot, trigger):
- - bot.write(('NOTICE', trigger.nick),
- - '\x01SOURCE https://github.com/sopel-irc/sopel/\x01')
- -
- -
- -@sopel.module.rule('\x01PING\s(.*)\x01')
- -@sopel.module.rate(10)
- -def ctcp_ping(bot, trigger):
- - text = trigger.group()
- - text = text.replace("PING ", "")
- - text = text.replace("\x01", "")
- - bot.write(('NOTICE', trigger.nick),
- - '\x01PING {0}\x01'.format(text))
- -
- -
- -@sopel.module.rule('\x01TIME\x01')
- -@sopel.module.rate(20)
- -def ctcp_time(bot, trigger):
- - dt = datetime.now()
- - current_time = dt.strftime("%A, %d. %B %Y %I:%M%p")
- - bot.write(('NOTICE', trigger.nick),
- - '\x01TIME {0}\x01'.format(current_time))
- diff --git a/sopel/modules/weather.py b/sopel/modules/weather.py
- deleted file mode 100644
- index ea03b46..0000000
- --- a/sopel/modules/weather.py
- +++ /dev/null
- @@ -1,183 +0,0 @@
- -# coding=utf-8
- -# Copyright 2008, Sean B. Palmer, inamidst.com
- -# Copyright 2012, Elsie Powell, embolalia.com
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -from sopel import web
- -from sopel.module import commands, example, NOLIMIT
- -
- -import xmltodict
- -
- -
- -def woeid_search(query):
- - """
- - Find the first Where On Earth ID for the given query. Result is the etree
- - node for the result, so that location data can still be retrieved. Returns
- - None if there is no result, or the woeid field is empty.
- - """
- - query = 'q=select * from geo.places where text="%s"' % query
- - body = web.get('http://query.yahooapis.com/v1/public/yql?' + query,
- - dont_decode=True)
- - parsed = xmltodict.parse(body).get('query')
- - results = parsed.get('results')
- - if results is None or results.get('place') is None:
- - return None
- - if type(results.get('place')) is list:
- - return results.get('place')[0]
- - return results.get('place')
- -
- -
- -def get_cover(parsed):
- - try:
- - condition = parsed['channel']['item']['yweather:condition']
- - except KeyError:
- - return 'unknown'
- - text = condition['@text']
- - # code = int(condition['code'])
- - # TODO parse code to get those little icon thingies.
- - return text
- -
- -
- -def get_temp(parsed):
- - try:
- - condition = parsed['channel']['item']['yweather:condition']
- - temp = int(condition['@temp'])
- - except (KeyError, ValueError):
- - return 'unknown'
- - f = round((temp * 1.8) + 32, 2)
- - return (u'%d\u00B0C (%d\u00B0F)' % (temp, f))
- -
- -
- -def get_humidity(parsed):
- - try:
- - humidity = parsed['channel']['yweather:atmosphere']['@humidity']
- - except (KeyError, ValueError):
- - return 'unknown'
- - return "Humidity: %s%%" % humidity
- -
- -
- -def get_wind(parsed):
- - try:
- - wind_data = parsed['channel']['yweather:wind']
- - kph = float(wind_data['@speed'])
- - m_s = float(round(kph / 3.6, 1))
- - speed = int(round(kph / 1.852, 0))
- - degrees = int(wind_data['@direction'])
- - except (KeyError, ValueError):
- - return 'unknown'
- -
- - if speed < 1:
- - description = 'Calm'
- - elif speed < 4:
- - description = 'Light air'
- - elif speed < 7:
- - description = 'Light breeze'
- - elif speed < 11:
- - description = 'Gentle breeze'
- - elif speed < 16:
- - description = 'Moderate breeze'
- - elif speed < 22:
- - description = 'Fresh breeze'
- - elif speed < 28:
- - description = 'Strong breeze'
- - elif speed < 34:
- - description = 'Near gale'
- - elif speed < 41:
- - description = 'Gale'
- - elif speed < 48:
- - description = 'Strong gale'
- - elif speed < 56:
- - description = 'Storm'
- - elif speed < 64:
- - description = 'Violent storm'
- - else:
- - description = 'Hurricane'
- -
- - if (degrees <= 22.5) or (degrees > 337.5):
- - degrees = u'\u2193'
- - elif (degrees > 22.5) and (degrees <= 67.5):
- - degrees = u'\u2199'
- - elif (degrees > 67.5) and (degrees <= 112.5):
- - degrees = u'\u2190'
- - elif (degrees > 112.5) and (degrees <= 157.5):
- - degrees = u'\u2196'
- - elif (degrees > 157.5) and (degrees <= 202.5):
- - degrees = u'\u2191'
- - elif (degrees > 202.5) and (degrees <= 247.5):
- - degrees = u'\u2197'
- - elif (degrees > 247.5) and (degrees <= 292.5):
- - degrees = u'\u2192'
- - elif (degrees > 292.5) and (degrees <= 337.5):
- - degrees = u'\u2198'
- -
- - return description + ' ' + str(m_s) + 'm/s (' + degrees + ')'
- -
- -
- -@commands('weather', 'wea')
- -@example('.weather London')
- -def weather(bot, trigger):
- - """.weather location - Show the weather at the given location."""
- -
- - location = trigger.group(2)
- - woeid = ''
- - if not location:
- - woeid = bot.db.get_nick_value(trigger.nick, 'woeid')
- - if not woeid:
- - return bot.msg(trigger.sender, "I don't know where you live. " +
- - 'Give me a location, like .weather London, or tell me where you live by saying .setlocation London, for example.')
- - else:
- - location = location.strip()
- - woeid = bot.db.get_nick_value(location, 'woeid')
- - if woeid is None:
- - first_result = woeid_search(location)
- - if first_result is not None:
- - woeid = first_result.get('woeid')
- -
- - if not woeid:
- - return bot.reply("I don't know where that is.")
- -
- - query = web.urlencode({'w': woeid, 'u': 'c'})
- - raw = web.get('http://weather.yahooapis.com/forecastrss?' + query,
- - dont_decode=True)
- - parsed = xmltodict.parse(raw).get('rss')
- - location = parsed.get('channel').get('title')
- -
- - cover = get_cover(parsed)
- - temp = get_temp(parsed)
- - humidity = get_humidity(parsed)
- - wind = get_wind(parsed)
- - bot.say(u'%s: %s, %s, %s, %s' % (location, cover, temp, humidity, wind))
- -
- -
- -@commands('setlocation', 'setwoeid')
- -@example('.setlocation Columbus, OH')
- -def update_woeid(bot, trigger):
- - """Set your default weather location."""
- - if not trigger.group(2):
- - bot.reply('Give me a location, like "Washington, DC" or "London".')
- - return NOLIMIT
- -
- - first_result = woeid_search(trigger.group(2))
- - if first_result is None:
- - return bot.reply("I don't know where that is.")
- -
- - woeid = first_result.get('woeid')
- -
- - bot.db.set_nick_value(trigger.nick, 'woeid', woeid)
- -
- - neighborhood = first_result.get('locality2') or ''
- - if neighborhood:
- - neighborhood = neighborhood.get('#text') + ', '
- - city = first_result.get('locality1') or ''
- - # This is to catch cases like 'Bawlf, Alberta' where the location is
- - # thought to be a "LocalAdmin" rather than a "Town"
- - if city:
- - city = city.get('#text')
- - else:
- - city = first_result.get('name')
- - state = first_result.get('admin1').get('#text') or ''
- - country = first_result.get('country').get('#text') or ''
- - uzip = first_result.get('postal').get('#text') or ''
- - bot.reply('I now have you at WOEID %s (%s%s, %s, %s %s)' %
- - (woeid, neighborhood, city, state, country, uzip))
- diff --git a/sopel/modules/wikipedia.py b/sopel/modules/wikipedia.py
- deleted file mode 100644
- index 69dffb9..0000000
- --- a/sopel/modules/wikipedia.py
- +++ /dev/null
- @@ -1,133 +0,0 @@
- -# coding=utf-8
- -# Copyright 2013 Elsie Powell - embolalia.com
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -from sopel import web, tools
- -from sopel.config.types import StaticSection, ValidatedAttribute
- -from sopel.module import NOLIMIT, commands, example, rule
- -import json
- -import re
- -
- -import sys
- -if sys.version_info.major < 3:
- - from urlparse import unquote as _unquote
- - unquote = lambda s: _unquote(s.encode('utf-8')).decode('utf-8')
- -else:
- - from urllib.parse import unquote
- -
- -REDIRECT = re.compile(r'^REDIRECT (.*)')
- -
- -
- -class WikipediaSection(StaticSection):
- - default_lang = ValidatedAttribute('default_lang', default='en')
- - """The default language to find articles from."""
- - lang_per_channel = ValidatedAttribute('lang_per_channel')
- -
- -
- -def setup(bot):
- - bot.config.define_section('wikipedia', WikipediaSection)
- -
- - regex = re.compile('([a-z]+).(wikipedia.org/wiki/)([^ ]+)')
- - if not bot.memory.contains('url_callbacks'):
- - bot.memory['url_callbacks'] = tools.SopelMemory()
- - bot.memory['url_callbacks'][regex] = mw_info
- -
- -
- -def configure(config):
- - config.define_section('wikipedia', WikipediaSection)
- - config.wikipedia.configure_setting(
- - 'default_lang',
- - "Enter the default language to find articles from."
- - )
- -
- -
- -def mw_search(server, query, num):
- - """
- - Searches the specified MediaWiki server for the given query, and returns
- - the specified number of results.
- - """
- - search_url = ('http://%s/w/api.php?format=json&action=query'
- - '&list=search&srlimit=%d&srprop=timestamp&srwhat=text'
- - '&srsearch=') % (server, num)
- - search_url += query
- - query = json.loads(web.get(search_url))
- - if 'query' in query:
- - query = query['query']['search']
- - return [r['title'] for r in query]
- - else:
- - return None
- -
- -
- -def say_snippet(bot, server, query, show_url=True):
- - page_name = query.replace('_', ' ')
- - query = query.replace(' ', '_')
- - snippet = mw_snippet(server, query)
- - msg = '[WIKIPEDIA] {} | "{}"'.format(page_name, snippet)
- - if show_url:
- - msg = msg + ' | https://{}/wiki/{}'.format(server, query)
- - bot.say(msg)
- -
- -
- -def mw_snippet(server, query):
- - """
- - Retrives a snippet of the specified length from the given page on the given
- - server.
- - """
- - snippet_url = ('https://' + server + '/w/api.php?format=json'
- - '&action=query&prop=extracts&exintro&explaintext'
- - '&exchars=300&redirects&titles=')
- - snippet_url += query
- - snippet = json.loads(web.get(snippet_url))
- - snippet = snippet['query']['pages']
- -
- - # For some reason, the API gives the page *number* as the key, so we just
- - # grab the first page number in the results.
- - snippet = snippet[list(snippet.keys())[0]]
- -
- - return snippet['extract']
- -
- -
- -@rule('.*/([a-z]+\.wikipedia.org)/wiki/([^ ]+).*')
- -def mw_info(bot, trigger, found_match=None):
- - """
- - Retrives a snippet of the specified length from the given page on the given
- - server.
- - """
- - match = found_match or trigger
- - say_snippet(bot, match.group(1), unquote(match.group(2)), show_url=False)
- -
- -
- -@commands('w', 'wiki', 'wik')
- -@example('.w San Francisco')
- -def wikipedia(bot, trigger):
- - lang = bot.config.wikipedia.default_lang
- -
- - #change lang if channel has custom language set
- - if (trigger.sender and not trigger.sender.is_nick() and
- - bot.config.wikipedia.lang_per_channel):
- - customlang = re.search('(' + trigger.sender + '):(\w+)',
- - bot.config.wikipedia.lang_per_channel)
- - if customlang is not None:
- - lang = customlang.group(2)
- -
- - if trigger.group(2) is None:
- - bot.reply("What do you want me to look up?")
- - return NOLIMIT
- -
- - query = trigger.group(2)
- - args = re.search(r'^-([a-z]{2,12})\s(.*)', query)
- - if args is not None:
- - lang = args.group(1)
- - query = args.group(2)
- -
- - if not query:
- - bot.reply('What do you want me to look up?')
- - return NOLIMIT
- - server = lang + '.wikipedia.org'
- - query = mw_search(server, query, 1)
- - if not query:
- - bot.reply("I can't find any results for that.")
- - return NOLIMIT
- - else:
- - query = query[0]
- - say_snippet(bot, server, query)
- diff --git a/sopel/modules/wiktionary.py b/sopel/modules/wiktionary.py
- deleted file mode 100644
- index 844181c..0000000
- --- a/sopel/modules/wiktionary.py
- +++ /dev/null
- @@ -1,101 +0,0 @@
- -# coding=utf-8
- -"""
- -wiktionary.py - Sopel Wiktionary Module
- -Copyright 2009, Sean B. Palmer, inamidst.com
- -Licensed under the Eiffel Forum License 2.
- -
- -http://sopel.chat
- -"""
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import re
- -from sopel import web
- -from sopel.module import commands, example
- -
- -uri = 'http://en.wiktionary.org/w/index.php?title=%s&printable=yes'
- -r_tag = re.compile(r'<[^>]+>')
- -r_ul = re.compile(r'(?ims)<ul>.*?</ul>')
- -
- -
- -def text(html):
- - text = r_tag.sub('', html).strip()
- - text = text.replace('\n', ' ')
- - text = text.replace('\r', '')
- - text = text.replace('(intransitive', '(intr.')
- - text = text.replace('(transitive', '(trans.')
- - return text
- -
- -
- -def wikt(word):
- - bytes = web.get(uri % web.quote(word))
- - bytes = r_ul.sub('', bytes)
- -
- - mode = None
- - etymology = None
- - definitions = {}
- - for line in bytes.splitlines():
- - if 'id="Etymology"' in line:
- - mode = 'etymology'
- - elif 'id="Noun"' in line:
- - mode = 'noun'
- - elif 'id="Verb"' in line:
- - mode = 'verb'
- - elif 'id="Adjective"' in line:
- - mode = 'adjective'
- - elif 'id="Adverb"' in line:
- - mode = 'adverb'
- - elif 'id="Interjection"' in line:
- - mode = 'interjection'
- - elif 'id="Particle"' in line:
- - mode = 'particle'
- - elif 'id="Preposition"' in line:
- - mode = 'preposition'
- - elif 'id="' in line:
- - mode = None
- -
- - elif (mode == 'etmyology') and ('<p>' in line):
- - etymology = text(line)
- - elif (mode is not None) and ('<li>' in line):
- - definitions.setdefault(mode, []).append(text(line))
- -
- - if '<hr' in line:
- - break
- - return etymology, definitions
- -
- -parts = ('preposition', 'particle', 'noun', 'verb',
- - 'adjective', 'adverb', 'interjection')
- -
- -
- -def format(result, definitions, number=2):
- - for part in parts:
- - if part in definitions:
- - defs = definitions[part][:number]
- - result += u' — {}: '.format(part)
- - n = ['%s. %s' % (i + 1, e.strip(' .')) for i, e in enumerate(defs)]
- - result += ', '.join(n)
- - return result.strip(' .,')
- -
- -
- -@commands('wt', 'define', 'dict')
- -@example('.wt bailiwick')
- -def wiktionary(bot, trigger):
- - """Look up a word on Wiktionary."""
- - word = trigger.group(2)
- - if word is None:
- - bot.reply('You must tell me what to look up!')
- - return
- -
- - _etymology, definitions = wikt(word)
- - if not definitions:
- - bot.say("Couldn't get any definitions for %s." % word)
- - return
- -
- - result = format(word, definitions)
- - if len(result) < 150:
- - result = format(word, definitions, 3)
- - if len(result) < 150:
- - result = format(word, definitions, 5)
- -
- - if len(result) > 300:
- - result = result[:295] + '[...]'
- - bot.say(result)
- diff --git a/sopel/modules/xkcd.py b/sopel/modules/xkcd.py
- deleted file mode 100644
- index d2baef9..0000000
- --- a/sopel/modules/xkcd.py
- +++ /dev/null
- @@ -1,105 +0,0 @@
- -# coding=utf-8
- -# Copyright 2010, Michael Yanovich (yanovich.net), and Morgan Goose
- -# Copyright 2012, Lior Ramati
- -# Copyright 2013, Elsie Powell (embolalia.com)
- -# Licensed under the Eiffel Forum License 2.
- -from __future__ import unicode_literals, absolute_import, print_function, division
- -
- -import json
- -import random
- -import re
- -import requests
- -from sopel import web
- -from sopel.modules.search import google_search
- -from sopel.module import commands
- -
- -ignored_sites = [
- - # For google searching
- - 'almamater.xkcd.com',
- - 'blog.xkcd.com',
- - 'blag.xkcd.com',
- - 'forums.xkcd.com',
- - 'fora.xkcd.com',
- - 'forums3.xkcd.com',
- - 'store.xkcd.com',
- - 'wiki.xkcd.com',
- - 'what-if.xkcd.com',
- -]
- -sites_query = ' site:xkcd.com -site:' + ' -site:'.join(ignored_sites)
- -
- -
- -def get_info(number=None):
- - if number:
- - url = 'http://xkcd.com/{}/info.0.json'.format(number)
- - else:
- - url = 'http://xkcd.com/info.0.json'
- - data = requests.get(url).json()
- - data['url'] = 'http://xkcd.com/' + str(data['num'])
- - return data
- -
- -
- -def google(query):
- - url = google_search(query + sites_query)
- - if not url:
- - return None
- - match = re.match('(?:https?://)?xkcd.com/(\d+)/?', url)
- - if match:
- - return match.group(1)
- -
- -
- -@commands('xkcd')
- -def xkcd(bot, trigger):
- - """
- - .xkcd - Finds an xkcd comic strip. Takes one of 3 inputs:
- - If no input is provided it will return a random comic
- - If numeric input is provided it will return that comic, or the nth-latest
- - comic if the number is non-positive
- - If non-numeric input is provided it will return the first google result for those keywords on the xkcd.com site
- - """
- - # get latest comic for rand function and numeric input
- - latest = get_info()
- - max_int = latest['num']
- -
- - # if no input is given (pre - lior's edits code)
- - if not trigger.group(2): # get rand comic
- - random.seed()
- - requested = get_info(random.randint(1, max_int + 1))
- - else:
- - query = trigger.group(2).strip()
- -
- - numbered = re.match(r"^(#|\+|-)?(\d+)$", query)
- - if numbered:
- - query = int(numbered.group(2))
- - if numbered.group(1) == "-":
- - query = -query
- - if query > max_int:
- - bot.say(("Sorry, comic #{} hasn't been posted yet. "
- - "The last comic was #{}").format(query, max_int))
- - return
- - elif query <= -max_int:
- - bot.say(("Sorry, but there were only {} comics "
- - "released yet so far").format(max_int))
- - return
- - elif abs(query) == 0:
- - requested = latest
- - elif query == 404 or max_int + query == 404:
- - bot.say("404 - Not Found") # don't error on that one
- - return
- - elif query > 0:
- - requested = get_info(query)
- - else:
- - # Negative: go back that many from current
- - requested = get_info(max_int + query)
- - else:
- - # Non-number: google.
- - if (query.lower() == "latest" or query.lower() == "newest"):
- - requested = latest
- - else:
- - number = google(query)
- - if not number:
- - bot.say('Could not find any comics for that query.')
- - return
- - requested = get_info(number)
- -
- - message = '{} [{}]'.format(requested['url'], requested['title'])
- - bot.say(message)
- diff --git a/sopel/trigger.py b/sopel/trigger.py
- index 1a03744..a91d5c4 100644
- --- a/sopel/trigger.py
- +++ b/sopel/trigger.py
- @@ -50,9 +50,9 @@ class PreTrigger(object):
- # TODO note what this is doing and why
- if ' :' in line:
- - argstr, text = line.split(' :', 1)
- + argstr, self.text = line.split(' :', 1)
- self.args = argstr.split(' ')
- - self.args.append(text)
- + self.args.append(self.text)
- else:
- self.args = line.split(' ')
- self.text = self.args[-1]
- @@ -141,6 +141,7 @@ class Trigger(unicode):
- ``('#example', '-m')``
- """
- tags = property(lambda self: self._pretrigger.tags)
- + text = property(lambda self: self._pretrigger.text)
- """A map of the IRCv3 message tags on the message."""
- admin = property(lambda self: self._admin)
- """True if the nick which triggered the command is one of the bot's admins.
- --
- 2.4.6
- From 4444bd1d630f85f6937a85b2c37ef1c7c7badf23 Mon Sep 17 00:00:00 2001
- From: S00ng <???>
- Date: Sun, 1 May 2016 15:22:08 +0200
- Subject: [PATCH 2/4] init machine learning
- ---
- sopel/modules/helloworld.py | 18 ++++++++++++++++++
- sopeldatawriter.py | 10 ++++++++++
- 2 files changed, 28 insertions(+)
- create mode 100644 sopel/modules/helloworld.py
- create mode 100644 sopeldatawriter.py
- diff --git a/sopel/modules/helloworld.py b/sopel/modules/helloworld.py
- new file mode 100644
- index 0000000..a7553cf
- --- /dev/null
- +++ b/sopel/modules/helloworld.py
- @@ -0,0 +1,18 @@
- +from sopel import module
- +import sopeldatawriter
- +
- +@module.rule('\w*')
- +def check(bot, trigger):
- + sopeldatawriter.user_data_write_csv(trigger.nick,trigger.text,"testfile.csv")
- + '''
- +@module.rule('hello|(hello\s*(everyone|d4t4))')
- +def hi(bot, trigger):
- + bot.say('Hello, ' + trigger.nick)
- +@module.rule('[a-zA-Z0-9_\s]*d4t4[a-zA-Z0-9_\s]*new[a-zA-Z0-9_\s]*here[a-zA-Z0-9_\s]*')
- +def new(bot, trigger):
- + bot.say('Yes I am new here')
- +@module.rule('\s*\w*\s*d4t4\s*\w*\s*introduce\s*\w*\s*yourself\s*\w*\s*')
- +def introduce(bot, trigger):
- + bot.say('My Name is D4t4. I am 1 day old, I have no gender , I am programmed from s00ng')
- + bot.say('I am a sopel module')
- +'''
- diff --git a/sopeldatawriter.py b/sopeldatawriter.py
- new file mode 100644
- index 0000000..77910cb
- --- /dev/null
- +++ b/sopeldatawriter.py
- @@ -0,0 +1,10 @@
- +import csv
- +
- +def user_data_write_csv(nick,text,file):
- + with open(file, 'a') as csvfile:
- + csvwriter = csv.writer(csvfile, delimiter=' ',quotechar='|', quoting=csv.QUOTE_MINIMAL)
- + csvwriter.writerow([nick,text])
- +def user_data_write_raw(nick,text,file):
- + with open(file, "a") as f:
- + data= "".join([trigger.nick ,"\n", trigger.text ,"\n"])
- + f.write(data)
- --
- 2.4.6
- From 89c614e45522e8c6066b7d006d874b8f68e4342d Mon Sep 17 00:00:00 2001
- From: s00ng <???>
- Date: Fri, 3 Jun 2016 22:18:16 +0200
- Subject: [PATCH 3/4] fix KeyError with multiple channels
- ---
- sopel/tools/target.py | 9 +++++----
- sopeldatawriter.py | 1 +
- 2 files changed, 6 insertions(+), 4 deletions(-)
- diff --git a/sopel/tools/target.py b/sopel/tools/target.py
- index 2ff0a91..963a439 100644
- --- a/sopel/tools/target.py
- +++ b/sopel/tools/target.py
- @@ -62,10 +62,11 @@ class Channel(object):
- """The topic of the channel."""
- def clear_user(self, nick):
- - user = self.users[nick]
- - user.channels.pop(self.name, None)
- - del self.users[nick]
- - del self.privileges[nick]
- + if nick in self.users:
- + user = self.users[nick]
- + user.channels.pop(self.name, None)
- + del self.users[nick]
- + del self.privileges[nick]
- def add_user(self, user):
- assert isinstance(user, User)
- diff --git a/sopeldatawriter.py b/sopeldatawriter.py
- index 77910cb..47b77f1 100644
- --- a/sopeldatawriter.py
- +++ b/sopeldatawriter.py
- @@ -4,6 +4,7 @@ def user_data_write_csv(nick,text,file):
- with open(file, 'a') as csvfile:
- csvwriter = csv.writer(csvfile, delimiter=' ',quotechar='|', quoting=csv.QUOTE_MINIMAL)
- csvwriter.writerow([nick,text])
- + csvfile.close()
- def user_data_write_raw(nick,text,file):
- with open(file, "a") as f:
- data= "".join([trigger.nick ,"\n", trigger.text ,"\n"])
- --
- 2.4.6
- From ca1568d577018943fd22aab83129cc58ab45a0e8 Mon Sep 17 00:00:00 2001
- From: s00ng <???>
- Date: Fri, 3 Jun 2016 22:20:19 +0200
- Subject: [PATCH 4/4] close the file after write in it
- ---
- sopeldatawriter.py | 1 +
- 1 file changed, 1 insertion(+)
- diff --git a/sopeldatawriter.py b/sopeldatawriter.py
- index 47b77f1..ea4d50f 100644
- --- a/sopeldatawriter.py
- +++ b/sopeldatawriter.py
- @@ -9,3 +9,4 @@ def user_data_write_raw(nick,text,file):
- with open(file, "a") as f:
- data= "".join([trigger.nick ,"\n", trigger.text ,"\n"])
- f.write(data)
- + f.close()
- --
- 2.4.6
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement