iranzo/stampython

View on GitHub
stampy/plugin/karma.py

Summary

Maintainability
D
3 days
Test Coverage
#!/usr/bin/env python
# encoding: utf-8
#
# Description: Plugin for processing karma commands
# Author: Pablo Iranzo Gomez (Pablo.Iranzo@gmail.com)

import datetime
import logging

from prettytable import from_db_cursor

import stampy.plugin.alias
import stampy.plugin.config
import stampy.stampy
from stampy.i18n import _
from stampy.i18n import _L


def init():
    """
    Initializes module
    :return: List of triggers for plugin
    """

    triggers = ["++", "--", u"—", "@", "^rank", "^srank", "^skarma", "==", "!="]

    stampy.stampy.cronme(name="karma", interval=24 * 60)

    return triggers


def cron():
    """
    Function to be executed periodically
    :return:
    """

    dokarmacleanup()


def run(message):  # do not edit this line
    """
    Executes plugin
    :param message: message to run against
    :return:
    """
    text = stampy.stampy.getmsgdetail(message)["text"]
    if text:
        karmacommands(message)
        karmawords(message)
    return


def help(message):  # do not edit this line
    """
    Returns help for plugin
    :param message: message to process
    :return: help text
    """
    commandtext = _("Use `word++` or `word--` to increment or decrement karma, a new message will be sent providing the new total\n\n")
    commandtext += _("Reply to a message with `++`, `--` to give user karma to telegram alias or `==` to repeat karma to the same words or `!=` to invert it\n")
    commandtext += _("Use `rank <word>` or `rank` to get value for actual word or top 10 rankings\n")
    commandtext += _("Use `srank <word>` to search for similar words already ranked\n\n")
    if stampy.stampy.is_owner(message):
        commandtext += _("Use `skarma <word>=<value>` to establish karma of a word\n\n")
        commandtext += _("Use `skarma purge` to clear old karma\n\n")

    return commandtext


def karmacommands(message):
    """
    Finds for commands affecting karma in messages
    :param message: message to process
    :return:
    """

    logger = logging.getLogger(__name__)

    msgdetail = stampy.stampy.getmsgdetail(message)

    texto = msgdetail["text"]
    chat_id = msgdetail["chat_id"]
    message_id = msgdetail["message_id"]

    # Process lines for commands in the first
    # word of the line (Telegram commands)
    word = texto.split()[0].lower()
    commandtext = False

    # Check first word for commands
    for case in stampy.stampy.Switch(word):
        if case('rank'):
            try:
                word = texto.split()[1]
            except:
                word = False
            commandtext = rank(word, gid=stampy.stampy.geteffectivegid(gid=chat_id))
            break
        if case('srank'):
            try:
                word = texto.split()[1]
            except:
                word = False
            commandtext = srank(word, gid=stampy.stampy.geteffectivegid(gid=chat_id))
            break
        if case('skarma'):
            try:
                word = texto.split()[1]
            except:
                word = False
            if "=" in word:
                key = stampy.plugin.alias.getalias(word=word.split('=')[0], gid=stampy.stampy.geteffectivegid(gid=chat_id))
                value = texto.split('=')[1:][0]
                text = _("Setting karma for `%s` to `%s`") % (key, value)
                stampy.stampy.sendmessage(chat_id=chat_id, text=text,
                                          reply_to_message_id=message_id,
                                          disable_web_page_preview=True,
                                          parse_mode="Markdown")
                putkarma(word=key, value=value, gid=stampy.stampy.geteffectivegid(gid=chat_id))
            elif word == "purge":
                dokarmacleanup()
            break
        if case():
            commandtext = False

    # If any of above cases did a match, send command
    if commandtext:
        stampy.stampy.sendmessage(chat_id=chat_id, text=commandtext,
                                  reply_to_message_id=message_id,
                                  parse_mode="Markdown")
        logger.debug(msg="karmacommand:  %s" % word)
    return


def rank(word=False, gid=0):
    """
    Outputs rank for word or top 10
    :param gid: Group ID to filter
    :param word: word to return rank for
    :return:
    """

    logger = logging.getLogger(__name__)
    if word:

        # if word is provided, return the rank value for that word

        if stampy.plugin.alias.getalias(word, gid=gid):
            word = stampy.plugin.alias.getalias(word, gid=gid)

        string = (word, gid)
        sql = "SELECT word,value,date FROM karma WHERE word='%s' and gid='%s';" % string
        cur = stampy.stampy.dbsql(sql)
        value = cur.fetchone()

        try:
            # Get value from SQL query
            value = value[1]

        except:
            # Value didn't exist before, return 0 value
            value = 0
        text = _("`%s` has `%s` karma points.") % (word, value)

    else:
        # if word is not provided, return top 10 words with top karma
        sql = "select word,value,date from karma where gid='%s' ORDER BY value DESC LIMIT 10;" % gid

        text = _("Global rankings:\n")
        cur = stampy.stampy.dbsql(sql)
        table = from_db_cursor(cur)
        text = "%s\n```%s```" % (text, table.get_string())
    logger.debug(msg=_L("Returning karma %s for word %s") % (text, word))
    return text


def srank(word=False, gid=0):
    """
    Search for rank for word
    :param gid: Group ID to filter
    :param word: word to search in database
    :return: table with the values for srank
    """
    logger = logging.getLogger(__name__)
    if stampy.plugin.alias.getalias(word, gid=gid):
        word = stampy.plugin.alias.getalias(word, gid=gid)
    text = ""
    if word is False:
        # If no word is provided to srank, call rank instead
        text = rank(word)
    else:
        string = "%" + "%s" % word + "%"
        sql = "SELECT word,value,date FROM karma WHERE word LIKE '%s' AND gid='%s' LIMIT 10;" % (string, gid)
        cur = stampy.stampy.dbsql(sql)
        table = from_db_cursor(cur)
        text = "%s\n```%s```" % (text, table.get_string())
    logger.debug(msg=_L("Returning srank for word: %s") % word)
    return text


def updatekarma(word=False, change=0, gid=0):
    """
    Updates karma for a word
    :param gid: Group ID to filter
    :param word:  Word to update
    :param change:  Change in karma
    :return:
    """

    logger = logging.getLogger(__name__)
    value = getkarma(word=word, gid=gid) + change
    putkarma(word, value, gid=gid)
    logger.debug(msg=_L("Putting karma of %s to %s") % (value, word))
    return value


def getkarma(word, gid=0):
    """
    Gets karma for a word
    :param gid: Group ID to filter
    :param word: word to get karma for
    :return: karma of given word
    """

    logger = logging.getLogger(__name__)
    string = (word, gid)
    sql = "SELECT word,value FROM karma WHERE word='%s' AND gid='%s';" % string
    cur = stampy.stampy.dbsql(sql)
    value = cur.fetchone()

    try:
        # Get value from SQL query
        value = int(value[1])

    except:
        # Value didn't exist before, return 0
        value = 0
    logger.debug(msg=_L("Getting karma for %s: %s") % (word, value))
    return value


def putkarma(word, value, gid=0):
    """
    Updates value of karma for a word
    :param gid: Group ID to filter
    :param word: Word to update
    :param value: Value of karma to set
    :return: sql execution
    """

    logger = logging.getLogger(__name__)
    date = datetime.datetime.now()
    datefor = date.strftime('%Y-%m-%d %H:%M:%S')

    sql = "DELETE FROM karma WHERE word = '%s' AND gid='%s';" % (word, gid)
    stampy.stampy.dbsql(sql)
    if value != 0:
        sql = "INSERT INTO karma(word,value,date,gid) VALUES('%s','%s', '%s', '%s');" % (word, value, datefor, gid)
        stampy.stampy.dbsql(sql)

    logger.debug(msg=_L("Putting karma of %s to %s") % (value, word))
    return


def stampyphant(chat_id="", karma=0):
    """
    Returns a sticker for big karma values
    :param chat_id:
    :param karma:
    :return:
    """

    logger = logging.getLogger(__name__)
    karma = "%s" % karma
    # Sticker definitions for each rank
    x00 = "BQADBAADYwAD17FYAAEidrCCUFH7AgI"
    x000 = "BQADBAADZQAD17FYAAEeeRNtkOWfBAI"
    x0000 = "BQADBAADZwAD17FYAAHHuNL2oLuShwI"
    x00000 = "BQADBAADaQAD17FYAAHzIBRZeY4uNAI"

    sticker = ""
    if karma[-5:] == "00000":
        sticker = x00000
    elif karma[-4:] == "0000":
        sticker = x0000
    elif karma[-3:] == "000":
        sticker = x000
    elif karma[-2:] == "00":
        sticker = x00

    text = _("Sticker for %s karma points") % karma

    if sticker != "":
        stampy.stampy.sendsticker(chat_id=chat_id, sticker=sticker, text="%s" % text)
        logger.debug(msg=text)
    return


def karmawords(message):
    """
    Finds for commands affecting karma in messages
    :param message: message to process
    :return:
    """
    karmaprocess(stampy.stampy.getmsgdetail(message))
    return


def karmaprocess(msgdetail):
    """
    Processes karma operators in text
    :param msgdetail: message details as per getmsgdetail
    :return:
    """

    logger = logging.getLogger(__name__)

    texto = msgdetail["text"]
    chat_id = msgdetail["chat_id"]
    message_id = msgdetail["message_id"]
    who_un = msgdetail["who_un"]
    who_gn = msgdetail["who_gn"]
    who_id = msgdetail["who_id"]
    who_ln = msgdetail["who_ln"]
    gid = stampy.stampy.geteffectivegid(gid=chat_id)

    # Define dictionary for text replacements
    dictionary = {
        "'": "",
        "@": "",
        "\n": " ",
        u"—": "--"
    }

    if not msgdetail["error"] and texto:
        # Search for telegram commands and if any disable text processing
        text_to_process = stampy.stampy.replace_all(texto,
                                                    dictionary).lower().split(
            " ")
    else:
        text_to_process = ""

    logger.debug(msg=_L("Text to process: %s") % " ".join(text_to_process))

    wordadd = []
    worddel = []

    # Pre-process text for "==" and "!="
    if msgdetail['replytotext']:
        if "==" in " ".join(text_to_process):
            newtext = stampy.stampy.replace_all(msgdetail['replytotext'], dictionary).lower().split(" ")
            text_to_process.extend(newtext)
        elif "!=" in " ".join(text_to_process):
            newdictionary = {
                "++": "+-",
                "--": "-+"
            }

            newtext = stampy.stampy.replace_all(msgdetail['replytotext'], newdictionary).lower().split(" ")

            # Finish inversion
            newdictionary = {
                "+-": "--",
                "-+": "++"
            }

            newtext = stampy.stampy.replace_all(" ".join(newtext), newdictionary).lower().split(" ")
            text_to_process = newtext

    # If operators are not there, exit faster
    if "--" in " ".join(text_to_process) or "++" in " ".join(text_to_process):
        logger.debug(msg=_L("Text has karma operators"))
        for word in text_to_process:
            if "++" in word or "--" in word:
                msg = _("Processing word %s sent by id %s with username %s (%s %s)") % (
                    word, who_id, who_un, who_gn, who_ln)
                logger.debug(msg)

                # This should help to avoid duplicate karma operations
                # in the same message

                oper = False

                if len(word) >= 4:
                    oper = word[-2:]
                    word = word[:-2]
                else:
                    # In case we've replied ++ or -- to a message,
                    # find username for it and add karma

                    if len(word) == 2:
                        oper = word[-2:]
                        try:
                            word = msgdetail["replyto"].lower()
                        except:
                            word = False

                if word and oper:
                    if stampy.plugin.alias.getalias(word, gid=gid):
                        word = stampy.plugin.alias.getalias(word, gid=gid).split(" ")
                    for item in word:
                        if stampy.plugin.alias.getalias(item, gid=gid):
                            item = stampy.plugin.alias.getalias(item, gid=gid)
                        if oper == "++" and item not in wordadd:
                            wordadd.append(item)
                        if oper == "--" and item not in worddel:
                            worddel.append(item)

    messagetext = ""
    for word in wordadd + worddel:
        change = 0
        oper = False
        if word in wordadd:
            change += 1
            oper = "++"
        if word in worddel:
            change -= 1
            oper = "--"

        if change != 0:
            msg = _("%s Found in %s at %s with id %s (%s), sent by id %s named %s (%s %s)") % (oper, word, chat_id, message_id, msgdetail["chat_name"], who_id, who_un, who_gn, who_ln)
            logger.debug(msg)

            karma = updatekarma(word=word, change=change, gid=stampy.stampy.geteffectivegid(gid=chat_id))
            if karma != 0:
                # Karma has changed, report back
                text = _("`%s` now has `%s` karma points.") % (word, karma)
            else:
                # New karma is 0
                text = _("`%s` now has no Karma and has been garbage collected.") % word

            # Send originating user for karma change a reply with
            # the new value

            modulo = int(stampy.plugin.config.gconfig(key="modulo", default="1", gid=chat_id))

            if modulo != 0:
                if karma % modulo == 0:
                    messagetext = messagetext + "\n" + text
            stampyphant(chat_id=chat_id, karma=karma)

    if messagetext != "":
        stampy.stampy.sendmessage(chat_id=chat_id, text=messagetext, reply_to_message_id=message_id, parse_mode="Markdown")

    return


def dokarmacleanup(word=False, maxage=int(stampy.plugin.config.config("maxage", default=180))):
    """
    Checks on the karma database the date of the last update in the word
    :param word: word to query in database
    :param maxage: defines maximum number of days to allow karma to be inactive
    """

    logger = logging.getLogger(__name__)

    if word:
        sql = "SELECT word,value,date FROM karma WHERE word=%s" % word
    else:
        sql = "SELECT word,value,date,gid FROM karma"

    words = []
    cur = stampy.stampy.dbsql(sql)

    logger.debug(msg=_L("Processing words for cleanup: %s") % words)

    for row in cur:
        # Process each word returned
        word = row[0]
        date = row[2]
        gid = row[3]

        if date and (date != "False"):
            worddate = datetime.datetime.strptime(date, '%Y-%m-%d %H:%M:%S')
        else:
            worddate = datetime.datetime.now()

        now = datetime.datetime.now()

        if (now - worddate).days > maxage:
            logger.debug(msg=_L("Word %s with %s inactivity days is going to be purged") % (word, (now - worddate).days))
            words.append({'word': word, 'gid': gid})

    for old in words:
        # Remove word from database based on prior loop
        putkarma(word=old['word'], value=0, gid=old['gid'])
    return