mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
code cleanup:
- restructured code - pep8 cleanup - spelling mistakes fixed - import fixes - added cli arg to use local server - got rid of globals.py
This commit is contained in:
parent
66eeb9d6f8
commit
5972aebc7d
@ -2,129 +2,21 @@ import ctypes
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from tkinter import messagebox
|
||||
|
||||
import win32con
|
||||
import win32gui
|
||||
from threading import Thread
|
||||
|
||||
import cv2
|
||||
import pywintypes
|
||||
import fishy
|
||||
from fishy.systems.fishing_event import HookEvent, StickEvent, LookEvent, IdleEvent
|
||||
from fishy.systems.fishing_mode import FishingMode
|
||||
from fishy.systems.globals import G
|
||||
from fishy.systems.pixel_loc import PixelLoc
|
||||
from fishy.systems.window import Window
|
||||
from fishy.systems.auto_update import auto_upgrade
|
||||
from fishy.systems import helper, web
|
||||
from fishy.systems.config import Config
|
||||
from fishy.systems.gui import GUI, GUIEvent, GUIFunction
|
||||
from fishy.systems.terms_gui import check_eula
|
||||
from fishy import web, helper, gui
|
||||
from fishy.gui import GUI
|
||||
from fishy.helper import Config
|
||||
from fishy.tech import Engine
|
||||
|
||||
|
||||
class Fishy:
|
||||
def __init__(self, gui_ref, gui_event_buffer, config):
|
||||
self.gui_events = gui_event_buffer
|
||||
self.start = False
|
||||
self.fishPixWindow = None
|
||||
self.fishy_thread = None
|
||||
self.gui = gui_ref
|
||||
self.config = config
|
||||
|
||||
def start_fishing(self, action_key: str, borderless: bool, collect_r: bool):
|
||||
"""
|
||||
Starts the fishing
|
||||
code explained in comments in detail
|
||||
"""
|
||||
|
||||
# initialize widow
|
||||
try:
|
||||
Window.Init(borderless)
|
||||
except pywintypes.error:
|
||||
logging.info("Game window not found")
|
||||
self.start = False
|
||||
return
|
||||
|
||||
# initializes fishing modes and their callbacks
|
||||
FishingMode("hook", 0, HookEvent(action_key, collect_r))
|
||||
FishingMode("stick", 1, StickEvent())
|
||||
FishingMode("look", 2, LookEvent(action_key))
|
||||
FishingMode("idle", 3, IdleEvent(self.config.get("uid")))
|
||||
|
||||
self.fishPixWindow = Window(color=cv2.COLOR_RGB2HSV)
|
||||
|
||||
# check for game window and stuff
|
||||
self.gui.call(GUIFunction.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:
|
||||
# Services to be ran in the start of the main loop
|
||||
Window.Loop()
|
||||
|
||||
# get the PixelLoc and find the color values, to give it to `FishingMode.Loop`
|
||||
self.fishPixWindow.crop = PixelLoc.val
|
||||
hueValue = self.fishPixWindow.getCapture()[0][0][0]
|
||||
FishingMode.Loop(hueValue)
|
||||
# Services to be ran in the end of the main loop
|
||||
Window.LoopEnd()
|
||||
logging.info("Fishing engine stopped")
|
||||
self.gui.call(GUIFunction.STARTED, (False,))
|
||||
|
||||
def start_event_handler(self):
|
||||
while True:
|
||||
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
|
||||
|
||||
def show_pixel_vals(self):
|
||||
def show():
|
||||
freq = 0.5
|
||||
t = 0
|
||||
while t < 10.0:
|
||||
t += freq
|
||||
logging.debug(str(FishingMode.CurrentMode.label) + ":" + str(self.fishPixWindow.getCapture()[0][0]))
|
||||
time.sleep(freq)
|
||||
|
||||
logging.debug("Will display pixel values for 10 seconds")
|
||||
time.sleep(5)
|
||||
Thread(target=show, args=()).start()
|
||||
|
||||
|
||||
def create_shortcut_first(gui, c):
|
||||
if not c.get("shortcut_created", False):
|
||||
helper.create_shortcut(gui)
|
||||
c.set("shortcut_created", True)
|
||||
|
||||
|
||||
def initialize_uid(config: Config):
|
||||
if config.get("uid") is not None:
|
||||
return
|
||||
|
||||
new_uid = helper.create_new_uid()
|
||||
if web.register_user(new_uid):
|
||||
config.set("uid", new_uid)
|
||||
else:
|
||||
logging.error("Couldn't register uid, some features might not work")
|
||||
|
||||
|
||||
def initialize(gui, c: Config, The_program_to_hide):
|
||||
create_shortcut_first(gui, c)
|
||||
initialize_uid(c)
|
||||
# noinspection PyBroadException
|
||||
def initialize(c: Config, window_to_hide):
|
||||
helper.create_shortcut_first(c)
|
||||
helper.initialize_uid(c)
|
||||
|
||||
new_session = web.get_session(c)
|
||||
if new_session is None:
|
||||
@ -140,52 +32,35 @@ def initialize(gui, c: Config, The_program_to_hide):
|
||||
logging.info("Running with admin privileges")
|
||||
|
||||
try:
|
||||
auto_upgrade()
|
||||
helper.auto_upgrade()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if not c.get("debug", False):
|
||||
win32gui.ShowWindow(The_program_to_hide, win32con.SW_HIDE)
|
||||
win32gui.ShowWindow(window_to_hide, win32con.SW_HIDE)
|
||||
helper.install_thread_excepthook()
|
||||
sys.excepthook = helper.unhandled_exception_logging
|
||||
|
||||
helper.check_addon()
|
||||
|
||||
|
||||
def wait_and_check(gui):
|
||||
time.sleep(10)
|
||||
if not G.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",))
|
||||
|
||||
|
||||
def ask_terms():
|
||||
messagebox.askquestion("Terms and Condition", )
|
||||
|
||||
|
||||
def main():
|
||||
The_program_to_hide = win32gui.GetForegroundWindow()
|
||||
|
||||
print("launching please wait...")
|
||||
|
||||
window_to_hide = win32gui.GetForegroundWindow()
|
||||
c = Config()
|
||||
events_buffer = []
|
||||
|
||||
if not check_eula(c):
|
||||
if not gui.check_eula(c):
|
||||
return
|
||||
|
||||
events_buffer = []
|
||||
gui = GUI(c, lambda a, b=None: events_buffer.append((a, b)))
|
||||
gui.start()
|
||||
gui_window = GUI(c, lambda a, b=None: events_buffer.append((a, b)))
|
||||
gui_window.start()
|
||||
|
||||
logging.info(f"Fishybot v{fishy.__version__}")
|
||||
initialize(gui, c, The_program_to_hide)
|
||||
initialize(c, window_to_hide)
|
||||
|
||||
bot = Fishy(gui, events_buffer, c)
|
||||
bot = Engine(gui_window, events_buffer, c)
|
||||
bot.start_event_handler()
|
||||
|
||||
|
||||
|
3
fishy/gui/__init__.py
Normal file
3
fishy/gui/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
from .gui import GUI
|
||||
from .terms_gui import check_eula
|
||||
from .comms import GUIFunction, GUIEvent
|
46
fishy/gui/comms.py
Normal file
46
fishy/gui/comms.py
Normal file
@ -0,0 +1,46 @@
|
||||
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
|
47
fishy/gui/gui.py
Normal file
47
fishy/gui/gui.py
Normal file
@ -0,0 +1,47 @@
|
||||
import logging
|
||||
from typing import Tuple, List, Callable, Optional
|
||||
import threading
|
||||
|
||||
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]):
|
||||
"""
|
||||
:param config: used to get and set configuration settings
|
||||
:param event_trigger: used to communicate with other threads
|
||||
"""
|
||||
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._bot_running = False
|
||||
|
||||
# UI items
|
||||
self._root = None
|
||||
self._console = None
|
||||
self._start_button = None
|
||||
self._notify = None
|
||||
self._notify_check = None
|
||||
|
||||
self._thread = threading.Thread(target=self.create, args=())
|
||||
|
||||
root_logger = logging.getLogger('')
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
new_console = GUIStreamHandler(self)
|
||||
root_logger.addHandler(new_console)
|
||||
|
||||
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))
|
29
fishy/gui/log_config.py
Normal file
29
fishy/gui/log_config.py
Normal file
@ -0,0 +1,29 @@
|
||||
from logging import StreamHandler
|
||||
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import GUI
|
||||
|
||||
|
||||
class GUIStreamHandler(StreamHandler):
|
||||
def __init__(self, gui):
|
||||
StreamHandler.__init__(self)
|
||||
self.gui = gui
|
||||
|
||||
def emit(self, record):
|
||||
from .comms import GUIFunction
|
||||
msg = self.format(record)
|
||||
self.gui.call(GUIFunction.LOG, (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'
|
159
fishy/gui/main_gui.py
Normal file
159
fishy/gui/main_gui.py
Normal file
@ -0,0 +1,159 @@
|
||||
import logging
|
||||
import threading
|
||||
import time
|
||||
from tkinter import *
|
||||
from tkinter.ttk import *
|
||||
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
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import GUI
|
||||
|
||||
|
||||
def _apply_theme(gui: 'GUI'):
|
||||
dark = gui._config.get("dark", 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"
|
||||
|
||||
|
||||
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'))
|
||||
|
||||
# region menu
|
||||
menubar = Menu(gui._root)
|
||||
|
||||
filemenu = Menu(menubar, tearoff=0)
|
||||
filemenu.add_command(label="Create Shortcut", command=helper.create_shortcut)
|
||||
|
||||
def _toggle_mode():
|
||||
gui._config.set("dark_mode", not gui._config.get("dark_mode", True))
|
||||
gui._start_restart = True
|
||||
|
||||
dark_mode_var = IntVar()
|
||||
dark_mode_var.set(int(gui._config.get('dark_mode', True)))
|
||||
filemenu.add_checkbutton(label="Dark Mode", command=_toggle_mode,
|
||||
variable=dark_mode_var)
|
||||
|
||||
menubar.add_cascade(label="File", menu=filemenu)
|
||||
|
||||
debug_menu = Menu(menubar, tearoff=0)
|
||||
debug_menu.add_command(label="Check PixelVal",
|
||||
command=lambda: gui._event_trigger(GUIEvent.CHECK_PIXELVAL, ()))
|
||||
|
||||
debug_var = IntVar()
|
||||
debug_var.set(int(gui._config.get('debug', False)))
|
||||
|
||||
def keep_console():
|
||||
gui._config.set("debug", bool(debug_var.get()))
|
||||
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="Log Dump", command=lambda: logging.error("Not Implemented"))
|
||||
debug_menu.add_command(label="Restart", command=helper.restart)
|
||||
menubar.add_cascade(label="Debug", menu=debug_menu)
|
||||
|
||||
help_menu = Menu(menubar, tearoff=0)
|
||||
help_menu.add_command(label="Troubleshoot Guide", command=lambda: logging.debug("Not Implemented"))
|
||||
help_menu.add_command(label="Need Help?", command=lambda: helper.open_web("http://discord.definex.in"))
|
||||
help_menu.add_command(label="Donate", command=lambda: helper.open_web("https://paypal.me/AdamSaudagar"))
|
||||
menubar.add_cascade(label="Help", menu=help_menu)
|
||||
|
||||
gui._root.config(menu=menubar)
|
||||
# endregion
|
||||
|
||||
# region console
|
||||
gui._console = Text(gui._root, state='disabled', wrap='none', background="#707070", fg="#ffffff")
|
||||
gui._console.pack(fill=BOTH, expand=True, pady=(15, 15), padx=(5, 5))
|
||||
gui._console.mark_set("sentinel", INSERT)
|
||||
gui._console.config(state=DISABLED)
|
||||
|
||||
controls_frame = Frame(gui._root)
|
||||
# endregion
|
||||
|
||||
# region controls
|
||||
left_frame = Frame(controls_frame)
|
||||
|
||||
Label(left_frame, text="Notification:").grid(row=0, column=0)
|
||||
|
||||
gui._notify = IntVar(0)
|
||||
gui._notify_check = Checkbutton(left_frame, command=lambda: _give_notification_link(gui),
|
||||
variable=gui._notify)
|
||||
gui._notify_check.grid(row=0, column=1)
|
||||
gui._notify_check['state'] = DISABLED
|
||||
|
||||
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]))
|
||||
|
||||
threading.Thread(target=update_notify_check).start()
|
||||
|
||||
Label(left_frame, text="Fullscreen: ").grid(row=1, column=0, pady=(5, 5))
|
||||
borderless = Checkbutton(left_frame, )
|
||||
borderless.grid(row=1, column=1)
|
||||
|
||||
left_frame.grid(row=0, column=0)
|
||||
|
||||
right_frame = Frame(controls_frame)
|
||||
|
||||
Label(right_frame, text="Action Key:").grid(row=0, column=0)
|
||||
action_key_entry = Entry(right_frame)
|
||||
action_key_entry.grid(row=0, column=1)
|
||||
action_key_entry.insert(0, gui._config.get("action_key", "e"))
|
||||
|
||||
Label(right_frame, text="Collect R: ").grid(row=1, column=0, pady=(5, 5))
|
||||
collect_r = Checkbutton(right_frame, variable=IntVar(value=1 if gui._config.get("collect_r", False) else 0))
|
||||
collect_r.grid(row=1, column=1)
|
||||
|
||||
right_frame.grid(row=0, column=1, padx=(50, 0))
|
||||
|
||||
controls_frame.pack()
|
||||
|
||||
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._config.set("action_key", action_key_entry.get(), False)
|
||||
gui._config.set("borderless", borderless.instate(['selected']), False)
|
||||
gui._config.set("collect_r", collect_r.instate(['selected']), False)
|
||||
gui._config.save_config()
|
||||
|
||||
gui._start_button["command"] = start_button_callback
|
||||
gui._start_button.pack(pady=(15, 15))
|
||||
# endregion
|
||||
|
||||
_apply_theme(gui)
|
||||
gui._root.update()
|
||||
gui._root.minsize(gui._root.winfo_width() + 10, gui._root.winfo_height() + 10)
|
||||
|
||||
def set_destroy():
|
||||
gui._destroyed = True
|
||||
|
||||
gui._root.protocol("WM_DELETE_WINDOW", set_destroy)
|
||||
gui._destroyed = False
|
||||
|
||||
while True:
|
||||
gui._root.update()
|
||||
_clear_function_queue(gui)
|
||||
if gui._start_restart:
|
||||
gui._root.destroy()
|
||||
gui._root.quit()
|
||||
gui._start_restart = False
|
||||
gui.create()
|
||||
if gui._destroyed:
|
||||
gui._event_trigger(GUIEvent.QUIT, ())
|
||||
break
|
||||
time.sleep(0.01)
|
61
fishy/gui/notification.py
Normal file
61
fishy/gui/notification.py
Normal file
@ -0,0 +1,61 @@
|
||||
import os
|
||||
import tempfile
|
||||
from tkinter import *
|
||||
from tkinter import messagebox
|
||||
from tkinter.ttk import *
|
||||
import pyqrcode
|
||||
|
||||
from fishy import web
|
||||
import typing
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import GUI
|
||||
|
||||
|
||||
def _give_notification_link(gui: 'GUI'):
|
||||
if web.is_subbed(gui._config.get("uid"))[0]:
|
||||
web.unsub(gui._config.get("uid"))
|
||||
return
|
||||
|
||||
# set notification checkbutton
|
||||
gui._notify.set(0)
|
||||
|
||||
def quit_top():
|
||||
top.destroy()
|
||||
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()
|
||||
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.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))
|
||||
|
||||
image = PhotoImage(file=t)
|
||||
canvas.create_image(0, 0, anchor=NW, image=image)
|
||||
|
||||
top.protocol("WM_DELETE_WINDOW", quit_top)
|
||||
top.grab_set()
|
||||
while top_running[0]:
|
||||
top.update()
|
||||
top.grab_release()
|
@ -2,11 +2,10 @@ import webbrowser
|
||||
from tkinter import *
|
||||
from tkinter.ttk import *
|
||||
|
||||
from fishy.systems import helper, web
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
from fishy.systems.config import Config
|
||||
from fishy import helper, web
|
||||
from fishy.helper import Config
|
||||
|
||||
hyperlinkPattern = re.compile(r'\[(?P<title>.*?)\]\((?P<address>.*?)\)')
|
||||
|
||||
@ -25,7 +24,7 @@ def _run_terms_window(config: Config):
|
||||
root.destroy()
|
||||
|
||||
def disable_enable_button():
|
||||
accept_button.config(state=NORMAL if checkValue.get() else DISABLED)
|
||||
accept_button.config(state=NORMAL if check_value.get() else DISABLED)
|
||||
|
||||
root = Tk()
|
||||
message = f'I agree to the [Terms of Service and Privacy Policy]({web.get_terms_page()})'
|
||||
@ -40,15 +39,15 @@ def _run_terms_window(config: Config):
|
||||
root.image = ImageTk.PhotoImage(root.image)
|
||||
canvas.create_image(0, 0, anchor=NW, image=root.image)
|
||||
|
||||
checkValue = IntVar(0)
|
||||
check_value = IntVar(0)
|
||||
|
||||
g1 = Frame(f)
|
||||
Checkbutton(g1, command=disable_enable_button, variable=checkValue).pack(side=LEFT)
|
||||
Checkbutton(g1, command=disable_enable_button, variable=check_value).pack(side=LEFT)
|
||||
text = Text(g1, width=len(hyperlinkPattern.sub('\g<title>', message)),
|
||||
height=1, borderwidth=0, highlightthickness=0)
|
||||
text["background"] = root["background"]
|
||||
|
||||
_formatHyperLink(text, message)
|
||||
_format_hyper_link(text, message)
|
||||
text.config(state=DISABLED)
|
||||
text.pack(side=LEFT)
|
||||
g1.pack()
|
||||
@ -67,7 +66,7 @@ def _run_terms_window(config: Config):
|
||||
root.mainloop()
|
||||
|
||||
|
||||
def _formatHyperLink(text, message):
|
||||
def _format_hyper_link(text, message):
|
||||
start = 0
|
||||
for index, match in enumerate(hyperlinkPattern.finditer(message)):
|
||||
groups = match.groupdict()
|
4
fishy/helper/__init__.py
Normal file
4
fishy/helper/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
from .auto_update import auto_upgrade
|
||||
from .config import Config
|
||||
from .helper import open_web, initialize_uid, install_thread_excepthook, unhandled_exception_logging, manifest_file, \
|
||||
create_shortcut_first, check_addon, restart, create_shortcut
|
@ -63,7 +63,7 @@ def _get_highest_version(index, pkg):
|
||||
def _get_current_version(pkg):
|
||||
"""
|
||||
Gets the current version of the package installed
|
||||
:param pkg: name of the installed backage
|
||||
:param pkg: name of the installed package
|
||||
:return: version normalized
|
||||
"""
|
||||
return _normalize_version(pkg_resources.get_distribution(pkg).version)
|
@ -15,6 +15,9 @@ from win32com.client import Dispatch
|
||||
import fishy
|
||||
import winshell
|
||||
|
||||
from fishy import web
|
||||
from . import Config
|
||||
|
||||
|
||||
def open_web(website):
|
||||
"""
|
||||
@ -26,7 +29,18 @@ def open_web(website):
|
||||
Thread(target=lambda: webbrowser.open(website, new=2)).start()
|
||||
|
||||
|
||||
def create_new_uid():
|
||||
def initialize_uid(config: Config):
|
||||
if config.get("uid") is not None:
|
||||
return
|
||||
|
||||
new_uid = _create_new_uid()
|
||||
if web.register_user(new_uid):
|
||||
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
|
||||
"""
|
||||
@ -45,12 +59,13 @@ def install_thread_excepthook():
|
||||
import sys
|
||||
run_old = threading.Thread.run
|
||||
|
||||
# noinspection PyBroadException
|
||||
def run(*args, **kwargs):
|
||||
try:
|
||||
run_old(*args, **kwargs)
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
raise
|
||||
except:
|
||||
except Exception:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
|
||||
threading.Thread.run = run
|
||||
@ -71,10 +86,16 @@ def manifest_file(rel_path):
|
||||
return os.path.join(os.path.dirname(fishy.__file__), rel_path)
|
||||
|
||||
|
||||
def create_shortcut(gui):
|
||||
def create_shortcut_first(c):
|
||||
if not c.get("shortcut_created", False):
|
||||
create_shortcut()
|
||||
c.set("shortcut_created", True)
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def create_shortcut():
|
||||
"""
|
||||
creates a new shortcut on desktop
|
||||
:param gui: does nothing todo
|
||||
"""
|
||||
try:
|
||||
desktop = winshell.desktop()
|
||||
@ -82,16 +103,17 @@ def create_shortcut(gui):
|
||||
|
||||
shell = Dispatch('WScript.Shell')
|
||||
shortcut = shell.CreateShortCut(path)
|
||||
shortcut.Targetpath = os.path.join(os.path.dirname(sys.executable), "python.exe")
|
||||
shortcut.TargetPath = os.path.join(os.path.dirname(sys.executable), "python.exe")
|
||||
shortcut.Arguments = "-m fishy"
|
||||
shortcut.IconLocation = manifest_file("icon.ico")
|
||||
shortcut.save()
|
||||
|
||||
logging.info("Shortcut created")
|
||||
except:
|
||||
except Exception:
|
||||
logging.error("Couldn't create shortcut")
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def check_addon():
|
||||
"""
|
||||
Extracts the addon from zip and installs it into the AddOn folder of eso
|
||||
@ -101,8 +123,8 @@ def check_addon():
|
||||
addon_dir = os.path.join(user, "Documents", "Elder Scrolls Online", "live", "Addons")
|
||||
if not os.path.exists(os.path.join(addon_dir, 'ProvisionsChalutier')):
|
||||
logging.info("Addon not found, installing it...")
|
||||
with ZipFile(manifest_file("ProvisionsChalutier.zip"), 'r') as zip:
|
||||
zip.extractall(path=addon_dir)
|
||||
with ZipFile(manifest_file("ProvisionsChalutier.zip"), 'r') as z:
|
||||
z.extractall(path=addon_dir)
|
||||
logging.info("Please make sure you enable \"Allow outdated addons\" in-game\n"
|
||||
"Also, make sure the addon is visible clearly on top left corner of the game window")
|
||||
except Exception:
|
@ -1,14 +0,0 @@
|
||||
class G:
|
||||
"""
|
||||
Initialize global variables used by different services
|
||||
"""
|
||||
fishCaught = 0
|
||||
totalFishCaught = 0
|
||||
stickInitTime = 0
|
||||
FishingStarted = False
|
||||
|
||||
fish_times = []
|
||||
hole_start_time = 0
|
||||
|
||||
_is_subbed = None
|
||||
_session_id = None
|
@ -1,300 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
import tempfile
|
||||
import time
|
||||
from enum import Enum
|
||||
from logging import StreamHandler
|
||||
from tkinter import *
|
||||
from tkinter import filedialog, messagebox
|
||||
from tkinter.ttk import *
|
||||
from typing import Tuple, List, Callable, Optional
|
||||
|
||||
import pyqrcode
|
||||
from ttkthemes import ThemedTk
|
||||
import threading
|
||||
|
||||
from fishy.systems.config import Config
|
||||
|
||||
|
||||
class GUIStreamHandler(StreamHandler):
|
||||
def __init__(self, gui):
|
||||
StreamHandler.__init__(self)
|
||||
self.gui = gui
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
self.gui.call(GUIFunction.LOG, (msg,))
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
class GUI:
|
||||
|
||||
def __init__(self, config: Config, event_trigger: Callable[[GUIEvent, Optional[Tuple]], None]):
|
||||
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._bot_running = False
|
||||
|
||||
# UI items
|
||||
self.root = None
|
||||
self.console = None
|
||||
self.start_button = None
|
||||
self.notify = None
|
||||
self.notify_check = None
|
||||
|
||||
self.thread = threading.Thread(target=self.create, args=())
|
||||
|
||||
rootLogger = logging.getLogger('')
|
||||
rootLogger.setLevel(logging.DEBUG)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
new_console = GUIStreamHandler(self)
|
||||
rootLogger.addHandler(new_console)
|
||||
|
||||
def create(self):
|
||||
from fishy.systems import helper
|
||||
from fishy.systems import web
|
||||
|
||||
self.root = ThemedTk(theme="equilux", background=True)
|
||||
self.root.title("Fiishybot for Elder Scrolls Online")
|
||||
self.root.geometry('650x550')
|
||||
|
||||
self.root.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
# region menu
|
||||
menubar = Menu(self.root)
|
||||
|
||||
filemenu = Menu(menubar, tearoff=0)
|
||||
filemenu.add_command(label="Create Shortcut", command=lambda: helper.create_shortcut(self))
|
||||
|
||||
dark_mode_var = IntVar()
|
||||
dark_mode_var.set(int(self.config.get('dark_mode', True)))
|
||||
filemenu.add_checkbutton(label="Dark Mode", command=self._toggle_mode,
|
||||
variable=dark_mode_var)
|
||||
|
||||
menubar.add_cascade(label="File", menu=filemenu)
|
||||
|
||||
debug_menu = Menu(menubar, tearoff=0)
|
||||
debug_menu.add_command(label="Check PixelVal",
|
||||
command=lambda: self._event_trigger(GUIEvent.CHECK_PIXELVAL, ()))
|
||||
|
||||
debug_var = IntVar()
|
||||
debug_var.set(int(self.config.get('debug', False)))
|
||||
|
||||
def keep_console():
|
||||
self.config.set("debug", bool(debug_var.get()))
|
||||
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="Log Dump", command=lambda: logging.error("Not Implemented"))
|
||||
debug_menu.add_command(label="Restart", command=helper.restart)
|
||||
menubar.add_cascade(label="Debug", menu=debug_menu)
|
||||
|
||||
help_menu = Menu(menubar, tearoff=0)
|
||||
help_menu.add_command(label="Troubleshoot Guide", command=lambda: logging.debug("Not Implemented"))
|
||||
help_menu.add_command(label="Need Help?", command=lambda: helper.open_web("http://discord.definex.in"))
|
||||
help_menu.add_command(label="Donate", command=lambda: helper.open_web("https://paypal.me/AdamSaudagar"))
|
||||
menubar.add_cascade(label="Help", menu=help_menu)
|
||||
|
||||
self.root.config(menu=menubar)
|
||||
# endregion
|
||||
|
||||
# region console
|
||||
self.console = Text(self.root, state='disabled', wrap='none', background="#707070", fg="#ffffff")
|
||||
self.console.pack(fill=BOTH, expand=True, pady=(15, 15), padx=(5, 5))
|
||||
self.console.mark_set("sentinel", INSERT)
|
||||
self.console.config(state=DISABLED)
|
||||
|
||||
controls_frame = Frame(self.root)
|
||||
# endregion
|
||||
|
||||
# region controls
|
||||
left_frame = Frame(controls_frame)
|
||||
|
||||
Label(left_frame, text="Notification:").grid(row=0, column=0)
|
||||
|
||||
self.notify = IntVar(0)
|
||||
self.notify_check = Checkbutton(left_frame, command=self.give_notification_link,
|
||||
variable=self.notify)
|
||||
self.notify_check.grid(row=0, column=1)
|
||||
self.notify_check['state'] = DISABLED
|
||||
|
||||
def update_notify_check():
|
||||
is_subbed = web.is_subbed(self.config.get('uid'))
|
||||
self.call(GUIFunction.SET_NOTIFY, (int(is_subbed[0]),is_subbed[1]))
|
||||
|
||||
threading.Thread(target=update_notify_check).start()
|
||||
|
||||
Label(left_frame, text="Fullscreen: ").grid(row=1, column=0, pady=(5, 5))
|
||||
borderless = Checkbutton(left_frame, )
|
||||
borderless.grid(row=1, column=1)
|
||||
|
||||
left_frame.grid(row=0, column=0)
|
||||
|
||||
right_frame = Frame(controls_frame)
|
||||
|
||||
Label(right_frame, text="Action Key:").grid(row=0, column=0)
|
||||
action_key_entry = Entry(right_frame)
|
||||
action_key_entry.grid(row=0, column=1)
|
||||
action_key_entry.insert(0, self.config.get("action_key", "e"))
|
||||
|
||||
Label(right_frame, text="Collect R: ").grid(row=1, column=0, pady=(5, 5))
|
||||
collect_r = Checkbutton(right_frame, variable=IntVar(value=1 if self.config.get("collect_r", False) else 0))
|
||||
collect_r.grid(row=1, column=1)
|
||||
|
||||
right_frame.grid(row=0, column=1, padx=(50, 0))
|
||||
|
||||
controls_frame.pack()
|
||||
|
||||
self.start_button = Button(self.root, text="STOP" if self._bot_running else "START", width=25)
|
||||
|
||||
def start_button_callback():
|
||||
args = (action_key_entry.get(),
|
||||
borderless.instate(['selected']),
|
||||
collect_r.instate(['selected']))
|
||||
self._event_trigger(GUIEvent.START_BUTTON, args)
|
||||
self._save_config(*args)
|
||||
|
||||
self.start_button["command"] = start_button_callback
|
||||
self.start_button.pack(pady=(15, 15))
|
||||
# endregion
|
||||
|
||||
self._apply_theme(self.config.get("dark_mode", True))
|
||||
self.root.update()
|
||||
self.root.minsize(self.root.winfo_width() + 10, self.root.winfo_height() + 10)
|
||||
self.root.protocol("WM_DELETE_WINDOW", self._set_destroyed)
|
||||
self.destroyed = False
|
||||
|
||||
while True:
|
||||
self.root.update()
|
||||
self._clear_function_queue()
|
||||
if self.start_restart:
|
||||
self.root.destroy()
|
||||
self.root.quit()
|
||||
self.start_restart = False
|
||||
self.create()
|
||||
if self.destroyed:
|
||||
self._event_trigger(GUIEvent.QUIT, ())
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
||||
def _clear_function_queue(self):
|
||||
while len(self._function_queue) > 0:
|
||||
func = self._function_queue.pop(0)
|
||||
|
||||
if func[0] == GUIFunction.LOG:
|
||||
self._write_to_console(func[1][0])
|
||||
elif func[0] == GUIFunction.STARTED:
|
||||
self._bot_running = func[1][0]
|
||||
self.start_button["text"] = "STOP" if self._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:
|
||||
self.notify.set(func[1][0])
|
||||
if func[1][1]:
|
||||
self.notify_check['state'] = NORMAL
|
||||
|
||||
def _apply_theme(self, dark):
|
||||
self.root["theme"] = "equilux" if dark else "breeze"
|
||||
self.console["background"] = "#707070" if dark else "#ffffff"
|
||||
self.console["fg"] = "#ffffff" if dark else "#000000"
|
||||
|
||||
def _toggle_mode(self):
|
||||
self.config.set("dark_mode", not self.config.get("dark_mode", True))
|
||||
self.start_restart = True
|
||||
|
||||
def _set_destroyed(self):
|
||||
self.destroyed = True
|
||||
|
||||
def _write_to_console(self, msg):
|
||||
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'
|
||||
|
||||
def _save_config(self, action_key, borderless, collect_r):
|
||||
self.config.set("action_key", action_key, False)
|
||||
self.config.set("borderless", borderless, False)
|
||||
self.config.set("collect_r", collect_r, False)
|
||||
self.config.save_config()
|
||||
|
||||
def start(self):
|
||||
self.thread.start()
|
||||
|
||||
def call(self, gui_func: GUIFunction, args: Tuple = None):
|
||||
self._function_queue.append((gui_func, args))
|
||||
|
||||
def give_notification_link(self):
|
||||
from fishy.systems import web
|
||||
|
||||
if web.is_subbed(self.config.get("uid"))[0]:
|
||||
web.unsub(self.config.get("uid"))
|
||||
return
|
||||
|
||||
# set notification checkbutton
|
||||
self.notify.set(0)
|
||||
|
||||
def quit_top():
|
||||
top.destroy()
|
||||
top_running[0] = False
|
||||
|
||||
def check():
|
||||
if web.is_subbed(self.config.get("uid"), False)[0]:
|
||||
self.notify.set(1)
|
||||
web.send_notification(self.config.get("uid"), "Sending a test notification :D")
|
||||
messagebox.showinfo("Note!", "Notification configured successfully!")
|
||||
quit_top()
|
||||
else:
|
||||
messagebox.showerror("Error", "Subscription wasn't successful")
|
||||
|
||||
print("got to {}".format(web.get_notification_page(self.config.get("uid"))))
|
||||
qrcode = pyqrcode.create(web.get_notification_page(self.config.get("uid")))
|
||||
t = os.path.join(tempfile.gettempdir(), "fishyqr.png")
|
||||
qrcode.png(t, scale=8)
|
||||
|
||||
top_running = [True]
|
||||
|
||||
top = Toplevel(background=self.root["background"])
|
||||
top.minsize(width=500, height=500)
|
||||
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))
|
||||
|
||||
image = PhotoImage(file=t)
|
||||
canvas.create_image(0, 0, anchor=NW, image=image)
|
||||
|
||||
top.protocol("WM_DELETE_WINDOW", quit_top)
|
||||
top.grab_set()
|
||||
while top_running[0]:
|
||||
top.update()
|
||||
top.grab_release()
|
@ -1,123 +0,0 @@
|
||||
import logging
|
||||
import traceback
|
||||
from functools import wraps
|
||||
|
||||
import requests
|
||||
from whatsmyip.ip import get_ip
|
||||
from whatsmyip.providers import GoogleDnsProvider
|
||||
|
||||
from fishy.systems import helper
|
||||
from fishy.systems.globals import G
|
||||
|
||||
domain = "https://fishyeso.herokuapp.com"
|
||||
# domain = "http://127.0.0.1:5000"
|
||||
|
||||
user = "/api/user"
|
||||
notify = "/api/notify"
|
||||
subscription = "/api/subscription/"
|
||||
hole_depleted = "/api/hole_depleted"
|
||||
session = "/api/session"
|
||||
terms = "/terms.html"
|
||||
|
||||
|
||||
def uses_session(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if get_session(args[0]) is None:
|
||||
logging.error("Couldn't create a session")
|
||||
return None
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def fallback(default):
|
||||
def inner(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
return default
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
def get_notification_page(uid):
|
||||
return domain + f"?uid={uid}"
|
||||
|
||||
|
||||
def get_terms_page():
|
||||
return domain + terms
|
||||
|
||||
|
||||
@fallback(False)
|
||||
def register_user(uid):
|
||||
ip = get_ip(GoogleDnsProvider)
|
||||
body = {"uid": uid, "ip": ip}
|
||||
response = requests.post(domain + user, json=body)
|
||||
return response.ok and response.json()["success"]
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def send_notification(uid, message):
|
||||
if not is_subbed(uid):
|
||||
return False
|
||||
|
||||
body = {"uid": uid, "message": message}
|
||||
requests.post(domain + notify, json=body)
|
||||
|
||||
|
||||
@uses_session
|
||||
@fallback(None)
|
||||
def send_hole_deplete(uid, fish_caught, hole_time, fish_times):
|
||||
hole_data = {
|
||||
"fish_caught": fish_caught,
|
||||
"hole_time": hole_time,
|
||||
"fish_times": fish_times,
|
||||
"session": get_session(uid)
|
||||
}
|
||||
|
||||
body = {"uid": uid, "hole_data": hole_data}
|
||||
requests.post(domain + hole_depleted, json=body)
|
||||
|
||||
|
||||
@fallback((False, False))
|
||||
def is_subbed(uid, lazy=True):
|
||||
if lazy and G._is_subbed is not None:
|
||||
return G._is_subbed, True
|
||||
|
||||
if uid is None:
|
||||
return False, False
|
||||
|
||||
body = {"uid": uid}
|
||||
response = requests.get(domain + subscription, params=body)
|
||||
G._is_subbed = response.json()["subbed"]
|
||||
return G._is_subbed, True
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def unsub(uid):
|
||||
G._is_subbed = False
|
||||
body = {"uid": uid}
|
||||
requests.delete(domain + subscription, json=body)
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def get_session(config, lazy=True):
|
||||
if lazy and G._session_id is not None:
|
||||
return G._session_id
|
||||
|
||||
body = {"uid": config.get("uid")}
|
||||
response = requests.post(domain + session, params=body)
|
||||
|
||||
if response.status_code == 405:
|
||||
config.delete("uid")
|
||||
helper.restart()
|
||||
return None
|
||||
|
||||
G._session_id = response.json()["session_id"]
|
||||
return G._session_id
|
1
fishy/tech/__init__.py
Normal file
1
fishy/tech/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .engine import Engine
|
108
fishy/tech/engine.py
Normal file
108
fishy/tech/engine.py
Normal file
@ -0,0 +1,108 @@
|
||||
import time
|
||||
from threading import Thread
|
||||
|
||||
import cv2
|
||||
import logging
|
||||
|
||||
import pywintypes
|
||||
|
||||
from fishy.gui import GUIFunction, GUIEvent
|
||||
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
|
||||
|
||||
|
||||
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",))
|
||||
|
||||
|
||||
class Engine:
|
||||
def __init__(self, gui_ref, gui_event_buffer, config):
|
||||
self.gui_events = gui_event_buffer
|
||||
self.start = False
|
||||
self.fishPixWindow = None
|
||||
self.fishy_thread = None
|
||||
self.gui = gui_ref
|
||||
self.config = config
|
||||
|
||||
def start_fishing(self, action_key: str, borderless: bool, collect_r: bool):
|
||||
"""
|
||||
Starts the fishing
|
||||
code explained in comments in detail
|
||||
"""
|
||||
|
||||
# initialize widow
|
||||
# noinspection PyUnresolvedReferences
|
||||
try:
|
||||
Window.init(borderless)
|
||||
except pywintypes.error:
|
||||
logging.info("Game window not found")
|
||||
self.start = False
|
||||
return
|
||||
|
||||
# initializes fishing modes and their callbacks
|
||||
FishingMode("hook", 0, HookEvent(action_key, collect_r))
|
||||
FishingMode("stick", 1, StickEvent())
|
||||
FishingMode("look", 2, LookEvent(action_key))
|
||||
FishingMode("idle", 3, IdleEvent(self.config.get("uid")))
|
||||
|
||||
self.fishPixWindow = Window(color=cv2.COLOR_RGB2HSV)
|
||||
|
||||
# check for game window and stuff
|
||||
self.gui.call(GUIFunction.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:
|
||||
# Services to be ran in the start of the main loop
|
||||
Window.loop()
|
||||
|
||||
# get the PixelLoc and find the color values, to give it to `FishingMode.Loop`
|
||||
self.fishPixWindow.crop = PixelLoc.val
|
||||
hue_value = self.fishPixWindow.get_capture()[0][0][0]
|
||||
FishingMode.loop(hue_value)
|
||||
# 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,))
|
||||
|
||||
def start_event_handler(self):
|
||||
while True:
|
||||
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
|
||||
|
||||
def _show_pixel_vals(self):
|
||||
def show():
|
||||
freq = 0.5
|
||||
t = 0
|
||||
while t < 10.0:
|
||||
t += freq
|
||||
logging.debug(str(FishingMode.CurrentMode.label) + ":" + str(self.fishPixWindow.get_capture()[0][0]))
|
||||
time.sleep(freq)
|
||||
|
||||
logging.debug("Will display pixel values for 10 seconds")
|
||||
time.sleep(5)
|
||||
Thread(target=show, args=()).start()
|
@ -9,17 +9,23 @@ from abc import abstractmethod, ABC
|
||||
|
||||
import pyautogui
|
||||
|
||||
from fishy.systems import web
|
||||
from fishy.systems.globals import G
|
||||
from fishy import web
|
||||
|
||||
_fishCaught = 0
|
||||
_totalFishCaught = 0
|
||||
_stickInitTime = 0
|
||||
_fish_times = []
|
||||
_hole_start_time = 0
|
||||
_FishingStarted = False
|
||||
|
||||
|
||||
class FishEvent(ABC):
|
||||
@abstractmethod
|
||||
def onEnterCallback(self, previousMode):
|
||||
def on_enter_callback(self, previous_mode):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def onExitCallback(self, currentMode):
|
||||
def on_exit_callback(self, current_mode):
|
||||
pass
|
||||
|
||||
|
||||
@ -28,20 +34,22 @@ class HookEvent(FishEvent):
|
||||
self.action_key = action_key
|
||||
self.collect_r = collect_r
|
||||
|
||||
def onEnterCallback(self, previousMode):
|
||||
def on_enter_callback(self, previous_mode):
|
||||
"""
|
||||
called when the fish hook is detected
|
||||
increases the `fishCaught` and `totalFishCaught`, calculates the time it took to catch
|
||||
presses e to catch the fish
|
||||
|
||||
:param previousMode: previous mode in the state machine
|
||||
:param previous_mode: previous mode in the state machine
|
||||
"""
|
||||
G.fishCaught += 1
|
||||
G.totalFishCaught += 1
|
||||
timeToHook = time.time() - G.stickInitTime
|
||||
G.fish_times.append(timeToHook)
|
||||
logging.info("HOOOOOOOOOOOOOOOOOOOOOOOK....... " + str(G.fishCaught) + " caught " + "in " + str(
|
||||
round(timeToHook, 2)) + " secs. " + "Total: " + str(G.totalFishCaught))
|
||||
global _fishCaught, _totalFishCaught
|
||||
|
||||
_fishCaught += 1
|
||||
_totalFishCaught += 1
|
||||
time_to_hook = time.time() - _stickInitTime
|
||||
_fish_times.append(time_to_hook)
|
||||
logging.info("HOOOOOOOOOOOOOOOOOOOOOOOK....... " + str(_fishCaught) + " caught " + "in " + str(
|
||||
round(time_to_hook, 2)) + " secs. " + "Total: " + str(_totalFishCaught))
|
||||
pyautogui.press(self.action_key)
|
||||
|
||||
if self.collect_r:
|
||||
@ -49,7 +57,7 @@ class HookEvent(FishEvent):
|
||||
pyautogui.press('r')
|
||||
time.sleep(0.1)
|
||||
|
||||
def onExitCallback(self, currentMode):
|
||||
def on_exit_callback(self, current_mode):
|
||||
pass
|
||||
|
||||
|
||||
@ -61,14 +69,14 @@ class LookEvent(FishEvent):
|
||||
def __init__(self, action_key: str):
|
||||
self.action_key = action_key
|
||||
|
||||
def onEnterCallback(self, previousMode):
|
||||
def on_enter_callback(self, previous_mode):
|
||||
"""
|
||||
presses e to throw the fishing rod
|
||||
:param previousMode: previous mode in the state machine
|
||||
:param previous_mode: previous mode in the state machine
|
||||
"""
|
||||
pyautogui.press(self.action_key)
|
||||
|
||||
def onExitCallback(self, currentMode):
|
||||
def on_exit_callback(self, current_mode):
|
||||
pass
|
||||
|
||||
|
||||
@ -80,26 +88,26 @@ class IdleEvent(FishEvent):
|
||||
def __init__(self, uid):
|
||||
"""
|
||||
sets the flag to send notification on phone
|
||||
:param use_net: true if user wants to send notification on phone
|
||||
"""
|
||||
self.uid = uid
|
||||
|
||||
def onEnterCallback(self, previousMode):
|
||||
def on_enter_callback(self, previous_mode):
|
||||
"""
|
||||
Resets the fishCaught counter and logs a message depending on the previous state
|
||||
:param previousMode: previous mode in the state machine
|
||||
:param previous_mode: previous mode in the state machine
|
||||
"""
|
||||
global _fishCaught
|
||||
|
||||
if G.fishCaught > 0:
|
||||
web.send_hole_deplete(self.uid, G.fishCaught, time.time() - G.hole_start_time, G.fish_times)
|
||||
G.fishCaught = 0
|
||||
if _fishCaught > 0:
|
||||
web.send_hole_deplete(self.uid, _fishCaught, time.time() - _hole_start_time, _fish_times)
|
||||
_fishCaught = 0
|
||||
|
||||
if previousMode.name == "hook":
|
||||
if previous_mode.name == "hook":
|
||||
logging.info("HOLE DEPLETED")
|
||||
else:
|
||||
logging.info("FISHING INTERRUPTED")
|
||||
|
||||
def onExitCallback(self, currentMode):
|
||||
def on_exit_callback(self, current_mode):
|
||||
pass
|
||||
|
||||
|
||||
@ -108,17 +116,19 @@ class StickEvent(FishEvent):
|
||||
State when fishing is going on
|
||||
"""
|
||||
|
||||
def onEnterCallback(self, previousMode):
|
||||
def on_enter_callback(self, previous_mode):
|
||||
"""
|
||||
resets the fishing timer
|
||||
:param previousMode: previous mode in the state machine
|
||||
:param previous_mode: previous mode in the state machine
|
||||
"""
|
||||
G.stickInitTime = time.time()
|
||||
G.FishingStarted = True
|
||||
global _stickInitTime, _hole_start_time, _fish_times, _FishingStarted
|
||||
|
||||
if G.fishCaught == 0:
|
||||
G.hole_start_time = time.time()
|
||||
G.fish_times = []
|
||||
_stickInitTime = time.time()
|
||||
_FishingStarted = True
|
||||
|
||||
def onExitCallback(self, currentMode):
|
||||
if _fishCaught == 0:
|
||||
_hole_start_time = time.time()
|
||||
_fish_times = []
|
||||
|
||||
def on_exit_callback(self, current_mode):
|
||||
pass
|
@ -3,7 +3,7 @@ class FishingMode:
|
||||
State machine for fishing modes
|
||||
|
||||
HValues hue values for each fishing mode
|
||||
CuurentCount number of times same hue color is read before it changes state
|
||||
CurrentCount number of times same hue color is read before it changes state
|
||||
CurrentMode current mode of the state machine
|
||||
PrevMode previous mode of the state machine
|
||||
FishingStarted probably does nothing (not sure though)
|
||||
@ -34,7 +34,7 @@ class FishingMode:
|
||||
FishingMode.Modes.append(self)
|
||||
|
||||
@staticmethod
|
||||
def GetByLabel(label):
|
||||
def get_by_label(label):
|
||||
"""
|
||||
find a state using label
|
||||
:param label: label integer
|
||||
@ -45,17 +45,16 @@ class FishingMode:
|
||||
return m
|
||||
|
||||
@staticmethod
|
||||
def Loop(hueValue):
|
||||
def loop(hue_values):
|
||||
"""
|
||||
Executed in the start of the main loop in fishy.py
|
||||
Changes modes, calls mode events (callbacks) when mode is changed
|
||||
|
||||
:param hueValue: huevValue read by the bot
|
||||
:param pause: true if bot is paused or not started
|
||||
:param hue_values: hue_values read by the bot
|
||||
"""
|
||||
current_label = 3
|
||||
for i, val in enumerate(FishingMode.HValues):
|
||||
if hueValue == val:
|
||||
if hue_values == val:
|
||||
current_label = i
|
||||
|
||||
# check if it passes threshold, if so change labelNum
|
||||
@ -66,14 +65,14 @@ class FishingMode:
|
||||
FishingMode.PrevLabel = current_label
|
||||
|
||||
if FishingMode.CurrentCount >= FishingMode.Threshold:
|
||||
FishingMode.CurrentMode = FishingMode.GetByLabel(current_label)
|
||||
FishingMode.CurrentMode = FishingMode.get_by_label(current_label)
|
||||
|
||||
if FishingMode.CurrentMode != FishingMode.PrevMode and FishingMode.PrevMode is not None:
|
||||
|
||||
if FishingMode.PrevMode.event is not None:
|
||||
FishingMode.PrevMode.event.onExitCallback(FishingMode.CurrentMode)
|
||||
FishingMode.PrevMode.event.on_exit_callback(FishingMode.CurrentMode)
|
||||
|
||||
if FishingMode.CurrentMode.event is not None:
|
||||
FishingMode.CurrentMode.event.onEnterCallback(FishingMode.PrevMode)
|
||||
FishingMode.CurrentMode.event.on_enter_callback(FishingMode.PrevMode)
|
||||
|
||||
FishingMode.PrevMode = FishingMode.CurrentMode
|
@ -1,7 +1,7 @@
|
||||
import cv2
|
||||
|
||||
|
||||
def GetKeypointFromImage(img):
|
||||
def get_keypoint_from_image(img):
|
||||
"""
|
||||
convert image int hsv
|
||||
creates a mask for brown color
|
||||
@ -13,10 +13,10 @@ def GetKeypointFromImage(img):
|
||||
"""
|
||||
|
||||
# Setup SimpleBlobDetector parameters.
|
||||
hsvImg = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
|
||||
hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
|
||||
lower = (99, 254, 100)
|
||||
upper = (100, 255, 101)
|
||||
mask = cv2.inRange(hsvImg, lower, upper)
|
||||
mask = cv2.inRange(hsv_img, lower, upper)
|
||||
|
||||
# Setup SimpleBlobDetector parameters.
|
||||
params = cv2.SimpleBlobDetector_Params()
|
||||
@ -38,12 +38,12 @@ def GetKeypointFromImage(img):
|
||||
detector = cv2.SimpleBlobDetector_create(params)
|
||||
|
||||
# Detect blobs.
|
||||
keypoints = detector.detect(mask)
|
||||
key_points = detector.detect(mask)
|
||||
|
||||
if len(keypoints) <= 0:
|
||||
if len(key_points) <= 0:
|
||||
return None
|
||||
|
||||
return int(keypoints[0].pt[0]), int(keypoints[0].pt[1])
|
||||
return int(key_points[0].pt[0]), int(key_points[0].pt[1])
|
||||
|
||||
|
||||
class PixelLoc:
|
@ -32,22 +32,21 @@ class Window:
|
||||
self.scale = scale
|
||||
|
||||
@staticmethod
|
||||
def Init(borderless: bool):
|
||||
def init(borderless: bool):
|
||||
"""
|
||||
Executed once before the main loop,
|
||||
Finds the game window, and calculates the offset to remove the title bar
|
||||
"""
|
||||
Window.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
|
||||
rect = win32gui.GetWindowRect(Window.hwnd)
|
||||
clientRect = win32gui.GetClientRect(Window.hwnd)
|
||||
Window.windowOffset = math.floor(((rect[2] - rect[0]) - clientRect[2]) / 2)
|
||||
Window.titleOffset = ((rect[3] - rect[1]) - clientRect[3]) - Window.windowOffset
|
||||
client_rect = win32gui.GetClientRect(Window.hwnd)
|
||||
Window.windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2)
|
||||
Window.titleOffset = ((rect[3] - rect[1]) - client_rect[3]) - Window.windowOffset
|
||||
if borderless:
|
||||
Window.titleOffset = 0
|
||||
|
||||
|
||||
@staticmethod
|
||||
def Loop():
|
||||
def loop():
|
||||
"""
|
||||
Executed in the start of the main loop
|
||||
finds the game window location and captures it
|
||||
@ -56,22 +55,22 @@ class Window:
|
||||
|
||||
bbox = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
|
||||
|
||||
tempScreen = np.array(ImageGrab.grab(bbox=bbox))
|
||||
temp_screen = np.array(ImageGrab.grab(bbox=bbox))
|
||||
|
||||
tempScreen = cv2.cvtColor(tempScreen, cv2.COLOR_BGR2RGB)
|
||||
temp_screen = cv2.cvtColor(temp_screen, cv2.COLOR_BGR2RGB)
|
||||
|
||||
rect = win32gui.GetWindowRect(Window.hwnd)
|
||||
crop = (rect[0] + Window.windowOffset, rect[1] + Window.titleOffset, rect[2] - Window.windowOffset,
|
||||
rect[3] - Window.windowOffset)
|
||||
|
||||
Window.Screen = tempScreen[crop[1]:crop[3], crop[0]:crop[2]]
|
||||
Window.Screen = temp_screen[crop[1]:crop[3], crop[0]:crop[2]]
|
||||
|
||||
if Window.Screen.size == 0:
|
||||
logging.info("Don't minimize or drag game window outside the screen")
|
||||
quit(1)
|
||||
|
||||
@staticmethod
|
||||
def LoopEnd():
|
||||
def loop_end():
|
||||
"""
|
||||
Executed in the end of the game loop
|
||||
"""
|
||||
@ -80,7 +79,7 @@ class Window:
|
||||
if not Window.showing:
|
||||
cv2.destroyAllWindows()
|
||||
|
||||
def getCapture(self):
|
||||
def get_capture(self):
|
||||
"""
|
||||
copies the recorded screen and then pre processes its
|
||||
:return: game window image
|
||||
@ -98,16 +97,16 @@ class Window:
|
||||
|
||||
return temp_img
|
||||
|
||||
def processedImage(self, func=None):
|
||||
def processed_image(self, func=None):
|
||||
"""
|
||||
processes the image using the function provided
|
||||
:param func: function to process image
|
||||
:return: processed image
|
||||
"""
|
||||
if func is None:
|
||||
return self.getCapture()
|
||||
return self.get_capture()
|
||||
else:
|
||||
return func(self.getCapture())
|
||||
return func(self.get_capture())
|
||||
|
||||
def show(self, name, resize=None, func=None):
|
||||
"""
|
||||
@ -116,7 +115,7 @@ class Window:
|
||||
:param resize: scale the image to make small images more visible
|
||||
:param func: function to process the image
|
||||
"""
|
||||
img = self.processedImage(func)
|
||||
img = self.processed_image(func)
|
||||
|
||||
if resize is not None:
|
||||
img = imutils.resize(img, width=resize)
|
2
fishy/web/__init__.py
Normal file
2
fishy/web/__init__.py
Normal file
@ -0,0 +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
|
34
fishy/web/decorators.py
Normal file
34
fishy/web/decorators.py
Normal file
@ -0,0 +1,34 @@
|
||||
import logging
|
||||
import traceback
|
||||
import typing
|
||||
from functools import wraps
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from fishy.web.web import get_session
|
||||
|
||||
|
||||
def uses_session(f):
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if get_session(args[0]) is None:
|
||||
logging.error("Couldn't create a session")
|
||||
return None
|
||||
else:
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def fallback(default):
|
||||
def inner(f):
|
||||
# noinspection PyBroadException
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
return default
|
||||
return wrapper
|
||||
|
||||
return inner
|
18
fishy/web/urls.py
Normal file
18
fishy/web/urls.py
Normal file
@ -0,0 +1,18 @@
|
||||
import sys
|
||||
|
||||
domain = "http://127.0.0.1:5000" if "--local-server" in sys.argv else "https://fishyeso.herokuapp.com"
|
||||
|
||||
user = domain + "/api/user"
|
||||
notify = domain + "/api/notify"
|
||||
subscription = domain + "/api/subscription/"
|
||||
hole_depleted = domain + "/api/hole_depleted"
|
||||
session = domain + "/api/session"
|
||||
terms = domain + "/terms.html"
|
||||
|
||||
|
||||
def get_notification_page(uid):
|
||||
return domain + f"?uid={uid}"
|
||||
|
||||
|
||||
def get_terms_page():
|
||||
return terms
|
85
fishy/web/web.py
Normal file
85
fishy/web/web.py
Normal file
@ -0,0 +1,85 @@
|
||||
import requests
|
||||
from whatsmyip.ip import get_ip
|
||||
from whatsmyip.providers import GoogleDnsProvider
|
||||
|
||||
from fishy import helper
|
||||
from . import urls
|
||||
from .decorators import fallback, uses_session
|
||||
|
||||
_is_subbed = None
|
||||
_session_id = None
|
||||
|
||||
|
||||
@fallback(False)
|
||||
def register_user(uid):
|
||||
ip = get_ip(GoogleDnsProvider)
|
||||
body = {"uid": uid, "ip": ip}
|
||||
response = requests.post(urls.user, json=body)
|
||||
return response.ok and response.json()["success"]
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def send_notification(uid, message):
|
||||
if not is_subbed(uid):
|
||||
return False
|
||||
|
||||
body = {"uid": uid, "message": message}
|
||||
requests.post(urls.notify, json=body)
|
||||
|
||||
|
||||
@uses_session
|
||||
@fallback(None)
|
||||
def send_hole_deplete(uid, fish_caught, hole_time, fish_times):
|
||||
hole_data = {
|
||||
"fish_caught": fish_caught,
|
||||
"hole_time": hole_time,
|
||||
"fish_times": fish_times,
|
||||
"session": get_session(uid)
|
||||
}
|
||||
|
||||
body = {"uid": uid, "hole_data": hole_data}
|
||||
requests.post(urls.hole_depleted, json=body)
|
||||
|
||||
|
||||
@fallback((False, False))
|
||||
def is_subbed(uid, lazy=True):
|
||||
global _is_subbed
|
||||
|
||||
if lazy and _is_subbed is not None:
|
||||
return _is_subbed, True
|
||||
|
||||
if uid is None:
|
||||
return False, False
|
||||
|
||||
body = {"uid": uid}
|
||||
response = requests.get(urls.subscription, params=body)
|
||||
_is_subbed = response.json()["subbed"]
|
||||
return _is_subbed, True
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def unsub(uid):
|
||||
global _is_subbed
|
||||
|
||||
_is_subbed = False
|
||||
body = {"uid": uid}
|
||||
requests.delete(urls.subscription, json=body)
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def get_session(config, lazy=True):
|
||||
global _session_id
|
||||
|
||||
if lazy and _session_id is not None:
|
||||
return _session_id
|
||||
|
||||
body = {"uid": config.get("uid")}
|
||||
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
|
Loading…
Reference in New Issue
Block a user