mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
Merge pull request #103 from fishyboteso/improvement/threading-rework
This commit is contained in:
commit
c0690ae7fa
@ -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__":
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
"""
|
||||
|
@ -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():
|
||||
|
@ -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 = [
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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 = {
|
||||
|
@ -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"])
|
||||
|
@ -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):
|
||||
|
@ -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'
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
|
@ -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:]))
|
||||
|
@ -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():
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user