aboutsummaryrefslogtreecommitdiff
path: root/nickrate.py
diff options
context:
space:
mode:
Diffstat (limited to 'nickrate.py')
-rwxr-xr-xnickrate.py293
1 files changed, 293 insertions, 0 deletions
diff --git a/nickrate.py b/nickrate.py
new file mode 100755
index 0000000..ca6c8fc
--- /dev/null
+++ b/nickrate.py
@@ -0,0 +1,293 @@
+#!/usr/bin/env python3
+#
+# Nickname rating system
+#
+# The MIT License (MIT)
+#
+# Copyright © 2019 by luk3yx.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+
+import re
+
+# stolen from http://pi.math.cornell.edu/~mec/2003-2004/cryptography/subs/frequencies.html
+letter_frequencies = {
+ "E": 12.02,
+ "T": 9.10,
+ "A": 8.12,
+ "O": 7.68,
+ "I": 7.31,
+ "N": 6.95,
+ "S": 6.28,
+ "R": 6.02,
+ "H": 5.92,
+ "D": 4.32,
+ "L": 3.98,
+ "U": 2.88,
+ "C": 2.71,
+ "M": 2.61,
+ "F": 2.30,
+ "Y": 2.11,
+ "W": 2.09,
+ "G": 2.03,
+ "P": 1.82,
+ "B": 1.49,
+ "V": 1.11,
+ "K": 0.69,
+ "X": 0.17,
+ "Q": 0.11,
+ "J": 0.10,
+ "Z": 0.07
+}
+default_letter_frequency = 4
+maximum_letter_frequency = 12
+
+def letter_frequency(l):
+ return letter_frequencies.get(l.upper(), default_letter_frequency)
+
+def inverse_letter_freqency(l):
+ return maximum_letter_frequency - letter_frequency(l)
+
+def rate_string(nick):
+ # Don't give a basic rating if the nickname is long
+ if len(nick) > 20:
+ return 0
+
+ # rate name based on letter frequency, divide by the length
+ return sum(map(inverse_letter_freqency, nick.upper()))
+
+def rate_repetition(repetition_length, last_rate):
+ if last_rate == 0 or repetition_length == 0:
+ return 0
+ # squaring the rating to avoid penalizing common double letters like Billy, dividing by 64 to bring it back to earth
+ m = (last_rate**2)/64
+ return repetition_length*m
+
+def repetition_rating(nick):
+ nick = nick.lower()
+ # look for repetition, remove points & remove repetitions for frequency analysis
+ repetition_rating = 0
+
+ # we're catching repetitions between 1 and 4 chars in length
+ # you can decrease this if you think it's looping too much
+ max_rep_len = 4
+
+ nick_len = len(nick)
+
+ # lets not let everyone do enormous loops
+ if nick_len > 20:
+ return 0
+
+ for c in range(1,max_rep_len+1):
+ last_rate = 0
+ repetition_length = 0
+ # accumulator, lets us skip ahead in the loop
+ a = 0
+ for N in range(nick_len):
+ n = N+a
+ nc = n+c
+ ncc = nc+c
+ if ncc > nick_len:
+ repetition_rating += rate_repetition(repetition_length, last_rate)
+ break
+ this_set = nick[n:nc]
+ next_set = nick[nc:ncc]
+ if this_set == next_set:
+ # rate the string here so we have it if we find the next one is not a repetition
+ if repetition_length == 0: # only the first time, each repetition has the same rating...
+ last_rate = rate_string(this_set)
+ a += c - 1
+ repetition_length += 1
+ else:
+ repetition_rating += rate_repetition(repetition_length, last_rate)
+ repetition_length = 0
+ return -round(repetition_rating)
+
+
+def plural(n): return '' if n == 1 else 's'
+
+class BasePointModifier:
+ __slots__ = ('text', 'points')
+
+ def __repr__(self):
+ return '<{} {}, {} point{}>'.format(type(self).__name__,
+ repr(self.text), self.points, plural(self.points))
+
+ def get_raw_description(self):
+ return repr(self.text)
+
+ def get_description(self, raw_points):
+ points = str(raw_points)
+ if not points.startswith('-'):
+ points = '+' + points
+ return '{} point{}: {}'.format(points, plural(raw_points),
+ self.get_raw_description())
+
+ def match(self, nick):
+ raise NotImplementedError('match() not implemented')
+
+ def get_points(self, nick):
+ return self.points if self.match(nick) else 0
+
+ def __init__(self, text, points):
+ self.text = str(text).lower()
+ self.points = points
+
+class PointModifier(BasePointModifier):
+ __slots__ = ('text', 'location')
+
+ def get_raw_description(self):
+ msg = repr(self.text)
+ if self.location == 'start':
+ msg = 'starts with ' + msg
+ elif self.location == 'end':
+ msg = 'ends with ' + msg
+ else:
+ msg = 'contains ' + msg
+ return 'Nickname ' + msg + '.'
+
+ def match(self, nick):
+ nick = nick.lower()
+ if self.location == 'start':
+ return nick.startswith(self.text)
+ elif self.location == 'end':
+ return nick.endswith(self.text)
+
+ return self.text in nick
+
+ def __init__(self, text, points, location=None):
+ super().__init__(text, points)
+ self.location = location
+
+class RegexPointModifier(BasePointModifier):
+ __slots__ = ('regex',)
+ def match(self, nick):
+ return bool(self.regex.search(nick))
+
+ def __init__(self, regex, points):
+ super().__init__(regex, points)
+ self.regex = re.compile(regex, flags=re.IGNORECASE)
+
+class CustomPointModifier(BasePointModifier):
+ __slots__ = ('_func', '_lower')
+
+ def match(self, nick):
+ if self._lower:
+ nick = nick.lower()
+ return bool(self._func(nick))
+
+ def get_raw_description(self):
+ return self.text
+
+ def __init__(self, func, points, description='<function>', lower=True):
+ super().__init__('', points)
+ self.text = description
+ self._func = func
+ self._lower = lower
+
+class DynamicPointModifier(CustomPointModifier):
+ def get_points(self, nick):
+ return self._func(nick)
+
+ def match(self, nick):
+ # return self.get_points(nick) != 0
+ raise NotImplementedError
+
+ def __init__(self, func, text):
+ super().__init__(func, 0, text)
+
+
+things = [
+ DynamicPointModifier(lambda n: round(rate_string(n)/len(n)), "Basic rating."),
+ DynamicPointModifier(repetition_rating, "Repeating letters."),
+ PointModifier('xX', -8, 'start'),
+ PointModifier('Xx', -8, 'end'),
+ PointModifier('pro', -8),
+ PointModifier('hack', -8),
+ PointModifier('hacker', -4),
+ PointModifier('lol', -8),
+ PointModifier('YT', -10),
+ PointModifier('cute', -10),
+ PointModifier('fortnite', -500),
+ PointModifier('owo', -500),
+ PointModifier('uwu', -500),
+ # This is weirdly non-specific, who are you targeting here?
+ RegexPointModifier('^[a-z]+[0-9]{3}(xx)?$', -5),
+ CustomPointModifier(lambda n : len(n) < 4, -15,
+ 'Insanely short nickname.', False),
+ CustomPointModifier(lambda n : len(n) > 14, -8, 'Long nickname.', False),
+ CustomPointModifier(lambda n : len(n) > 20, -15,
+ 'Obnoxiously long nickname.', False),
+ CustomPointModifier(lambda n : not n.isalnum(), -15,
+ 'Non-alphanumeric characters used.', False),
+ CustomPointModifier(lambda n : n == 'plebs', -9000,
+ 'Sharing the nickname of a troll.'),
+ CustomPointModifier(lambda n : n.isupper(), -8,
+ 'Entirely uppercase nickname.', False),
+ CustomPointModifier(lambda n : n in ('lurk', 'lurk3', 'lurk`', 'lurklite',
+ 'father billy'), 9001, "Over 9000.")
+]
+
+things.sort(key=lambda t : t.points)
+things = tuple(things)
+
+def rate_nick(nick):
+ score = 0
+ lnick = nick.lower()
+ reasons = []
+
+ for thing in things:
+ points = thing.get_points(nick)
+ if points != 0:
+ score += points
+ reasons.append(thing.get_description(points))
+
+ return score, reasons
+
+def main():
+ import argparse
+ parser = argparse.ArgumentParser()
+ parser.add_argument('nickname', help='The nickname to rate.')
+ args = parser.parse_args()
+ nick = args.nickname
+ del parser, args
+
+ score, reasons = rate_nick(nick)
+ print('Score for nickname {}: {}'.format(repr(nick), score))
+ for reason in reasons:
+ print(' •', reason)
+
+if __name__ == '__main__':
+ main()
+elif 'register_command' in globals():
+ @register_command('nickrate')
+ def nickrate_cmd(irc, hostmask, is_admin, args):
+ r = hostmask[0] + ('' if hostmask[0].endswith('>') else ':')
+ nick = args[-1].strip()
+ if not nick or nick.startswith('<@'):
+ return irc.msg(args[0],
+ r + ' Invalid syntax! Syntax: .nickrate <nickname>')
+
+ score, reasons = rate_nick(nick)
+ msg = '{} Rating for nickname {}: \x02{} point{}.\x02'.format(r,
+ repr(nick), score, plural(score))
+ if irc.msglen > 1024 and reasons:
+ msg = msg + '\n • ' + '\n • '.join(reasons)
+ irc.msg(args[0], msg)