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:
DESKTOP-JVKHS7I\Adam 2020-05-14 07:33:13 +05:30
parent 66eeb9d6f8
commit 5972aebc7d
26 changed files with 722 additions and 658 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@ -0,0 +1 @@
from .engine import Engine

108
fishy/tech/engine.py Normal file
View 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()

View File

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

View File

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

View File

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

View File

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