162 lines
5.2 KiB
Python

#!/usr/bin/env python3
# coding=utf-8
# Sed Module for Drastikbot
#
# Replace text using sed.
#
# This module keeps a buffer of the last posted messages
# and when the substitution command is issued it calls sed
# and sends the result.
#
# Depends:
# - sed :: default unix program, should be in the repos
'''
Copyright (C) 2018 drastik.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import subprocess
import re
from dbot_tools import p_truncate
class Module:
def __init__(self):
self.auto = True
self.manual = {
"desc": (
"Usage: s/regexp/replacement/flags"
" | Try to match a 'regexp' with one of the previous"
" messages posted and replace it with 'replacement'."
" For flags and a detailed explanation see:"
" https://www.gnu.org/software/sed/manual/html_node/"
"The-_0022s_0022-Command.html"
" | Extensions: \"s///-n\" n is the number of matches to skip"
" If the 'number' flag is used -n should be used after it.")
}
def write(varget, varset, channel, msg):
msgdict = varget('msgdict', defval={channel: [msg]})
try:
msgdict[channel].append(msg)
except KeyError:
msgdict.update({channel: [msg]})
if len(msgdict[channel]) > 50:
del msgdict[channel][0]
varset('msgdict', msgdict)
def read(varget, channel):
return varget('msgdict')[channel]
def call_sed(msg, sed_args):
echo = ['echo', msg]
p = subprocess.run(echo, stdout=subprocess.PIPE)
echo_outs = p.stdout
sed = ['sed', '-r', '--sandbox',
f's/{sed_args[1]}/{sed_args[2]}/{sed_args[3]}']
p = subprocess.run(sed, stdout=subprocess.PIPE, input=echo_outs)
return p.stdout.decode('utf-8')
def main(i, irc):
sed_parse = re.compile('(?<!\\\\)/')
sed_cmd = re.compile('^s/.*/.*')
sed_out = ''
if not sed_cmd.match(i.msg):
write(i.varget, i.varset, i.channel, i.msg)
return
sed_args = sed_parse.split(i.msg)
if len(sed_args) < 4:
# check if the last / is missed etc.
return
msglist = read(i.varget, i.channel)
# Extension to allow the user match previous messages.
# It uses the special syntax: "s///-n" where n is the
# number of matches to skip.
# Sideffect: because it matches upto two decimals after -,
# sed string positioning (or other commands
# with decimals in them) should be issued
# before the - command or two characters after
# the - .
# The following code is for parsing the command. It
# edits the "goback" variable which will be used in the
# loop below.
# We use two decimals because the queue save upto 50
# messages.
goback = False
if '-' in sed_args[3]:
s = sed_args[3]
idx = s.index('-')
if len(s[idx:]) > 2 and s[idx + 2].isdecimal():
goback = int(f'{s[idx + 1]}{s[idx + 2]}')
sed_args[3] = f'{s[:idx]}{s[idx + 3:]}'
elif len(s[idx:]) > 1 and s[idx + 1].isdecimal():
goback = int(s[idx + 1])
sed_args[3] = f'{s[:idx]}{s[idx + 2:]}'
n = 1
while n <= 50:
if 'i' in sed_args[3]:
db_search = re.search(sed_args[1], msglist[-n], re.I)
else:
db_search = re.search(sed_args[1], msglist[-n])
if db_search:
if goback:
# Check if the goback command was issued.
goback -= 1
n = n + 1
continue
a = n
break
else:
a = False
n = n + 1
if a:
if '\x01ACTION' in msglist[-a][:7]:
msg_len = irc.var.msg_len - 9 - len(i.channel) - 10 - 2
sed_out = call_sed(msglist[-a][7:], sed_args)
sed_out = sed_out.rstrip('\n').replace('\x01', "").replace('\ca', "")
sed_out = p_truncate(sed_out, msg_len, 98, True)
irc.privmsg(i.channel, f'\x01ACTION {sed_out}')
else:
msg_len = irc.var.msg_len - 9 - len(i.channel) - 2
sed_out = call_sed(msglist[-a], sed_args).strip()
sed_out = sed_out.rstrip('\n').replace('\x01', "").replace('\ca', "")
sed_out = p_truncate(sed_out, msg_len, 98, True)
irc.privmsg(i.channel, sed_out)
if sed_out:
# We try to limit the string saved in the queue to avoid:
# OSError: [Errno 7] Argument list too long: 'echo'
# when calling 'echo' in @call_sed .
# 512 chars are more than enough, since the bot will
# never be able to send a message with that many.
write(i.varget, i.varset, i.channel, sed_out)
# write(dbc, i.channel, i.msg) # save commands