def play_sound(sound): """Play the sound given""" try: playsound(sound) except Exception as e: eqa_settings.log("play sound: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e))
def process(exit_flag, log_q, action_q): """ Process: log_q Produce action_q """ try: while not exit_flag.is_set(): time.sleep(0.001) if not log_q.empty(): # Read raw log line log_line = log_q.get() log_q.task_done() # Strip line of trailing space and lowercase everything line = log_line.strip() # Split timestamp and message payload timestamp, payload = line[1:].split("] ", 1) timestamp = timestamp.split(" ")[3] + ".00" # Determine line type line_type = determine(payload) # Build and queue action new_message = eqa_struct.message( timestamp, line_type, "null", "null", payload.lower() ) action_q.put(new_message) except Exception as e: eqa_settings.log( "process_log: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e) )
def callback(event): try: if event.mask == pyinotify.IN_CLOSE_WRITE: log_q.put(eqa_parser.last_line(char_log)) except Exception as e: eqa_settings.log("log watch callback: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e))
def draw_chars(stdscr, chars, char, selected): """Draw character selection component for settings""" try: y, x = stdscr.getmaxyx() charscr_width = int(x / 3) # Pending general scrolling method charscr_height = len(chars) + 2 charscr = stdscr.derwin(charscr_height, charscr_width, 5, 3) charscr.clear() charscr.box() count = 0 while count < len(chars): char_name, char_server = chars[count].split("_") if selected == count: charscr.addstr( len(chars) - count, 2, char_name + " " + char_server, curses.color_pair(1), ) else: charscr.addstr( len(chars) - count, 2, char_name + " " + char_server, curses.color_pair(2), ) count += 1 except Exception as e: eqa_settings.log("draw chars: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e))
def draw_state(stdscr, state, raid): """Draw state""" y, x = stdscr.getmaxyx() center_y = int(y / 2) center_x = int(x / 2) # Clear and box stdscr.clear() stdscr.box() # Draw tabs draw_tabs(stdscr, "state") # Show some state try: # server state stdscr.addstr(5, 5, "Server", curses.color_pair(2)) stdscr.addstr(5, 16, ": ", curses.color_pair(1)) stdscr.addstr(5, 18, state.server.title(), curses.color_pair(3)) # char stdscr.addstr(7, 5, "Character", curses.color_pair(2)) stdscr.addstr(7, 16, ": ", curses.color_pair(1)) stdscr.addstr(7, 18, state.char.title(), curses.color_pair(3)) # zone stdscr.addstr(9, 5, "Zone", curses.color_pair(2)) stdscr.addstr(9, 16, ": ", curses.color_pair(1)) stdscr.addstr(9, 18, state.zone.title(), curses.color_pair(3)) # loc stdscr.addstr(11, 5, "Location", curses.color_pair(2)) stdscr.addstr(11, 16, ": ", curses.color_pair(1)) stdscr.addstr(11, 18, str(state.loc[0]), curses.color_pair(3)) stdscr.addstr(11, 24, " : ", curses.color_pair(2)) stdscr.addstr(11, 26, str(state.loc[1]), curses.color_pair(3)) stdscr.addstr(11, 32, " : ", curses.color_pair(2)) stdscr.addstr(11, 34, str(state.loc[2]), curses.color_pair(3)) # direction stdscr.addstr(13, 5, "Direction", curses.color_pair(2)) stdscr.addstr(13, 16, ": ", curses.color_pair(1)) stdscr.addstr(13, 18, state.direction.title(), curses.color_pair(3)) # raid state stdscr.addstr(15, 5, "Raid", curses.color_pair(2)) stdscr.addstr(15, 16, ": ", curses.color_pair(1)) if not raid.is_set(): stdscr.addstr(15, 18, "False", curses.color_pair(3)) else: stdscr.addstr(15, 18, "True", curses.color_pair(3)) # afk state stdscr.addstr(17, 5, "AFK", curses.color_pair(2)) stdscr.addstr(17, 16, ": ", curses.color_pair(1)) stdscr.addstr(17, 18, state.afk.title(), curses.color_pair(3)) except Exception as e: eqa_settings.log("draw state: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e))
def last_line(character_log): """Reads and returns last line""" try: with open(character_log, "r") as f: content = deque(f, 1) return content[0] except Exception as e: eqa_settings.log("last_line: " + str(e))
def speak(phrase, play, sound_file_path): """Play a spoken phrase""" try: if not os.path.exists(sound_file_path + phrase + ".wav"): tts = gtts.gTTS(text=phrase, lang="en") tts.save(sound_file_path + phrase + ".wav") if play == "true": play_sound(sound_file_path + phrase + ".wav") except Exception as e: eqa_settings.log("speak: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e))
def read(exit_flag, keyboard_q, stdscr): """ Consume: keyboard events Produce: keyboard_q """ key = "" try: while not exit_flag.is_set(): key = stdscr.getch() keyboard_q.put(key) sys.exit() except Exception as e: eqa_settings.log("read keys: " + str(e)) sys.exit()
def draw_page(stdscr, page, events, state, setting, selected_char, raid): y, x = stdscr.getmaxyx() try: if x >= 80 and y >= 40: if page == "events": draw_events_frame(stdscr, state.char, state.zone, events) elif page == "state": draw_state(stdscr, state, raid) elif page == "settings": draw_settings(stdscr, state, setting, selected_char) elif page == "help": draw_help(stdscr) else: draw_toosmall(stdscr) except Exception as e: eqa_settings.log("draw_page: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e))
def draw_events(stdscr, events): """Draw events window component of events""" y, x = stdscr.getmaxyx() center_y = int(y / 2) bottom_y = center_y - 4 top_y = 2 eventscr = stdscr.derwin(center_y - 3, x - 4, 3, 2) eventscr.clear() try: count = 0 while count < (bottom_y + 1) and count < len(events): event_num = len(events) - count - 1 event = events[(count * -1) - 1] c_y = bottom_y - count draw_ftime(eventscr, event.timestamp, c_y) eventscr.addch(c_y, 14, curses.ACS_VLINE) eventscr.addstr(c_y, 16, str(event.payload), curses.color_pair(1)) count += 1 except Exception as e: eqa_settings.log("draw events: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e))
def process(config, sound_q, exit_flag, cfg_reload): """ Process: sound_q Produce: sound event """ tmp_sound_file_path = "/tmp/eqa/sound/" if not os.path.exists(tmp_sound_file_path): os.makedirs(tmp_sound_file_path) try: while not exit_flag.is_set() and not cfg_reload.is_set(): time.sleep(0.001) if not sound_q.empty(): sound_event = sound_q.get() sound_q.task_done() if sound_event.sound == "speak": speak(sound_event.payload, "true", tmp_sound_file_path) elif sound_event.sound == "alert": alert(config, sound_event.payload) else: speak(sound_event.payload, "true", tmp_sound_file_path) display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "[Malformed sound event] " + sound_event.sound, )) except Exception as e: eqa_settings.log("process_sound: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e)) sys.exit() sys.exit()
def determine(line): """Determine type of line""" try: # Melee Combat if ( re.fullmatch( r"^[a-zA-Z\s]+ (hits|crushes|slashes|pierces|bashes|backstabs|bites|kicks|claws|gores|punches|strikes|slices) [a-zA-Z\s]+ for \d+ points of damage\.", line, ) is not None ): line_type = "combat_other_melee" elif ( re.fullmatch( r"^[a-zA-Z\s]+ tries to (hit|crush|slash|pierce|bash|backstab|bite|kick|claw|gore|punch|strike|slice) [a-zA-Z\s]+, but misses\!", line, ) is not None ): line_type = "combat_other_melee_miss" elif ( re.fullmatch( r"^[a-zA-Z\s]+ (hits|crushes|slashes|pierces|bashes|backstabs|bites|kicks|claws|gores|punches|strikes|slices) you for \d+ points of damage\.", line, ) is not None ): line_type = "combat_you_receive_melee" elif ( re.fullmatch( r"^You (hit|crush|slash|pierce|bash|backstab|bite|kick|claw|gore|punch|strike|slice) [a-zA-Z\s]+ for \d+ points of damage\.", line, ) is not None ): line_type = "combat_you_melee" elif ( re.fullmatch( r"^You try to (hit|crush|slash|pierce|bash|backstab|bite|kick|claw|gore|punch|strike|slice) [a-zA-Z\s]+, but miss\!", line, ) is not None ): line_type = "combat_you_melee_miss" # Player Messages elif re.fullmatch(r"^\w+ tells you, \'.+\'$", line) is not None: line_type = "tell" elif re.fullmatch(r"^\w+ says, \'.+\'$", line) is not None: line_type = "say" elif re.fullmatch(r"^\w+ shouts, \'.+\'$", line) is not None: line_type = "shout" elif re.fullmatch(r"^\w+ tells the guild, \'.+\'$", line) is not None: line_type = "guild" elif re.fullmatch(r"^\w+ tells the group, \'.+\'$", line) is not None: line_type = "group" elif re.fullmatch(r"^\w+ says out of character, \'.+\'$", line) is not None: line_type = "ooc" elif ( re.fullmatch(r"^\w+ auctions, \'(.+|)(WTS|selling|Selling)(.+|)\'$", line) is not None ): line_type = "auction_wts" elif ( re.fullmatch(r"^\w+ auctions, \'(.+|)(WTB|buying|Buying)(.+|)\'$", line) is not None ): line_type = "auction_wtb" elif re.fullmatch(r"^\w+ auctions, \'.+\'$", line) is not None: line_type = "auction" elif re.fullmatch(r"^You told \w+, \'.+\'$", line) is not None: line_type = "you_tell" elif re.fullmatch(r"^You say, \'.+\'$", line) is not None: line_type = "you_say" elif re.fullmatch(r"^You shout, \'.+\'$", line) is not None: line_type = "you_shout" elif re.fullmatch(r"^You say to your guild, \'.+\'$", line) is not None: line_type = "you_guild" elif re.fullmatch(r"^You tell your party, \'.+\'$", line) is not None: line_type = "you_group" elif re.fullmatch(r"^You say out of character, \'.+\'$", line) is not None: line_type = "you_ooc" elif re.fullmatch(r"^You auction, \'.+\'$", line) is not None: line_type = "you_auction" # Player Status elif ( re.fullmatch( r"^Your Location is [-]?(?:\d*\.)?\d+\,\ [-]?(?:\d*\.)?\d+\,\ [-]?(?:\d*\.)?\d+$", line, ) is not None ): line_type = "location" elif ( re.fullmatch( r"^You think you are heading (?:North(?:East|West)?|South(?:East|West)?|(?:Ea|We)st)\.$", line, ) is not None ): line_type = "direction" elif ( re.fullmatch(r"^You have no idea what direction you are facing\.$", line) is not None ): line_type = "direction_miss" elif re.fullmatch(r"^You have entered .+\.$", line) is not None: line_type = "you_new_zone" elif ( re.fullmatch(r"^You have healed .+ for \d+ points of damage\.$", line) is not None ): line_type = "you_healed" elif ( re.fullmatch(r"^You are now A\.F\.K\. \(Away From Keyboard\)\.", line) is not None ): line_type = "you_afk_on" elif re.fullmatch(r"^You are now Looking For a Group\.", line) is not None: line_type = "you_lfg_on" elif ( re.fullmatch(r"^You are no longer A\.F\.K\. \(Away From Keyboard\)\.", line) is not None ): line_type = "you_afk_off" elif ( re.fullmatch(r"^You are no longer Looking For a Group\.", line) is not None ): line_type = "you_lfg_off" elif re.fullmatch(r"^You are out of food\.", line) is not None: line_type = "you_outfood" elif re.fullmatch(r"^You are out of drink\.", line) is not None: line_type = "you_outdrink" elif re.fullmatch(r"^You are out of food and drink\.", line) is not None: line_type = "you_outfooddrink" elif re.fullmatch(r"^You are out of food and low on drink\.", line) is not None: line_type = "you_outfoodlowdrink" elif re.fullmatch(r"^You are out of drink and low on food\.", line) is not None: line_type = "you_outdrinklowfood" elif re.fullmatch(r"^You are thirsty\.", line) is not None: line_type = "you_thirsty" elif re.fullmatch(r"^You are hungry\.", line) is not None: line_type = "you_hungry" # Spells elif re.fullmatch(r"^You forget .+\.", line) is not None: line_type = "you_spell_forget" elif re.fullmatch(r"^\w+\'s spell fizzles\!$", line) is not None: line_type = "spell_fizzle" elif re.fullmatch(r"^Your spell is interrupted\.", line) is not None: line_type = "you_spell_fizzle" elif re.fullmatch(r"^\w+\'s casting is interrupted\!\.", line) is not None: line_type = "spell_fizzle" elif re.fullmatch(r"^Your target resisted the .+ spell\.$", line) is not None: line_type = "spell_resist" elif ( re.fullmatch( r"^.+ w(?:ere|as) hit by non-melee for \d+ ?(points of) damage\.$", line ) is not None ): line_type = "spell_damage" elif re.fullmatch(r"^\w+ begins to regenerate\.$", line) is not None: line_type = "spell_regen" elif re.fullmatch(r"^Your charm spell has worn off\.$", line) is not None: line_type = "spell_break_charm" elif re.fullmatch(r"^Your Ensnare spell has worn off\.$", line) is not None: line_type = "spell_break_ensnare" # Emotes elif re.fullmatch(r"^\w+ bows before \w+\.$", line) is not None: line_type = "emote_bow" elif re.fullmatch(r"^\w+ thanks \w+ heartily\.$", line) is not None: line_type = "emote_thank" elif re.fullmatch(r"^\w+ waves at \w+\.$", line) is not None: line_type = "emote_wave" elif ( re.fullmatch( r"^\w+ grabs hold of \w+ and begins to dance with (?:h(?:er|im)|it)\.$", line, ) is not None ): line_type = "emote_dance" elif re.fullmatch(r"^\w+ bonks \w+ on the head\!$", line) is not None: line_type = "emote_bonk" elif re.fullmatch(r"^\w+ beams a smile at (a|) \w+$", line) is not None: line_type = "emote_smile" elif re.fullmatch(r"^\w+ cheers at \w+$", line) is not None: line_type = "emote_cheer" # World Status elif re.fullmatch(r"^It begins to rain\.$", line) is not None: line_type = "weather_start_rain" elif re.fullmatch(r"^It begins to snow\.$", line) is not None: line_type = "weather_start_snow" elif re.fullmatch(r"^Players in EverQuest\:$", line) is not None: line_type = "who_top" elif re.fullmatch(r"^---------------------------------$", line) is not None: line_type = "who_line" elif ( re.fullmatch( r"^\[\d+ (?:(?:(?:Shadow )?Knigh|Hierophan|Revenan)t|(?:Elemental|Phantasm)ist|High Priest|Illusionist|(?:Grandmast|P(?:athfind|reserv)|C(?:hannel|avali)|(?:Enchan|Mas)t|(?:Begu|Def)il|Conjur|Sorcer|Wa(?:nder|rd)|(?:Crusa|Outri)d|Rang|Evok|Reav)er|Necromancer|(?:B(?:lackgu)?|Wiz)ard|Grave Lord|(?:T(?:roubadou|empla)|Warrio|Vica)r|A(?:rch Mage|ssassin)|Minstrel|Virtuoso|(?:(?:Myrmid|Champi)o|Magicia|Shama)n|(?:Discipl|Oracl|R(?:ogu|ak))e|Luminary|Warlock|Heretic|Paladin|(?:Warlor|Drui)d|Cleric|Mystic|Monk)\] \w+ \((?:Barbarian|Halfling|Half\-Elf|(?:Dark|High) Elf|Wood Elf|Skeleton|Erudite|Iksar|Troll|(?:Gnom|Ogr)e|Dwarf|Human)\)(?:( \<[a-zA-Z\s]+\> ZONE\: \w+| \<[a-zA-Z\s]+\>|))$", line, ) is not None ): line_type = "who_player" elif ( re.fullmatch( r"^AFK \[\d+ (?:(?:(?:Shadow )?Knigh|Hierophan|Revenan)t|(?:Elemental|Phantasm)ist|High Priest|Illusionist|(?:Grandmast|P(?:athfind|reserv)|C(?:hannel|avali)|(?:Enchan|Mas)t|(?:Begu|Def)il|Conjur|Sorcer|Wa(?:nder|rd)|(?:Crusa|Outri)d|Rang|Evok|Reav)er|Necromancer|(?:B(?:lackgu)?|Wiz)ard|Grave Lord|(?:T(?:roubadou|empla)|Warrio|Vica)r|A(?:rch Mage|ssassin)|Minstrel|Virtuoso|(?:(?:Myrmid|Champi)o|Magicia|Shama)n|(?:Discipl|Oracl|R(?:ogu|ak))e|Luminary|Warlock|Heretic|Paladin|(?:Warlor|Drui)d|Cleric|Mystic|Monk)\] \w+ \((?:Barbarian|Halfling|Half\-Elf|(?:Dark|High) Elf|Wood Elf|Skeleton|Erudite|Iksar|Troll|(?:Gnom|Ogr)e|Dwarf|Human)\)(?:( \<[a-zA-Z\s]+\> ZONE\: \w+| \<[a-zA-Z\s]+\>|))$", line, ) is not None ): line_type = "who_player_afk" elif ( re.fullmatch( r"^\<LINKDEAD\>\[\d+ (?:(?:(?:Shadow )?Knigh|Hierophan|Revenan)t|(?:Elemental|Phantasm)ist|High Priest|Illusionist|(?:Grandmast|P(?:athfind|reserv)|C(?:hannel|avali)|(?:Enchan|Mas)t|(?:Begu|Def)il|Conjur|Sorcer|Wa(?:nder|rd)|(?:Crusa|Outri)d|Rang|Evok|Reav)er|Necromancer|(?:B(?:lackgu)?|Wiz)ard|Grave Lord|(?:T(?:roubadou|empla)|Warrio|Vica)r|A(?:rch Mage|ssassin)|Minstrel|Virtuoso|(?:(?:Myrmid|Champi)o|Magicia|Shama)n|(?:Discipl|Oracl|R(?:ogu|ak))e|Luminary|Warlock|Heretic|Paladin|(?:Warlor|Drui)d|Cleric|Mystic|Monk)\] \w+ \((?:Barbarian|Halfling|Half\-Elf|(?:Dark|High) Elf|Wood Elf|Skeleton|Erudite|Iksar|Troll|(?:Gnom|Ogr)e|Dwarf|Human)\)(?:( \<[a-zA-Z\s]+\> ZONE\: \w+| \<[a-zA-Z\s]+\>|))$", line, ) is not None ): line_type = "who_player_linkdead" elif ( re.fullmatch( r"^There (is|are) \d+ (player|players) in [a-zA-Z\s]+\.$", line ) is not None ): line_type = "who_total" elif ( re.fullmatch( r"^There are no players in EverQuest that match those who filters\.$", line, ) is not None ): line_type = "who_total" elif ( re.fullmatch( r"^Your faction standing with \w+ (?:could not possibly get any|got) (?:better|worse)\.$", line, ) is not None ): line_type = "faction_line" elif re.fullmatch(r"^Your target has been cured\.$", line) is not None: line_type = "target_cured" elif ( re.fullmatch( r"^It will take you about (30|25|20|15|10|5) seconds to prepare your camp\.$", line, ) is not None ): line_type = "you_camping" elif ( re.fullmatch(r"^You abandon your preparations to camp\.$", line) is not None ): line_type = "you_camping_abandoned" elif re.fullmatch(r"^[a-zA-Z\s]+ engages \w+\!$", line) is not None: line_type = "engage" elif re.fullmatch(r"^LOADING, PLEASE WAIT\.\.\.$", line) is not None: line_type = "zoning" elif re.fullmatch(r"^\*\*.+", line) is not None: line_type = "random" elif re.fullmatch(r"^\w+ invites you to join a group\.$", line) is not None: line_type = "group_invite" elif ( re.fullmatch( r"^(Targeted \((NPC|Player)\)\: [a-zA-Z\s]+|You no longer have a target\.)", line, ) is not None ): line_type = "target" else: line_type = "undetermined" except Exception as e: eqa_settings.log( "process_log (determine): Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e) ) return line_type
def process( keyboard_q, system_q, display_q, sound_q, exit_flag, heal_parse, spell_parse, raid, chars, ): """ Process: keyboard_q Produce: sound_q, display_q, system_q, heal_q, damage_q """ key = "" page = "events" settings = "character" selected_char = 0 while key != ord("q") and key != 27: try: # Get key time.sleep(0.001) if not keyboard_q.empty(): key = keyboard_q.get() keyboard_q.task_done() # Handle resize event if key == curses.KEY_RESIZE: display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "draw", "redraw", "null")) # Handle tab keys if key == curses.KEY_F1: display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "draw", "events", "null")) page = "events" elif key == curses.KEY_F2: display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "draw", "state", "null")) page = "state" elif key == curses.KEY_F3: display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "draw", "settings", "null")) page = "settings" elif key == curses.KEY_F4: display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "draw", "help", "null")) page = "help" elif key == curses.KEY_F12: system_q.put( eqa_struct.message( eqa_settings.eqa_time(), "system", "reload_config", "null", "null", )) # Events keys if page == "events": if key == ord("c"): display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "event", "clear", "null")) elif key == ord("r"): if not raid.is_set(): raid.set() display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "Raid mode enabled", )) sound_q.put( eqa_struct.sound("speak", "Raid mode enabled")) elif raid.is_set(): raid.clear() display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "Raid mode disabled", )) sound_q.put( eqa_struct.sound("speak", "Raid mode disabled")) display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "draw", "events", "null")) # State keys elif page == "state": pass # Settings keys elif page == "settings": if settings == "character": if key == curses.KEY_UP or key == ord("w"): if selected_char < len(chars) - 1: selected_char += 1 display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "update", "selected_char", selected_char, )) elif key == curses.KEY_DOWN or key == ord("s"): if selected_char > 0: selected_char -= 1 display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "update", "selected_char", selected_char, )) elif key == curses.KEY_RIGHT or key == ord("d"): display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "update", "zone", "unavailable", )) system_q.put( eqa_struct.message( eqa_settings.eqa_time(), "system", "new_character", "null", chars[selected_char], )) elif key == ord(" "): pass # cycle to next setting # Help keys elif page == "help": pass except Exception as e: eqa_settings.log("process keys: " + str(e)) eqa_settings.log("setting exit_flag") exit_flag.set() sys.exit() exit_flag.set() sys.exit()
def process( action_q, system_q, display_q, sound_q, heal_q, damage_q, exit_flag, heal_parse, spell_parse, raid, cfg_reload, config, base_path, ): """ Process: action_q Produce: sound_q, display_q, system_q, heal_q, damage_q """ try: while not exit_flag.is_set() and not cfg_reload.is_set(): time.sleep(0.001) if not action_q.empty(): new_message = action_q.get() action_q.task_done() line_type = new_message.type line_time = new_message.timestamp line_tx = new_message.tx line_rx = new_message.rx check_line = new_message.payload check_line_list = new_message.payload.split(" ") # Line specific checks if line_type == "undetermined": undetermined_line(check_line, base_path) if line_type == "location": loc = [ float(check_line_list[3].replace(",", "")), float(check_line_list[4].replace(",", "")), float(check_line_list[5].replace(",", "")), ] system_q.put( eqa_struct.message(eqa_settings.eqa_time(), "system", "loc", "null", loc)) elif line_type == "direction": direction = check_line_list[-1].replace(".", "") system_q.put( eqa_struct.message( eqa_settings.eqa_time(), "system", "direction", "null", direction, )) elif line_type.startswith("you_afk"): if line_type == "you_afk_on": display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "You are now AFK", )) system_q.put( eqa_struct.message(eqa_settings.eqa_time(), "system", "afk", "null", "true")) elif line_type == "you_afk_off": display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "You are no longer AFK", )) system_q.put( eqa_struct.message( eqa_settings.eqa_time(), "system", "afk", "null", "false", )) elif line_type == "you_new_zone": nz_iter = 0 current_zone = "" while check_line_list.index("entered") + nz_iter + 1 < len( check_line_list): current_zone += ( check_line_list[check_line_list.index("entered") + nz_iter + 1] + " ") nz_iter += 1 current_zone = current_zone[:-2] current_zone = current_zone.rstrip(".") sound_q.put(eqa_struct.sound("speak", current_zone)) display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "update", "zone", current_zone)) system_q.put( eqa_struct.message( eqa_settings.eqa_time(), "system", "zone", "null", current_zone, )) if current_zone not in config["zones"].keys(): eqa_config.add_zone(current_zone, base_path) elif current_zone in config["zones"].keys( ) and not raid.is_set(): if config["zones"][current_zone] == "raid": raid.set() display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "Raid mode auto-enabled", )) sound_q.put( eqa_struct.sound("speak", "Raid mode enabled")) elif current_zone in config["zones"].keys( ) and raid.is_set(): if config["zones"][current_zone] != "raid": raid.clear() display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "Raid mode auto-disabled", )) sound_q.put( eqa_struct.sound("speak", "Raid mode disabled")) # If line_type is a parsable type if line_type in config["line"].keys(): # If line_type is parsed for as true if config["line"][line_type]["reaction"] == "true": for keyphrase, value in config["line"][line_type][ "alert"].items(): if str(keyphrase).lower( ) in check_line and value == "true": sound_q.put( eqa_struct.sound("alert", line_type)) display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", line_type + ": " + check_line[0:65], )) elif (str(keyphrase).lower() in check_line and value == "raid" and raid.is_set()): if keyphrase == "assist" or keyphrase == "rampage": payload = keyphrase + " on " + check_line_list[ 0] else: payload = keyphrase sound_q.put(eqa_struct.sound("speak", payload)) display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", line_type + ": " + check_line[0:65], )) # Or if line_type is parsed for as all elif config["line"][line_type]["reaction"] == "all": # Heal parse if heal_parse.is_set() and line_type == "you_healed": heal_q.put( eqa_struct.heal( datetime.datetime.now(), "heal", "you", check_line_list[3], check_line_list[5], )) # Spell damage parse elif spell_parse.is_set( ) and line_type == "spell_damage": length = check_line_list.index("was") sp_iter = 0 name = "" while sp_iter < length: name = name + check_line_list[sp_iter] + " " sp_iter += 1 damaged_q.put( eqa_struct.heal( datetime.datetime.now(), "spell", "null", name, check_line_list[-4], )) # DoT damage parse elif spell_parse.is_set( ) and line_type == "dot_damage": length = check_line_list.index("has") dp_iter = 0 name = "" damage = int(check_line_list[length + 2]) while dp_iter < length: name = name + check_line_list[dp_iter] + " " dp_iter += 1 damaged_q.put( eqa_struct.heal(datetime.datetime.now(), "dot", "null", name, damage)) # Notify on all other all alerts else: sound_q.put(eqa_struct.sound("alert", line_type)) display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", line_type + ": " + check_line[0:65], )) # Or if line_type is parsed for as a spoken alert elif config["line"][line_type]["reaction"] == "speak": display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "event", "events", check_line)) sound_q.put(eqa_struct.sound("speak", check_line)) # For triggers requiring all line_types if config["line"]["all"]["reaction"] == "true": for keyphrase, value in config["alert"]["all"].items(): if keyphrase in check_line: sound_q.put( eqa_struct.sound("alert", line_type)) display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", line_type + ": " + check_line[0:65], )) # If line_type is not a parsable type else: eqa_config.add_type(line_type, base_path) display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "added: " + line_type, )) system_q.put( eqa_struct.message( eqa_settings.eqa_time(), "system", "reload_config", "null", "null", )) except Exception as e: eqa_settings.log("process action: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e)) sys.exit(0)
def main(): """Main method, does the good stuff""" # Paths home = os.path.expanduser("~") base_path = home + "/.eqa/" # Queues keyboard_q = queue.Queue() action_q = queue.Queue() display_q = queue.Queue() sound_q = queue.Queue() system_q = queue.Queue() log_q = queue.Queue() heal_q = queue.Queue() damage_q = queue.Queue() # Bootstraps bootstraps if not os.path.exists(base_path + "config.json"): bootstrap(base_path) # Thread Events raid = threading.Event() cfg_reload = threading.Event() heal_parse = threading.Event() spell_parse = threading.Event() exit_flag = threading.Event() # Build initial state logging.basicConfig(filename=base_path + "log/eqalert.log", level=logging.INFO) eqa_config.update_logs(base_path) config = eqa_config.read_config(base_path) server = config["last_state"]["server"] char = config["last_state"]["character"] char_log = (config["settings"]["paths"]["char_log"] + config["char_logs"][char + "_" + server]["file_name"]) state = eqa_config.get_last_state(base_path) # Ensure the character log file exists if not os.path.exists(char_log): print( "Please review `settings > paths` in config.json, a log with that default server and character cannot be found." ) exit(1) # Ensure the configuration is up-to-date if "version" not in config["settings"]: print( "Please move or delete your current configuration. A new config.json file must be generated." ) exit(1) elif not config["settings"]["version"] == str( pkg_resources.get_distribution("eqalert").version): print( "Please move or delete your current configuration. A new config.json file must be generated." ) exit(1) # Initialize curses screen = eqa_curses.init(state) ## Consume keyboard events ## Produce keyoard_q read_keys = threading.Thread(target=eqa_keys.read, args=(exit_flag, keyboard_q, screen)) read_keys.daemon = True read_keys.start() ## Process log_q ## Produce action_q process_log = threading.Thread(target=eqa_parser.process, args=(exit_flag, log_q, action_q)) process_log.daemon = True process_log.start() ## Process keyboard_q ## Produce display_q, sound_q, system_q process_keys = threading.Thread( target=eqa_keys.process, args=( keyboard_q, system_q, display_q, sound_q, exit_flag, heal_parse, spell_parse, raid, state.chars, ), ) process_keys.daemon = True process_keys.start() ## Consume action_q ## Produce display_q, sound_q, system_q, heal_q, damage_q process_action = threading.Thread( target=eqa_action.process, args=( action_q, system_q, display_q, sound_q, heal_q, damage_q, exit_flag, heal_parse, spell_parse, raid, cfg_reload, config, base_path, ), ) process_action.daemon = True process_action.start() ## Consume sound_q process_sound = threading.Thread(target=eqa_sound.process, args=(config, sound_q, exit_flag, cfg_reload)) process_sound.daemon = True process_sound.start() ## Consume display_q process_display = threading.Thread(target=eqa_curses.display, args=(screen, display_q, state, raid, exit_flag)) process_display.daemon = True process_display.start() ## Consume char_log ## Produce log_q def callback(event): try: if event.mask == pyinotify.IN_CLOSE_WRITE: log_q.put(eqa_parser.last_line(char_log)) except Exception as e: eqa_settings.log("log watch callback: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e)) try: log_watch = pyinotify.WatchManager() log_watch.add_watch(char_log, pyinotify.IN_CLOSE_WRITE, callback) log_notifier = pyinotify.ThreadedNotifier(log_watch) log_notifier.daemon = True log_notifier.start() except Exception as e: eqa_settings.log("log watch callback: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e)) # And we're on display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "draw", "events", "null")) display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "event", "events", "Initialized")) sound_q.put(eqa_struct.sound("speak", "initialized")) ## Consume system_q try: while not exit_flag.is_set(): time.sleep(0.001) if not system_q.empty(): new_message = system_q.get() system_q.task_done() if new_message.type == "system": # Update zone if new_message.tx == "zone": state.set_zone(new_message.payload) eqa_config.set_last_state(state, base_path) # Update afk status elif new_message.tx == "afk": state.set_afk(new_message.payload) eqa_config.set_last_state(state, base_path) # Update location elif new_message.tx == "loc": state.set_loc(new_message.payload) eqa_config.set_last_state(state, base_path) # Update direction elif new_message.tx == "direction": state.set_direction(new_message.payload) eqa_config.set_last_state(state, base_path) # Update character elif new_message.tx == "new_character": new_char_log = ( config["settings"]["paths"]["char_log"] + config["char_logs"][ new_message.payload]["file_name"]) # Ensure char/server combo exists as file if os.path.exists(new_char_log): # Stop watch on current log log_watch.rm_watch(char_log, pyinotify.IN_CLOSE_WRITE, callback) # Set new character char_name, char_server = new_message.payload.split( "_") state.set_char(char_name) state.set_server(char_server) eqa_config.set_last_state(state, base_path) char_log = new_char_log # Start new log watch log_watch.add_watch(char_log, pyinotify.IN_CLOSE_WRITE, callback) display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "Character changed to " + state.char + " on " + state.server, )) sound_q.put( eqa_struct.sound( "speak", "Character changed to " + state.char + " on " + state.server, )) else: display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "Unable to change characters, please review logs", )) eqa_settings.log("Could not find file: " + new_char_log) # Reload config elif new_message.tx == "reload_config": # Reload config eqa_config.update_logs(base_path) config = eqa_config.read_config(base_path) # Reread characters state.set_chars(eqa_config.get_config_chars(config)) # Stop process_action and process_sound cfg_reload.set() process_action.join() process_sound.join() cfg_reload.clear() # Start process_action and process_sound process_action = threading.Thread( target=eqa_action.process, args=( action_q, system_q, display_q, sound_q, heal_q, damage_q, exit_flag, heal_parse, spell_parse, raid, cfg_reload, config, base_path, ), ) process_action.daemon = True process_action.start() process_sound = threading.Thread( target=eqa_sound.process, args=(config, sound_q, exit_flag, cfg_reload), ) process_sound.daemon = True process_sound.start() display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", "Configuration reloaded", )) sound_q.put( eqa_struct.sound("speak", "Configuration reloaded")) else: display_q.put( eqa_struct.display( eqa_settings.eqa_time(), "event", "events", new_message.type + ": " + new_message.payload, )) except Exception as e: eqa_settings.log("main: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e)) pass # Exit display_q.put( eqa_struct.display(eqa_settings.eqa_time(), "event", "events", "Exiting")) read_keys.join() process_log.join() process_keys.join() process_action.join() process_sound.join() process_display.join() log_watch.rm_watch(char_log, pyinotify.IN_CLOSE_WRITE, callback) log_notifier.stop() eqa_curses.close_screens(screen)
def display(stdscr, display_q, state, raid, exit_flag): """ Process: display_q Produce: display event """ events = [] page = "events" setting = "character" selected_char = 0 try: while not exit_flag.is_set(): time.sleep(0.001) if not display_q.empty(): display_event = display_q.get() display_q.task_done() # Display Var Update if display_event.type == "update": if display_event.screen == "setting": setting = display_event.payload draw_page(stdscr, page, events, state, setting, selected_char, raid) elif display_event.screen == "selected_char": selected_char = display_event.payload draw_page(stdscr, page, events, state, setting, selected_char, raid) elif display_event.screen == "select_char": selected_char = display_event.payload state.char = state.chars[selected_char] draw_page(stdscr, page, events, state, setting, selected_char, raid) elif display_event.screen == "zone": zone = display_event.payload draw_page(stdscr, page, events, state, setting, selected_char, raid) elif display_event.screen == "char": state.char = display_event.payload draw_page(stdscr, page, events, state, setting, selected_char, raid) # Display Draw elif display_event.type == "draw": if display_event.screen != "redraw": page = display_event.screen draw_page(stdscr, page, events, state, setting, selected_char, raid) # Draw Update elif display_event.type == "event": if display_event.screen == "events": events.append(display_event) if page == "events": draw_page( stdscr, page, events, state, setting, selected_char, raid, ) elif display_event.screen == "clear": events = [] draw_page(stdscr, page, events, state, setting, selected_char, raid) except Exception as e: eqa_settings.log("display: Error on line " + str(sys.exc_info()[-1].tb_lineno) + ": " + str(e)) sys.exit()