diff --git a/build.bat b/build.bat index aa69ad5..7a51856 100644 --- a/build.bat +++ b/build.bat @@ -1,6 +1,5 @@ @echo off rd build dist /s /q -call activate ./venv python ./setup.py sdist python ./setup.py bdist_wheel PAUSE diff --git a/fishy/__init__.py b/fishy/__init__.py index 00b55e3..f877530 100644 --- a/fishy/__init__.py +++ b/fishy/__init__.py @@ -1,2 +1,2 @@ from fishy.__main__ import main -__version__ = "0.3.2" +__version__ = "0.3.3" diff --git a/fishy/__main__.py b/fishy/__main__.py index 32a4d38..afdc468 100644 --- a/fishy/__main__.py +++ b/fishy/__main__.py @@ -49,18 +49,18 @@ def main(): window_to_hide = win32gui.GetForegroundWindow() c = Config() - events_buffer = [] if not gui.check_eula(c): return - gui_window = GUI(c, lambda a, b=None: events_buffer.append((a, b))) + bot = Engine(c, lambda: gui_window) + gui_window = GUI(c, lambda: bot) + gui_window.start() logging.info(f"Fishybot v{fishy.__version__}") initialize(c, window_to_hide) - bot = Engine(gui_window, events_buffer, c) bot.start_event_handler() diff --git a/fishy/gui/__init__.py b/fishy/gui/__init__.py index ac61885..6cd565a 100644 --- a/fishy/gui/__init__.py +++ b/fishy/gui/__init__.py @@ -1,3 +1,2 @@ from .gui import GUI from .terms_gui import check_eula -from .comms import GUIFunction, GUIEvent diff --git a/fishy/gui/comms.py b/fishy/gui/comms.py deleted file mode 100644 index 76c3eb8..0000000 --- a/fishy/gui/comms.py +++ /dev/null @@ -1,46 +0,0 @@ -import threading -from enum import Enum -from tkinter import * -from tkinter import messagebox, filedialog - -from .log_config import _write_to_console -import typing - -if typing.TYPE_CHECKING: - from . import GUI - - -class GUIEvent(Enum): - START_BUTTON = 0 # args: ip: str, action_key: str, fullscreen: bool, collect_r: bool - CHECK_PIXELVAL = 1 - QUIT = 2 - - -class GUIFunction(Enum): - LOG = 0 # args: str - STARTED = 1 # args: bool - ASK_DIRECTORY = 2 # callback: callable - SHOW_ERROR = 3 - SET_NOTIFY = 4 - - -def _clear_function_queue(gui: 'GUI'): - while len(gui._function_queue) > 0: - func = gui._function_queue.pop(0) - - if func[0] == GUIFunction.LOG: - _write_to_console(gui, func[1][0]) - elif func[0] == GUIFunction.STARTED: - gui._bot_running = func[1][0] - gui._start_button["text"] = "STOP" if gui._bot_running else "START" - elif func[0] == GUIFunction.ASK_DIRECTORY: - messagebox.showinfo("Directory?", func[1][1]) - path = filedialog.askdirectory() - if path != '': - threading.Thread(target=func[1][0], args=(path,)).start() - elif func[0] == GUIFunction.SHOW_ERROR: - messagebox.showerror("ERROR", func[1][0]) - elif func[0] == GUIFunction.SET_NOTIFY: - gui._notify.set(func[1][0]) - if func[1][1]: - gui._notify_check['state'] = NORMAL diff --git a/fishy/gui/funcs.py b/fishy/gui/funcs.py new file mode 100644 index 0000000..6f5c79c --- /dev/null +++ b/fishy/gui/funcs.py @@ -0,0 +1,23 @@ +from tkinter import messagebox, NORMAL + + +# noinspection PyProtectedMember +class GUIFuncs: + def __init__(self, gui): + self.gui = gui + + def set_notify(self, flag): + def func(): + self.gui._notify_check['state'] = NORMAL + self.gui._notify.set(flag) + self.gui.call_in_thread(func) + + def show_error(self, error): + self.gui.call_in_thread(lambda: messagebox.showerror("ERROR", error)) + + def bot_started(self, started): + def func(): + self.gui._bot_running = started + self.gui._start_button["text"] = "STOP" if self.gui._bot_running else "START" + + self.gui.call_in_thread(func) diff --git a/fishy/gui/gui.py b/fishy/gui/gui.py index 2cafc5f..66f2f6a 100644 --- a/fishy/gui/gui.py +++ b/fishy/gui/gui.py @@ -1,25 +1,27 @@ import logging -from typing import Tuple, List, Callable, Optional +from typing import List, Callable import threading +from fishy.gui.funcs import GUIFuncs +from fishy.tech import Engine from . import main_gui -from .comms import GUIEvent, GUIFunction from .log_config import GUIStreamHandler from fishy.helper import Config class GUI: - def __init__(self, config: Config, event_trigger: Callable[[GUIEvent, Optional[Tuple]], None]): + def __init__(self, config: Config, get_engine: Callable[[], Engine]): """ :param config: used to get and set configuration settings - :param event_trigger: used to communicate with other threads """ + self.funcs = GUIFuncs(self) + self.get_engine = get_engine + self._config = config self._start_restart = False self._destroyed = True self._log_strings = [] - self._function_queue: List[Tuple[GUIFunction, Tuple]] = [] - self._event_trigger = event_trigger + self._function_queue: List[Callable] = [] self._bot_running = False # UI items @@ -37,11 +39,20 @@ class GUI: new_console = GUIStreamHandler(self) root_logger.addHandler(new_console) + @property + def engine(self): + return self.get_engine().funcs + def create(self): main_gui._create(self) def start(self): self._thread.start() - def call(self, gui_func, args): - self._function_queue.append((gui_func, args)) + def _clear_function_queue(self): + while len(self._function_queue) > 0: + func = self._function_queue.pop(0) + func() + + def call_in_thread(self, func: Callable): + self._function_queue.append(func) diff --git a/fishy/gui/log_config.py b/fishy/gui/log_config.py index c4e6ce8..585a9b4 100644 --- a/fishy/gui/log_config.py +++ b/fishy/gui/log_config.py @@ -12,9 +12,8 @@ class GUIStreamHandler(StreamHandler): self.gui = gui def emit(self, record): - from .comms import GUIFunction msg = self.format(record) - self.gui.call(GUIFunction.LOG, (msg,)) + self.gui.call_in_thread(lambda: _write_to_console(self.gui, msg)) def _write_to_console(root: 'GUI', msg): diff --git a/fishy/gui/main_gui.py b/fishy/gui/main_gui.py index 334815a..cae33a6 100644 --- a/fishy/gui/main_gui.py +++ b/fishy/gui/main_gui.py @@ -7,7 +7,6 @@ from ttkthemes import ThemedTk from fishy import helper, web -from .comms import GUIEvent, GUIFunction, _clear_function_queue from .notification import _give_notification_link import typing @@ -16,7 +15,7 @@ if typing.TYPE_CHECKING: def _apply_theme(gui: 'GUI'): - dark = gui._config.get("dark", True) + dark = gui._config.get("dark_mode", True) gui._root["theme"] = "equilux" if dark else "breeze" gui._console["background"] = "#707070" if dark else "#ffffff" gui._console["fg"] = "#ffffff" if dark else "#000000" @@ -25,7 +24,6 @@ def _apply_theme(gui: 'GUI'): def _create(gui: 'GUI'): gui._root = ThemedTk(theme="equilux", background=True) gui._root.title("Fishybot for Elder Scrolls Online") - gui._root.geometry('650x550') gui._root.iconbitmap(helper.manifest_file('icon.ico')) @@ -48,7 +46,7 @@ def _create(gui: 'GUI'): debug_menu = Menu(menubar, tearoff=0) debug_menu.add_command(label="Check PixelVal", - command=lambda: gui._event_trigger(GUIEvent.CHECK_PIXELVAL, ())) + command=lambda: gui.engine.check_pixel_val()) debug_var = IntVar() debug_var.set(int(gui._config.get('debug', False))) @@ -93,7 +91,8 @@ def _create(gui: 'GUI'): def update_notify_check(): is_subbed = web.is_subbed(gui._config.get('uid')) - gui.call(GUIFunction.SET_NOTIFY, (int(is_subbed[0]), is_subbed[1])) + if is_subbed[1]: + gui.funcs.set_notify(is_subbed[0]) threading.Thread(target=update_notify_check).start() @@ -121,10 +120,9 @@ def _create(gui: 'GUI'): gui._start_button = Button(gui._root, text="STOP" if gui._bot_running else "START", width=25) def start_button_callback(): - args = (action_key_entry.get(), - borderless.instate(['selected']), - collect_r.instate(['selected'])) - gui._event_trigger(GUIEvent.START_BUTTON, args) + gui.engine.start_button_pressed(action_key_entry.get(), + borderless.instate(['selected']), + collect_r.instate(['selected'])) gui._config.set("action_key", action_key_entry.get(), False) gui._config.set("borderless", borderless.instate(['selected']), False) @@ -147,13 +145,13 @@ def _create(gui: 'GUI'): while True: gui._root.update() - _clear_function_queue(gui) + gui._clear_function_queue() if gui._start_restart: gui._root.destroy() gui._root.quit() gui._start_restart = False gui.create() if gui._destroyed: - gui._event_trigger(GUIEvent.QUIT, ()) + gui.engine.quit() break time.sleep(0.01) diff --git a/fishy/gui/notification.py b/fishy/gui/notification.py index 647408f..c91ab02 100644 --- a/fishy/gui/notification.py +++ b/fishy/gui/notification.py @@ -1,9 +1,8 @@ -import os -import tempfile +import time from tkinter import * from tkinter import messagebox from tkinter.ttk import * -import pyqrcode +from tkhtmlview import HTMLLabel from fishy import web import typing @@ -12,6 +11,7 @@ if typing.TYPE_CHECKING: from . import GUI +# noinspection PyProtectedMember def _give_notification_link(gui: 'GUI'): if web.is_subbed(gui._config.get("uid"))[0]: web.unsub(gui._config.get("uid")) @@ -25,37 +25,49 @@ def _give_notification_link(gui: 'GUI'): top_running[0] = False def check(): - if web.is_subbed(gui._config.get("uid"), False)[0]: - gui._notify.set(1) - web.send_notification(gui._config.get("uid"), "Sending a test notification :D") - messagebox.showinfo("Note!", "Notification configured successfully!") - quit_top() + if web.sub(gui._config.get("uid"), discord_name.get()): + if web.is_subbed(gui._config.get("uid"), False)[0]: + gui._notify.set(1) + messagebox.showinfo("Note!", "Notification configured successfully!") + quit_top() else: messagebox.showerror("Error", "Subscription wasn't successful") - print("got to {}".format(web.get_notification_page(gui._config.get("uid")))) - qrcode = pyqrcode.create(web.get_notification_page(gui._config.get("uid"))) - t = os.path.join(tempfile.gettempdir(), "fishyqr.png") - qrcode.png(t, scale=8) - top_running = [True] top = Toplevel(background=gui._root["background"]) - top.minsize(width=500, height=500) + top.minsize(width=300, height=300) top.title("Notification Setup") - Label(top, text="Step 1.").pack(pady=(5, 5)) - Label(top, text="Scan the QR Code on your Phone and press \"Enable Notification\"").pack(pady=(5, 5)) - canvas = Canvas(top, width=qrcode.get_png_size(8), height=qrcode.get_png_size(8)) - canvas.pack(pady=(5, 5)) - Label(top, text="Step 2.").pack(pady=(5, 5)) - Button(top, text="Check", command=check).pack(pady=(5, 5)) + html_label = HTMLLabel(top, + html=f'
' + f'

Step 1.
' + f'Join Discord server

' + f'

Step 2.
' + f'Enter username (ex. Fishy#1234)' + f'

', background=gui._root["background"]) - image = PhotoImage(file=t) - canvas.create_image(0, 0, anchor=NW, image=image) + html_label.pack(pady=(20, 5)) + html_label.fit_height() + + discord_name = Entry(top, justify=CENTER, font="Calibri 15") + discord_name.pack(padx=(15, 15), expand=True, fill=BOTH) + + html_label = HTMLLabel(top, + html=f'
' + f'

Step 3.
' + f'Install Discord App on your phone

' + f'

Step 4.

' + f'
', background=gui._root["background"]) + + html_label.pack(pady=(5, 5)) + html_label.fit_height() + + Button(top, text="REGISTER", command=check).pack(pady=(5, 20)) top.protocol("WM_DELETE_WINDOW", quit_top) top.grab_set() while top_running[0]: top.update() + time.sleep(0.01) top.grab_release() diff --git a/fishy/tech/__init__.py b/fishy/tech/__init__.py index c2fcbb1..8840c2a 100644 --- a/fishy/tech/__init__.py +++ b/fishy/tech/__init__.py @@ -1 +1,2 @@ from .engine import Engine +from .funcs import EngineFuncs diff --git a/fishy/tech/engine.py b/fishy/tech/engine.py index 63972ea..cd9f805 100644 --- a/fishy/tech/engine.py +++ b/fishy/tech/engine.py @@ -1,4 +1,6 @@ import time +import typing +from collections import Callable from threading import Thread import cv2 @@ -6,34 +8,44 @@ import logging import pywintypes -from fishy.gui import GUIFunction, GUIEvent +from fishy.tech.funcs import EngineFuncs from . import fishing_event from .fishing_event import HookEvent, StickEvent, LookEvent, IdleEvent from .fishing_mode import FishingMode from .pixel_loc import PixelLoc from .window import Window +if typing.TYPE_CHECKING: + from fishy.gui import GUI + def _wait_and_check(gui): time.sleep(10) if not fishing_event._FishingStarted: - gui.call(GUIFunction.SHOW_ERROR, ("Doesn't look like fishing has started\n\n" - "Make sure ProvisionsChalutier addon is visible clearly on top " - "left corner of the screen, either,\n" - "1) Outdated addons are disabled\n" - "2) Other addons are overlapping ProvisionsChalutier\n" - "3) Post processing (re shader) is on\n\n" - "If fixing those doesnt work, try running the bot as admin",)) + gui.show_error("Doesn't look like fishing has started\n\n" + "Make sure ProvisionsChalutier addon is visible clearly on top " + "left corner of the screen, either,\n" + "1) Outdated addons are disabled\n" + "2) Other addons are overlapping ProvisionsChalutier\n" + "3) Post processing (re shader) is on\n\n" + "If fixing those doesnt work, try running the bot as admin") class Engine: - def __init__(self, gui_ref, gui_event_buffer, config): - self.gui_events = gui_event_buffer + def __init__(self, config, gui_ref: 'Callable[[], GUI]'): + self.funcs = EngineFuncs(self) + self.get_gui = gui_ref + self.start = False self.fishPixWindow = None self.fishy_thread = None - self.gui = gui_ref self.config = config + self.event_handler_running = True + self.gui_events = [] + + @property + def gui(self): + return self.get_gui().funcs def start_fishing(self, action_key: str, borderless: bool, collect_r: bool): """ @@ -59,7 +71,7 @@ class Engine: self.fishPixWindow = Window(color=cv2.COLOR_RGB2HSV) # check for game window and stuff - self.gui.call(GUIFunction.STARTED, (True,)) + self.gui.bot_started(True) logging.info("Starting the bot engine, look at the fishing hole to start fishing") Thread(target=_wait_and_check, args=(self.gui,)).start() while self.start: @@ -73,26 +85,13 @@ class Engine: # Services to be ran in the end of the main loop Window.loop_end() logging.info("Fishing engine stopped") - self.gui.call(GUIFunction.STARTED, (False,)) + self.gui.bot_started(False) def start_event_handler(self): - while True: + while self.event_handler_running: while len(self.gui_events) > 0: event = self.gui_events.pop(0) - - if event[0] == GUIEvent.START_BUTTON: - self.start = not self.start - if self.start: - self.fishy_thread = Thread(target=self.start_fishing, args=(*event[1],)) - self.fishy_thread.start() - elif event[0] == GUIEvent.CHECK_PIXELVAL: - if self.start: - self._show_pixel_vals() - else: - logging.debug("Start the engine first before running this command") - elif event[0] == GUIEvent.QUIT: - self.start = False - return + event() def _show_pixel_vals(self): def show(): @@ -106,3 +105,6 @@ class Engine: logging.debug("Will display pixel values for 10 seconds") time.sleep(5) Thread(target=show, args=()).start() + + + diff --git a/fishy/tech/funcs.py b/fishy/tech/funcs.py new file mode 100644 index 0000000..25a5751 --- /dev/null +++ b/fishy/tech/funcs.py @@ -0,0 +1,33 @@ +import logging +from threading import Thread + + +# noinspection PyProtectedMember +class EngineFuncs: + def __init__(self, engine): + self.engine = engine + + def start_button_pressed(self, *params): + def func(): + self.engine.start = not self.engine.start + if self.engine.start: + self.engine.fishy_thread = Thread(target=self.engine.start_fishing, args=(*params,)) + self.engine.fishy_thread.start() + + self.engine.gui_events.append(func) + + def check_pixel_val(self): + def func(): + if self.engine.start: + self.engine._show_pixel_vals() + else: + logging.debug("Start the engine first before running this command") + + self.engine.gui_events.append(func) + + def quit(self): + def func(): + self.engine.start = False + self.engine.event_handler_running = False + + self.engine.gui_events.append(func) diff --git a/fishy/web/__init__.py b/fishy/web/__init__.py index 26b6f24..6912bae 100644 --- a/fishy/web/__init__.py +++ b/fishy/web/__init__.py @@ -1,2 +1,2 @@ from .urls import get_notification_page, get_terms_page -from .web import register_user, send_notification, send_hole_deplete, is_subbed, unsub, get_session +from .web import register_user, send_notification, send_hole_deplete, is_subbed, unsub, get_session, sub diff --git a/fishy/web/urls.py b/fishy/web/urls.py index 3edc54c..f51acdf 100644 --- a/fishy/web/urls.py +++ b/fishy/web/urls.py @@ -1,6 +1,11 @@ import sys -domain = "http://127.0.0.1:5000" if "--local-server" in sys.argv else "https://fishyeso.herokuapp.com" +if "--local-server" in sys.argv: + domain = "http://127.0.0.1:5000" +elif "--test-server" in sys.argv: + domain = "https://fishyeso-test.herokuapp.com" +else: + domain = "https://fishyeso.herokuapp.com" user = domain + "/api/user" notify = domain + "/api/notify" diff --git a/fishy/web/web.py b/fishy/web/web.py index b124df9..de00f5d 100644 --- a/fishy/web/web.py +++ b/fishy/web/web.py @@ -41,8 +41,20 @@ def send_hole_deplete(uid, fish_caught, hole_time, fish_times): requests.post(urls.hole_depleted, json=body) +@fallback(False) +def sub(uid, name): + body = {"uid": uid, "discord_name": name} + response = requests.post(urls.subscription, json=body) + return response.json()["success"] + + @fallback((False, False)) def is_subbed(uid, lazy=True): + """ + :param uid: + :param lazy: + :return: Tuple[is_subbed, success] + """ global _is_subbed if lazy and _is_subbed is not None: diff --git a/requirements.txt b/requirements.txt index b36676a..4dfd550 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +urllib3 winshell imutils numpy @@ -9,5 +10,5 @@ pyautogui requests beautifulsoup4 whatsmyip -pyqrcode pypng +tkhtmlview diff --git a/test.ps1 b/test.ps1 new file mode 100644 index 0000000..2968834 --- /dev/null +++ b/test.ps1 @@ -0,0 +1,9 @@ +cd temp\test +Remove-Item venv -Recurse +& conda create --prefix venv python=3.7 -y +conda activate ./venv +cd ../../dist +pip install ((dir).Name | grep whl) +python -m fishy +cd .. +conda deactivate \ No newline at end of file