diff --git a/README.md b/README.md index 18c920d..3d6dc34 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,10 @@ To use QuizMaster the bbs_admin_list is the QuizMaster, who can `q: start` and ` Players can `q: join` to join the game, `q: leave` to leave the game, `q: score` to see their score, and `q: top` to see the top 3 players. To Answer a question, just type the answer prefixed with `q: ` +#### Word of the Day Games +Simple word fun, bingo and customizable word of the day lists via JSON files. Slot Machine for Emoji ๐ŸŽฐ +[modules/games/README.md](modules/games/README.md) + #### Survey To use the Survey feature edit the json files in data/survey multiple surveys are possible such as `survey snow` you can pull data back with `survey report` or `survey report snow` diff --git a/mesh_bot.py b/mesh_bot.py index 4fc4a8d..a2d51c5 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -1785,6 +1785,24 @@ def onReceive(packet, interface): # send a hello message as a DM if not train_qrz: send_message(f"Hello {name} {qrz_hello_string}", channel_number, message_from_id, rxNode) + + # handle mini games + if wordOfTheDay: + #word of the day game play on non bot messages + happened, old_entry, new_entry, bingo_win, bingo_message = theWordOfTheDay.did_it_happen(message_string) + if happened: + wordWas = old_entry['word'] + metaWas = old_entry['meta'] + msg = f"๐ŸŽ‰ {get_name_from_number(message_from_id, 'long', rxNode)} found the Word of the Day๐ŸŽŠ:\n {wordWas}, {metaWas}" + send_message(msg, channel_number, 0, rxNode) + if bingo_win: + msg = f"๐ŸŽ‰ {get_name_from_number(message_from_id, 'long', rxNode)} scored BINGO!๐Ÿฅณ {bingo_message}" + send_message(msg, channel_number, 0, rxNode) + + slotMachine = theWordOfTheDay.emojiMiniGame(message_string, emojiSeen=emojiSeen, nodeID=message_from_id, nodeInt=rxNode) + if slotMachine: + msg = f"๐ŸŽ‰ {get_name_from_number(message_from_id, 'long', rxNode)} played the Slot Machine and got: {slotMachine} ๐Ÿฅณ" + send_message(msg, channel_number, 0, rxNode) else: # Evaluate non TEXT_MESSAGE_APP packets consumeMetadata(packet, rxNode, channel_number) diff --git a/modules/games/README.md b/modules/games/README.md new file mode 100644 index 0000000..4f3f6c1 --- /dev/null +++ b/modules/games/README.md @@ -0,0 +1,37 @@ +## Word of the Day Game โ€” Rules & Features + +- **Word of the Day:** + Each day, a new word is chosen from `data/wotd.json` (or a default list if missing). Mention the word (or its leet/1337 variants) in chat to win and trigger a new word. +- **Bingo Mini-Game:** + A random 3x3 bingo card of words, drawn from `data/bingo.json` (or a default list if missing). Mention words from the card in chat. Complete a row, column, or diagonal to win BINGO and get a new card. +- **Emoji Mini-Game:** + Use emojis in chat to: + - Play a slot machine: send the same emoji several times in a row to hit the JACKPOT! +- **Data Files:** + - `data/wotd.json`: List of words and definitions for the Word of the Day. +[ + { + "word": "serendipity", + "meta": "The occurrence of events by chance in a happy or beneficial way." + }, + { + "word": "ephemeral", + "meta": "Lasting for a very short time." + }, + { + "word": "sonder", + "meta": "The realization that each passerby has a life as vivid and complex as your own." + } +] + - `data/bingo.json`: List of animal words for bingo cards. +[ + "dog", + "cat", + "fish", + "bird", + "hamster", + "rabbit", + "turtle", + "lizard", + "snake" +] \ No newline at end of file diff --git a/modules/games/tictactoe.py b/modules/games/tictactoe.py index 700a5ae..0d46709 100644 --- a/modules/games/tictactoe.py +++ b/modules/games/tictactoe.py @@ -5,7 +5,7 @@ from modules.log import * import random import time -# to molly and jake, I miss you both so much. +# to (max), molly and jake, I miss you both so much. if disable_emojis_in_games: X = "X" diff --git a/modules/games/wodt.py b/modules/games/wodt.py new file mode 100644 index 0000000..fa5bffa --- /dev/null +++ b/modules/games/wodt.py @@ -0,0 +1,297 @@ +#python word of the day game module for meshing-around bot +from modules.log import * +import random +import json +import os +import time +from itertools import product + +class WordOfTheDayGame: + def __init__(self): + default_word_list = [ + {'word': 'serendipity', 'meta': 'The occurrence of events by chance in a happy or beneficial way.'}, + {'word': 'ephemeral', 'meta': 'Lasting for a very short time.'}, + {'word': 'sonder', 'meta': 'The realization that each passerby has a life as vivid and complex as your own.'}, + {'word': 'petrichor', 'meta': 'A pleasant smell that frequently accompanies the first rain after a long period of warm, dry weather.'}, + ] + json_path = os.path.join('data', 'wotd.json') + if os.path.exists(json_path): + try: + with open(json_path, 'r') as f: + self.word_list = json.load(f) + except FileNotFoundError: + logger.debug("System: WoTd: Failed to load data/wotd.json, using default word list.") + self.word_list = default_word_list + except json.JSONDecodeError: + logger.warning("System: WoTd: JSON decode error in data/wotd.json, example format: [{\"word\": \"example\", \"definition\": \"An example definition.\"}]") + self.word_list = default_word_list + else: + logger.debug("System: WoTd: data/wotd.json not found, using default word list.") + self.word_list = default_word_list + + # Load bingo card words from JSON if available + default_bingo_card = [ + "dog", "cat", "fish", "bird", "hamster", "rabbit", "turtle", "lizard", "snake", "frog", + "horse", "cow", "pig", "sheep", "goat", "chicken", "duck", "turkey", "peacock", "parrot", + "elephant", "lion", "tiger", "bear", "wolf", "fox", "deer", "moose", "zebra", "giraffe", + "monkey", "ape", "chimpanzee", "gorilla", "orangutan", "kangaroo", "koala", "panda", + "whale", "dolphin", "shark", "octopus", "crab", "lobster", "jellyfish", "seahorse", + "ant", "bee", "butterfly", "dragonfly", "spider", "ladybug" + ] + bingo_json_path = os.path.join('data', 'bingo.json') + if os.path.exists(bingo_json_path): + try: + with open(bingo_json_path, 'r') as f: + bingoCard = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + logger.debug("System: WoTd: Failed to load data/bingo.json, using default bingo card. example format: [\"word1\", \"word2\", ...]") + bingoCard = default_bingo_card + else: + logger.debug("System: WoTd: data/bingo.json not found, using default bingo card.") + bingoCard = default_bingo_card + + # Create a set for faster lookup + self.bingoCardSet = set(bingoCard) + + self.leet_dict = { + 'a': ['4', '@'], + 'b': ['8'], + 'e': ['3'], + 'i': ['1', '!', '|'], + 'l': ['1', '|', '7'], + 'o': ['0'], + 's': ['5', '$'], + 't': ['7', '+'], + 'g': ['9', '6'], + } + # Initialize the word of the day + self.word_of_the_day_entry = random.choice(self.word_list) + logger.debug(f"System: WoTd: Initialized with word of the day '{self.word_of_the_day_entry['word']}'.") + # Initialize bingo card + self.generate_bingo_card(3) + logger.debug("System: BINGO: " + ". ".join(" | ".join(row) for row in self.bingo_card)) + + def get_emoji_type(self, emoji, randomReturn=False): + smileys = "๐Ÿ˜€๐Ÿ˜๐Ÿ˜‚๐Ÿคฃ๐Ÿ˜ƒ๐Ÿ˜„๐Ÿ˜…๐Ÿ˜†๐Ÿ˜‰๐Ÿ˜Š๐Ÿ˜‹๐Ÿ˜Ž๐Ÿ˜๐Ÿ˜˜๐Ÿฅฐ๐Ÿ˜—๐Ÿ˜™๐Ÿ˜š๐Ÿ™‚๐Ÿค—๐Ÿคฉ๐Ÿค”๐Ÿคจ๐Ÿ˜๐Ÿ˜‘๐Ÿ˜ถ๐Ÿ™„๐Ÿ˜๐Ÿ˜ฃ๐Ÿ˜ฅ๐Ÿ˜ฎ๐Ÿค๐Ÿ˜ฏ๐Ÿ˜ช๐Ÿ˜ซ๐Ÿ˜ด๐Ÿ˜Œ๐Ÿ˜›๐Ÿ˜œ๐Ÿ˜๐Ÿคค๐Ÿ˜’๐Ÿ˜“๐Ÿ˜”๐Ÿ˜•๐Ÿ™ƒ๐Ÿค‘๐Ÿ˜ฒโ˜น๏ธ๐Ÿ™๐Ÿ˜–๐Ÿ˜ž๐Ÿ˜Ÿ๐Ÿ˜ค๐Ÿ˜ข๐Ÿ˜ญ๐Ÿ˜ฆ๐Ÿ˜ง๐Ÿ˜จ๐Ÿ˜ฉ๐Ÿคฏ๐Ÿ˜ฌ๐Ÿ˜ฐ๐Ÿ˜ฑ๐Ÿฅต๐Ÿฅถ๐Ÿ˜ณ๐Ÿคช๐Ÿ˜ต๐Ÿ˜ก๐Ÿ˜ ๐Ÿคฌ๐Ÿ˜ท๐Ÿค’๐Ÿค•๐Ÿคข๐Ÿคฎ๐Ÿคง๐Ÿ˜‡๐Ÿฅณ๐Ÿฅบ๐Ÿค " + animals = "๐Ÿถ๐Ÿฑ๐Ÿญ๐Ÿน๐Ÿฐ๐ŸฆŠ๐Ÿป๐Ÿผ๐Ÿจ๐Ÿฏ๐Ÿฆ๐Ÿฎ๐Ÿท๐Ÿฝ๐Ÿธ๐Ÿต๐Ÿ™ˆ๐Ÿ™‰๐Ÿ™Š๐Ÿ’๐Ÿ”๐Ÿง๐Ÿฆ๐Ÿค๐Ÿฃ๐Ÿฅ๐Ÿฆ†๐Ÿฆ…๐Ÿฆ‰๐Ÿฆ‡๐Ÿบ๐Ÿ—๐Ÿด๐Ÿฆ„๐Ÿ๐Ÿ›๐Ÿฆ‹๐ŸŒ๐Ÿž๐Ÿœ๐ŸฆŸ๐Ÿฆ—๐Ÿ•ท๏ธ๐Ÿ•ธ๏ธ๐Ÿข๐Ÿ๐ŸฆŽ๐Ÿฆ‚๐Ÿฆ€๐Ÿฆž๐Ÿฆ๐Ÿฆ‘๐Ÿ™๐Ÿฆ‘๐Ÿ ๐ŸŸ๐Ÿก๐Ÿฌ๐Ÿฆˆ๐Ÿณ๐Ÿ‹๐ŸŠ๐Ÿ…๐Ÿ†๐Ÿฆ“๐Ÿฆ๐Ÿฆง๐Ÿ˜๐Ÿฆ›๐Ÿฆ๐Ÿช๐Ÿซ๐Ÿฆ’๐Ÿฆ˜๐Ÿƒ๐Ÿ‚๐Ÿ„๐ŸŽ๐Ÿ–๐Ÿ๐Ÿ‘๐Ÿฆ™๐Ÿ๐ŸฆŒ๐Ÿ•๐Ÿฉ๐Ÿฆฎ๐Ÿ•โ€๐Ÿฆบ๐Ÿˆ๐Ÿ“๐Ÿฆƒ๐Ÿฆš๐Ÿฆœ๐Ÿฆข๐Ÿฆฉ๐Ÿ•Š๏ธ๐Ÿ‡๐Ÿฆ๐Ÿฆจ๐Ÿฆก๐Ÿฆฆ๐Ÿฆฅ๐Ÿ๐Ÿ€๐Ÿฟ๏ธ๐Ÿฆ”" + fruit = "๐ŸŽ๐ŸŠ๐ŸŒ๐Ÿ‰๐Ÿ‡๐Ÿ“๐Ÿ’๐Ÿ‘๐Ÿฅญ๐Ÿ๐Ÿฅฅ๐Ÿฅ๐Ÿ…๐Ÿฅ‘๐Ÿ†๐Ÿฅ”๐Ÿฅ•๐ŸŒฝ๐ŸŒถ๏ธ๐Ÿฅ’๐Ÿฅฌ๐Ÿฅฆ๐Ÿง„๐Ÿง…๐Ÿ„๐Ÿฅœ๐ŸŒฐ" + categories = {'smileys': smileys, 'animals': animals, 'fruit': fruit} + if randomReturn: + cat = random.choice(list(categories)) + return random.choice(categories[cat]) + for cat, chars in categories.items(): + if emoji in chars: + return cat + return False + + def reset_word_of_the_day(self): + logger.debug("System: WoTd: Resetting Word of the Day.") + self.word_of_the_day_entry = random.choice(self.word_list) + + def generate_leet_variants(self, word): + chars = [] + for c in word.lower(): + if c in self.leet_dict: + chars.append([c] + self.leet_dict[c]) + else: + chars.append([c]) + variants = set() + for combo in product(*chars): + variant = ''.join(combo) + variants.add(variant) + if len(variants) > 128: + break + return variants + + def did_it_happen(self, string_of_text=''): + """ + Check if the current word of the day (or its leet variants) appears in the text. + Also check for a bingo win. + Returns: + (wotd_found, old_entry, new_entry, bingo_win, bingo_message) + """ + text = string_of_text.lower() + words_in_text = set(text.split()) + word = self.word_of_the_day_entry['word'].lower() + variants = self.generate_leet_variants(word) + wotd_found = False + old_entry = None + new_entry = None + + for variant in variants: + if variant in words_in_text: + old_entry = self.word_of_the_day_entry + self.reset_word_of_the_day() + new_entry = self.word_of_the_day_entry + wotd_found = True + break + + bingo_win, bingo_message = self.b_i_n_g_o(string_of_text) + return wotd_found, old_entry, new_entry, bingo_win, bingo_message + + def generate_bingo_card(self, size=9): + """ + Generate a random bingo card of given size (size x size) from the bingoCardSet. + Returns a 2D list representing the bingo card. + """ + words = random.sample(list(self.bingoCardSet), size * size) + card = [words[i*size:(i+1)*size] for i in range(size)] + self.bingo_card = card + self.bingo_card_matches = [[False]*size for _ in range(size)] + return card + + def b_i_n_g_o(self, string_of_text=''): + """ + Check if any words in the text match the bingo card. + If a row, column, or diagonal is fully matched, return True and the winning line. + Otherwise, return False and None. + """ + if not hasattr(self, 'bingo_card'): + logger.debug("System: WoTd: Generating new bingo card.") + self.generate_bingo_card(3) + + words_in_text = set(string_of_text.lower().split()) + size = len(self.bingo_card) + # Mark matches + for i in range(size): + for j in range(size): + if self.bingo_card[i][j].lower() in words_in_text: + self.bingo_card_matches[i][j] = True + + # Check rows + for i in range(size): + if all(self.bingo_card_matches[i]): + winning_row = self.bingo_card[i] + logger.debug("System: BINGO achieved, generating new bingo card.") + self.generate_bingo_card(size) # Reset board after win + return True, f"BINGO! Row {i+1}: {winning_row}" + + # Check columns + for j in range(size): + if all(self.bingo_card_matches[i][j] for i in range(size)): + col = [self.bingo_card[i][j] for i in range(size)] + return True, f"BINGO! Column {j+1}: {col}" + + # Check diagonals + if all(self.bingo_card_matches[i][i] for i in range(size)): + diag = [self.bingo_card[i][i] for i in range(size)] + return True, f"BINGO! Diagonal: {diag}" + if all(self.bingo_card_matches[i][size-1-i] for i in range(size)): + diag = [self.bingo_card[i][size-1-i] for i in range(size)] + return True, f"BINGO! Diagonal: {diag}" + + return False, None + + def extract_emojis(self, text): + emojis = [] + for char in text: + cp = ord(char) + # Common emoji Unicode ranges + if ( + 0x1F600 <= cp <= 0x1F64F or # Emoticons + 0x1F300 <= cp <= 0x1F5FF or # Symbols & pictographs + 0x1F680 <= cp <= 0x1F6FF or # Transport & map symbols + 0x1F1E6 <= cp <= 0x1F1FF or # Regional indicator symbols + 0x2600 <= cp <= 0x26FF or # Misc symbols + 0x2700 <= cp <= 0x27BF or # Dingbats + 0x1F900 <= cp <= 0x1F9FF or # Supplemental symbols & pictographs + 0x1FA70 <= cp <= 0x1FAFF or # Symbols & pictographs extended-A + 0x2B50 == cp or # Star + 0x2B55 == cp # Heavy large circle + ): + emojis.append(char) + return emojis + + def emojiMiniGame(self, string_of_text='', nodeID=0, nodeInt=1, emojiSeen=False): + from modules.system import meshLeaderboard + """ + Track emoji usage, Returns a string if the mini-game is won, else None. + If emojiSeen is False, only update mostMessages leaderboard and skip emoji logic. + """ + + # --- Always update mostMessages leaderboard --- + if 'nodeMessageCounts' in meshLeaderboard and meshLeaderboard['nodeMessageCounts']: + max_node = max(meshLeaderboard['nodeMessageCounts'], key=meshLeaderboard['nodeMessageCounts'].get) + meshLeaderboard['mostMessages'] = { + 'nodeID': max_node, + 'value': meshLeaderboard['nodeMessageCounts'][max_node], + 'timestamp': time.time() + } + + + emoji = None # Placeholder: extract emoji from string_of_text if needed + emojis = self.extract_emojis(string_of_text) + if not emojiSeen and not emojis: + return None + logger.debug(f"System: WoTd: Emoji mini-game processing for nodeID {nodeID} with emojis: {emojis}") + # --- 1. Update meshLeaderboard for emoji usage --- + if 'emojiCounts' not in meshLeaderboard: + meshLeaderboard['emojiCounts'] = {} + if 'emojiTypeCounts' not in meshLeaderboard: + meshLeaderboard['emojiTypeCounts'] = {} + + meshLeaderboard['emojiCounts'].setdefault(nodeID, {}) + for emoji in emojis: + meshLeaderboard['emojiCounts'][nodeID][emoji] = meshLeaderboard['emojiCounts'][nodeID].get(emoji, 0) + 1 + + # --- Update the leaderboard record for most emojis --- + # Flatten per-node emoji counts to total per node + emoji_totals = {nid: sum(emojicounts.values()) for nid, emojicounts in meshLeaderboard['emojiCounts'].items() if isinstance(emojicounts, dict)} + if emoji_totals: + max_node = max(emoji_totals, key=emoji_totals.get) + meshLeaderboard['emojiCounts']['nodeID'] = max_node + meshLeaderboard['emojiCounts']['value'] = emoji_totals[max_node] + meshLeaderboard['emojiCounts']['timestamp'] = time.time() + + # --- 2. Track most used of a type (e.g., smileys, animals, etc.) --- + emoji_type = self.get_emoji_type(emoji) + meshLeaderboard['emojiTypeCounts'].setdefault(emoji_type, {}) + meshLeaderboard['emojiTypeCounts'][emoji_type][emoji] = meshLeaderboard['emojiTypeCounts'][emoji_type].get(emoji, 0) + 1 + + # --- 3. Slot machine mini-game --- + if 'emojiSlotWindow' not in meshLeaderboard: + meshLeaderboard['emojiSlotWindow'] = [] + meshLeaderboard['emojiSlotWindow'].append(emoji) + # Randomize jackpot length after each win + if not hasattr(self, 'slot_jackpot_length'): + self.slot_jackpot_length = random.choice([3,4,5]) # JackPot length can be 3, 4, or 5 + if len(meshLeaderboard['emojiSlotWindow']) > self.slot_jackpot_length: + meshLeaderboard['emojiSlotWindow'].pop(0) + + # --- 3a. Detect spam of 3 identical emojis in a row --- + if len(meshLeaderboard['emojiSlotWindow']) >= 5: + last_three = meshLeaderboard['emojiSlotWindow'][-3:] + if len(set(last_three)) == 1: + # Option: Randomly add an emoji to break the spam + random_emoji = self.get_emoji_type('', randomReturn=True) + meshLeaderboard['emojiSlotWindow'].append(random_emoji) + logger.debug(f"System: WoTd: Detected emoji spam, added random emoji '{random_emoji}' to slot window.") + # Optionally, you can still scramble or pop as well if you want + random.shuffle(meshLeaderboard['emojiSlotWindow']) + meshLeaderboard['emojiSlotWindow'].pop() + + # # Debug: Show slot window status before jackpot check + # logger.debug( + # f"Emoji Slot Window: {meshLeaderboard['emojiSlotWindow']} | " + # f"Jackpot Length: {self.slot_jackpot_length} | " + # f"Unique: {set(meshLeaderboard['emojiSlotWindow'])} | " + # f"Needed: {self.slot_jackpot_length - len(meshLeaderboard['emojiSlotWindow'])}" + # ) + + # Jackpot: all emojis in window are the same + if ( + len(meshLeaderboard['emojiSlotWindow']) == self.slot_jackpot_length and + len(set(meshLeaderboard['emojiSlotWindow'])) == 1 + ): + winner_msg = f"๐ŸŽฐ JACKPOT! {emoji * self.slot_jackpot_length}" + meshLeaderboard['emojiSlotWindow'] = [] + self.slot_jackpot_length = random.choice([3, 4, 5]) # Randomize jackpot length after win + return winner_msg + + return None + +# Example usage: +# theWordOfTheDay = WordOfTheDayGame() +# happened, entry = theWordOfTheDay.did_it_happen("I love serendipity!") +# if happened: +# print(f"Found the word of the day: {entry['word']} - {entry['meta']}") \ No newline at end of file diff --git a/modules/system.py b/modules/system.py index ce8a1f8..3708598 100644 --- a/modules/system.py +++ b/modules/system.py @@ -217,7 +217,7 @@ if quiz_enabled: from modules.games.quiz import * # from the spudgunman/meshing-around repo trap_list = trap_list + trap_list_quiz # items quiz, q: help_message = help_message + ", quiz" - games_enabled = True + # games not enabled for quiz if survey_enabled: from modules.survey import * # from the spudgunman/meshing-around repo @@ -225,6 +225,11 @@ if survey_enabled: help_message = help_message + ", survey" games_enabled = True +if wordOfTheDay: + from modules.games.wodt import WordOfTheDayGame # from the spudgunman/meshing-around repo + theWordOfTheDay = WordOfTheDayGame() + # this runs in background and wont enable other games + # Games Configuration if games_enabled is True: help_message = help_message + ", games" @@ -1361,6 +1366,7 @@ def initializeMeshLeaderboard(): 'coldestTemp': {'nodeID': None, 'value': 999, 'timestamp': 0}, # ๐Ÿฅถ 'hottestTemp': {'nodeID': None, 'value': -999, 'timestamp': 0}, # ๐Ÿฅต 'worstAirQuality': {'nodeID': None, 'value': 0, 'timestamp': 0}, # ๐Ÿ’จ + 'mostTMessages': {'nodeID': None, 'value': 0, 'timestamp': 0}, # ๐Ÿ’ฌ 'mostMessages': {'nodeID': None, 'value': 0, 'timestamp': 0}, # ๐Ÿ’ฌ 'highestDBm': {'nodeID': None, 'value': -999, 'timestamp': 0}, # ๐Ÿ“ถ 'weakestDBm': {'nodeID': None, 'value': 999, 'timestamp': 0}, # ๐Ÿ“ถ @@ -1370,7 +1376,10 @@ def initializeMeshLeaderboard(): 'adminPackets': [], # ๐Ÿšจ 'tunnelPackets': [], # ๐Ÿšจ 'audioPackets': [], # โ˜Ž๏ธ - 'simulatorPackets': [] # ๐Ÿค– + 'simulatorPackets': [], # ๐Ÿค– + 'emojiCounts': {}, # Track emoji counts per node + 'emojiTypeCounts': {}, # Track emoji type counts + 'nodeMessageCounts': {} # Track total message counts per node } initializeMeshLeaderboard() @@ -1393,10 +1402,10 @@ def consumeMetadata(packet, rxNode=0, channel=-1): node_message_count[nodeID] = node_message_count.get(nodeID, 0) + 1 meshLeaderboard['nodeMessageCounts'] = node_message_count - if node_message_count[nodeID] > meshLeaderboard['mostMessages']['value']: - meshLeaderboard['mostMessages']['value'] = node_message_count[nodeID] - meshLeaderboard['mostMessages']['nodeID'] = nodeID - meshLeaderboard['mostMessages']['timestamp'] = time.time() + if node_message_count[nodeID] > meshLeaderboard['mostTMessages']['value']: + meshLeaderboard['mostTMessages']['value'] = node_message_count[nodeID] + meshLeaderboard['mostTMessages']['nodeID'] = nodeID + meshLeaderboard['mostTMessages']['timestamp'] = time.time() # consider Meta for highest and weakest DBm if packet.get('rxSnr') is not None: @@ -1759,11 +1768,11 @@ def saveLeaderboard(): def loadLeaderboard(): global meshLeaderboard try: - defaults = {} initializeMeshLeaderboard() + defaults = meshLeaderboard.copy() with open('data/leaderboard.pkl', 'rb') as f: - meshLeaderboard = pickle.load(f) - defaults.update(meshLeaderboard) # loaded values overwrite defaults + loaded = pickle.load(f) + defaults.update(loaded) # loaded values overwrite defaults meshLeaderboard = defaults if logMetaStats: logger.debug("System: Mesh Leaderboard loaded from leaderboard.pkl") @@ -1855,10 +1864,22 @@ def get_mesh_leaderboard(msg, fromID, deviceID): result += f"๐Ÿ“ถ Best RF: {value} dBm {get_name_from_number(nodeID, 'short', 1)}\n" # Most Telemetry Messages + if 'nodeMessageCounts' in meshLeaderboard and meshLeaderboard['mostTMessages']['nodeID'] is not None: + nodeID = meshLeaderboard['mostTMessages']['nodeID'] + value = meshLeaderboard['mostTMessages']['value'] + result += f"๐Ÿ’ฌ Most Telemetry: {value} {get_name_from_number(nodeID, 'short', 1)}\n" + + # Most Emojis + if 'nodeMessageCounts' in meshLeaderboard and meshLeaderboard['emojiCounts']['nodeID'] is not None: + nodeID = meshLeaderboard['emojiCounts']['nodeID'] + value = meshLeaderboard['emojiCounts']['value'] + result += f"๐Ÿคช Most Emojis: {value} {get_name_from_number(nodeID, 'short', 1)}\n" + + # Most Messages if 'nodeMessageCounts' in meshLeaderboard and meshLeaderboard['mostMessages']['nodeID'] is not None: nodeID = meshLeaderboard['mostMessages']['nodeID'] value = meshLeaderboard['mostMessages']['value'] - result += f"๐Ÿ’ฌ Most Telemetry: {value} {get_name_from_number(nodeID, 'short', 1)}\n" + result += f"๐Ÿ’ฌ Most Messages: {value} {get_name_from_number(nodeID, 'short', 1)}\n" # Most WiFi devices seen if meshLeaderboard.get('mostPaxWiFi', {}).get('nodeID'):