140 lines
4.9 KiB
Python
140 lines
4.9 KiB
Python
|
# 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 tools import Identifier, SopelMemory
|
||
|
from module import rule, priority
|
||
|
from 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: re.sub(rest[0], rest[1], s, 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)
|