0.3.3 reworked notifications again (now uses discord chat)

- fixed ui scaling issues
- now uses callables for thread comunication instead of enums (removed comms.py altogether)
- added options to run using test server
This commit is contained in:
DESKTOP-JVKHS7I\Adam 2020-05-19 08:41:58 +05:30
parent b1ee1d1188
commit 3e96fbed2c
18 changed files with 184 additions and 126 deletions

View File

@ -1,6 +1,5 @@
@echo off @echo off
rd build dist /s /q rd build dist /s /q
call activate ./venv
python ./setup.py sdist python ./setup.py sdist
python ./setup.py bdist_wheel python ./setup.py bdist_wheel
PAUSE PAUSE

View File

@ -1,2 +1,2 @@
from fishy.__main__ import main from fishy.__main__ import main
__version__ = "0.3.2" __version__ = "0.3.3"

View File

@ -49,18 +49,18 @@ def main():
window_to_hide = win32gui.GetForegroundWindow() window_to_hide = win32gui.GetForegroundWindow()
c = Config() c = Config()
events_buffer = []
if not gui.check_eula(c): if not gui.check_eula(c):
return 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() gui_window.start()
logging.info(f"Fishybot v{fishy.__version__}") logging.info(f"Fishybot v{fishy.__version__}")
initialize(c, window_to_hide) initialize(c, window_to_hide)
bot = Engine(gui_window, events_buffer, c)
bot.start_event_handler() bot.start_event_handler()

View File

@ -1,3 +1,2 @@
from .gui import GUI from .gui import GUI
from .terms_gui import check_eula from .terms_gui import check_eula
from .comms import GUIFunction, GUIEvent

View File

@ -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

23
fishy/gui/funcs.py Normal file
View File

@ -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)

View File

@ -1,25 +1,27 @@
import logging import logging
from typing import Tuple, List, Callable, Optional from typing import List, Callable
import threading import threading
from fishy.gui.funcs import GUIFuncs
from fishy.tech import Engine
from . import main_gui from . import main_gui
from .comms import GUIEvent, GUIFunction
from .log_config import GUIStreamHandler from .log_config import GUIStreamHandler
from fishy.helper import Config from fishy.helper import Config
class GUI: 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 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._config = config
self._start_restart = False self._start_restart = False
self._destroyed = True self._destroyed = True
self._log_strings = [] self._log_strings = []
self._function_queue: List[Tuple[GUIFunction, Tuple]] = [] self._function_queue: List[Callable] = []
self._event_trigger = event_trigger
self._bot_running = False self._bot_running = False
# UI items # UI items
@ -37,11 +39,20 @@ class GUI:
new_console = GUIStreamHandler(self) new_console = GUIStreamHandler(self)
root_logger.addHandler(new_console) root_logger.addHandler(new_console)
@property
def engine(self):
return self.get_engine().funcs
def create(self): def create(self):
main_gui._create(self) main_gui._create(self)
def start(self): def start(self):
self._thread.start() self._thread.start()
def call(self, gui_func, args): def _clear_function_queue(self):
self._function_queue.append((gui_func, args)) 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)

View File

@ -12,9 +12,8 @@ class GUIStreamHandler(StreamHandler):
self.gui = gui self.gui = gui
def emit(self, record): def emit(self, record):
from .comms import GUIFunction
msg = self.format(record) 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): def _write_to_console(root: 'GUI', msg):

View File

@ -7,7 +7,6 @@ from ttkthemes import ThemedTk
from fishy import helper, web from fishy import helper, web
from .comms import GUIEvent, GUIFunction, _clear_function_queue
from .notification import _give_notification_link from .notification import _give_notification_link
import typing import typing
@ -16,7 +15,7 @@ if typing.TYPE_CHECKING:
def _apply_theme(gui: 'GUI'): 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._root["theme"] = "equilux" if dark else "breeze"
gui._console["background"] = "#707070" if dark else "#ffffff" gui._console["background"] = "#707070" if dark else "#ffffff"
gui._console["fg"] = "#ffffff" if dark else "#000000" gui._console["fg"] = "#ffffff" if dark else "#000000"
@ -25,7 +24,6 @@ def _apply_theme(gui: 'GUI'):
def _create(gui: 'GUI'): def _create(gui: 'GUI'):
gui._root = ThemedTk(theme="equilux", background=True) gui._root = ThemedTk(theme="equilux", background=True)
gui._root.title("Fishybot for Elder Scrolls Online") gui._root.title("Fishybot for Elder Scrolls Online")
gui._root.geometry('650x550')
gui._root.iconbitmap(helper.manifest_file('icon.ico')) gui._root.iconbitmap(helper.manifest_file('icon.ico'))
@ -48,7 +46,7 @@ def _create(gui: 'GUI'):
debug_menu = Menu(menubar, tearoff=0) debug_menu = Menu(menubar, tearoff=0)
debug_menu.add_command(label="Check PixelVal", 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 = IntVar()
debug_var.set(int(gui._config.get('debug', False))) debug_var.set(int(gui._config.get('debug', False)))
@ -93,7 +91,8 @@ def _create(gui: 'GUI'):
def update_notify_check(): def update_notify_check():
is_subbed = web.is_subbed(gui._config.get('uid')) 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() 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) gui._start_button = Button(gui._root, text="STOP" if gui._bot_running else "START", width=25)
def start_button_callback(): def start_button_callback():
args = (action_key_entry.get(), gui.engine.start_button_pressed(action_key_entry.get(),
borderless.instate(['selected']), borderless.instate(['selected']),
collect_r.instate(['selected'])) collect_r.instate(['selected']))
gui._event_trigger(GUIEvent.START_BUTTON, args)
gui._config.set("action_key", action_key_entry.get(), False) gui._config.set("action_key", action_key_entry.get(), False)
gui._config.set("borderless", borderless.instate(['selected']), False) gui._config.set("borderless", borderless.instate(['selected']), False)
@ -147,13 +145,13 @@ def _create(gui: 'GUI'):
while True: while True:
gui._root.update() gui._root.update()
_clear_function_queue(gui) gui._clear_function_queue()
if gui._start_restart: if gui._start_restart:
gui._root.destroy() gui._root.destroy()
gui._root.quit() gui._root.quit()
gui._start_restart = False gui._start_restart = False
gui.create() gui.create()
if gui._destroyed: if gui._destroyed:
gui._event_trigger(GUIEvent.QUIT, ()) gui.engine.quit()
break break
time.sleep(0.01) time.sleep(0.01)

View File

@ -1,9 +1,8 @@
import os import time
import tempfile
from tkinter import * from tkinter import *
from tkinter import messagebox from tkinter import messagebox
from tkinter.ttk import * from tkinter.ttk import *
import pyqrcode from tkhtmlview import HTMLLabel
from fishy import web from fishy import web
import typing import typing
@ -12,6 +11,7 @@ if typing.TYPE_CHECKING:
from . import GUI from . import GUI
# noinspection PyProtectedMember
def _give_notification_link(gui: 'GUI'): def _give_notification_link(gui: 'GUI'):
if web.is_subbed(gui._config.get("uid"))[0]: if web.is_subbed(gui._config.get("uid"))[0]:
web.unsub(gui._config.get("uid")) web.unsub(gui._config.get("uid"))
@ -25,37 +25,49 @@ def _give_notification_link(gui: 'GUI'):
top_running[0] = False top_running[0] = False
def check(): def check():
if web.sub(gui._config.get("uid"), discord_name.get()):
if web.is_subbed(gui._config.get("uid"), False)[0]: if web.is_subbed(gui._config.get("uid"), False)[0]:
gui._notify.set(1) gui._notify.set(1)
web.send_notification(gui._config.get("uid"), "Sending a test notification :D")
messagebox.showinfo("Note!", "Notification configured successfully!") messagebox.showinfo("Note!", "Notification configured successfully!")
quit_top() quit_top()
else: else:
messagebox.showerror("Error", "Subscription wasn't successful") 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_running = [True]
top = Toplevel(background=gui._root["background"]) top = Toplevel(background=gui._root["background"])
top.minsize(width=500, height=500) top.minsize(width=300, height=300)
top.title("Notification Setup") top.title("Notification Setup")
Label(top, text="Step 1.").pack(pady=(5, 5)) html_label = HTMLLabel(top,
Label(top, text="Scan the QR Code on your Phone and press \"Enable Notification\"").pack(pady=(5, 5)) html=f'<div style="color: {gui._console["fg"]}; text-align: center">'
canvas = Canvas(top, width=qrcode.get_png_size(8), height=qrcode.get_png_size(8)) f'<p><span style="font-size:20px">Step 1.</span><br/>'
canvas.pack(pady=(5, 5)) f'Join <a href="https://discord.definex.in/">Discord server</a></p>'
Label(top, text="Step 2.").pack(pady=(5, 5)) f'<p><span style="font-size:20px">Step 2.</span><br/>'
Button(top, text="Check", command=check).pack(pady=(5, 5)) f'Enter username (ex. Fishy#1234)'
f'</div>', background=gui._root["background"])
image = PhotoImage(file=t) html_label.pack(pady=(20, 5))
canvas.create_image(0, 0, anchor=NW, image=image) 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'<div style="color: {gui._console["fg"]}; text-align: center">'
f'<p><span style="font-size:20px">Step 3.</span><br/>'
f'Install Discord App on your phone</p>'
f'<p><span style="font-size:20px">Step 4.</span><br/></p>'
f'</div>', 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.protocol("WM_DELETE_WINDOW", quit_top)
top.grab_set() top.grab_set()
while top_running[0]: while top_running[0]:
top.update() top.update()
time.sleep(0.01)
top.grab_release() top.grab_release()

View File

@ -1 +1,2 @@
from .engine import Engine from .engine import Engine
from .funcs import EngineFuncs

View File

@ -1,4 +1,6 @@
import time import time
import typing
from collections import Callable
from threading import Thread from threading import Thread
import cv2 import cv2
@ -6,34 +8,44 @@ import logging
import pywintypes import pywintypes
from fishy.gui import GUIFunction, GUIEvent from fishy.tech.funcs import EngineFuncs
from . import fishing_event from . import fishing_event
from .fishing_event import HookEvent, StickEvent, LookEvent, IdleEvent from .fishing_event import HookEvent, StickEvent, LookEvent, IdleEvent
from .fishing_mode import FishingMode from .fishing_mode import FishingMode
from .pixel_loc import PixelLoc from .pixel_loc import PixelLoc
from .window import Window from .window import Window
if typing.TYPE_CHECKING:
from fishy.gui import GUI
def _wait_and_check(gui): def _wait_and_check(gui):
time.sleep(10) time.sleep(10)
if not fishing_event._FishingStarted: if not fishing_event._FishingStarted:
gui.call(GUIFunction.SHOW_ERROR, ("Doesn't look like fishing has started\n\n" gui.show_error("Doesn't look like fishing has started\n\n"
"Make sure ProvisionsChalutier addon is visible clearly on top " "Make sure ProvisionsChalutier addon is visible clearly on top "
"left corner of the screen, either,\n" "left corner of the screen, either,\n"
"1) Outdated addons are disabled\n" "1) Outdated addons are disabled\n"
"2) Other addons are overlapping ProvisionsChalutier\n" "2) Other addons are overlapping ProvisionsChalutier\n"
"3) Post processing (re shader) is on\n\n" "3) Post processing (re shader) is on\n\n"
"If fixing those doesnt work, try running the bot as admin",)) "If fixing those doesnt work, try running the bot as admin")
class Engine: class Engine:
def __init__(self, gui_ref, gui_event_buffer, config): def __init__(self, config, gui_ref: 'Callable[[], GUI]'):
self.gui_events = gui_event_buffer self.funcs = EngineFuncs(self)
self.get_gui = gui_ref
self.start = False self.start = False
self.fishPixWindow = None self.fishPixWindow = None
self.fishy_thread = None self.fishy_thread = None
self.gui = gui_ref
self.config = config 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): 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) self.fishPixWindow = Window(color=cv2.COLOR_RGB2HSV)
# check for game window and stuff # 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") logging.info("Starting the bot engine, look at the fishing hole to start fishing")
Thread(target=_wait_and_check, args=(self.gui,)).start() Thread(target=_wait_and_check, args=(self.gui,)).start()
while self.start: while self.start:
@ -73,26 +85,13 @@ class Engine:
# Services to be ran in the end of the main loop # Services to be ran in the end of the main loop
Window.loop_end() Window.loop_end()
logging.info("Fishing engine stopped") logging.info("Fishing engine stopped")
self.gui.call(GUIFunction.STARTED, (False,)) self.gui.bot_started(False)
def start_event_handler(self): def start_event_handler(self):
while True: while self.event_handler_running:
while len(self.gui_events) > 0: while len(self.gui_events) > 0:
event = self.gui_events.pop(0) event = self.gui_events.pop(0)
event()
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
def _show_pixel_vals(self): def _show_pixel_vals(self):
def show(): def show():
@ -106,3 +105,6 @@ class Engine:
logging.debug("Will display pixel values for 10 seconds") logging.debug("Will display pixel values for 10 seconds")
time.sleep(5) time.sleep(5)
Thread(target=show, args=()).start() Thread(target=show, args=()).start()

33
fishy/tech/funcs.py Normal file
View File

@ -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)

View File

@ -1,2 +1,2 @@
from .urls import get_notification_page, get_terms_page 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

View File

@ -1,6 +1,11 @@
import sys 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" user = domain + "/api/user"
notify = domain + "/api/notify" notify = domain + "/api/notify"

View File

@ -41,8 +41,20 @@ def send_hole_deplete(uid, fish_caught, hole_time, fish_times):
requests.post(urls.hole_depleted, json=body) 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)) @fallback((False, False))
def is_subbed(uid, lazy=True): def is_subbed(uid, lazy=True):
"""
:param uid:
:param lazy:
:return: Tuple[is_subbed, success]
"""
global _is_subbed global _is_subbed
if lazy and _is_subbed is not None: if lazy and _is_subbed is not None:

View File

@ -1,3 +1,4 @@
urllib3
winshell winshell
imutils imutils
numpy numpy
@ -9,5 +10,5 @@ pyautogui
requests requests
beautifulsoup4 beautifulsoup4
whatsmyip whatsmyip
pyqrcode
pypng pypng
tkhtmlview

9
test.ps1 Normal file
View File

@ -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