some refactoring, removed some useless stuff, added some useless stuff

This commit is contained in:
iou1name 2018-01-05 08:46:52 -05:00
parent 04cc2a2084
commit e6e8d544d2
27 changed files with 682 additions and 1549 deletions

32
bot.py
View File

@ -1,12 +1,8 @@
# coding=utf-8 #! /usr/bin/env python3
# Copyright 2008, Sean B. Palmer, inamidst.com # -*- coding: utf-8 -*-
# Copyright © 2012, Elad Alfassa <elad@fedoraproject.org> """
# Copyright 2012-2015, Elsie Powell, http://embolalia.com The core bot class. Say good bye to PYthon 2.
# """
# Licensed under the Eiffel Forum License 2.
from __future__ import unicode_literals, absolute_import, print_function, division
import collections import collections
import os import os
import re import re
@ -27,13 +23,6 @@ import loader
LOGGER = get_logger(__name__) LOGGER = get_logger(__name__)
if sys.version_info.major >= 3:
unicode = str
basestring = str
py3 = True
else:
py3 = False
class _CapReq(object): class _CapReq(object):
def __init__(self, prefix, module, failure=None, arg=None, success=None): def __init__(self, prefix, module, failure=None, arg=None, success=None):
@ -70,7 +59,6 @@ class Sopel(irc.Bot):
""" """
self._command_groups = collections.defaultdict(list) self._command_groups = collections.defaultdict(list)
"""A mapping of module names to a list of commands in it.""" """A mapping of module names to a list of commands in it."""
self.stats = {} # deprecated, remove in 7.0
self._times = {} self._times = {}
""" """
A dictionary mapping lower-case'd nicks to dictionaries which map A dictionary mapping lower-case'd nicks to dictionaries which map
@ -261,10 +249,6 @@ class Sopel(irc.Bot):
else: else:
self.write(['JOIN', channel, password]) self.write(['JOIN', channel, password])
def msg(self, recipient, text, max_messages=1):
# Deprecated, but way too much of a pain to remove.
self.say(text, recipient, max_messages)
def say(self, text, recipient, max_messages=1): def say(self, text, recipient, max_messages=1):
"""Send ``text`` as a PRIVMSG to ``recipient``. """Send ``text`` as a PRIVMSG to ``recipient``.
@ -288,7 +272,7 @@ class Sopel(irc.Bot):
max_text_length = 400 max_text_length = 400
max_messages=1000 max_messages=1000
# Encode to bytes, for propper length calculation # Encode to bytes, for propper length calculation
if isinstance(text, unicode): if isinstance(text, str):
encoded_text = text.encode('utf-8') encoded_text = text.encode('utf-8')
else: else:
encoded_text = text encoded_text = text
@ -306,7 +290,7 @@ class Sopel(irc.Bot):
excess = encoded_text[last_space + 1:] excess = encoded_text[last_space + 1:]
encoded_text = encoded_text[:last_space] encoded_text = encoded_text[:last_space]
# We'll then send the excess at the end # We'll then send the excess at the end
# Back to unicode again, so we don't screw things up later. # Back to str again, so we don't screw things up later.
text = encoded_text.decode('utf-8') text = encoded_text.decode('utf-8')
try: try:
self.sending.acquire() self.sending.acquire()
@ -327,7 +311,7 @@ class Sopel(irc.Bot):
# Now that we've sent the first part, we need to send the rest. Doing # Now that we've sent the first part, we need to send the rest. Doing
# this recursively seems easier to me than iteratively # this recursively seems easier to me than iteratively
if excess: if excess:
self.msg(recipient, excess, max_messages - 1) self.say(recipient, excess, max_messages - 1)
def notice(self, text, dest): def notice(self, text, dest):
"""Send an IRC NOTICE to a user or a channel. """Send an IRC NOTICE to a user or a channel.

View File

@ -37,10 +37,7 @@ def auth_after_register(bot):
"""Do NickServ/AuthServ auth""" """Do NickServ/AuthServ auth"""
if bot.config.core.auth_method == 'nickserv': if bot.config.core.auth_method == 'nickserv':
nickserv_name = bot.config.core.auth_target or 'NickServ' nickserv_name = bot.config.core.auth_target or 'NickServ'
bot.msg( bot.say('IDENTIFY %s' % bot.config.core.auth_password, nickserv_name)
nickserv_name,
'IDENTIFY %s' % bot.config.core.auth_password
)
elif bot.config.core.auth_method == 'authserv': elif bot.config.core.auth_method == 'authserv':
account = bot.config.core.auth_username account = bot.config.core.auth_username
@ -107,7 +104,7 @@ def startup(bot, trigger):
"more secure. If you'd like to do this, make sure you're logged in " "more secure. If you'd like to do this, make sure you're logged in "
"and reply with \"{}useserviceauth\"" "and reply with \"{}useserviceauth\""
).format(bot.config.core.help_prefix) ).format(bot.config.core.help_prefix)
bot.msg(bot.config.core.owner, msg) bot.say(msg, bot.config.core.owner)
@module.require_privmsg() @module.require_privmsg()
@ -262,7 +259,7 @@ def track_nicks(bot, trigger):
debug_msg = ("Nick changed by server. " debug_msg = ("Nick changed by server. "
"This can cause unexpected behavior. Please restart the bot.") "This can cause unexpected behavior. Please restart the bot.")
LOGGER.critical(debug_msg) LOGGER.critical(debug_msg)
bot.msg(bot.config.core.owner, privmsg) bot.say(privmsg, bot.config.core.owner)
return return
for channel in bot.privileges: for channel in bot.privileges:

88
irc.py
View File

@ -1,43 +1,24 @@
# coding=utf-8 #! /usr/bin/env python3
# irc.py - An Utility IRC Bot # -*- coding: utf-8 -*-
# Copyright 2008, Sean B. Palmer, inamidst.com """
# Copyright 2012, Elsie Powell, http://embolalia.com Core IRC functionality. Support for Python 2 is largely gone.
# Copyright © 2012, Elad Alfassa <elad@fedoraproject.org> """
#
# Licensed under the Eiffel Forum License 2.
#
# When working on core IRC protocol related features, consult protocol
# documentation at http://www.irchelp.org/irchelp/rfc/
from __future__ import unicode_literals, absolute_import, print_function, division
import sys import sys
import os
import time import time
import socket import socket
import asyncore
import asynchat
import os
import codecs import codecs
import traceback import traceback
import threading
from datetime import datetime
import errno
import ssl
import asyncore
import asynchat
from logger import get_logger from logger import get_logger
from tools import stderr, Identifier from tools import stderr, Identifier
from trigger import PreTrigger from trigger import PreTrigger
try:
import ssl
if not hasattr(ssl, 'match_hostname'):
# Attempt to import ssl_match_hostname from python-backports
import backports.ssl_match_hostname
ssl.match_hostname = backports.ssl_match_hostname.match_hostname
ssl.CertificateError = backports.ssl_match_hostname.CertificateError
has_ssl = True
except ImportError:
# no SSL support
has_ssl = False
import errno
import threading
from datetime import datetime
if sys.version_info.major >= 3:
unicode = str
LOGGER = get_logger(__name__) LOGGER = get_logger(__name__)
@ -67,17 +48,6 @@ class Bot(asynchat.async_chat):
self.writing_lock = threading.Lock() self.writing_lock = threading.Lock()
self.raw = None self.raw = None
# Right now, only accounting for two op levels.
# This might be expanded later.
# These lists are filled in startup.py, as of right now.
# Are these even touched at all anymore? Remove in 7.0.
self.ops = dict()
"""Deprecated. Use bot.channels instead."""
self.halfplus = dict()
"""Deprecated. Use bot.channels instead."""
self.voices = dict()
"""Deprecated. Use bot.channels instead."""
# We need this to prevent error loops in handle_error # We need this to prevent error loops in handle_error
self.error_count = 0 self.error_count = 0
@ -85,10 +55,6 @@ class Bot(asynchat.async_chat):
""" Set to True when a server has accepted the client connection and """ Set to True when a server has accepted the client connection and
messages can be sent and received. """ messages can be sent and received. """
# Work around bot.connecting missing in Python older than 2.7.4
if not hasattr(self, "connecting"):
self.connecting = False
def log_raw(self, line, prefix): def log_raw(self, line, prefix):
"""Log raw line to the raw log.""" """Log raw line to the raw log."""
if not self.config.core.log_raw: if not self.config.core.log_raw:
@ -103,7 +69,7 @@ class Bot(asynchat.async_chat):
os._exit(1) os._exit(1)
f = codecs.open(os.path.join(self.config.core.logdir, 'raw.log'), f = codecs.open(os.path.join(self.config.core.logdir, 'raw.log'),
'a', encoding='utf-8') 'a', encoding='utf-8')
f.write(prefix + unicode(time.time()) + "\t") f.write(prefix + str(time.time()) + "\t")
temp = line.replace('\n', '') temp = line.replace('\n', '')
f.write(temp) f.write(temp)
@ -112,11 +78,8 @@ class Bot(asynchat.async_chat):
def safe(self, string): def safe(self, string):
"""Remove newlines from a string.""" """Remove newlines from a string."""
if sys.version_info.major >= 3 and isinstance(string, bytes): if isinstance(string, bytes):
string = string.decode('utf8') string = string.decode('utf8')
elif sys.version_info.major < 3:
if not isinstance(string, unicode):
string = unicode(string, encoding='utf8')
string = string.replace('\n', '') string = string.replace('\n', '')
string = string.replace('\r', '') string = string.replace('\r', '')
return string return string
@ -162,12 +125,9 @@ class Bot(asynchat.async_chat):
if self.config.core.bind_host else None) if self.config.core.bind_host else None)
self.set_socket(socket.create_connection((host, port), self.set_socket(socket.create_connection((host, port),
source_address=source_address)) source_address=source_address))
if self.config.core.use_ssl and has_ssl: if self.config.core.use_ssl:
self.send = self._ssl_send self.send = self._ssl_send
self.recv = self._ssl_recv self.recv = self._ssl_recv
elif not has_ssl and self.config.core.use_ssl:
stderr('SSL is not avilable on your system, attempting connection '
'without it')
self.connect((host, port)) self.connect((host, port))
try: try:
asyncore.loop() asyncore.loop()
@ -200,7 +160,7 @@ class Bot(asynchat.async_chat):
self.close() self.close()
def handle_connect(self): def handle_connect(self):
if self.config.core.use_ssl and has_ssl: if self.config.core.use_ssl:
if not self.config.core.verify_ssl: if not self.config.core.verify_ssl:
self.ssl = ssl.wrap_socket(self.socket, self.ssl = ssl.wrap_socket(self.socket,
do_handshake_on_connect=True, do_handshake_on_connect=True,
@ -293,17 +253,17 @@ class Bot(asynchat.async_chat):
raise raise
def collect_incoming_data(self, data): def collect_incoming_data(self, data):
# We can't trust clients to pass valid unicode. # We can't trust clients to pass valid str.
try: try:
data = unicode(data, encoding='utf-8') data = str(data, encoding='utf-8')
except UnicodeDecodeError: except strDecodeError:
# not unicode, let's try cp1252 # not str, let's try cp1252
try: try:
data = unicode(data, encoding='cp1252') data = str(data, encoding='cp1252')
except UnicodeDecodeError: except strDecodeError:
# Okay, let's try ISO8859-1 # Okay, let's try ISO8859-1
try: try:
data = unicode(data, encoding='iso8859-1') data = str(data, encoding='iso8859-1')
except: except:
# Discard line if encoding is unknown # Discard line if encoding is unknown
return return

View File

@ -1,6 +1,8 @@
# coding=utf-8 #! /usr/bin/env python3
from __future__ import unicode_literals, absolute_import, print_function, division # -*- coding: utf-8 -*-
"""
Methods for loading modules.
"""
import imp import imp
import os.path import os.path
import re import re

2
logger.py Normal file → Executable file
View File

@ -13,7 +13,7 @@ class IrcLoggingHandler(logging.Handler):
def emit(self, record): def emit(self, record):
try: try:
msg = self.format(record) msg = self.format(record)
self._bot.msg(self._channel, msg) self._bot.say(msg, self._channel)
except (KeyboardInterrupt, SystemExit): except (KeyboardInterrupt, SystemExit):
raise raise
except: except:

View File

@ -59,7 +59,7 @@ def interval(*args):
@sopel.module.interval(5) @sopel.module.interval(5)
def spam_every_5s(bot): def spam_every_5s(bot):
if "#here" in bot.channels: if "#here" in bot.channels:
bot.msg("#here", "It has been five seconds!") bot.say("It has been five seconds!", "#here")
""" """
def add_attribute(function): def add_attribute(function):

View File

@ -98,7 +98,7 @@ def msg(bot, trigger):
if not channel or not message: if not channel or not message:
return return
bot.msg(channel, message) bot.say(message, channel)
@module.require_privmsg @module.require_privmsg
@ -119,7 +119,7 @@ def me(bot, trigger):
return return
msg = '\x01ACTION %s\x01' % action msg = '\x01ACTION %s\x01' % action
bot.msg(channel, msg) bot.say(msg, channel)
@module.event('INVITE') @module.event('INVITE')

View File

@ -127,62 +127,6 @@ def unban(bot, trigger):
bot.write(['MODE', channel, '-b', banmask]) 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_chanmsg
@require_privilege(OP, 'You are not a channel operator.') @require_privilege(OP, 'You are not a channel operator.')
@commands('kickban', 'kb') @commands('kickban', 'kb')

View File

@ -18,5 +18,5 @@ def announce(bot, trigger):
bot.reply('Sorry, I can\'t let you do that') bot.reply('Sorry, I can\'t let you do that')
return return
for channel in bot.channels: for channel in bot.channels:
bot.msg(channel, '[ANNOUNCEMENT] %s' % trigger.group(2)) bot.say('[ANNOUNCEMENT] %s' % trigger.group(2), channel)
bot.reply('Announce complete.') bot.reply('Announce complete.')

View File

@ -4,6 +4,7 @@
ASCII ASCII
""" """
from io import BytesIO from io import BytesIO
import argparse
import requests import requests
from PIL import Image, ImageFont, ImageDraw from PIL import Image, ImageFont, ImageDraw
@ -168,6 +169,8 @@ def image_to_ascii(image=None, reverse=False, brail=False, color=None,**kwargs):
image = image.convert(image.palette.mode) image = image.convert(image.palette.mode)
if image.mode == "RGBA": if image.mode == "RGBA":
image = alpha_composite(image).convert("RGB") image = alpha_composite(image).convert("RGB")
if image.mode == "L":
image = image.convert("RGB")
image = scale_image(image) image = scale_image(image)

View File

@ -1,97 +0,0 @@
# coding=utf-8
"""Bugzilla issue reporting module
Copyright 2013-2015, Embolalia, embolalia.com
Licensed under the Eiffel Forum License 2.
"""
import re
import xmltodict
import requests
import tools
from config.types import StaticSection, ListAttribute
from logger import get_logger
from 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 = requests.get(url)
bug = xmltodict.parse(data).get('bugzilla').get('bug')
error = bug.get('@error', None) # error="NotPermitted"
if error:
LOGGER.warning('Bugzilla error: %s' % error)
bot.say('[BUGZILLA] Unable to get infomation for '
'linked bug (%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)

View File

@ -1,276 +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.
try:
import pytz
except ImportError:
pytz = None
from module import commands, example, OP
from tools.time import (
get_timezone, format_time, validate_format, validate_timezone
)
from 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)

View File

@ -83,15 +83,15 @@ def f_etymology(bot, trigger):
result = etymology(word) result = etymology(word)
except IOError: except IOError:
msg = "Can't connect to etymonline.com (%s)" % (etyuri % word) msg = "Can't connect to etymonline.com (%s)" % (etyuri % word)
bot.msg(trigger.sender, msg) bot.say(msg)
return NOLIMIT return NOLIMIT
except (AttributeError, TypeError): except (AttributeError, TypeError):
result = None result = None
if result is not None: if result is not None:
bot.msg(trigger.sender, result) bot.say(result)
else: else:
uri = etysearch % word uri = etysearch % word
msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri) msg = 'Can\'t find the etymology for "%s". Try %s' % (word, uri)
bot.msg(trigger.sender, msg) bot.say(msg)
return NOLIMIT return NOLIMIT

28
modules/grog.py Executable file
View File

@ -0,0 +1,28 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Selects a random Grog of Substantial Whimsy effect.
"""
import os
import random
from module import commands, example
@commands("grog")
@example(".grog")
def grog(bot, trigger):
"""
Picks a random status effect from Grog of Substantial Whimsy effect.
"""
with open(os.path.join(bot.config.homedir, "grog.txt"), "r") as file:
data = file.read().split("\n")
num = 0
if trigger.group(2):
try:
num = int(trigger.group(2)) - 1
except:
pass
if num and num < len(data):
bot.say(data[num])
else:
bot.say(random.choice(data))

View File

@ -8,18 +8,8 @@ Licensed under the Eiffel Forum License 2.
http://sopel.chat http://sopel.chat
""" """
from __future__ import unicode_literals, absolute_import, print_function, division
import textwrap
import collections
import json
from logger import get_logger
from module import commands, rule, example, priority from module import commands, rule, example, priority
logger = get_logger(__name__)
@rule('$nick' '(?i)(help|doc) +([A-Za-z]+)(?:\?+)?$') @rule('$nick' '(?i)(help|doc) +([A-Za-z]+)(?:\?+)?$')
@example('.help tell') @example('.help tell')
@commands('help', 'commands') @commands('help', 'commands')
@ -30,8 +20,8 @@ def help(bot, trigger):
name = trigger.group(2) name = trigger.group(2)
name = name.lower() name = name.lower()
if name in bot.doc: if name not in bot.doc:
print(bot.doc[name]) return
newlines = [''] newlines = ['']
lines = list(filter(None, bot.doc[name][0])) lines = list(filter(None, bot.doc[name][0]))
lines = list(map(str.strip, lines)) lines = list(map(str.strip, lines))
@ -46,7 +36,13 @@ def help(bot, trigger):
for msg in newlines: for msg in newlines:
bot.say(msg) bot.say(msg)
else: else:
helps = list(bot.command_groups) command_groups = list(bot.command_groups.values())
helps.sort() commands = []
msg = "Available commands: " + ', '.join(helps) for group in command_groups:
if type(group) == list:
commands += group
else:
commands += [group]
commands.sort()
msg = "Available commands: " + ', '.join(commands)
bot.say(msg) bot.say(msg)

View File

@ -43,7 +43,7 @@ def roomTemp(bot, trigger):
@module.require_admin @module.require_admin
@module.commands('inkwrite') @module.commands('inkwrite')
def roomTemp(bot, trigger): def inkWrite(bot, trigger):
""" """
Writes shit to my e-ink screen. Writes shit to my e-ink screen.
""" """

View File

@ -1,432 +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
"""
import time
import os
from config.types import StaticSection, FilenameAttribute, ValidatedAttribute
from module import example, commands, rule, priority
from 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 + 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 + 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, verify=bot.config.core.verify_ssl)
pass
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.')

View File

@ -123,13 +123,13 @@ def pickMovie(bot, trigger):
bot.memory['movie_lock'].acquire() bot.memory['movie_lock'].acquire()
conn = bot.db.connect() conn = bot.db.connect()
cur = conn.cursor() cur = conn.cursor()
cur.execute("SELECT * FROM movie WHERE times_watched < 1 AND shitpost = 0") cur.execute("SELECT movie_title FROM movie WHERE " + \
movieList = cur.fetchall() "times_watched < 1 AND shitpost = 0 ORDER BY RANDOM() LIMIT 1;")
movie = cur.fetchone()
conn.close() conn.close()
roll = random.randint(0, len(movieList)-1)
bot.memory['movie_lock'].release() bot.memory['movie_lock'].release()
bot.reply(movieList[roll][0]) bot.reply(movie[0])
@module.require_admin @module.require_admin

View File

@ -70,9 +70,9 @@ def setup(bot):
for oldtime in oldtimes: for oldtime in oldtimes:
for (channel, nick, message) in bot.rdb[oldtime]: for (channel, nick, message) in bot.rdb[oldtime]:
if message: if message:
bot.msg(channel, nick + ': ' + message) bot.say(nick + ': ' + message)
else: else:
bot.msg(channel, nick + '!') bot.say(nick + '!')
del bot.rdb[oldtime] del bot.rdb[oldtime]
dump_database(bot.rfn, bot.rdb) dump_database(bot.rfn, bot.rdb)
time.sleep(2.5) time.sleep(2.5)

View File

@ -112,17 +112,3 @@ def search(bot, trigger):
result = '%s (b), %s (d)' % (bu, du) result = '%s (b), %s (d)' % (bu, du)
bot.reply(result) 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://requestssitedev.de/temp-bin/suggest.pl?q='
answer = requests.get(uri + query.replace('+', '%2B'))
if answer:
bot.say(answer)
else:
bot.reply('Sorry, no result.')

View File

@ -5,6 +5,8 @@ When was this user last seen.
""" """
import time import time
import datetime import datetime
import argparse
from tools import Identifier from tools import Identifier
from tools.time import get_timezone, format_time, relativeTime from tools.time import get_timezone, format_time, relativeTime
from module import commands, rule, priority, thread from module import commands, rule, priority, thread
@ -13,10 +15,16 @@ from module import commands, rule, priority, thread
@commands('seen') @commands('seen')
def seen(bot, trigger): def seen(bot, trigger):
"""Reports when and where the user was last seen.""" """Reports when and where the user was last seen."""
if not trigger.group(2): parser = argparse.ArgumentParser()
bot.say(".seen <nick> - Reports when <nick> was last seen.") parser.add_argument("nick")
return parser.add_argument("-l", "--last", action="store_true")
nick = trigger.group(2).strip() args = parser.parse_args(trigger.group(2).split())
# if not trigger.group(2):
# bot.say(".seen <nick> - Reports when <nick> was last seen.")
# return
# nick = trigger.group(2).strip()
nick = args.nick
if nick == bot.nick: if nick == bot.nick:
bot.reply("I'm right here!") bot.reply("I'm right here!")
return return
@ -34,6 +42,9 @@ def seen(bot, trigger):
reltime = relativeTime(bot, nick, timestamp) reltime = relativeTime(bot, nick, timestamp)
msg = "Last heard from \x0308{}\x03 at {} (\x0312{}\x03) in \x0312{}".format(nick, timestamp, reltime, channel) msg = "Last heard from \x0308{}\x03 at {} (\x0312{}\x03) in \x0312{}".format(nick, timestamp, reltime, channel)
if args.last:
msg += "\x03 with \"\x0308{}\x03\"".format(message)
bot.reply(msg) bot.reply(msg)
else: else:
bot.say("I haven't seen \x0308{}".format(nick)) bot.say("I haven't seen \x0308{}".format(nick))

View File

@ -124,7 +124,7 @@ def f_remind(bot, trigger):
dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell
def getReminders(bot, channel, key, tellee): def getReminders(bot, key, tellee):
lines = [] lines = []
template = "%s: \x0310%s\x03 (\x0308%s\x03) %s [\x0312%s\x03]" template = "%s: \x0310%s\x03 (\x0308%s\x03) %s [\x0312%s\x03]"
@ -136,7 +136,7 @@ def getReminders(bot, channel, key, tellee):
try: try:
del bot.memory['reminders'][key] del bot.memory['reminders'][key]
except KeyError: except KeyError:
bot.msg(channel, 'Er...') bot.say('Er...')
finally: finally:
bot.memory['tell_lock'].release() bot.memory['tell_lock'].release()
return lines return lines
@ -147,7 +147,6 @@ def getReminders(bot, channel, key, tellee):
def message(bot, trigger): def message(bot, trigger):
tellee = trigger.nick tellee = trigger.nick
channel = trigger.sender
if not os.path.exists(bot.tell_filename): if not os.path.exists(bot.tell_filename):
return return
@ -158,9 +157,9 @@ def message(bot, trigger):
for remkey in remkeys: for remkey in remkeys:
if not remkey.endswith('*') or remkey.endswith(':'): if not remkey.endswith('*') or remkey.endswith(':'):
if tellee == remkey: if tellee == remkey:
reminders.extend(getReminders(bot, channel, remkey, tellee)) reminders.extend(getReminders(bot, remkey, tellee))
elif tellee.startswith(remkey.rstrip('*:')): elif tellee.startswith(remkey.rstrip('*:')):
reminders.extend(getReminders(bot, channel, remkey, tellee)) reminders.extend(getReminders(bot, remkey, tellee))
for line in reminders[:maximum]: for line in reminders[:maximum]:
bot.say(line) bot.say(line)
@ -168,7 +167,7 @@ def message(bot, trigger):
if reminders[maximum:]: if reminders[maximum:]:
bot.say('Further messages sent privately') bot.say('Further messages sent privately')
for line in reminders[maximum:]: for line in reminders[maximum:]:
bot.msg(tellee, line) bot.say(line, tellee)
if len(bot.memory['reminders'].keys()) != remkeys: if len(bot.memory['reminders'].keys()) != remkeys:
dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell dumpReminders(bot.tell_filename, bot.memory['reminders'], bot.memory['tell_lock']) # @@ tell

View File

@ -15,6 +15,7 @@ if sys.version_info.major >= 3:
@example('.u ‽', 'U+203D INTERROBANG (‽)') @example('.u ‽', 'U+203D INTERROBANG (‽)')
@example('.u 203D', 'U+203D INTERROBANG (‽)') @example('.u 203D', 'U+203D INTERROBANG (‽)')
def codepoint(bot, trigger): def codepoint(bot, trigger):
"""Looks up unicode information."""
arg = trigger.group(2) arg = trigger.group(2)
if not arg: if not arg:
bot.reply('What code point do you want me to look up?') bot.reply('What code point do you want me to look up?')

View File

@ -122,7 +122,7 @@ def weather(bot, trigger):
if not location: if not location:
woeid = bot.db.get_nick_value(trigger.nick, 'woeid') woeid = bot.db.get_nick_value(trigger.nick, 'woeid')
if not woeid: if not woeid:
return bot.msg(trigger.sender, "I don't know where you live. " + return bot.say("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.') 'Give me a location, like .weather London, or tell me where you live by saying .setlocation London, for example.')
else: else:
location = location.strip() location = location.strip()

View File

@ -16,6 +16,9 @@ import wolframalpha
@example('.wa 2+2', '[W|A] 2+2 = 4') @example('.wa 2+2', '[W|A] 2+2 = 4')
@example('.wa python language release date', '[W|A] Python | date introduced = 1991') @example('.wa python language release date', '[W|A] Python | date introduced = 1991')
def wa_command(bot, trigger): def wa_command(bot, trigger):
"""
Queries WolframAlpha.
"""
msg = None msg = None
if not trigger.group(2): if not trigger.group(2):
msg = 'You must provide a query.' msg = 'You must provide a query.'

View File

@ -1,5 +1,5 @@
#!/usr/bin/env python2.7 #! /usr/bin/env python3
# coding=utf-8 # -*- coding: utf-8 -*-
""" """
Sopel - An IRC Bot Sopel - An IRC Bot
Copyright 2008, Sean B. Palmer, inamidst.com Copyright 2008, Sean B. Palmer, inamidst.com
@ -8,18 +8,8 @@ Licensed under the Eiffel Forum License 2.
http://sopel.chat http://sopel.chat
""" """
from __future__ import unicode_literals, absolute_import, print_function, division
import sys import sys
from tools import stderr from tools import stderr
if sys.version_info < (2, 7):
stderr('Error: Requires Python 2.7 or later. Try python2.7 sopel')
sys.exit(1)
if sys.version_info.major == 3 and sys.version_info.minor < 3:
stderr('Error: When running on Python 3, Python 3.3 is required.')
sys.exit(1)
import os import os
import argparse import argparse
import signal import signal
@ -57,36 +47,71 @@ def main(argv=None):
global homedir global homedir
# Step One: Parse The Command Line # Step One: Parse The Command Line
try: try:
parser = argparse.ArgumentParser(description='Sopel IRC Bot', parser = argparse.ArgumentParser(
description='Sopel IRC Bot',
usage='%(prog)s [options]') usage='%(prog)s [options]')
parser.add_argument('-c', '--config', metavar='filename', parser.add_argument(
'-c',
'--config',
metavar='filename',
help='use a specific configuration file') help='use a specific configuration file')
parser.add_argument("-d", '--fork', action="store_true", parser.add_argument(
dest="daemonize", help="Daemonize sopel") "-d",
parser.add_argument("-q", '--quit', action="store_true", dest="quit", '--fork',
action="store_true",
dest="daemonize",
help="Daemonize sopel")
parser.add_argument(
"-q",
'--quit',
action="store_true",
dest="quit",
help="Gracefully quit Sopel") help="Gracefully quit Sopel")
parser.add_argument("-k", '--kill', action="store_true", dest="kill", parser.add_argument(
"-k",
'--kill',
action="store_true",
dest="kill",
help="Kill Sopel") help="Kill Sopel")
parser.add_argument("-l", '--list', action="store_true", parser.add_argument(
"-l",
'--list',
action="store_true",
dest="list_configs", dest="list_configs",
help="List all config files found") help="List all config files found")
parser.add_argument("-m", '--migrate', action="store_true", parser.add_argument(
"-m",
'--migrate',
action="store_true",
dest="migrate_configs", dest="migrate_configs",
help="Migrate config files to the new format") help="Migrate config files to the new format")
parser.add_argument('--quiet', action="store_true", dest="quiet", parser.add_argument(
'--quiet',
action="store_true",
dest="quiet",
help="Supress all output") help="Supress all output")
parser.add_argument('-w', '--configure-all', action='store_true', parser.add_argument(
dest='wizard', help='Run the configuration wizard.') '-w',
parser.add_argument('--configure-modules', action='store_true', '--configure-all',
dest='mod_wizard', help=( action='store_true',
'Run the configuration wizard, but only for the ' dest='wizard',
'module configuration options.')) help='Run the configuration wizard.')
parser.add_argument('-v', '--version', action="store_true", parser.add_argument(
dest="version", help="Show version number and exit") '--configure-modules',
action='store_true',
dest='mod_wizard',
help='Run the configuration wizard, but only for the module ' + \
'configuration options.')
parser.add_argument(
'-v',
'--version',
action="store_true",
dest="version",
help="Show version number and exit")
if argv: if argv:
opts = parser.parse_args(argv) args = parser.parse_args(argv)
else: else:
opts = parser.parse_args() args = parser.parse_args()
# Step Two: "Do not run as root" checks. # Step Two: "Do not run as root" checks.
try: try:
@ -100,21 +125,21 @@ def main(argv=None):
stderr('Error: Do not run Sopel as Administrator.') stderr('Error: Do not run Sopel as Administrator.')
sys.exit(1) sys.exit(1)
if opts.version: if args.version:
py_ver = '%s.%s.%s' % (sys.version_info.major, py_ver = '%s.%s.%s' % (sys.version_info.major,
sys.version_info.minor, sys.version_info.minor,
sys.version_info.micro) sys.version_info.micro)
print('Sopel %s (running on python %s)' % (__version__, py_ver)) print('Sopel %s (running on python %s)' % (__version__, py_ver))
print('http://sopel.chat/') print('http://sopel.chat/')
return return
elif opts.wizard: elif args.wizard:
_wizard('all', opts.config) _wizard('all', args.config)
return return
elif opts.mod_wizard: elif args.mod_wizard:
_wizard('mod', opts.config) _wizard('mod', args.config)
return return
if opts.list_configs: if args.list_configs:
configs = enumerate_configs() configs = enumerate_configs()
print('Config files in ~/.sopel:') print('Config files in ~/.sopel:')
if len(configs) is 0: if len(configs) is 0:
@ -125,7 +150,7 @@ def main(argv=None):
print('-------------------------') print('-------------------------')
return return
config_name = opts.config or 'default' config_name = args.config or 'default'
configpath = find_config(config_name) configpath = find_config(config_name)
if not os.path.isfile(configpath): if not os.path.isfile(configpath):
@ -147,17 +172,17 @@ def main(argv=None):
logfile = os.path.os.path.join(config_module.core.logdir, 'stdio.log') logfile = os.path.os.path.join(config_module.core.logdir, 'stdio.log')
config_module._is_daemonized = opts.daemonize config_module._is_daemonized = args.daemonize
sys.stderr = tools.OutputRedirect(logfile, True, opts.quiet) sys.stderr = tools.OutputRedirect(logfile, True, args.quiet)
sys.stdout = tools.OutputRedirect(logfile, False, opts.quiet) sys.stdout = tools.OutputRedirect(logfile, False, args.quiet)
# Handle --quit, --kill and saving the PID to file # Handle --quit, --kill and saving the PID to file
pid_dir = config_module.core.pid_dir pid_dir = config_module.core.pid_dir
if opts.config is None: if args.config is None:
pid_file_path = os.path.join(pid_dir, 'sopel.pid') pid_file_path = os.path.join(pid_dir, 'sopel.pid')
else: else:
basename = os.path.basename(opts.config) basename = os.path.basename(args.config)
if basename.endswith('.cfg'): if basename.endswith('.cfg'):
basename = basename[:-4] basename = basename[:-4]
pid_file_path = os.path.join(pid_dir, 'sopel-%s.pid' % basename) pid_file_path = os.path.join(pid_dir, 'sopel-%s.pid' % basename)
@ -168,28 +193,28 @@ def main(argv=None):
except ValueError: except ValueError:
old_pid = None old_pid = None
if old_pid is not None and tools.check_pid(old_pid): if old_pid is not None and tools.check_pid(old_pid):
if not opts.quit and not opts.kill: if not args.quit and not args.kill:
stderr('There\'s already a Sopel instance running with this config file') stderr('There\'s already a Sopel instance running with this config file')
stderr('Try using the --quit or the --kill options') stderr('Try using the --quit or the --kill options')
sys.exit(1) sys.exit(1)
elif opts.kill: elif args.kill:
stderr('Killing the sopel') stderr('Killing the sopel')
os.kill(old_pid, signal.SIGKILL) os.kill(old_pid, signal.SIGKILL)
sys.exit(0) sys.exit(0)
elif opts.quit: elif args.quit:
stderr('Signaling Sopel to stop gracefully') stderr('Signaling Sopel to stop gracefully')
if hasattr(signal, 'SIGUSR1'): if hasattr(signal, 'SIGUSR1'):
os.kill(old_pid, signal.SIGUSR1) os.kill(old_pid, signal.SIGUSR1)
else: else:
os.kill(old_pid, signal.SIGTERM) os.kill(old_pid, signal.SIGTERM)
sys.exit(0) sys.exit(0)
elif opts.kill or opts.quit: elif args.kill or args.quit:
stderr('Sopel is not running!') stderr('Sopel is not running!')
sys.exit(1) sys.exit(1)
elif opts.quit or opts.kill: elif args.quit or args.kill:
stderr('Sopel is not running!') stderr('Sopel is not running!')
sys.exit(1) sys.exit(1)
if opts.daemonize: if args.daemonize:
child_pid = os.fork() child_pid = os.fork()
if child_pid is not 0: if child_pid is not 0:
sys.exit() sys.exit()

3
sopel
View File

@ -1,5 +1,4 @@
#!/usr/bin/python3 #! /usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
import sys import sys