From fcd3e87793f44c20c92bdadb4c12c4915b7b411e Mon Sep 17 00:00:00 2001 From: luk3yx Date: Sat, 19 Mar 2022 17:12:19 +1300 Subject: Initial public commit --- nickrate.py | 293 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100755 nickrate.py (limited to 'nickrate.py') 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='', 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 ') + + 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) -- cgit v1.2.3