Beispiel #1
0
def getPlugins():
    """
    Gets list of plugins in the plugins folder
    :return: list of plugins available
    """

    __name__ = 'stampy.stampy.plugins'
    logger = logging.getLogger(__name__)
    plugins = []

    possibleplugins = os.listdir(PluginFolder)
    for i in possibleplugins:
        if i != "__init__.py" and os.path.splitext(i)[1] == ".py":
            i = os.path.splitext(i)[0]
        try:
            info = imp.find_module(i, [PluginFolder])
        except:
            info = False
        if i and info:
            if i not in plugin.config.config(key='disabled_plugins',
                                             default=''):
                logger.debug(msg=_L("Plugin added: %s") % i)
                plugins.append({"name": i, "info": info})
            else:
                logger.debug(msg=_L("Plugin disabled: %s") % i)

    return plugins
Beispiel #2
0
def sendimage(chat_id=0, image="", text="", reply_to_message_id=""):
    """
    Sends an image to chat_id as a reply to a message received
    :param chat_id: ID of the chat
    :param image: image URI
    :param text: Additional text or caption
    :param reply_to_message_id:
    :return:
    """

    logger = logging.getLogger(__name__)

    if not image:
        return False

    url = "%s%s/sendPhoto" % (plugin.config.config(key='url'),
                              plugin.config.config(key='token'))
    payload = {'chat_id': chat_id}

    if reply_to_message_id:
        payload['reply_to_message_id'] = reply_to_message_id
    if text:
        payload['caption'] = text.encode('utf-8')

    logger.debug(msg=_L("Sending image: %s") % text)

    # Download image first to later send it
    rawimage = requests.get(image, stream=True)
    sent = False

    if rawimage.status_code == 200:
        rawimage.raw.decode_content = True

        # Send image
        files = {'photo': rawimage.raw}

        # It this is executed as per unit testing, skip sending message
        UTdisable = not plugin.config.config(key='unittest', default=False)
        Silent = not plugin.config.gconfig(
            key='silent', default=False, gid=geteffectivegid(gid=chat_id))

        try:
            if UTdisable and Silent:
                output = requests.post(url, files=files, data=payload)
                sent = {"message": json.loads(output.text)['result']}
            else:
                sent = False
        except:
            logger.debug(msg=_L("Failure sending image: %s") % image)
            sent = False
    else:
        logger.debug(msg=_L("Failure downloading image: %s") % image)

    # Check if there's something to forward and do it
    plugin.forward.forwardmessage(sent)

    return sent
Beispiel #3
0
def getme():
    """
    Gets bot user
    :return: returns the items obtained
    """

    logger = logging.getLogger(__name__)

    if not plugin.config.config(key='myself', default=False):

        url = "%s%s/getMe" % (plugin.config.config(key='url'),
                              plugin.config.config(key='token'))
        message = "%s" % url
        try:
            result = json.load(urllib.urlopen(message))['result']['username']
        except:
            result = 'stampy'

        plugin.config.setconfig(key='myself', value=result)

    else:
        result = plugin.config.config(key='myself')

    logger.info(msg=_L("Getting bot details and returning: %s") % result)
    return result
Beispiel #4
0
def getitems(var):
    """
    Returns list of items even if provided args are lists of lists
    :param var: list or value to pass
    :return: unique list of values
    """

    logger = logging.getLogger(__name__)

    result = []
    if not isinstance(var, list):
        result.append(var)
    else:
        for elem in var:
            result.extend(getitems(elem))

    # Do cleanup of duplicates
    final = []
    for elem in result:
        if elem not in final:
            final.append(elem)

    # As we call recursively, don't log calls for just one ID
    if len(final) > 1:
        logger.debug(msg=_L("Final deduplicated list: %s") % final)
    return final
Beispiel #5
0
def sendsticker(chat_id=0, sticker="", text="", reply_to_message_id=""):
    """
    Sends a sticker to chat_id as a reply to a message received
    :param chat_id: ID of the chat
    :param sticker: Sticker identification
    :param text: Additional text
    :param reply_to_message_id:
    :return:
    """

    logger = logging.getLogger(__name__)
    url = "%s%s/sendSticker" % (plugin.config.config(key='url'),
                                plugin.config.config(key='token'))
    message = "%s?chat_id=%s" % (url, chat_id)
    message = "%s&sticker=%s" % (message, sticker)
    if reply_to_message_id:
        message += "&reply_to_message_id=%s" % reply_to_message_id
    logger.debug(msg=_L("Sending sticker: %s") % text)

    # It this is executed as per unit testing, skip sending message
    UTdisable = not plugin.config.config(key='unittest', default=False)
    Silent = not plugin.config.gconfig(
        key='silent', default=False, gid=geteffectivegid(gid=chat_id))
    if UTdisable and Silent:
        sent = {"message": json.load(urllib.urlopen(message))['result']}
    else:
        sent = False

    # Check if there's something to forward and do it
    plugin.forward.forwardmessage(sent)

    return
Beispiel #6
0
def is_owner_or_admin(message, strict=False):
    """
    Check if user is owner or admin for group
    :param strict: Defines if we target the actual gid, not effective
    :param message: message to check
    :return: True on owner or admin
    """

    logger = logging.getLogger(__name__)
    admin = False
    owner = False
    msgdetail = getmsgdetail(message)
    if strict:
        chat_id = msgdetail["chat_id"]
    else:
        chat_id = geteffectivegid(msgdetail["chat_id"])

    # if we're on a user private chat, return admin true
    if chat_id > 0:
        admin = True
        logger.debug(msg=_L("We're admin of private chats"))
    else:
        # Check if we're owner
        owner = is_owner(message)
        if not owner:
            logger.debug(msg=_L("We're not owner of public chats"))
            # Check if we are admin of chat
            for each in plugin.config.config(key='admin',
                                             default="",
                                             gid=chat_id).split(" "):
                if each == msgdetail["who_un"]:
                    admin = True
                    logger.debug(msg=_L("We're admin of public chat"))

            # If we're not admin and admin is empty, consider ourselves admin
            if not admin:
                if plugin.config.config(key='admin', gid=chat_id,
                                        default="") == "":
                    logger.debug(msg=_L(
                        "We're admin because no admin listed on public chat"))
                    admin = True

    return owner or admin
Beispiel #7
0
def shouldrun(name):
    """
    Checks name on database to see if it should run or not and updates as executed
    :param name: Name to check on database
    :return: Bool
    """

    sql = "SELECT name,lastchecked,interval from cron where name='%s'" % name
    cur = dbsql(sql)

    date = utize(datetime.datetime.now())

    # Formatted date to write back in database
    datefor = date.strftime('%Y/%m/%d %H:%M:%S')

    # Convert to epoch to properly compare
    dateforts = time.mktime(date.timetuple())

    code = False

    for row in cur:
        (name, lastchecked, interval) = row

        try:
            # Parse date or if in error, use past
            datelast = utize(dateutil.parser.parse(lastchecked))

        except:
            datelast = utize(datetime.datetime(year=1981, month=1, day=24))

        # Get time since last check on the feed (epoch)
        datelastts = time.mktime(datelast.timetuple())
        timediff = int((dateforts - datelastts) / 60)

        # Check if interval is defined or set default
        interval = int(interval)

        if interval == 0:
            interval = 1440

        # If more time has passed since last check than the interval for
        # checks, run the check

        if timediff < interval:
            code = False
        else:
            code = True

    # Update db with results
    if code:
        sql = "UPDATE cron SET lastchecked='%s' where name='%s'" % (datefor,
                                                                    name)
        logger.debug(msg=_L("Updating last checked as per %s") % sql)
        dbsql(sql=sql)
    return code
Beispiel #8
0
def initplugins():
    """
    Initializes plugins
    :return: list of plugin modules initialized
    """

    __name__ = 'stampy.stampy.plugins'
    logger = logging.getLogger(__name__)

    plugs = []
    plugtriggers = {}
    for i in getPlugins():
        logger.debug(msg=_L("Processing plugin initialization: %s") %
                     i["name"])
        newplug = loadPlugin(i)
        plugs.append(newplug)
        triggers = []
        for each in newplug.init():
            triggers.append(each)
        plugtriggers[i["name"]] = triggers
        logger.debug(msg=_L("Plugin %s is triggered by %s") %
                     (i["name"], triggers))
    return plugs, plugtriggers
Beispiel #9
0
def processcron():
    """
    This function processes plugins with cron features
    """

    logger = logging.getLogger(__name__)

    # Call plugins to process message
    global plugs
    global plugtriggers

    for i in plugs:
        name = i.__name__.split(".")[-1]
        if shouldrun(name=name):
            logger.debug(msg=_L("Processing plugin cron: %s") % name)
            i.cron()
Beispiel #10
0
def clearupdates(offset):
    """
    Marks updates as already processed so they are removed by API
    :param offset:
    :return:
    """

    logger = logging.getLogger(__name__)
    url = "%s%s/getUpdates" % (plugin.config.config(key='url'),
                               plugin.config.config(key='token'))
    message = "%s?" % url
    message += "offset=%s&" % offset
    try:
        result = json.load(urllib.urlopen(message))
    except:
        result = False
    logger.info(msg=_L("Clearing messages at %s") % offset)
    return result
Beispiel #11
0
def getupdates(offset=0, limit=100):
    """
    Gets updates (new messages from server)
    :param offset: last update id
    :param limit: maximum number of messages to gather
    :return: returns the items obtained
    """

    logger = logging.getLogger(__name__)
    url = "%s%s/getUpdates" % (plugin.config.config(key='url'),
                               plugin.config.config(key='token'))
    message = "%s?" % url
    if offset != 0:
        message += "offset=%s&" % offset
    message += "limit=%s" % limit
    try:
        result = json.load(urllib.urlopen(message))['result']
    except:
        result = []
    for item in result:
        logger.info(msg=_L("Getting updates and returning: %s") % item)
        yield item
Beispiel #12
0
def createorupdatedb():
    """
    Create database if it doesn't exist or upgrade it to head
    :return:
    """

    logger = logging.getLogger(__name__)

    import alembic.config
    alembicArgs = [
        '-x',
        'database=%s' % options.database,
        '--raiseerr',
        'upgrade',
        'head',
    ]

    logger.debug(msg=_L(
        "Using alembic to upgrade/create database to expected revision"))

    alembic.config.main(argv=alembicArgs)

    return
Beispiel #13
0
def loglevel():
    """
    This functions stores or sets the proper log level based on the
    database configuration
    """

    logger = logging.getLogger(__name__)
    level = False

    for case in Switch(plugin.config.config(key="verbosity").lower()):
        # choices=["info", "debug", "warn", "critical"])
        if case('debug'):
            level = logging.DEBUG
            break
        if case('critical'):
            level = logging.CRITICAL
            break
        if case('warn'):
            level = logging.WARN
            break
        if case('info'):
            level = logging.INFO
            break
        if case():
            # Default to DEBUG log level
            level = logging.DEBUG

    # If logging level has changed, redefine in logger,
    # database and send message
    if logging.getLevelName(
            logger.level).lower() != plugin.config.config(key="verbosity"):
        logger.setLevel(level)
        logger.info(msg=_L("Logging level set to %s") %
                    plugin.config.config(key="verbosity"))
        plugin.config.setconfig(key="verbosity",
                                value=logging.getLevelName(
                                    logger.level).lower())
Beispiel #14
0
            attempt = attempt + 1
            try:
                cur.execute(sql)
                con.commit()
                worked = True
                attempt = 60
            except:
                exc_info = sys.exc_info()
                traceback.print_exception(*exc_info)
                worked = False
                sleep(random.randint(0, 10))
        else:
            attempt = 60

        if not worked:
            logger.critical(msg=_L("Error # %s on SQL execution: %s") %
                            (attempt, sql))

    return cur


def sendmessage(chat_id=0,
                text="",
                reply_to_message_id=False,
                disable_web_page_preview=True,
                parse_mode=False,
                extra=False):
    """
    Sends a message to a chat
    :param chat_id: chat_id to receive the message
    :param text: message text
Beispiel #15
0
def sendmessage(chat_id=0,
                text="",
                reply_to_message_id=False,
                disable_web_page_preview=True,
                parse_mode=False,
                extra=False):
    """
    Sends a message to a chat
    :param chat_id: chat_id to receive the message
    :param text: message text
    :param reply_to_message_id: message_id to reply
    :param disable_web_page_preview: do not expand links to include preview
    :param parse_mode: use specific format (markdown, html)
    :param extra: extra parameters to send
                 (for future functions like keyboard_markup)
    :return:
    """

    logger = logging.getLogger(__name__)
    url = "%s%s/sendMessage" % (plugin.config.config(key="url"),
                                plugin.config.config(key='token'))
    lines = text.split("\n")
    maxlines = 15
    if len(lines) > maxlines:
        # message might be too big for single message (max 4K)
        if "```" in text:
            markdown = True
        else:
            markdown = False

        texto = string.join(lines[0:maxlines], "\n")
        if markdown:
            texto = "%s```" % texto

        # Send first batch
        sendmessage(chat_id=chat_id,
                    text=texto,
                    reply_to_message_id=reply_to_message_id,
                    disable_web_page_preview=disable_web_page_preview,
                    parse_mode=parse_mode,
                    extra=extra)
        # Send remaining batch
        texto = string.join(lines[maxlines:], "\n")
        if markdown:
            texto = "```%s" % texto
        sendmessage(chat_id=chat_id,
                    text=texto,
                    reply_to_message_id=False,
                    disable_web_page_preview=disable_web_page_preview,
                    parse_mode=parse_mode,
                    extra=extra)
        return

    overridegid = plugin.config.config(key='overridegid', gid=0, default=False)
    if overridegid:
        chat_id = overridegid

    message = "%s?chat_id=%s&text=%s" % (
        url, chat_id, urllib.quote_plus(text.encode('utf-8')))
    if reply_to_message_id:
        message += "&reply_to_message_id=%s" % reply_to_message_id
    if disable_web_page_preview:
        message += "&disable_web_page_preview=1"
    if parse_mode:
        message += "&parse_mode=%s" % parse_mode
    if extra:
        message += "&%s" % extra

    code = False
    attempt = 0
    while not code:
        # It this is executed as per unit testing, skip sending message
        UTdisable = not plugin.config.config(key='unittest', default=False)
        Silent = not plugin.config.gconfig(
            key='silent', default=False, gid=geteffectivegid(gid=chat_id))
        if UTdisable and Silent:
            result = json.load(urllib.urlopen(message))
            code = result['ok']
        else:
            code = True
            result = ""

        if attempt > 0:
            logger.error(
                msg=_L("ERROR (%s) sending message: Code: %s : Text: %s") %
                (attempt, code, result))

        attempt += 1
        sleep(1)
        if not code:
            error = result['description']
            if 'entity starting at byte offset' in error:
                # Trim the byte offset to make this error more generic
                error = error[:103]

            for case in Switch(error):
                if case(u"Bad Request: message text is empty"):
                    # Message is empty, no need to resend
                    attempt = 61
                    break

                if case(u"Forbidden: bot can't initiate conversation with a user"
                        ):
                    # Bot hasn't been authorized by user, cancelling
                    attempt = 61
                    break

                if case(u"Bad Request: reply message not found"):
                    # Original reply has been deleted
                    attempt = 61
                    break

                if case(u"Forbidden: bot was blocked by the user"):
                    # User blocked the bot
                    attempt = 61
                    break

                if case(u"Bad Request: can't parse entities in message text: Can't find end of the entity starting at byte offset"
                        ):
                    attempt = 61
                    break

                if case(u'Forbidden: bot was kicked from the supergroup chat'):
                    attempt = 61
                    break

                if case(u'Bad Request: chat not found'):
                    attempt = 61
                    break

        # exit after 60 retries with 1 second delay each
        if attempt > 60:
            logger.error(
                msg=_L("PERM ERROR sending message: Code: %s : Text: %s") %
                (code, result))
            code = True

    try:
        sent = {"message": result['result']}
    except:
        sent = False

    if sent:
        # Check if there's something to forward and do it
        plugin.forward.forwardmessage(sent)

    logger.debug(msg=_L("Sending message: Code: %s : Text: %s") % (code, text))
    return code
Beispiel #16
0
def process(messages):
    """
    This function processes the updates in the Updates URL at Telegram
    for finding commands, karma changes, config, etc
    """

    logger = logging.getLogger(__name__)

    # check if Log level has changed
    loglevel()

    # Main code for processing the karma updates
    date = 0
    lastupdateid = 0
    count = 0

    # Process each message available in URL and search for karma operators
    for message in messages:
        # Count messages in each batch
        count += 1

        # Forward message if defined
        plugin.forward.forwardmessage(message)

        # Call plugins to process message
        global plugs
        global plugtriggers

        msgdetail = getmsgdetail(message)
        botname = getme()

        # Write the line for debug
        messageline = _L("TEXT: %s : %s : %s") % (
            msgdetail["chat_name"], msgdetail["name"], msgdetail["text"])
        logger.debug(msg=messageline)

        # Process group configuration for language
        chat_id = msgdetail['chat_id']
        chlang(lang=plugin.config.gconfig(key='language', gid=chat_id))

        try:
            command = msgdetail["text"].split()[0].lower().replace(
                '@%s' % botname, '')
            texto = msgdetail["text"].lower()
            date = msgdetail["datefor"]
        except:
            command = ""
            texto = ""
            date = 0

        for i in plugs:
            name = i.__name__.split(".")[-1]

            runplugin = False
            for trigger in plugtriggers[name]:
                if "*" in trigger:
                    runplugin = True
                    break
                elif trigger[0] == "^":
                    if command == trigger[1:]:
                        runplugin = True
                        break
                elif trigger in texto:
                    runplugin = True
                    break

            code = False
            if runplugin:
                logger.debug(msg=_L("Processing plugin: %s") % name)
                code = i.run(message=message)

            if code:
                # Plugin has changed triggers, reload
                plugtriggers[name] = i.init()
                logger.debug(msg=_L("New triggers for %s: %s") %
                             (name, plugtriggers[name]))

        # Update last message id to later clear it from the server
        if msgdetail["update_id"] > lastupdateid:
            lastupdateid = msgdetail["update_id"]

    if date != 0:
        logger.info(msg=_L("Last processed message at: %s") % date)
    if lastupdateid != 0:
        logger.debug(msg=_L("Last processed update_id : %s") % lastupdateid)
    if count != 0:
        logger.info(msg=_L("Number of messages in this batch: %s") % count)

    # clear updates (marking messages as read)
    if lastupdateid != 0:
        clearupdates(offset=lastupdateid + 1)
Beispiel #17
0
def main():
    """
    Main code for the bot
    """

    # Main code
    logger = logging.getLogger(__name__)

    # Set database name in config
    if options.database:
        createorupdatedb()
        plugin.config.setconfig(key='database', value=options.database)

    # Configure logging
    conflogging(target="stampy")

    # Configuring alembic logger
    conflogging(target="alembic")

    logger.info(msg=_L("Started execution"))

    if not plugin.config.config(key='sleep'):
        plugin.config.setconfig(key='sleep', value=10)

    # Remove our name so it is retrieved on boot
    plugin.config.deleteconfig(key='myself')

    # Check if we've the token required to access or exit
    if not plugin.config.config(key='token'):
        if options.token:
            token = options.token
            plugin.config.setconfig(key='token', value=token)
        else:
            msg = _(
                "Token required for operation, please check https://core.telegram.org/bots"
            )
            logger.critical(msg)
            sys.exit(1)

    # Check if we've URL defined on DB or on cli and store
    if not plugin.config.config(key='url'):
        if options.url:
            plugin.config.setconfig(key='url', value=options.url)

    # Check if we've owner defined in DB or on cli and store
    if not plugin.config.config(key='owner'):
        if options.owner:
            plugin.config.setconfig(key='owner', value=options.owner)

    # Initialize modules
    global plugs
    global plugtriggers
    plugs, plugtriggers = plugins.initplugins()

    logger.debug(msg=_L("Plug triggers reported: %s") % plugtriggers)

    # Check operation mode and call process as required
    if options.daemon or plugin.config.config(key='daemon'):
        plugin.config.setconfig(key='daemon', value=True)
        logger.info(msg=_L("Running in daemon mode"))
        while plugin.config.config(key='daemon') == 'True':
            process(getupdates())
            processcron()
            sleep(int(plugin.config.config(key='sleep')))
    else:
        logger.info(msg=_L("Running in one-shoot mode"))
        process(getupdates())
        processcron()

    logger.info(msg=_L("Stopped execution"))
    logging.shutdown()
    sys.exit(0)