def read_schedule(self): """ Reads the schedule that is saved to the storage. :return: The read schedule. """ try: filepath = self.__root_path.joinpath(PLANNING_FILE) data = filepath.read_bytes() bundle = Schedule() bundle.ParseFromString(data) except: raise IOError("Impossible de lire le planning du stockage.") return bundle
def test_schedule_write_then_read(self): """Writes then reads a schedule to/from the storage.""" schedule = Schedule() player1 = RatedPlayer() player1.gamer_tag = "Walnut Waffle" player1.rating = RatedPlayer.Rating.EXPERIENCED player2 = RatedPlayer() player2.gamer_tag = "Oby1Chick" player2.rating = RatedPlayer.Rating.INTERMEDIATE activity1 = Activity() activity1.id.type = ActivityID.Type.GARDEN_OF_SALVATION activity1.id.when.datetime = '2020-9-1' activity1.state = Activity.State.NOT_STARTED activity1.squad.players.append(player1) schedule.activities.append(activity1) activity2 = Activity() activity2.id.type = ActivityID.Type.LEVIATHAN_PRESTIGE activity2.state = Activity.State.FINISHED activity2.squad.substitutes.append(player2) schedule.activities.append(activity2) self.sut.write_schedule(schedule) schedule2 = self.sut.read_schedule() self.assertEqual(schedule, schedule2) self.assertRaises(IOError, self.sut.read_api_bundle)
def test_clearall(self): """Verifies clearall intents are properly executed.""" (feedback, images) = self.execute("!cortana clearall") expectation = Schedule() self.assertEqual(self.storage.read_schedule(), expectation) self.assertEqual( feedback, "Toutes les activités du planning sont désormais supprimées.") self.assertIsNone(images)
async def handle_message(self, message): """Real message handler.""" if message.author == self.user or not message.content.startswith( "!cortana "): return print() print(message.guild.name) print(message.author) print(message.content) # Init storage for current guild. now = datetime.now(TIMEZONE) storage = Storage(ROOT_DIRECTORY.joinpath(str(message.guild.id))) # Make sure a basic bundle is available. bundle = None try: bundle = storage.read_api_bundle() except: await self.answer_message( "Une synchronisation des joueurs doit être effectuée.\nVeuillez patienter...", message) bundle = self.__fetcher.fetch(now) storage.write_api_bundle(bundle) await self.answer_message("Synchronisation terminée", message) # Make sure a basic schedule is available. try: storage.read_schedule() except: storage.write_schedule(Schedule()) # Parse intent. intent = self.__parser.parse(message.content, bundle, now) # Execute intent. if intent.HasField( 'global_intent') and intent.global_intent.sync_bundle: await self.answer_message( "Synchronisation en cours.\nVeuillez patienter...", message) executor = Executor(storage, self.__fetcher, self.__generator) (feedback, images) = executor.execute(intent, now) # Post message. await self.answer_message(feedback, message) # Post images. if not images: return for index, image in enumerate(images): file = discord.File(fp=image, filename="Affiche" + str(index) + ".gif") await message.channel.send(file=file)
def test_clearpast(self): """Verifies clearpast intents are properly executed.""" schedule_before = self.storage.read_schedule() (feedback, images) = self.execute("!cortana clearpast") expected_activities = schedule_before.activities expected_activities.pop(0) expectation = Schedule() expectation.activities.extend(expected_activities) self.assertTrue( feedback.startswith( "Les activités des semaines précédentes ont été suprimées.")) self.assertIsNone(images) self.assertEqual(self.storage.read_schedule(), expectation)
def make_schedule(): """Constructs a schedule for test cases.""" s = Schedule() a = s.activities.add() a.state = Activity.State.FINISHED a.id.type = ActivityID.Type.LEVIATHAN a.id.when.datetime = '2020-08-09T21:15:00+02:00' a.id.when.time_specified = True (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Franstuk" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "SuperFayaChonch" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "croptus" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "affectevil" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER a = s.activities.add() a.state = Activity.State.NOT_STARTED a.id.type = ActivityID.Type.SPIRE_OF_STARS_PRESTIGE a.id.when.datetime = '2020-08-12' a.id.when.time_specified = False (a.squad.players.add()).gamer_tag = "snippro34" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Jezehbell" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.substitutes.add()).gamer_tag = "NaughtySoft" a.squad.substitutes[-1].rating = RatedPlayer.Rating.INTERMEDIATE a = s.activities.add() a.state = Activity.State.MILESTONED a.milestone = "Save au boss" a.id.type = ActivityID.Type.SCOURGE_OF_THE_PAST a.id.when.datetime = '2020-08-16T14:30:00+02:00' a.id.when.time_specified = True (a.squad.players.add()).gamer_tag = "Duality Cobra" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Franstuk" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "croptus" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER a = s.activities.add() a.state = Activity.State.NOT_STARTED a.id.type = ActivityID.Type.GARDEN_OF_SALVATION a.id.when.datetime = '2020-08-17T21:15:00+02:00' a.id.when.time_specified = True (a.squad.players.add()).gamer_tag = "Hartog31" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "Franstuk" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.substitutes.add()).gamer_tag = "croptus" a.squad.substitutes[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.substitutes.add()).gamer_tag = "affectevil" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER a = s.activities.add() a.state = Activity.State.NOT_STARTED a.id.type = ActivityID.Type.GARDEN_OF_SALVATION a.id.when.datetime = '2020-08-25' a.id.when.time_specified = False (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER return s
def test_schedule(self): """ Verifies the hash of generated GIFs to ensure the images stay the same. """ s = Schedule() a = s.activities.add() a.id.type = ActivityID.Type.LEVIATHAN a.id.when.datetime = '2020-07-14T23:55:00+02:00' a.id.when.time_specified = True a.state = Activity.State.NOT_STARTED (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Franstuck" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "SuperFayaChonch" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "croptus7490" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "Affectevil" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER a = s.activities.add() a.id.type = ActivityID.Type.SPIRE_OF_STARS_PRESTIGE a.id.when.datetime = '2020-07-23' a.id.when.time_specified = False a.state = Activity.State.NOT_STARTED (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "Franstuck" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "SuperFayaChonch" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.substitutes.add()).gamer_tag = "croptus7490" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "Affectevil" a.squad.substitutes[-1].rating = RatedPlayer.Rating.INTERMEDIATE a = s.activities.add() a.id.type = ActivityID.Type.SCOURGE_OF_THE_PAST a.id.when.datetime = '2020-10-25T16:30:00+01:00' a.id.when.time_specified = True a.state = Activity.State.MILESTONED a.milestone = "save au boss" (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.UNKNOWN (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Franstuck" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "Dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "SuperFayaChonch" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.substitutes.add()).gamer_tag = "croptus7490" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "Affectevil" a.squad.substitutes[-1].rating = RatedPlayer.Rating.INTERMEDIATE a = s.activities.add() a.id.type = ActivityID.Type.GARDEN_OF_SALVATION a.id.when.datetime = '2020-08-25T22:35:00+02:00' a.id.when.time_specified = True a.state = Activity.State.FINISHED (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Franstuck" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "SuperFayaChonch" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.substitutes.add()).gamer_tag = "croptus7490" a.squad.substitutes[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.substitutes.add()).gamer_tag = "Affectevil" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER a = s.activities.add() a.id.type = ActivityID.Type.WRATH_OF_THE_MACHINE a.id.when.datetime = '2019-01-25T04:42:00+01:00' a.id.when.time_specified = True a.state = Activity.State.NOT_STARTED (a.squad.players.add()).gamer_tag = "Cosa58" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Walnut Waffle" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Oby1Chick" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Franstuck" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "Dark0l1ght" a.squad.players[-1].rating = RatedPlayer.Rating.INTERMEDIATE (a.squad.players.add()).gamer_tag = "SuperFayaChonch" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "croptus7490" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.substitutes.add()).gamer_tag = "Affectevil" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER a = s.activities.add() a.id.type = ActivityID.Type.LAST_WISH a.state = Activity.State.NOT_STARTED (a.squad.players.add()).gamer_tag = "snippro34" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "Jezehbell" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.players.add()).gamer_tag = "SuperFayaChonch" a.squad.players[-1].rating = RatedPlayer.Rating.BEGINNER (a.squad.players.add()).gamer_tag = "Carnage" a.squad.players[-1].rating = RatedPlayer.Rating.EXPERIENCED (a.squad.substitutes.add()).gamer_tag = "Oby1Chick" a.squad.substitutes[-1].rating = RatedPlayer.Rating.BEGINNER gen = img_generator.Generator(tz.gettz('Europe/Paris'), "fr") gifs = gen.generate_images(s) hash_reference = { "Darwin": [ "6c3a69069413955d2007ebf10fee7d289769aedec2b015fa0b56e742735d4beb", "941aa1047be76e8e98ecfe7dff9ad6bd2083e5b2ca1c6d913bd3fd634a0b9e8d" ], "Linux": [ "0d4939f0a14a4e8ce0982f93eaaa2f37b244cf3a2354fa3fe351d84874b07c1f", "5713ac4a16f7701ca7eba86fd8063899cc95753412edbd5a19b96a14b0b2b64e" ] } detected_os = system() self.assertTrue(detected_os in hash_reference.keys(), "OS not supported to run tests") gif_hashes = [] for gif in gifs: self.assertEqual(gif.tell(), 0) gif.seek(0, io.SEEK_END) self.assertLess(gif.tell(), 8*1024*1024) gif.seek(0, io.SEEK_SET) with tempfile.NamedTemporaryFile(delete=False, suffix='.gif') as temp: temp.write(gif.getbuffer()) print("NOTE - the result of this test can be visualized here: ", temp.name) gif_hashes.append(hashlib.sha256(gif.getbuffer()).hexdigest()) self.assertEqual(gif_hashes, hash_reference[detected_os], True)
def test_no_permission(self): """Verifies the storage raises an IOError when encountering permission errors.""" s = storage.Storage(Path('/')) self.assertRaises(IOError, s.write_schedule, Schedule()) self.assertRaises(IOError, s.write_api_bundle, APIBundle())
def execute_global_intent(self, global_intent, now): """ :param global_intent: The global_intent to execute. :param now: Now as a datetime. :return: An (execution feedback message, generated BytesIO images or None) tuple. :raises: If the intent could be executed for some reason. """ if global_intent.HasField('credits'): #! raid credits return "Mes créateurs sont Walnut & Oby ❤️", None if global_intent.HasField('help'): # !cortana help help_str = "Guide d'utilisation:\n" \ "Cortana gère et génère les affiches d'un planning d'activités Destiny.\n" \ "Elle connaît les noms et les niveaux d'expérience des joueurs de la FE11 " \ "et des clans alliés.\nCe guide détaille les commandes pour utiliser Cortana.\n" \ "Dans toutes les commandes détaillées ci-dessous il faut savoir que: \n\n" \ "-[nom activité] " \ "doit être remplacé par un vrai nom, style calus, fleche, fleau, jds, couronne, " \ "etc... Plusieurs formats sont supportés (e.g. leviathan, jardin du salut, " \ "couronne du malheur, flèche prestige, flèche d'étoiles prestige marchent aussi)." \ " Les fautes d'orthographe sont aussi gérées dans la limite du raisonnable. " \ "(e.g. kalus, fleo, devorer, etc... sont aussi supportés).\n\n" \ "- (date) indique une date optionnelle. Plusieurs formats de " \ "date sont gérés (e.g. mercredi, 17/08, dimanche 21h, demain 22h30, 20/8 20h30, "\ "etc...)\nLa date est utile pour 2 cas de figures:\n" \ "1) Afficher la date sur les affiches.\n" \ "2) Différencier deux activités du même type dans une commande.\n" \ "La date peut généralement être ignorée dans une commande qui modifie " \ "les détails d'une activité qui est la seule de son type dans le planning.\n\n" \ "- [date] indique une date obligatoire. Par exemple pour la commande de " \ "modification d'une date d'activité.\n\n" \ "- [gamer_tags] indique une liste de gamer tags (au minimum 1 gamer tag).\n" \ "Cortana est capable de comprendre et corriger " \ "les gamer tags même si ils sont mal ortographiés. " \ "(e.g. cosa, croptus, darklight, franstuck, hartok etc...)\n\n\n" \ "Liste des commandes: \n\n" \ "!cortana [nom activité] (date) [gamer_tag1, ...] => Créé/modifie une escouade " \ "pour/d'une activité. Précédez un gamer tag par '-' pour signaler que le joueur " \ "doit être retiré et non ajouté.\n\n" \ "!cortana backup [nom activité] (date) [gamer_tag1, ...] => Pareil que la " \ "commande du dessus mais pour gérer les remplaçants.\n\n" \ "!cortana date [nom activité] (ancienne date) [nouvelle date] => Ajoute ou " \ "modifie une date à une activité.\n\n" \ "!cortana milestone [nom activité] (date) => Précise une save ou un statut pour " \ "l'activité concernée. (e.g. !cortana milestone jds save au boss, " \ "!cortana milestone leviathan prestige reporté)." \ "!cortana finish [nom activité] (date) => Marque l'activité comme terminée.\n\n" \ "!cortana info [nom activité] (date) => Affiche les détails de l'activité dans " \ "un format texte.\n\n" \ "!cortana clear [nom activité] (date) => Supprime l'activité du planning.\n\n" \ "!cortana images => Génère les affiches pour toutes les activités " \ "du planning.\n\n" \ "!cortana help => Affiche le guide d'utilisation.\n\n" \ "!cortana infoall => Affiche tout le planning dans un format texte.\n\n" \ "!cortana clearpast => Supprime toutes les activités des semaines " \ "précédentes.\n\n" \ "!cortana clearall => Supprime toutes les activités.\n\n" \ "!cortana sync => Synchronise la liste des joueurs et leurs stats.\n" \ "Utile quand des nouveaux membres rejoignent le clan.\n" \ "Utile pour que Cortana soit au courant des changements de niveaux d'expérience " \ "des joueurs.\nAttention toutefois, les changements de niveaux d'expérience ne " \ "sont pas directement reflétés dans les affiches. " \ "Ils seront appliqués à partir des prochaines commandes (nouvelle création " \ "d'activité ou mise à jour d'escouade).\n\n" \ "!cortana lastsync => Affiche la dernière date de synchronisation.\n\n" \ "!cortana credits => Affiche les noms de mes créateurs." return help_str, None if global_intent.HasField('generate_images'): # !cortana images schedule = self.__storage.read_schedule() images = self.__img_generator.generate_images(schedule) if len(images) == 0: return "Il n'y a aucune activité dans le planning pour le moment.", [] return "Affiches pour les activités en cours:", images if global_intent.HasField('sync_bundle'): # !cortana sync bundle = self.__api_fetcher.fetch(now) self.__storage.write_api_bundle(bundle) return "Joueurs et niveaux d'experiences synchronisés.", None if global_intent.HasField('get_last_bundle_sync_datetime'): # !cortana lastsync bundle = self.__storage.read_api_bundle() return "Dernière synchronisation: " + bundle.last_sync_datetime, None if global_intent.HasField('clear_all'): # !cortana clearall schedule = Schedule() self.__storage.write_schedule(schedule) return "Toutes les activités du planning sont désormais supprimées.", None if global_intent.HasField('clear_past'): # !cortana clearpast schedule = self.__storage.read_schedule() threshold = dateparser.parse(CLEARPAST_WEEKDAY, settings={ 'PREFER_DATES_FROM': 'past', 'RELATIVE_BASE': now }) threshold = threshold.replace(hour=CLEARPAST_HOUR) threshold = threshold.replace(tzinfo=TIMEZONE) activities = filter( lambda a: not a.id.when or threshold <= to_datetime( a.id.when, now.tzinfo), schedule.activities) schedule = Schedule() schedule.activities.extend(activities) self.__storage.write_schedule(schedule) feedback = "Les activités des semaines précédentes ont été suprimées.\n" feedback += "Les activités restantes dans le planning sont:\n" feedback += str(schedule) return feedback, None if global_intent.HasField('info_all'): # !cortana infoall schedule = self.__storage.read_schedule() return str(schedule) if len( schedule.activities) > 0 else "Le planning est vide.", None raise ValueError("Commande invalide")