2010-03-27 05:12:26 +00:00
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
2014-01-07 21:41:07 +00:00
|
|
|
|
import math
|
2013-09-20 20:01:54 +00:00
|
|
|
|
import random
|
2010-03-27 05:12:26 +00:00
|
|
|
|
import re
|
2014-01-07 19:17:14 +00:00
|
|
|
|
import threading
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
from util import hook
|
|
|
|
|
|
|
|
|
|
|
2014-05-02 04:45:37 +00:00
|
|
|
|
def sanitize(s):
|
|
|
|
|
return re.sub(r'[\x00-\x1f]', '', s)
|
|
|
|
|
|
|
|
|
|
|
2010-03-27 05:12:26 +00:00
|
|
|
|
@hook.command
|
|
|
|
|
def munge(inp, munge_count=0):
|
|
|
|
|
reps = 0
|
|
|
|
|
for n in xrange(len(inp)):
|
|
|
|
|
rep = character_replacements.get(inp[n])
|
|
|
|
|
if rep:
|
|
|
|
|
inp = inp[:n] + rep.decode('utf8') + inp[n + 1:]
|
|
|
|
|
reps += 1
|
|
|
|
|
if reps == munge_count:
|
|
|
|
|
break
|
|
|
|
|
return inp
|
|
|
|
|
|
2014-01-14 21:12:37 +00:00
|
|
|
|
|
2014-01-07 19:17:14 +00:00
|
|
|
|
class PaginatingWinnower(object):
|
2014-01-14 21:12:37 +00:00
|
|
|
|
|
2014-01-07 19:17:14 +00:00
|
|
|
|
def __init__(self):
|
|
|
|
|
self.lock = threading.Lock()
|
|
|
|
|
self.last_input = []
|
|
|
|
|
self.recent = set()
|
|
|
|
|
|
|
|
|
|
def winnow(self, inputs, limit=400, ordered=False):
|
|
|
|
|
"remove random elements from the list until it's short enough"
|
|
|
|
|
with self.lock:
|
|
|
|
|
# try to remove elements that were *not* removed recently
|
|
|
|
|
inputs_sorted = sorted(inputs)
|
|
|
|
|
if inputs_sorted == self.last_input:
|
|
|
|
|
same_input = True
|
|
|
|
|
else:
|
|
|
|
|
same_input = False
|
|
|
|
|
self.last_input = inputs_sorted
|
|
|
|
|
self.recent.clear()
|
|
|
|
|
|
|
|
|
|
combiner = lambda l: u', '.join(l)
|
|
|
|
|
suffix = ''
|
|
|
|
|
|
|
|
|
|
while len(combiner(inputs)) >= limit:
|
|
|
|
|
if same_input and any(inp in self.recent for inp in inputs):
|
|
|
|
|
if ordered:
|
|
|
|
|
for inp in self.recent:
|
|
|
|
|
if inp in inputs:
|
|
|
|
|
inputs.remove(inp)
|
|
|
|
|
else:
|
2014-01-14 21:12:37 +00:00
|
|
|
|
inputs.remove(
|
|
|
|
|
random.choice([inp for inp in inputs if inp in self.recent]))
|
2014-01-07 19:17:14 +00:00
|
|
|
|
else:
|
|
|
|
|
if ordered:
|
|
|
|
|
inputs.pop()
|
|
|
|
|
else:
|
|
|
|
|
inputs.pop(random.randint(0, len(inputs) - 1))
|
|
|
|
|
suffix = ' ...'
|
|
|
|
|
|
|
|
|
|
self.recent.update(inputs)
|
|
|
|
|
return combiner(inputs) + suffix
|
|
|
|
|
|
|
|
|
|
winnow = PaginatingWinnower().winnow
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
2014-01-14 21:12:37 +00:00
|
|
|
|
|
2010-03-27 05:12:26 +00:00
|
|
|
|
def add_tag(db, chan, nick, subject):
|
|
|
|
|
match = db.execute('select * from tag where lower(nick)=lower(?) and'
|
|
|
|
|
' chan=? and lower(subject)=lower(?)',
|
|
|
|
|
(nick, chan, subject)).fetchall()
|
|
|
|
|
if match:
|
|
|
|
|
return 'already tagged'
|
2010-03-27 08:42:27 +00:00
|
|
|
|
|
2010-03-27 05:12:26 +00:00
|
|
|
|
db.execute('replace into tag(chan, subject, nick) values(?,?,?)',
|
|
|
|
|
(chan, subject, nick))
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
return 'tag added'
|
2010-03-27 08:42:27 +00:00
|
|
|
|
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
def delete_tag(db, chan, nick, del_tag):
|
|
|
|
|
count = db.execute('delete from tag where lower(nick)=lower(?) and'
|
|
|
|
|
' chan=? and lower(subject)=lower(?)',
|
|
|
|
|
(nick, chan, del_tag)).rowcount
|
|
|
|
|
db.commit()
|
2010-03-27 08:42:27 +00:00
|
|
|
|
|
2010-03-27 05:12:26 +00:00
|
|
|
|
if count:
|
|
|
|
|
return 'deleted'
|
|
|
|
|
else:
|
|
|
|
|
return 'tag not found'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tag_counts_by_chan(db, chan):
|
|
|
|
|
tags = db.execute("select subject, count(*) from tag where chan=?"
|
|
|
|
|
" group by lower(subject)"
|
|
|
|
|
" order by lower(subject)", (chan,)).fetchall()
|
|
|
|
|
|
|
|
|
|
tags.sort(key=lambda x: x[1], reverse=True)
|
|
|
|
|
if not tags:
|
|
|
|
|
return 'no tags in %s' % chan
|
2014-01-07 19:17:14 +00:00
|
|
|
|
return winnow(['%s (%d)' % row for row in tags], ordered=True)
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_tags_by_nick(db, chan, nick):
|
2014-01-07 19:17:14 +00:00
|
|
|
|
tags = db.execute("select subject from tag where lower(nick)=lower(?)"
|
2010-03-27 05:12:26 +00:00
|
|
|
|
" and chan=?"
|
|
|
|
|
" order by lower(subject)", (nick, chan)).fetchall()
|
2014-01-07 19:17:14 +00:00
|
|
|
|
if tags:
|
|
|
|
|
return 'tags for "%s": ' % munge(nick, 1) + winnow([
|
2014-01-14 21:12:37 +00:00
|
|
|
|
tag[0] for tag in tags])
|
2014-01-07 19:17:14 +00:00
|
|
|
|
else:
|
|
|
|
|
return ''
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
|
2013-11-20 00:27:25 +00:00
|
|
|
|
def get_nicks_by_tagset(db, chan, tagset):
|
|
|
|
|
nicks = None
|
|
|
|
|
for tag in tagset.split('&'):
|
|
|
|
|
tag = tag.strip()
|
2010-03-27 08:42:27 +00:00
|
|
|
|
|
2013-11-20 00:27:25 +00:00
|
|
|
|
current_nicks = db.execute("select nick from tag where " +
|
|
|
|
|
"lower(subject)=lower(?)"
|
|
|
|
|
" and chan=?", (tag, chan)).fetchall()
|
|
|
|
|
|
|
|
|
|
if not current_nicks:
|
|
|
|
|
return "tag '%s' not found" % tag
|
|
|
|
|
|
|
|
|
|
if nicks is None:
|
|
|
|
|
nicks = set(current_nicks)
|
|
|
|
|
else:
|
|
|
|
|
nicks.intersection_update(current_nicks)
|
|
|
|
|
|
|
|
|
|
nicks = [munge(x[0], 1) for x in sorted(nicks)]
|
2010-03-27 05:12:26 +00:00
|
|
|
|
if not nicks:
|
2014-01-07 21:41:07 +00:00
|
|
|
|
return 'no nicks found with tags "%s"' % tagset
|
2013-11-20 00:27:25 +00:00
|
|
|
|
return 'nicks tagged "%s": ' % tagset + winnow(nicks)
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@hook.command
|
|
|
|
|
def tag(inp, chan='', db=None):
|
2014-04-18 05:27:15 +00:00
|
|
|
|
'.tag <nick> <tag> -- marks <nick> as <tag> {related: .untag, .tags, .tagged, .is}'
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
db.execute('create table if not exists tag(chan, subject, nick)')
|
|
|
|
|
|
2014-01-14 01:38:42 +00:00
|
|
|
|
add = re.match(r'(\S+) (.+)', inp)
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
if add:
|
|
|
|
|
nick, subject = add.groups()
|
2014-01-30 22:53:54 +00:00
|
|
|
|
if nick.lower() == 'list':
|
|
|
|
|
return 'tag syntax has changed. try .tags or .tagged instead'
|
|
|
|
|
elif nick.lower() == 'del':
|
|
|
|
|
return 'tag syntax has changed. try ".untag %s" instead' % subject
|
2014-05-02 04:45:37 +00:00
|
|
|
|
return add_tag(db, chan, sanitize(nick), sanitize(subject))
|
2010-03-27 05:12:26 +00:00
|
|
|
|
else:
|
|
|
|
|
tags = get_tags_by_nick(db, chan, inp)
|
2014-01-07 19:17:14 +00:00
|
|
|
|
if tags:
|
|
|
|
|
return tags
|
2010-03-27 05:12:26 +00:00
|
|
|
|
else:
|
2014-01-07 19:17:14 +00:00
|
|
|
|
return tag.__doc__
|
|
|
|
|
|
2014-01-14 21:12:37 +00:00
|
|
|
|
|
2014-01-14 01:38:42 +00:00
|
|
|
|
@hook.command
|
|
|
|
|
def untag(inp, chan='', db=None):
|
2014-04-18 05:27:15 +00:00
|
|
|
|
'.untag <nick> <tag> -- unmarks <nick> as <tag> {related: .tag, .tags, .tagged, .is}'
|
2014-01-14 01:38:42 +00:00
|
|
|
|
|
|
|
|
|
delete = re.match(r'(\S+) (.+)$', inp)
|
|
|
|
|
|
|
|
|
|
if delete:
|
|
|
|
|
nick, del_tag = delete.groups()
|
|
|
|
|
return delete_tag(db, chan, nick, del_tag)
|
|
|
|
|
else:
|
|
|
|
|
return untag.__doc__
|
|
|
|
|
|
2014-01-14 21:12:37 +00:00
|
|
|
|
|
2014-01-07 19:17:14 +00:00
|
|
|
|
@hook.command
|
|
|
|
|
def tags(inp, chan='', db=None):
|
2014-04-18 05:27:15 +00:00
|
|
|
|
'.tags <nick>/list -- get list of tags for <nick>, or a list of tags {related: .tag, .untag, .tagged, .is}'
|
2014-01-07 19:17:14 +00:00
|
|
|
|
if inp == 'list':
|
|
|
|
|
return get_tag_counts_by_chan(db, chan)
|
|
|
|
|
|
|
|
|
|
tags = get_tags_by_nick(db, chan, inp)
|
|
|
|
|
if tags:
|
|
|
|
|
return tags
|
|
|
|
|
else:
|
|
|
|
|
return get_nicks_by_tagset(db, chan, inp)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@hook.command
|
|
|
|
|
def tagged(inp, chan='', db=None):
|
2014-04-18 05:27:15 +00:00
|
|
|
|
'.tagged <tag> [& tag...] -- get nicks marked as <tag> (separate multiple tags with &) {related: .tag, .untag, .tags, .is}'
|
2014-01-07 19:17:14 +00:00
|
|
|
|
|
|
|
|
|
return get_nicks_by_tagset(db, chan, inp)
|
2010-03-27 08:42:27 +00:00
|
|
|
|
|
2014-04-18 05:27:15 +00:00
|
|
|
|
@hook.command('is')
|
2014-02-19 04:30:23 +00:00
|
|
|
|
def is_tagged(inp, chan='', db=None):
|
2014-04-18 05:27:15 +00:00
|
|
|
|
'.is <nick> <tag> -- checks if <nick> has been marked as <tag> {related: .tag, .untag, .tags, .tagged}'
|
2014-02-19 04:30:23 +00:00
|
|
|
|
|
|
|
|
|
args = re.match(r'(\S+) (.+)$', inp)
|
|
|
|
|
|
|
|
|
|
if args:
|
|
|
|
|
nick, tag = args.groups()
|
2014-04-18 05:27:15 +00:00
|
|
|
|
found = db.execute("select 1 from tag"
|
|
|
|
|
" where lower(nick)=lower(?)"
|
|
|
|
|
" and lower(subject)=lower(?)"
|
|
|
|
|
" and chan=?", (nick, tag, chan)).fetchone()
|
|
|
|
|
if found:
|
|
|
|
|
return 'yes'
|
|
|
|
|
else:
|
|
|
|
|
return 'no'
|
2014-02-19 04:30:23 +00:00
|
|
|
|
else:
|
|
|
|
|
return is_tagged.__doc__
|
2014-01-14 21:12:37 +00:00
|
|
|
|
|
2014-01-07 21:41:07 +00:00
|
|
|
|
def distance(lat1, lon1, lat2, lon2):
|
|
|
|
|
deg_to_rad = math.pi / 180
|
|
|
|
|
lat1 *= deg_to_rad
|
|
|
|
|
lat2 *= deg_to_rad
|
|
|
|
|
lon1 *= deg_to_rad
|
|
|
|
|
lon2 *= deg_to_rad
|
|
|
|
|
|
2014-01-14 21:12:37 +00:00
|
|
|
|
R = 6371 # km
|
|
|
|
|
d = math.acos(math.sin(lat1) * math.sin(lat2) +
|
|
|
|
|
math.cos(lat1) * math.cos(lat2) *
|
|
|
|
|
math.cos(lon2 - lon1)) * R
|
2014-01-07 21:41:07 +00:00
|
|
|
|
return d
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@hook.command(autohelp=False)
|
|
|
|
|
def near(inp, nick='', chan='', db=None):
|
|
|
|
|
try:
|
2014-01-14 21:12:37 +00:00
|
|
|
|
loc = db.execute("select lat, lon from location where chan=? and nick=lower(?)",
|
|
|
|
|
(chan, nick)).fetchone()
|
2014-01-07 21:41:07 +00:00
|
|
|
|
except db.OperationError:
|
|
|
|
|
loc = None
|
|
|
|
|
|
|
|
|
|
if loc is None:
|
|
|
|
|
return 'use .weather <loc> first to set your location'
|
|
|
|
|
|
|
|
|
|
lat, lon = loc
|
|
|
|
|
|
|
|
|
|
db.create_function('distance', 4, distance)
|
|
|
|
|
nearby = db.execute("select nick, distance(lat, lon, ?, ?) as dist from location where chan=?"
|
2014-01-14 21:12:37 +00:00
|
|
|
|
" and nick != lower(?) order by dist limit 20", (lat, lon, chan, nick)).fetchall()
|
2014-01-14 21:01:56 +00:00
|
|
|
|
|
2014-01-17 21:37:09 +00:00
|
|
|
|
in_miles = 'mi' in inp.lower()
|
|
|
|
|
|
2014-01-14 21:01:56 +00:00
|
|
|
|
out = '(km) '
|
2014-01-17 21:37:09 +00:00
|
|
|
|
factor = 1.0
|
|
|
|
|
if in_miles:
|
|
|
|
|
out = '(mi) '
|
|
|
|
|
factor = 0.621
|
|
|
|
|
|
2014-01-14 21:01:56 +00:00
|
|
|
|
while nearby and len(out) < 200:
|
|
|
|
|
nick, dist = nearby.pop(0)
|
2014-01-17 21:37:09 +00:00
|
|
|
|
out += '%s:%.0f ' % (munge(nick, 1), dist * factor)
|
2014-01-07 21:41:07 +00:00
|
|
|
|
|
2014-01-14 21:01:56 +00:00
|
|
|
|
return out
|
2014-01-07 21:41:07 +00:00
|
|
|
|
|
2010-03-27 05:12:26 +00:00
|
|
|
|
|
|
|
|
|
character_replacements = {
|
|
|
|
|
'a': 'ä',
|
|
|
|
|
# 'b': 'Б',
|
|
|
|
|
'c': 'ċ',
|
|
|
|
|
'd': 'đ',
|
|
|
|
|
'e': 'ë',
|
|
|
|
|
'f': 'ƒ',
|
|
|
|
|
'g': 'ġ',
|
|
|
|
|
'h': 'ħ',
|
|
|
|
|
'i': 'í',
|
|
|
|
|
'j': 'ĵ',
|
|
|
|
|
'k': 'ķ',
|
|
|
|
|
'l': 'ĺ',
|
|
|
|
|
# 'm': 'ṁ',
|
|
|
|
|
'n': 'ñ',
|
|
|
|
|
'o': 'ö',
|
|
|
|
|
'p': 'ρ',
|
|
|
|
|
# 'q': 'ʠ',
|
|
|
|
|
'r': 'ŗ',
|
|
|
|
|
's': 'š',
|
|
|
|
|
't': 'ţ',
|
|
|
|
|
'u': 'ü',
|
|
|
|
|
# 'v': '',
|
|
|
|
|
'w': 'ω',
|
|
|
|
|
'x': 'χ',
|
|
|
|
|
'y': 'ÿ',
|
|
|
|
|
'z': 'ź',
|
|
|
|
|
'A': 'Å',
|
|
|
|
|
'B': 'Β',
|
|
|
|
|
'C': 'Ç',
|
|
|
|
|
'D': 'Ď',
|
|
|
|
|
'E': 'Ē',
|
|
|
|
|
# 'F': 'Ḟ',
|
|
|
|
|
'G': 'Ġ',
|
|
|
|
|
'H': 'Ħ',
|
|
|
|
|
'I': 'Í',
|
|
|
|
|
'J': 'Ĵ',
|
|
|
|
|
'K': 'Ķ',
|
|
|
|
|
'L': 'Ĺ',
|
|
|
|
|
'M': 'Μ',
|
|
|
|
|
'N': 'Ν',
|
|
|
|
|
'O': 'Ö',
|
|
|
|
|
'P': 'Р',
|
|
|
|
|
# 'Q': 'Q',
|
|
|
|
|
'R': 'Ŗ',
|
|
|
|
|
'S': 'Š',
|
|
|
|
|
'T': 'Ţ',
|
|
|
|
|
'U': 'Ů',
|
|
|
|
|
# 'V': 'Ṿ',
|
|
|
|
|
'W': 'Ŵ',
|
|
|
|
|
'X': 'Χ',
|
|
|
|
|
'Y': 'Ỳ',
|
|
|
|
|
'Z': 'Ż'}
|