From 310b1ae5e42e1edfa8cb4240028575d3e3e1193c Mon Sep 17 00:00:00 2001 From: 0xDEAD10CC <0x@DEAD10.CC> Date: Tue, 25 Jul 2017 04:10:42 -0700 Subject: [PATCH] Initial commit --- src/CoindroidAPI.py | 25 +++++++++++ src/Currency.py | 96 +++++++++++++++++++++++++++++++++++++++++ src/Droid.py | 92 +++++++++++++++++++++++++++++++++++++++ src/Event.py | 103 ++++++++++++++++++++++++++++++++++++++++++++ src/__init__.py | 0 src/main.py | 30 +++++++++++++ 6 files changed, 346 insertions(+) create mode 100644 src/CoindroidAPI.py create mode 100644 src/Currency.py create mode 100644 src/Droid.py create mode 100644 src/Event.py create mode 100644 src/__init__.py create mode 100644 src/main.py diff --git a/src/CoindroidAPI.py b/src/CoindroidAPI.py new file mode 100644 index 0000000..4637c4d --- /dev/null +++ b/src/CoindroidAPI.py @@ -0,0 +1,25 @@ +import logging, requests +from gevent import Greenlet + + +class CoindroidAPITask(Greenlet): + def __init__(self, **kwargs): + Greenlet.__init__(self) + self.eventsEvent = kwargs.get("eventsEvent") + self.eventsEvent.clear() + self.logger = logging.getLogger(self.__class__.__name__) + self.apiSession = requests.Session() + self.lastTransaction = None + self.currentTransaction = None + self.apiUrl = kwargs.get("apiUrl") + self.pause = kwargs.get("pause") + if not self.apiUrl: + self.logger.debug("apiUrl not set. Setting to https://api.coindroids.com/") + self.apiUrl = "https://api.coindroids.com/" + else: + self.logger.debug("Setting apiUrl to {}".format(self.apiUrl)) + if not self.pause: + self.logger.debug("pause not set. Setting to 10...") + self.pause = 10 + else: + self.logger.debug("Setting pause to {}...".format(self.pause)) diff --git a/src/Currency.py b/src/Currency.py new file mode 100644 index 0000000..631ff75 --- /dev/null +++ b/src/Currency.py @@ -0,0 +1,96 @@ +import logging +from CoindroidAPI import CoindroidAPITask +from collections import namedtuple +from gevent import sleep + + +class CurrencyTask(CoindroidAPITask): + def __init__(self, **kwargs): + super(CurrencyTask, self).__init__(**kwargs) + self.logger = logging.getLogger(self.__class__.__name__) + self.currencyComparisonTuple = namedtuple("currencyComparison", + ["added", "removed", "modified", "same"]) + self.updateCurrency() + self.eventsEvent.set() + + def dictCompare(self, d1, d2): + d1_keys = set(d1.keys()) + d2_keys = set(d2.keys()) + intersect_keys = d1_keys.intersection(d2_keys) + added = d1_keys - d2_keys + removed = d2_keys - d1_keys + modified = {o: (d1[o], d2[o]) for o in intersect_keys if d1[o] != d2[o]} + same = set(o for o in intersect_keys if d1[o] == d2[o]) + return added, removed, modified, same + + def updateCurrency(self): + while True: + try: + request = self.apiSession.get(self.apiUrl + "currency", + params={"id": "eq.2"}) + except: + self.logger.exception("Unable to get currency!") + self.logger.error("Waiting for update...") + sleep(self.pause) + continue + if self.currentTransaction: + self.lastTransaction = self.currentTransaction.copy() + self.currentTransaction = request.json()[0] + self.logger.info("Retrieved currency from Coindroids...") + self.logger.debug("{}".format(self.currentTransaction.get("last_ingested"))) + if not self.lastTransaction: + self.logger.info("No previous currency available... Waiting for update...") + self.lastTransaction = self.currentTransaction.copy() + self.currentTransaction = None + sleep(self.pause) + continue + break + + def compareCurrency(self): + currencyComparison = self.currencyComparisonTuple( + *self.dictCompare(self.lastTransaction, self.currentTransaction)) + for name, value in currencyComparison._asdict().items(): + if "same" in name: + continue + if value: + self.logger.debug("currency {}: {}".format(name, value)) + eventUpdate = False + if currencyComparison.modified: + if "overall_purse_sum" in currencyComparison.modified: + self.logger.info("overall_purse_sum updated from {0[0]} to {0[1]}...".format( + currencyComparison.modified["overall_purse_sum"])) + if "total_number_of_attacks" in currencyComparison.modified: + self.logger.info("total_number_of_attacks updated from {0[0]} to {0[1]}...".format( + currencyComparison.modified["total_number_of_attacks"])) + eventUpdate = True + if "total_amount_of_attacks" in currencyComparison.modified: + self.logger.info("total_amount_of_attacks updated from {0[0]} to {0[1]}...".format( + currencyComparison.modified["total_amount_of_attacks"])) + eventUpdate = True + if "total_number_of_item_purchases" in currencyComparison.modified: + self.logger.info("total_number_of_item_purchases updated from {0[0]} to {0[1]}...".format( + currencyComparison.modified["total_number_of_item_purchases"])) + if "total_droids" in currencyComparison.modified: + self.logger.info("total_droids updated from {0[0]} to {0[1]}...".format( + currencyComparison.modified["total_droids"])) + if "percentage_of_droids" in currencyComparison.modified: + self.logger.info("percentage_of_droids updated from {0[0]} to {0[1]}...".format( + currencyComparison.modified["percentage_of_droids"])) + if "pending_payouts" in currencyComparison.modified: + self.logger.info("pending_payouts updated from {0[0]} to {0[1]}...".format( + currencyComparison.modified["pending_payouts"])) + if eventUpdate: + self.logger.info("Need to update events...") + return eventUpdate + + def _run(self): + self.logger.info("Running {}".format(self.__class__.__name__)) + while True: + update = self.compareCurrency() + if update: + self.eventsEvent.set() + sleep(self.pause) + else: + sleep(self.pause) + self.logger.debug("Updating currency...") + self.updateCurrency() diff --git a/src/Droid.py b/src/Droid.py new file mode 100644 index 0000000..8d932bb --- /dev/null +++ b/src/Droid.py @@ -0,0 +1,92 @@ +import logging +from CoindroidAPI import CoindroidAPITask +from gevent import sleep + +# todo Flesh out getting events to Droids and having them act accordingly + +class DroidTask(CoindroidAPITask): + def __init__(self, **kwargs): + """ + This is a DroidTask + It performs actions on events received from:w + + :param \**kwargs: + See below + :Keyword Arguments: + * *apiUrl* (``str``) -- + Coindroids API URL. + Default is ``https://api.coindroids.com/`` + * *pause* (``int``) -- + Time to wait between requests + """ + super(EventTask, self).__init__(**kwargs) + self.logger = logging.getLogger(__name__) + self.updateEvents() + + def updateEvents(self): + while True: + try: + request = self.apiSession.get(self.apiUrl + "event", + params={"order" : "block_height.desc", + "currency_id": "eq.2"}) + # "involved_droids": ""}).json() + except: + self.logger.exception("Unable to get events!") + self.logger.error("Waiting for update...") + sleep(self.pause) + continue + self.currentTransaction = request.json() + self.logger.info("Retrieved events from Coindroids...") + self.logger.debug("{} events found...".format(len(self.currentTransaction))) + if not self.lastTransaction: + self.logger.info("No previous events available... Waiting for update...") + self.lastTransaction = self.currentTransaction.copy() + self.currentTransaction = None + sleep(self.pause) + continue + break + + def compareEvents(self): + """ + Compares old events to new events + :return: list of events + """ + setLastEventsIDs = set([x.get("action_id") for x in self.lastTransaction]) + setEventsIDs = set([x.get("action_id") for x in self.currentTransaction]) + setRemovedEvents = setLastEventsIDs - setEventsIDs + setAddedEvents = setEventsIDs - setLastEventsIDs + ret = [] + if setRemovedEvents: + self.logger.debug("Events removed from this update: {}".format(setRemovedEvents)) + if setAddedEvents: + self.logger.debug("Added events:") + for event_id in setAddedEvents: + for event in self.currentTransaction: + if event: + if event["action_id"] == event_id: + ret.append(event) + self.logger.debug("At block_height {}, action_id {} - {}({}):{}({}) did {} on {}".format( + *(event.get(i) for i in ("block_height", + "action_id", "player_username", "player_id", + "droid_name", "droidID", "action_type", + "target")))) + else: + self.logger.error("Found empty event!") + return ret + + def _run(self): + self.logger.info("Running {}".format(self.__class__.__name__)) + while True: + if self.eventsEvent.ready(): + self.logger.debug("{} woken up...".format(self.__class__.__name__)) + for _ in range(5): + self.logger.debug("Trying to get updated events ({}/5)".format(_)) + update = self.compareEvents() + if update: + self.logger.debug("TODO: Send events to queue for worker threads") + break + else: + self.logger.debug("No events detected") + sleep(5) + sleep(self.pause) + continue diff --git a/src/Event.py b/src/Event.py new file mode 100644 index 0000000..08a7b21 --- /dev/null +++ b/src/Event.py @@ -0,0 +1,103 @@ +import logging +from CoindroidAPI import CoindroidAPITask +from gevent import sleep +from twilio.rest import Client as TwilioClient + + +class EventTask(CoindroidAPITask): + def __init__(self, **kwargs): + super(EventTask, self).__init__(**kwargs) + self.logger = logging.getLogger(self.__class__.__name__) + twilioAccount = "ACdfcd2b529db83954e8a0041796072960" + twilioToken = "ab9b98a0b3da8a728d14774192773af2" + self.twilioClient = TwilioClient(twilioAccount, twilioToken) + self.updateEvents() + + def updateEvents(self, updateLast=True): + while True: + try: + request = self.apiSession.get(self.apiUrl + "event", + params={"order" : "block_height.desc", + "currency_id": "eq.2"}) + # "involved_droids": ""}).json() + except: + self.logger.exception("Unable to get events!") + self.logger.error("Waiting for update...") + sleep(self.pause) + continue + if not updateLast: + self.logger.debug("Not updating last transaction...") + if (self.currentTransaction and updateLast): + self.logger.debug("Updating previous transaction...") + self.lastTransaction = self.currentTransaction.copy() + self.currentTransaction = request.json() + self.logger.info("Retrieved events from Coindroids...") + self.logger.debug("{} events found...".format(len(self.currentTransaction))) + if not self.lastTransaction: + self.logger.info("No previous events available... Waiting for update...") + self.lastTransaction = self.currentTransaction.copy() + self.currentTransaction = None + sleep(self.pause) + continue + break + + def compareEvents(self): + setLastEventsIDs = set([x.get("action_id") for x in self.lastTransaction]) + setEventsIDs = set([x.get("action_id") for x in self.currentTransaction]) + setAddedEvents = setEventsIDs - setLastEventsIDs + ret = [] + if setAddedEvents: + self.logger.debug("Added events:") + for event_id in setAddedEvents: + for event in self.currentTransaction: + if event: + if event["action_id"] == event_id: + ret.append(event) + keys = ["block_height", "action_id", "player_username", "player_id", "droid_name", + "droid_id", "action_type"] + message_args = [event.get(i) for i in keys] + if event.get("target"): + message_args.extend([event["target"].get("name"), event["target"].get("id")]) + if (event["target"].get("name") == "bob") or (event["target"].get("id") == 160): + self.logger.debug("TEMP - bob's got an action!!! sending sms!!") + try: + self.twilioClient.messages.create( + to="+14088963912", + from_="+14159037708", + body="At block_height {}, action_id {} - {}({}):{}({}) did {} on {}({})".format( + *message_args) + ) + except: + self.logger.exception("Unable to send SMS!!") + else: + self.logger.warning("Event has no target!") + message_args.extend([None, None]) + self.logger.debug( + "At block_height {}, action_id {} - {}({}):{}({}) did {} on {}({})".format( + *message_args)) + else: + self.logger.error("Found empty event!") + return ret + + def _run(self): + self.logger.info("Running {}".format(self.__class__.__name__)) + while True: + if self.eventsEvent.ready(): + self.logger.debug("{} woken up...".format(self.__class__.__name__)) + for _ in range(5): + self.logger.debug("Trying to get updated events ({}/5)".format(_)) + self.updateEvents(updateLast=False) + update = self.compareEvents() + if update: + self.eventsEvent.clear() + self.logger.debug("TODO: Send events to queue for worker threads") + self.updateEvents() + break + else: + self.logger.debug("No events detected") + sleep(5) + self.updateEvents(updateLast=False) + else: + self.logger.error("No events detected after 5 rounds!") + sleep(self.pause) + continue diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..6fc5ef5 --- /dev/null +++ b/src/main.py @@ -0,0 +1,30 @@ +# pycoindroid v0.1 +# © 2017 DEAD10CC <0x@DEAD10.CC> +# a dumb hack on Coindroids + +import gevent, logging +from gevent.event import Event +from Currency import CurrencyTask +from Event import EventTask + +formatter = logging.Formatter('%(asctime)s - %(name)s %(funcName)s():%(lineno)d - %(levelname)s - %(message)s') +logger = logging.getLogger() +logger.setLevel(logging.DEBUG) +ch = logging.StreamHandler() +ch.setLevel(logging.DEBUG) +ch.setFormatter(formatter) +logger.addHandler(ch) + + +def main(): + logger = logging.getLogger(__name__) + while True: + logger.info("Starting main loop...") + eventsEvent = Event() + currencyTask = CurrencyTask.spawn(eventsEvent=eventsEvent) + eventTask = EventTask.spawn(eventsEvent=eventsEvent) + gevent.joinall([currencyTask, eventsEvent]) + logger.error("Main loop ended... This shouldn't happen?") + +if __name__ == "__main__": + main()