diff --git a/fishy/__main__.py b/fishy/__main__.py index 1b66f90..68eb611 100644 --- a/fishy/__main__.py +++ b/fishy/__main__.py @@ -2,16 +2,15 @@ import ctypes import logging import os import sys -import traceback import win32con import win32gui import fishy -from fishy import gui, helper, web -from fishy.constants import chalutier, lam2, fishyqr, libgps +from fishy.gui import GUI, splash, update_dialog, check_eula +from fishy import helper, web from fishy.engine.common.event_handler import EngineEventHandler -from fishy.gui import GUI, splash, update_dialog +from fishy.gui.log_config import GuiLogger from fishy.helper import hotkey from fishy.helper.active_poll import active from fishy.helper.config import config @@ -32,12 +31,12 @@ def initialize(window_to_hide): Migration.migrate() helper.create_shortcut_first() - helper.initialize_uid() new_session = web.get_session() + if new_session is None: logging.error("Couldn't create a session, some features might not work") - print(f"created session {new_session}") + logging.debug(f"created session {new_session}") try: is_admin = os.getuid() == 0 @@ -46,20 +45,6 @@ def initialize(window_to_hide): if is_admin: logging.info("Running with admin privileges") - try: - if helper.upgrade_avail() and not config.get("dont_ask_update", False): - cv, hv = helper.versions() - update_now, dont_ask_update = update_dialog.start(cv, hv) - if dont_ask_update: - config.set("dont_ask_update", dont_ask_update) - else: - config.delete("dont_ask_update") - - if update_now: - helper.auto_upgrade() - except Exception: - logging.error(traceback.format_exc()) - if not config.get("debug", False) and check_window_name(win32gui.GetWindowText(window_to_hide)): win32gui.ShowWindow(window_to_hide, win32con.SW_HIDE) helper.install_thread_excepthook() @@ -69,36 +54,42 @@ def initialize(window_to_hide): def main(): - active.init() - config.init() - splash.start() - hotkey.init() - print("launching please wait...") - pil_logger = logging.getLogger('PIL') - pil_logger.setLevel(logging.INFO) + config.init() + if not check_eula(): + return + + finish_splash = splash.start() + logger = GuiLogger() + config.start_backup_scheduler() + active.init() + hotkey.init() + + def on_gui_load(): + finish_splash() + update_dialog.check_update(gui) + logger.connect(gui) window_to_hide = win32gui.GetForegroundWindow() - if not gui.check_eula(): - return - - bot = EngineEventHandler(lambda: gui_window) - gui_window = GUI(lambda: bot) + bot = EngineEventHandler(lambda: gui) + gui = GUI(lambda: bot, on_gui_load) hotkey.start() logging.info(f"Fishybot v{fishy.__version__}") initialize(window_to_hide) - gui_window.start() + gui.start() active.start() - bot.start_event_handler() - config.stop() + bot.start_event_handler() # main thread loop + hotkey.stop() active.stop() + config.stop() + bot.stop() if __name__ == "__main__": diff --git a/fishy/engine/common/IEngine.py b/fishy/engine/common/IEngine.py index 7e4e24a..b101d4d 100644 --- a/fishy/engine/common/IEngine.py +++ b/fishy/engine/common/IEngine.py @@ -1,21 +1,27 @@ +import logging import typing -from abc import ABC, abstractmethod from threading import Thread from typing import Callable +import cv2 + +from fishy.engine.common.window import WindowClient from fishy.gui.funcs import GUIFuncsMock +from fishy.helper.helper import print_exc if typing.TYPE_CHECKING: from fishy.gui import GUI -class IEngine(ABC): +class IEngine: def __init__(self, gui_ref: 'Callable[[], GUI]'): self.get_gui = gui_ref - self.start = False + # 0 - off, 1 - running, 2 - quitting + self.state = 0 self.window = None self.thread = None + self.name = "default" @property def gui(self): @@ -24,10 +30,52 @@ class IEngine(ABC): return self.get_gui().funcs - @abstractmethod - def toggle_start(self): - ... + @property + def start(self): + return self.state == 1 + + def toggle_start(self): + if self.state == 0: + self.turn_on() + else: + self.turn_off() + + def turn_on(self): + self.state = 1 + self.thread = Thread(target=self._crash_safe) + self.thread.start() + + def join(self): + if self.thread: + logging.debug(f"waiting for {self.name} engine") + self.thread.join() + + def turn_off(self): + """ + this method only signals the thread to close using start flag, + its the responsibility of the thread to shut turn itself off + """ + if self.state == 1: + logging.debug(f"sending turn off signal to {self.name} engine") + self.state = 2 + else: + logging.debug(f"{self.name} engine already signaled to turn off ") + # todo: implement force turn off on repeated calls + + # noinspection PyBroadException + def _crash_safe(self): + logging.debug(f"starting {self.name} engine thread") + self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name=f"{self.name} debug") + self.gui.bot_started(True) + try: + self.run() + except Exception: + logging.error(f"Unhandled exception occurred while running {self.name} engine") + print_exc() + self.state = 0 + self.gui.bot_started(False) + self.window.destroy() + logging.debug(f"{self.name} engine thread safely exiting") - @abstractmethod def run(self): - ... + raise NotImplementedError diff --git a/fishy/engine/common/event_handler.py b/fishy/engine/common/event_handler.py index c8105a5..52d60ef 100644 --- a/fishy/engine/common/event_handler.py +++ b/fishy/engine/common/event_handler.py @@ -1,10 +1,13 @@ import logging import time +from fishy.helper import auto_update + from fishy.engine import SemiFisherEngine from fishy.engine.fullautofisher.engine import FullAuto +# to test only gui without engine code interfering class IEngineHandler: def __init__(self): ... @@ -21,7 +24,10 @@ class IEngineHandler: def check_pixel_val(self): ... - def quit(self): + def set_update(self, version): + ... + + def quit_me(self): ... @@ -31,6 +37,9 @@ class EngineEventHandler(IEngineHandler): self.event_handler_running = True self.event = [] + self.update_flag = False + self.to_version = "" + self.semi_fisher_engine = SemiFisherEngine(gui_ref) self.full_fisher_engine = FullAuto(gui_ref) @@ -56,9 +65,24 @@ class EngineEventHandler(IEngineHandler): self.event.append(func) - def quit(self): + def set_update(self, version): + self.to_version = version + self.update_flag = True + self.quit_me() + + def stop(self): + self.semi_fisher_engine.join() + self.full_fisher_engine.join() + if self.update_flag: + auto_update.update_now(self.to_version) + + def quit_me(self): def func(): - self.semi_fisher_engine.start = False + if self.semi_fisher_engine.start: + self.semi_fisher_engine.turn_off() + if self.full_fisher_engine.start: + self.semi_fisher_engine.turn_off() + self.event_handler_running = False self.event.append(func) diff --git a/fishy/engine/common/qr_detection.py b/fishy/engine/common/qr_detection.py index d93b8b4..5a77db2 100644 --- a/fishy/engine/common/qr_detection.py +++ b/fishy/engine/common/qr_detection.py @@ -1,13 +1,7 @@ -import logging -import os -from datetime import datetime - import cv2 import numpy as np from pyzbar.pyzbar import decode, ZBarSymbol -from fishy.helper.helper import get_documents - def image_pre_process(img): scale_percent = 100 # percent of original size @@ -38,7 +32,7 @@ def get_qr_location(og_img): cv2.drawContours(mask, cnt, i, 255, -1) x, y, w, h = cv2.boundingRect(cnt[i]) qr_result = decode(og_img[y:h + y, x:w + x], - symbols=[ZBarSymbol.QRCODE]) + symbols=[ZBarSymbol.QRCODE]) if qr_result: valid_crops.append(((x, y, x + w, y + h), area)) @@ -47,14 +41,7 @@ def get_qr_location(og_img): # noinspection PyBroadException def get_values_from_image(img): - try: - for qr in decode(img, symbols=[ZBarSymbol.QRCODE]): - vals = qr.data.decode('utf-8').split(",") - return tuple(float(v) for v in vals) - - logging.error("FishyQR not found, try to drag it around and try again") - return None - except Exception: - logging.error("Couldn't read coods, make sure 'crop' calibration is correct") - cv2.imwrite(os.path.join(get_documents(), "fishy_failed_reads", f"{datetime.now()}.jpg"), img) - return None + for qr in decode(img, symbols=[ZBarSymbol.QRCODE]): + vals = qr.data.decode('utf-8').split(",") + return tuple(float(v) for v in vals) + return None diff --git a/fishy/engine/common/window.py b/fishy/engine/common/window.py index 405695d..a27614d 100644 --- a/fishy/engine/common/window.py +++ b/fishy/engine/common/window.py @@ -28,7 +28,7 @@ class WindowClient: window_server.start() WindowClient.clients.append(self) - def destory(self): + def destroy(self): if self in WindowClient.clients: WindowClient.clients.remove(self) if len(WindowClient.clients) == 0: @@ -47,9 +47,9 @@ class WindowClient: return None if not window_server.screen_ready(): - print("waiting for screen...") + logging.debug("waiting for screen...") helper.wait_until(window_server.screen_ready) - print("screen ready, continuing...") + logging.debug("screen ready, continuing...") temp_img = WindowServer.Screen @@ -83,13 +83,13 @@ class WindowClient: if func is None: return img - else: - return func(img) + + return func(img) def show(self, to_show, resize=None, func=None): """ Displays the processed image for debugging purposes - :param ready_img: send ready image, just show the `ready_img` directly + :param to_show: false to destroy the window :param resize: scale the image to make small images more visible :param func: function to process the image """ diff --git a/fishy/engine/common/window_server.py b/fishy/engine/common/window_server.py index 0cc4133..8b1e8fd 100644 --- a/fishy/engine/common/window_server.py +++ b/fishy/engine/common/window_server.py @@ -1,17 +1,15 @@ import logging import math -import traceback from enum import Enum from threading import Thread -import cv2 import d3dshot import pywintypes import win32api import win32gui from ctypes import windll -from fishy.helper.config import config +from fishy.helper.helper import print_exc class Status(Enum): @@ -28,7 +26,7 @@ class WindowServer: windowOffset = None hwnd = None status = Status.STOPPED - d3: d3dshot.D3DShot = None + d3: d3dshot.D3DShot = d3dshot.create(capture_output="numpy") monitor_top_left = None @@ -37,6 +35,7 @@ def init(): Executed once before the main loop, Finds the game window, and calculates the offset to remove the title bar """ + # noinspection PyUnresolvedReferences try: WindowServer.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online") @@ -48,9 +47,7 @@ def init(): WindowServer.windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2) WindowServer.status = Status.RUNNING - d3 = d3dshot.create(capture_output="numpy") - d3.display = next((m for m in d3.displays if m.hmonitor == monitor_id), None) - WindowServer.d3 = d3 + WindowServer.d3.display = next((m for m in WindowServer.d3.displays if m.hmonitor == monitor_id), None) except pywintypes.error: logging.error("Game window not found") @@ -84,20 +81,21 @@ def loop(): WindowServer.status = Status.CRASHED -def loop_end(): - cv2.waitKey(25) - - # noinspection PyBroadException def run(): # todo use config + logging.debug("window server started") while WindowServer.status == Status.RUNNING: try: loop() except Exception: - traceback.print_exc() + print_exc() WindowServer.status = Status.CRASHED - loop_end() + + if WindowServer.status == Status.CRASHED: + logging.debug("window server crashed") + elif WindowServer.status == Status.STOPPED: + logging.debug("window server stopped") def start(): diff --git a/fishy/engine/fullautofisher/controls.py b/fishy/engine/fullautofisher/controls.py index e18db67..faf66a9 100644 --- a/fishy/engine/fullautofisher/controls.py +++ b/fishy/engine/fullautofisher/controls.py @@ -4,6 +4,8 @@ from pynput.keyboard import Key from fishy.helper import hotkey +# todo: unused code remove it + def get_controls(controls: 'Controls'): controls = [ diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index 974f22e..83ccd52 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -1,13 +1,10 @@ import logging import math import time -import traceback from threading import Thread -import cv2 from pynput import keyboard, mouse -from fishy.constants import fishyqr, lam2, libgps from fishy.engine import SemiFisherEngine from fishy.engine.common.IEngine import IEngine from fishy.engine.common.window import WindowClient @@ -17,12 +14,10 @@ from fishy.engine.fullautofisher.mode.player import Player from fishy.engine.fullautofisher.mode.recorder import Recorder from fishy.engine.common.qr_detection import (get_qr_location, get_values_from_image, image_pre_process) -from fishy.engine.semifisher import fishing_event, fishing_mode +from fishy.engine.semifisher import fishing_mode from fishy.engine.semifisher.fishing_mode import FishingMode -from fishy.helper import helper, hotkey from fishy.helper.config import config -from fishy.helper.helper import log_raise, wait_until, is_eso_active -from fishy.helper.helper import sign +from fishy.helper.helper import wait_until, is_eso_active, sign, print_exc mse = mouse.Controller() kb = keyboard.Controller() @@ -35,6 +30,7 @@ class FullAuto(IEngine): from fishy.engine.fullautofisher.test import Test super().__init__(gui_ref) + self.name = "FullAuto" self._curr_rotate_y = 0 self.fisher = SemiFisherEngine(None) @@ -45,9 +41,6 @@ class FullAuto(IEngine): self.mode = None def run(self): - self.gui.bot_started(True) - self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="Full auto debug") - self.mode = None if config.get("calibrate", False): self.mode = Calibrator(self) @@ -55,7 +48,11 @@ class FullAuto(IEngine): self.mode = Player(self) elif FullAutoMode(config.get("full_auto_mode", 0)) == FullAutoMode.Recorder: self.mode = Recorder(self) + else: + logging.error("not a valid mode selected") + return + # block thread until game window becomes active if not is_eso_active(): logging.info("Waiting for eso window to be active...") wait_until(lambda: is_eso_active() or not self.start) @@ -63,37 +60,34 @@ class FullAuto(IEngine): logging.info("starting in 2 secs...") time.sleep(2) + if not self._pre_run_checks(): + return + + if config.get("tabout_stop", 1): + self.stop_on_inactive() + # noinspection PyBroadException try: - if self.window.get_capture() is None: - log_raise("Game window not found") - - self.window.crop = get_qr_location(self.window.get_capture()) - if self.window.crop is None: - log_raise("FishyQR not found, try to drag it around and try again") - - if not (type(self.mode) is Calibrator) and not self.calibrator.all_calibrated(): - log_raise("you need to calibrate first") - - self.fisher.toggle_start() - fishing_event.unsubscribe() - if self.show_crop: - self.start_show() - - if config.get("tabout_stop", 1): - self.stop_on_inactive() - self.mode.run() - except Exception: - traceback.print_exc() - self.start = False + logging.error("exception occurred while running full auto mode") + print_exc() - self.gui.bot_started(False) - self.window.show(False) - logging.info("Quitting") - self.window.destory() - self.fisher.toggle_start() + def _pre_run_checks(self): + if self.window.get_capture() is None: + logging.error("Game window not found") + return False + + self.window.crop = get_qr_location(self.window.get_capture()) + if self.window.crop is None: + logging.error("FishyQR not found, try to drag it around and try again") + return False + + if not (type(self.mode) is Calibrator) and not self.calibrator.all_calibrated(): + logging.error("you need to calibrate first") + return False + + return True def start_show(self): def func(): @@ -103,8 +97,11 @@ class FullAuto(IEngine): def stop_on_inactive(self): def func(): - wait_until(lambda: not is_eso_active()) - self.start = False + logging.debug("stop on inactive started") + wait_until(lambda: not is_eso_active() or not self.start) + if self.start and not is_eso_active(): + self.turn_off() + logging.debug("stop on inactive stopped") Thread(target=func).start() def get_coords(self): @@ -115,20 +112,21 @@ class FullAuto(IEngine): todo its waiting for qr which doesn't block the engine when commanded to close """ img = self.window.processed_image(func=image_pre_process) - return get_values_from_image(img)[:3] + values = get_values_from_image(img) + return values[:3] if values else None def move_to(self, target) -> bool: current = self.get_coords() if not current: return False - print(f"Moving from {(current[0], current[1])} to {target}") + logging.debug(f"Moving from {(current[0], current[1])} to {target}") move_vec = target[0] - current[0], target[1] - current[1] dist = math.sqrt(move_vec[0] ** 2 + move_vec[1] ** 2) - print(f"distance: {dist}") + logging.debug(f"distance: {dist}") if dist < 5e-05: - print("distance very small skipping") + logging.debug("distance very small skipping") return True target_angle = math.degrees(math.atan2(-move_vec[1], move_vec[0])) + 90 @@ -138,11 +136,12 @@ class FullAuto(IEngine): return False walking_time = dist / self.calibrator.move_factor - print(f"walking for {walking_time}") + logging.debug(f"walking for {walking_time}") kb.press('w') time.sleep(walking_time) kb.release('w') - print("done") + logging.debug("done") + # todo: maybe check if it reached the destination before returning true? return True def rotate_to(self, target_angle, from_angle=None) -> bool: @@ -156,7 +155,7 @@ class FullAuto(IEngine): target_angle = 360 + target_angle while target_angle > 360: target_angle -= 360 - print(f"Rotating from {from_angle} to {target_angle}") + logging.debug(f"Rotating from {from_angle} to {target_angle}") angle_diff = target_angle - from_angle @@ -165,7 +164,7 @@ class FullAuto(IEngine): rotate_times = int(angle_diff / self.calibrator.rot_factor) * -1 - print(f"rotate_times: {rotate_times}") + logging.debug(f"rotate_times: {rotate_times}") for _ in range(abs(rotate_times)): mse.move(sign(rotate_times) * FullAuto.rotate_by * -1, 0) @@ -177,6 +176,7 @@ class FullAuto(IEngine): valid_states = [fishing_mode.State.LOOKING, fishing_mode.State.FISHING] _hole_found_flag = FishingMode.CurrentMode in valid_states + # if vertical movement is disabled if not config.get("look_for_hole", 1): return _hole_found_flag @@ -197,16 +197,8 @@ class FullAuto(IEngine): time.sleep(0.05) self._curr_rotate_y -= 0.05 - def toggle_start(self): - self.start = not self.start - if self.start: - self.thread = Thread(target=self.run) - self.thread.start() - if __name__ == '__main__': - logging.getLogger("").setLevel(logging.DEBUG) - hotkey.initalize() # noinspection PyTypeChecker bot = FullAuto(None) bot.toggle_start() diff --git a/fishy/engine/fullautofisher/mode/calibrator.py b/fishy/engine/fullautofisher/mode/calibrator.py index c1805f9..86cb887 100644 --- a/fishy/engine/fullautofisher/mode/calibrator.py +++ b/fishy/engine/fullautofisher/mode/calibrator.py @@ -20,7 +20,7 @@ kb = keyboard.Controller() offset = 0 -def get_crop_coods(window): +def get_crop_coords(window): img = window.get_capture() img = cv2.inRange(img, 0, 1) @@ -37,6 +37,8 @@ def get_crop_coods(window): x, y, w, h = cv2.boundingRect(cnt[i]) return x, y + offset, x + w, y + h - offset + return None + def _update_factor(key, value): full_auto_factors = config.get("full_auto_factors", {}) @@ -75,25 +77,25 @@ class Calibrator(IMode): def _walk_calibrate(self): walking_time = 3 - coods = self.engine.get_coords() - if coods is None: + coords = self.engine.get_coords() + if coords is None: return - x1, y1, rot1 = coods + x1, y1, rot1 = coords kb.press('w') time.sleep(walking_time) kb.release('w') time.sleep(0.5) - coods = self.engine.get_coords() - if coods is None: + coords = self.engine.get_coords() + if coords is None: return - x2, y2, rot2 = coods + x2, y2, rot2 = coords move_factor = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / walking_time _update_factor("move_factor", move_factor) - logging.info("walk calibrate done") + logging.info(f"walk calibrate done, move_factor: {move_factor}") def _rotate_calibrate(self): from fishy.engine.fullautofisher.engine import FullAuto @@ -119,7 +121,7 @@ class Calibrator(IMode): rot_factor = (rot3 - rot2) / rotate_times _update_factor("rot_factor", rot_factor) - logging.info("rotate calibrate done") + logging.info(f"rotate calibrate done, rot_factor: {rot_factor}") def run(self): self._walk_calibrate() diff --git a/fishy/engine/fullautofisher/mode/player.py b/fishy/engine/fullautofisher/mode/player.py index a573788..ed6d4c5 100644 --- a/fishy/engine/fullautofisher/mode/player.py +++ b/fishy/engine/fullautofisher/mode/player.py @@ -2,14 +2,11 @@ import logging import math import pickle import time -from pprint import pprint import typing -from threading import Thread from fishy.engine.fullautofisher.mode.imode import IMode from fishy.engine.semifisher import fishing_event, fishing_mode -from fishy.helper.helper import log_raise, wait_until, kill_thread if typing.TYPE_CHECKING: from fishy.engine.fullautofisher.engine import FullAuto @@ -56,23 +53,29 @@ class Player(IMode): self.timeline = None def run(self): - self._init() + if not self._init(): + return + while self.engine.start: self._loop() time.sleep(0.1) + logging.info("player stopped") - def _init(self): + def _init(self) -> bool: self.timeline = get_rec_file() if not self.timeline: - log_raise("data not found, can't start") - logging.info("starting player") + logging.error("data not found, can't start") + return False coords = self.engine.get_coords() if not coords: - log_raise("QR not found") + logging.error("QR not found") + return False self.i = find_nearest(self.timeline, coords)[0] + logging.info("starting player") + return True def _loop(self): action = self.timeline[self.i] @@ -87,21 +90,22 @@ class Player(IMode): if not self.engine.rotate_to(action[1][2]): return - fishing_mode.subscribers.append(self._hole_complete_callback) - fishing_event.subscribe() + self.engine.fisher.turn_on() # scan for fish hole logging.info("scanning") # if found start fishing and wait for hole to complete if self.engine.look_for_hole(): logging.info("starting fishing") + fishing_mode.subscribers.append(self._hole_complete_callback) self.hole_complete_flag = False helper.wait_until(lambda: self.hole_complete_flag or not self.engine.start) + fishing_mode.subscribers.remove(self._hole_complete_callback) + self.engine.rotate_back() else: logging.info("no hole found") # continue when hole completes - fishing_mode.subscribers.remove(self._hole_complete_callback) - fishing_event.unsubscribe() + self.engine.fisher.turn_off() self.next() diff --git a/fishy/engine/fullautofisher/mode/recorder.py b/fishy/engine/fullautofisher/mode/recorder.py index 0237c11..fae6734 100644 --- a/fishy/engine/fullautofisher/mode/recorder.py +++ b/fishy/engine/fullautofisher/mode/recorder.py @@ -9,20 +9,18 @@ import typing from tkinter.filedialog import asksaveasfile from fishy.engine.fullautofisher.mode import player -from fishy.helper import helper -from fishy.helper.helper import empty_function, log_raise +from fishy.helper.helper import empty_function from fishy.helper.hotkey.process import Key from fishy.helper.popup import PopUp -from playsound import playsound from fishy.helper.config import config if typing.TYPE_CHECKING: from fishy.engine.fullautofisher.engine import FullAuto from fishy.engine.fullautofisher.mode.imode import IMode -from fishy.helper.hotkey.hotkey_process import HotKey, hotkey +from fishy.helper.hotkey.hotkey_process import hotkey class Recorder(IMode): @@ -49,48 +47,50 @@ class Recorder(IMode): old_timeline = player.get_rec_file() if not old_timeline: - log_raise("Edit mode selected, but no fishy file selected") + logging.error("Edit mode selected, but no fishy file selected") + return coords = self.engine.get_coords() if not coords: - log_raise("QR not found") + logging.error("QR not found") + return start_from = player.find_nearest(old_timeline, coords) if not self.engine.move_to(start_from[2]): - log_raise("QR not found") + logging.error("QR not found") + return logging.info("starting, press LMB to mark hole") hotkey.hook(Key.LMB, self._mark_hole) self.timeline = [] - + last_coord = None while self.engine.start: start_time = time.time() - coods = self.engine.get_coords() - if not coods: + coords = self.engine.get_coords() + if not coords: + logging.warning("missed a frame, as qr not be read properly...") time.sleep(0.1) continue - self.timeline.append(("move_to", (coods[0], coods[1]))) + self.timeline.append(("move_to", (coords[0], coords[1]))) + # maintaining constant frequency for recording time_took = time.time() - start_time if time_took <= Recorder.recording_fps: time.sleep(Recorder.recording_fps - time_took) else: logging.warning("Took too much time to record") + last_coord = coords hotkey.free(Key.LMB) if config.get("edit_recorder_mode"): logging.info("moving to nearest coord in recording") - - # todo allow the user the chance to wait for qr - coords = self.engine.get_coords() - if not coords: - log_raise("QR not found") - - end = player.find_nearest(old_timeline, coords) + end = player.find_nearest(old_timeline, last_coord) self.engine.move_to(end[2]) + + # recording stitching part1 = old_timeline[:start_from[0]] part2 = old_timeline[end[0]:] self.timeline = part1 + self.timeline + part2 diff --git a/fishy/engine/fullautofisher/test.py b/fishy/engine/fullautofisher/test.py index 07c48c9..e4f3c12 100644 --- a/fishy/engine/fullautofisher/test.py +++ b/fishy/engine/fullautofisher/test.py @@ -9,7 +9,7 @@ class Test: self.target = None # noinspection PyProtectedMember - def print_coods(self): + def print_coords(self): logging.info(self.engine.get_coords()) def set_target(self): diff --git a/fishy/engine/semifisher/engine.py b/fishy/engine/semifisher/engine.py index fbbbd62..5852c95 100644 --- a/fishy/engine/semifisher/engine.py +++ b/fishy/engine/semifisher/engine.py @@ -4,19 +4,14 @@ import typing from threading import Thread from typing import Callable, Optional -import cv2 from fishy.engine.semifisher.fishing_mode import FishingMode -from fishy.helper.helper import log_raise -from playsound import playsound - from fishy.engine.common.IEngine import IEngine from fishy.engine.common.qr_detection import get_qr_location, get_values_from_image, image_pre_process from fishy.engine.common.window import WindowClient from fishy.engine.semifisher import fishing_event, fishing_mode from fishy.engine.semifisher.fishing_event import FishEvent -from fishy.helper import helper -from fishy.helper.luaparser import sv_color_extract +from fishy.helper.helper import print_exc if typing.TYPE_CHECKING: from fishy.gui import GUI @@ -26,57 +21,69 @@ class SemiFisherEngine(IEngine): def __init__(self, gui_ref: Optional['Callable[[], GUI]']): super().__init__(gui_ref) self.window = None + self.name = "SemiFisher" def run(self): """ Starts the fishing code explained in comments in detail """ - fishing_event.init() - self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="semifisher debug") - - # check for game window and stuff - self.gui.bot_started(True) - if self.get_gui: logging.info("Starting the bot engine, look at the fishing hole to start fishing") Thread(target=self._wait_and_check).start() - self.window.crop = get_qr_location(self.window.get_capture()) - if self.window.crop is None: - log_raise("FishyQR not found, try to drag it around and try again") + capture = self.window.get_capture() + if capture is None: + logging.error("couldn't get game capture") + return - while self.start and WindowClient.running(): + self.window.crop = get_qr_location(capture) + if not self.window.crop: + logging.error("FishyQR not found, try to drag it around and try again") + return + + fishing_event.init() + # noinspection PyBroadException + try: + self._engine_loop() + except Exception: + logging.error("exception occurred while running engine loop") + print_exc() + + fishing_event.unsubscribe() + + def _engine_loop(self): + skip_count = 0 + while self.state == 1 and WindowClient.running(): capture = self.window.processed_image(func=image_pre_process) # if window server crashed if capture is None: - self.gui.bot_started(False) - self.toggle_start() - continue + logging.error("Couldn't capture window stopping engine") + return # crop qr and get the values from it values = get_values_from_image(capture) - if values is None: - self.gui.bot_started(False) - self.toggle_start() - continue + # if fishyqr fails to get read multiple times, stop the bot + if not values: + skip_count += 1 + if skip_count >= 5: + logging.error("Couldn't read values from FishyQR, Stopping engine...") + return + else: + skip_count = 0 - fishing_mode.loop(values[3]) + if values: + fishing_mode.loop(values[3]) time.sleep(0.1) - self.window.show(False) - logging.info("Fishing engine stopped") - self.gui.bot_started(False) - fishing_event.unsubscribe() - self.window.destory() - def _wait_and_check(self): time.sleep(10) - if not FishEvent.FishingStarted and self.start: + if not FishEvent.FishingStarted and self.state == 1: logging.warning("Doesn't look like fishing has started \n" "Check out #faqs on our discord channel to troubleshoot the issue") + # TODO: remove this, no longer needed def show_pixel_vals(self): def show(): freq = 0.5 @@ -90,15 +97,6 @@ class SemiFisherEngine(IEngine): time.sleep(5) Thread(target=show, args=()).start() - def toggle_start(self): - self.start = not self.start - if self.start: - self.thread = Thread(target=self.run) - self.thread.start() - playsound(helper.manifest_file("beep.wav"), False) - else: - helper.playsound_multiple(helper.manifest_file("beep.wav")) - if __name__ == '__main__': logging.getLogger("").setLevel(logging.DEBUG) diff --git a/fishy/engine/semifisher/fishing_event.py b/fishy/engine/semifisher/fishing_event.py index 8619c88..a66c75b 100644 --- a/fishy/engine/semifisher/fishing_event.py +++ b/fishy/engine/semifisher/fishing_event.py @@ -78,9 +78,6 @@ def subscribe(): if fisher_callback not in fishing_mode.subscribers: fishing_mode.subscribers.append(fisher_callback) - if FishingMode.CurrentMode == State.LOOKING: - fisher_callback(FishingMode.CurrentMode) - def fisher_callback(event: State): callbacks_map = { diff --git a/fishy/gui/config_top.py b/fishy/gui/config_top.py index 1fed5d0..e682231 100644 --- a/fishy/gui/config_top.py +++ b/fishy/gui/config_top.py @@ -2,21 +2,16 @@ import logging import os import tkinter as tk import tkinter.ttk as ttk -import typing from tkinter.filedialog import askopenfilename from fishy.engine.common.event_handler import IEngineHandler from fishy.engine.fullautofisher.mode.imode import FullAutoMode -from fishy.helper import helper from fishy import web from fishy.helper import helper from fishy.helper.config import config from fishy.helper.popup import PopUp -if typing.TYPE_CHECKING: - from fishy.gui import GUI - def start_fullfisher_config(gui: 'GUI'): top = PopUp(helper.empty_function, gui._root, background=gui._root["background"]) diff --git a/fishy/gui/discord_login.py b/fishy/gui/discord_login.py index d353266..91b082c 100644 --- a/fishy/gui/discord_login.py +++ b/fishy/gui/discord_login.py @@ -26,6 +26,7 @@ def discord_login(gui: 'GUI'): top.destroy() top_running[0] = False + # noinspection PyUnresolvedReferences def check(): code = int(login_code.get()) if login_code.get().isdigit() else 0 if web.login(config.get("uid"), code): diff --git a/fishy/gui/gui.py b/fishy/gui/gui.py index 45af6d4..a01272d 100644 --- a/fishy/gui/gui.py +++ b/fishy/gui/gui.py @@ -1,4 +1,3 @@ -import logging import queue import threading import tkinter as tk @@ -8,15 +7,13 @@ from dataclasses import dataclass from ttkthemes import ThemedTk -from fishy.engine.common.event_handler import EngineEventHandler, IEngineHandler +from fishy.engine.common.event_handler import IEngineHandler from fishy.gui import config_top from fishy.gui.funcs import GUIFuncs -from fishy.web import web from ..helper.config import config from ..helper.helper import wait_until from . import main_gui -from .log_config import GUIStreamHandler @dataclass @@ -26,9 +23,10 @@ class EngineRunner: class GUI: - def __init__(self, get_engine: Callable[[], IEngineHandler]): + def __init__(self, get_engine: Callable[[], IEngineHandler], on_ready: Callable): self.funcs = GUIFuncs(self) self.get_engine = get_engine + self.on_ready = on_ready self.config = config self._start_restart = False @@ -52,12 +50,6 @@ class GUI: self._notify = None self.login = None - root_logger = logging.getLogger('') - root_logger.setLevel(logging.DEBUG) - logging.getLogger('urllib3').setLevel(logging.WARNING) - new_console = GUIStreamHandler(self) - root_logger.addHandler(new_console) - @property def engine(self): return self.get_engine() @@ -99,3 +91,17 @@ class GUI: def _get_start_stop_text(self): return "STOP (F9)" if self._bot_running else "START (F9)" + + def write_to_console(self, msg): + if not self._console: + return + + numlines = self._console.index('end - 1 line').split('.')[0] + self._console['state'] = 'normal' + if int(numlines) >= 50: # delete old lines + self._console.delete(1.0, 2.0) + if self._console.index('end-1c') != '1.0': # new line for each log + self._console.insert('end', '\n') + self._console.insert('end', msg) + self._console.see("end") # scroll to bottom + self._console['state'] = 'disabled' diff --git a/fishy/gui/log_config.py b/fishy/gui/log_config.py index d304666..5182c00 100644 --- a/fishy/gui/log_config.py +++ b/fishy/gui/log_config.py @@ -1,29 +1,36 @@ import logging -import typing from logging import StreamHandler, Formatter -if typing.TYPE_CHECKING: - from . import GUI +from fishy.helper.config import config -class GUIStreamHandler(StreamHandler): - def __init__(self, gui): +class GuiLogger(StreamHandler): + def __init__(self): StreamHandler.__init__(self) - self.gui = gui + + self.renderer = None + self._temp_buffer = [] + + formatter = Formatter('%(levelname)s - %(message)s') + self.setFormatter(formatter) + logging_config = {"comtypes": logging.INFO, + "PIL": logging.INFO, + "urllib3": logging.WARNING, + "": logging.DEBUG} + for name, level in logging_config.items(): + _logger = logging.getLogger(name) + _logger.setLevel(level) + self.setLevel(logging.DEBUG if config.get("debug", False) else logging.INFO) + logging.getLogger("").addHandler(self) def emit(self, record): - self.setLevel(logging.INFO) msg = self.format(record) - self.gui.call_in_thread(lambda: _write_to_console(self.gui, msg)) + if self.renderer: + self.renderer(msg) + else: + self._temp_buffer.append(msg) - -def _write_to_console(root: 'GUI', msg): - numlines = root._console.index('end - 1 line').split('.')[0] - root._console['state'] = 'normal' - if int(numlines) >= 50: # delete old lines - root._console.delete(1.0, 2.0) - if root._console.index('end-1c') != '1.0': # new line for each log - root._console.insert('end', '\n') - root._console.insert('end', msg) - root._console.see("end") # scroll to bottom - root._console['state'] = 'disabled' + def connect(self, gui): + self.renderer = lambda m: gui.call_in_thread(lambda: gui.write_to_console(m)) + while self._temp_buffer: + self.renderer(self._temp_buffer.pop(0)) diff --git a/fishy/gui/main_gui.py b/fishy/gui/main_gui.py index 741fe3f..2912005 100644 --- a/fishy/gui/main_gui.py +++ b/fishy/gui/main_gui.py @@ -4,12 +4,13 @@ import tkinter as tk import tkinter.ttk as ttk import typing +from fishy.gui import update_dialog from ttkthemes import ThemedTk from fishy import helper from fishy.web import web -from ..constants import chalutier, lam2, fishyqr +from ..constants import fishyqr from ..helper.config import config from .discord_login import discord_login from ..helper.hotkey.hotkey_process import hotkey @@ -31,6 +32,7 @@ def _create(gui: 'GUI'): engines = gui.engines gui._root = ThemedTk(theme="equilux", background=True) + gui._root.attributes('-alpha', 0.0) gui._root.title("Fishybot for Elder Scrolls Online") gui._root.iconbitmap(helper.manifest_file('icon.ico')) @@ -55,8 +57,11 @@ def _create(gui: 'GUI'): dark_mode_var.set(int(config.get('dark_mode', True))) filemenu.add_checkbutton(label="Dark Mode", command=_toggle_mode, variable=dark_mode_var) - if config.get("dont_ask_update", False): - filemenu.add_command(label="Update", command=helper.update) + + def update(): + config.delete("dont_ask_update") + update_dialog.check_update(gui, True) + filemenu.add_command(label="Update", command=update) def installer(): if filemenu.entrycget(4, 'label') == "Remove FishyQR": @@ -81,7 +86,6 @@ def _create(gui: 'GUI'): logging.debug("Restart to update the changes") debug_menu.add_checkbutton(label="Keep Console", command=keep_console, variable=debug_var) - debug_menu.add_command(label="Restart", command=helper.restart) menubar.add_cascade(label="Debug", menu=debug_menu) help_menu = tk.Menu(menubar, tearoff=0) @@ -127,7 +131,7 @@ def _create(gui: 'GUI'): hotkey.hook(Key.F9, gui.funcs.start_engine) - # noinspection PyProtectedMember + # noinspection PyProtectedMember,PyUnresolvedReferences def set_destroy(): if gui._bot_running: if not tk.messagebox.askyesno(title="Quit?", message="Bot engine running. Quit Anyway?"): @@ -139,6 +143,10 @@ def _create(gui: 'GUI'): gui._root.protocol("WM_DELETE_WINDOW", set_destroy) gui._destroyed = False + gui._root.update() + gui._clear_function_queue() + gui._root.after(0, gui._root.attributes, "-alpha", 1.0) + gui.on_ready() while True: gui._root.update() gui._clear_function_queue() @@ -148,6 +156,6 @@ def _create(gui: 'GUI'): gui._start_restart = False gui.create() if gui._destroyed: - gui.engine.quit() + gui.engine.quit_me() break time.sleep(0.01) diff --git a/fishy/gui/splash.py b/fishy/gui/splash.py index f273674..ecd2499 100644 --- a/fishy/gui/splash.py +++ b/fishy/gui/splash.py @@ -1,6 +1,8 @@ +import logging import time import tkinter as tk -from multiprocessing import Process +from multiprocessing import Process, Queue +from threading import Thread from PIL import Image, ImageTk @@ -8,12 +10,14 @@ from fishy.helper import helper from fishy.helper.config import config -def show(win_loc): +def show(win_loc, q): + logging.debug("started splash process") dim = (300, 200) top = tk.Tk() top.overrideredirect(True) top.lift() + top.attributes('-topmost', True) top.title("Loading...") top.resizable(False, False) @@ -31,10 +35,29 @@ def show(win_loc): loc = (win_loc or default_loc).split("+")[1:] top.geometry("{}x{}+{}+{}".format(dim[0], dim[1], int(loc[0]) + int(dim[0] / 2), int(loc[1]) + int(dim[1] / 2))) - top.update() - time.sleep(3) + def waiting(): + q.get() + time.sleep(0.2) + running[0] = False + Thread(target=waiting).start() + + running = [True] + while running[0]: + top.update() + time.sleep(0.1) + top.destroy() + logging.debug("ended splash process") + + +def create_finish(q): + def finish(): + q.put("stop") + + return finish def start(): - Process(target=show, args=(config.get("win_loc"),)).start() + q = Queue() + Process(target=show, args=(config.get("win_loc"), q,)).start() + return create_finish(q) diff --git a/fishy/gui/update_dialog.py b/fishy/gui/update_dialog.py index 4d881d4..ecf9b8e 100644 --- a/fishy/gui/update_dialog.py +++ b/fishy/gui/update_dialog.py @@ -1,11 +1,22 @@ +import logging import tkinter as tk -from multiprocessing import Manager, Process -from fishy import helper +from fishy.helper import helper, auto_update +from fishy.helper.config import config +from fishy.helper.popup import PopUp -def show(currentversion, newversion, returns): - top = tk.Tk() +def _show(gui, currentversion, newversion, returns): + + def _clickYes(): + returns[0], returns[1] = True, False + top.quit_top() + + def _clickNo(): + returns[0], returns[1] = False, bool(cbVar.get()) + top.quit_top() + + top = PopUp(helper.empty_function, gui._root) top.title("A wild fishy update appeared!") top.iconbitmap(helper.manifest_file('icon.ico')) @@ -19,14 +30,6 @@ def show(currentversion, newversion, returns): top.update() buttonWidth = int(dialogLabel.winfo_width() / 2) - 20 - def _clickYes(): - returns[0], returns[1] = True, False - top.destroy() - - def _clickNo(): - returns[0], returns[1] = False, bool(cbVar.get()) - top.destroy() - pixelVirtual = tk.PhotoImage(width=1, height=1) # trick to use buttonWidth as pixels, not #symbols dialogBtnNo = tk.Button(top, text="No " + str(chr(10005)), fg='red4', command=_clickNo, image=pixelVirtual, width=buttonWidth, compound="c") @@ -37,14 +40,23 @@ def show(currentversion, newversion, returns): dialogBtnYes.focus_set() top.protocol('WM_DELETE_WINDOW', _clickNo) - - top.update() - top.mainloop() + top.start() -def start(currentversion, newversion): - returns = Manager().dict() - p = Process(target=show, args=(currentversion, newversion, returns)) - p.start() - p.join() - return returns[0], returns[1] +def check_update(gui, manual_check=False): + if not auto_update.upgrade_avail() or config.get("dont_ask_update", False): + if manual_check: + logging.info("No update is available.") + return + + cv, hv = auto_update.versions() + returns = [None, None] + _show(gui, cv, hv, returns) + [update_now, dont_ask_update] = returns + if dont_ask_update: + config.set("dont_ask_update", dont_ask_update) + else: + config.delete("dont_ask_update") + + if update_now: + gui.engine.set_update(hv) diff --git a/fishy/helper/__init__.py b/fishy/helper/__init__.py index 084d649..686e1a3 100644 --- a/fishy/helper/__init__.py +++ b/fishy/helper/__init__.py @@ -1,9 +1,8 @@ -from .auto_update import auto_upgrade, upgrade_avail, versions from .config import Config from .helper import (addon_exists, create_shortcut, create_shortcut_first, - get_addonversion, get_savedvarsdir, initialize_uid, + get_addonversion, get_savedvarsdir, install_addon, install_thread_excepthook, manifest_file, not_implemented, open_web, playsound_multiple, - remove_addon, restart, unhandled_exception_logging, - update, install_required_addons) + remove_addon, unhandled_exception_logging, + install_required_addons) from .luaparser import sv_color_extract diff --git a/fishy/helper/active_poll.py b/fishy/helper/active_poll.py index 6807b80..1f2a5ca 100644 --- a/fishy/helper/active_poll.py +++ b/fishy/helper/active_poll.py @@ -1,3 +1,5 @@ +import logging + from event_scheduler import EventScheduler from fishy.web import web @@ -13,11 +15,14 @@ class active: active._scheduler = EventScheduler() active._scheduler.start() + logging.debug("active scheduler initialized") @staticmethod def start(): active._scheduler.enter_recurring(60, 1, web.ping) + logging.debug("active scheduler started") @staticmethod def stop(): active._scheduler.stop(hard_stop=True) + logging.debug("active scheduler stopped") diff --git a/fishy/helper/auto_update.py b/fishy/helper/auto_update.py index c6be7d0..ed7bb8e 100644 --- a/fishy/helper/auto_update.py +++ b/fishy/helper/auto_update.py @@ -38,29 +38,29 @@ def _normalize_version(v): return rv -def _get_highest_version(index, pkg): +def _get_highest_version(_index, _pkg): """ Crawls web for latest version name then returns latest version - :param index: website to check - :param pkg: package name + :param _index: website to check + :param _pkg: package name :return: latest version normalized """ - url = "{}/{}/".format(index, pkg) + url = "{}/{}/".format(_index, _pkg) html = urllib.request.urlopen(url) if html.getcode() != 200: raise Exception # not found soup = BeautifulSoup(html.read(), "html.parser") - versions = [] + _versions = [] for link in soup.find_all('a'): text = link.get_text() try: - version = re.search(pkg + r'-(.*)\.tar\.gz', text).group(1) - versions.append(_normalize_version(version)) + version = re.search(_pkg + r'-(.*)\.tar\.gz', text).group(1) + _versions.append(_normalize_version(version)) except AttributeError: pass - if len(versions) == 0: + if len(_versions) == 0: raise Exception # no version - return max(versions) + return max(_versions) def _get_current_version(): @@ -88,13 +88,12 @@ def upgrade_avail(): return _get_current_version() < _get_highest_version(index, pkg) -def auto_upgrade(): +def update_now(version): """ public function, compares current version with the latest version (from web), if current version is older, then it updates and restarts the script """ - version = _hr_version(_get_highest_version(index, pkg)) logging.info(f"Updating to v{version}, Please Wait...") subprocess.call(["python", '-m', 'pip', 'install', '--upgrade', 'fishy', '--user']) execl(sys.executable, *([sys.executable, '-m', 'fishy'] + sys.argv[1:])) diff --git a/fishy/helper/config.py b/fishy/helper/config.py index 773c32b..6a050a6 100644 --- a/fishy/helper/config.py +++ b/fishy/helper/config.py @@ -3,6 +3,7 @@ config.py Saves configuration in file as json file """ import json +import logging import os # path to save the configuration file from typing import Optional @@ -46,28 +47,32 @@ class Config: self._config_dict = json.loads(open(filename()).read()) except json.JSONDecodeError: try: - print("Config file got corrupted, trying to restore backup") + logging.warning("Config file got corrupted, trying to restore backup") self._config_dict = json.loads(open(temp_file).read()) self.save_config() except (FileNotFoundError, json.JSONDecodeError): - print("couldn't restore, creating new") + logging.warning("couldn't restore, creating new") os.remove(filename()) self._config_dict = dict() else: self._config_dict = dict() + logging.debug("config initialized") + def start_backup_scheduler(self): self._create_backup() self._scheduler.start() self._scheduler.enter_recurring(5 * 60, 1, self._create_backup) + logging.debug("scheduler started") def stop(self): self._scheduler.stop(True) + logging.debug("config stopped") def _create_backup(self): with open(temp_file, 'w') as f: f.write(json.dumps(self._config_dict)) - print("created backup") + logging.debug("created backup") def _sort_dict(self): tmpdict = dict() @@ -90,8 +95,13 @@ class config: @staticmethod def init(): - config._instance = Config() - config._instance.initialize() + if not config._instance: + config._instance = Config() + config._instance.initialize() + + @staticmethod + def start_backup_scheduler(): + config._instance.start_backup_scheduler() @staticmethod def stop(): diff --git a/fishy/helper/helper.py b/fishy/helper/helper.py index 56a0a93..d14d505 100644 --- a/fishy/helper/helper.py +++ b/fishy/helper/helper.py @@ -21,7 +21,6 @@ from win32comext.shell import shell, shellcon from win32gui import GetForegroundWindow, GetWindowText import fishy -from fishy import web from fishy.constants import libgps, lam2, fishyqr from fishy.helper.config import config @@ -66,19 +65,6 @@ def open_web(website): Thread(target=lambda: webbrowser.open(website, new=2)).start() -def initialize_uid(): - from .config import config - - if config.get("uid") is not None: - return - - new_uid = web.register_user() - if new_uid is not None: - config.set("uid", new_uid) - else: - logging.error("Couldn't register uid, some features might not work") - - def _create_new_uid(): """ Creates a unique id for user @@ -141,8 +127,8 @@ def create_shortcut(anti_ghosting: bool): desktop = winshell.desktop() path = os.path.join(desktop, "Fishybot ESO.lnk") - shell = Dispatch('WScript.Shell') - shortcut = shell.CreateShortCut(path) + _shell = Dispatch('WScript.Shell') + shortcut = _shell.CreateShortCut(path) if anti_ghosting: shortcut.TargetPath = r"C:\Windows\System32\cmd.exe" @@ -157,7 +143,7 @@ def create_shortcut(anti_ghosting: bool): logging.info("Shortcut created") except Exception: - traceback.print_exc() + print_exc() logging.error("Couldn't create shortcut") @@ -182,6 +168,7 @@ def addon_exists(name, url=None, v=None): def get_addonversion(name, url=None, v=None): if addon_exists(name): txt = name + ".txt" + # noinspection PyBroadException try: with open(os.path.join(get_addondir(), name, txt)) as f: for line in f: @@ -219,7 +206,7 @@ def install_addon(name, url, v=None): return 0 except Exception: logging.error("Could not install Add-On " + name + ", try doing it manually") - traceback.print_exc() + print_exc() return 1 @@ -239,22 +226,11 @@ def get_documents(): return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0) -def restart(): - os.execl(sys.executable, *([sys.executable] + sys.argv)) - - def log_raise(msg): logging.error(msg) raise Exception(msg) -def update(): - from .config import config - - config.delete("dont_ask_update") - restart() - - def is_eso_active(): return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online" @@ -264,9 +240,9 @@ def _get_id(thread): # returns id of the respective thread if hasattr(thread, '_thread_id'): return thread._thread_id - for id, thread in threading._active.items(): + for _id, thread in threading._active.items(): if thread is thread: - return id + return _id def kill_thread(thread): @@ -276,3 +252,8 @@ def kill_thread(thread): if res > 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) print('Exception raise failure') + + +def print_exc(): + logging.error(traceback.format_exc()) + traceback.print_exc() diff --git a/fishy/helper/hotkey/hotkey_process.py b/fishy/helper/hotkey/hotkey_process.py index e82efed..f8219c7 100644 --- a/fishy/helper/hotkey/hotkey_process.py +++ b/fishy/helper/hotkey/hotkey_process.py @@ -1,3 +1,4 @@ +import logging import time from multiprocessing import Process, Queue from threading import Thread @@ -69,10 +70,11 @@ class HotKey: def start(self): self.process.start() self.event.start() + logging.debug("hotkey process started") def stop(self): self.inq.put("stop") self.outq.put("stop") self.process.join() self.event.join() - print("hotkey process ended") + logging.debug("hotkey process ended") diff --git a/fishy/helper/migration.py b/fishy/helper/migration.py index d6e992c..e45e7a9 100644 --- a/fishy/helper/migration.py +++ b/fishy/helper/migration.py @@ -2,8 +2,7 @@ import logging from fishy.helper.auto_update import _normalize_version -from fishy.constants import chalutier, version -from fishy.helper import helper +from fishy.constants import version from .config import config diff --git a/fishy/helper/popup.py b/fishy/helper/popup.py index 7a2b304..5243fdb 100644 --- a/fishy/helper/popup.py +++ b/fishy/helper/popup.py @@ -19,6 +19,7 @@ class PopUp(Toplevel): super().__init__(*args, **kwargs) self.running = True self.quit_callback = quit_callback + self.protocol("WM_DELETE_WINDOW", self.quit_top) def quit_top(self): self.quit_callback() @@ -26,7 +27,6 @@ class PopUp(Toplevel): self.running = False def start(self): - self.protocol("WM_DELETE_WINDOW", self.quit_top) self.grab_set() center(self) while self.running: diff --git a/fishy/web/web.py b/fishy/web/web.py index 83da91a..2c69ea7 100644 --- a/fishy/web/web.py +++ b/fishy/web/web.py @@ -1,9 +1,9 @@ +import logging + import requests from whatsmyip.ip import get_ip from whatsmyip.providers import GoogleDnsProvider -from fishy import helper - from ..constants import apiversion from ..helper.config import config from . import urls @@ -86,8 +86,6 @@ def sub(): @fallback((False, False)) def is_subbed(): """ - :param uid: - :param lazy: :return: Tuple[is_subbed, success] """ @@ -100,8 +98,8 @@ def is_subbed(): if response.status_code != 200: return False, False - is_subbed = response.json()["subbed"] - return is_subbed, True + _is_subbed = response.json()["subbed"] + return _is_subbed, True @fallback(None) @@ -112,24 +110,48 @@ def unsub(): return result["success"] -@fallback(None) def get_session(lazy=True): global _session_id + # lazy loading logic if lazy and _session_id is not None: return _session_id - body = {"uid": config.get("uid"), "apiversion": apiversion} + # check if user has uid + uid = config.get("uid") + + # then create session + if uid: + _session_id, online = _create_new_session(uid) + # if not, create new id then try creating session again + else: + uid = register_user() + logging.debug("New User, generated new uid") + if uid: + _session_id, online = _create_new_session(uid) + else: + online = False + + # when the user is already registered but session is not created as uid is not found + if online and not _session_id: + logging.error("user not found, generating new uid.. contact dev if you don't want to loose data") + new_uid = register_user() + _session_id, online = _create_new_session(new_uid) + config.set("uid", new_uid) + config.set("old_uid", uid) + + return _session_id + + +@fallback((None, False)) +def _create_new_session(uid): + body = {"uid": uid, "apiversion": apiversion} response = requests.post(urls.session, params=body) if response.status_code == 405: - config.delete("uid") - helper.restart() - return None - - _session_id = response.json()["session_id"] - return _session_id + return None, True + return response.json()["session_id"], True @fallback(False) def has_beta():