mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
Compare commits
No commits in common. "main" and "0.5.12" have entirely different histories.
46
.github/workflows/python-publish.yml
vendored
46
.github/workflows/python-publish.yml
vendored
@ -1,46 +0,0 @@
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
changed: ${{ steps.changed-files-specific.outputs.any_changed }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0 # OR "2" -> To retrieve the preceding commit.
|
||||
- name: Get changed files in the docs folder
|
||||
id: changed-files-specific
|
||||
uses: tj-actions/changed-files@v41
|
||||
with:
|
||||
files: fishy/version.txt # Alternatively using: `docs/**` or `docs`
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: check
|
||||
if: ${{ needs.check.outputs.changed == 'true' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v3
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install build
|
||||
- name: Build package
|
||||
run: python -m build
|
||||
- name: Publish package
|
||||
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
@ -1,7 +1,6 @@
|
||||
include LICENSE
|
||||
include README.md
|
||||
include requirements.txt
|
||||
include fishy/version.txt
|
||||
include fishy/icon.ico
|
||||
include fishy/fishybot_logo.png
|
||||
include fishy/sound.mp3
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Fishybot ESO 🎣
|
||||
Auto fishing bot for Elder Scrolls Online. The Bot automatically fishes until the fishing hole disappears. It also sends notification via discord when it stops fishing. We also have a leaderboard for the amount of fishes you caught. Become the master fisher and swim in perfect roes 😉
|
||||
|
||||
Botting does violate ESO's terms of service, so you could get banned. **This software doesn't come with any Liability or Warranty, I am not responsible if you do get banned.**
|
||||
Botting does violate ESO's terms of service, so technically you could get banned. But this bot doesn't read or write memory from ESO so they won't know you are using a bot. **This software doesn't come with any Liability or Warranty, I am not responsible if you do get banned.**
|
||||
|
||||
- Check out the [Showcase Video](https://www.youtube.com/watch?v=THQ66lG4ImU).
|
||||
- [How to Install ?](https://github.com/fishyboteso/fishyboteso/wiki/Installation)
|
||||
|
@ -1,5 +1,4 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
from fishy import constants
|
||||
|
||||
|
||||
# this prevents importing from package while setup
|
||||
@ -8,4 +7,4 @@ def main():
|
||||
actual_main()
|
||||
|
||||
|
||||
__version__ = (Path(os.path.dirname(__file__)) / "version.txt").read_text()
|
||||
__version__ = constants.version
|
||||
|
@ -1,27 +1,36 @@
|
||||
import ctypes
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
import win32con
|
||||
import win32gui
|
||||
|
||||
import fishy
|
||||
from fishy.gui import GUI, update_dialog, check_eula
|
||||
from fishy.gui import GUI, splash, update_dialog, check_eula
|
||||
from fishy import helper, web
|
||||
from fishy.engine.common.event_handler import EngineEventHandler
|
||||
from fishy.gui.log_config import GuiLogger
|
||||
from fishy.gui.splash import Splash
|
||||
from fishy.helper import hotkey
|
||||
from fishy.helper.active_poll import active
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.hotkey.hotkey_process import hotkey
|
||||
from fishy.helper.migration import Migration
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
def check_window_name(title):
|
||||
titles = ["Command Prompt", "PowerShell", "Fishy"]
|
||||
for t in titles:
|
||||
if t in title:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def initialize():
|
||||
def initialize(window_to_hide):
|
||||
Migration.migrate()
|
||||
|
||||
if not config.get("shortcut_created", False):
|
||||
os_services.create_shortcut(False)
|
||||
config.set("shortcut_created", True)
|
||||
helper.create_shortcut_first()
|
||||
|
||||
new_session = web.get_session()
|
||||
|
||||
@ -29,66 +38,58 @@ def initialize():
|
||||
logging.error("Couldn't create a session, some features might not work")
|
||||
logging.debug(f"created session {new_session}")
|
||||
|
||||
if os_services.is_admin():
|
||||
try:
|
||||
is_admin = os.getuid() == 0
|
||||
except AttributeError:
|
||||
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
if is_admin:
|
||||
logging.info("Running with admin privileges")
|
||||
|
||||
if not config.get("debug", False):
|
||||
os_services.hide_terminal()
|
||||
if not config.get("debug", False) and check_window_name(win32gui.GetWindowText(window_to_hide)):
|
||||
win32gui.ShowWindow(window_to_hide, win32con.SW_HIDE)
|
||||
helper.install_thread_excepthook()
|
||||
sys.excepthook = helper.unhandled_exception_logging
|
||||
|
||||
helper.install_required_addons()
|
||||
|
||||
|
||||
def on_gui_load(gui, splash, logger):
|
||||
splash.finish()
|
||||
update_dialog.check_update(gui)
|
||||
logger.connect(gui)
|
||||
|
||||
|
||||
def main():
|
||||
print("launching please wait...")
|
||||
|
||||
if not os_services.init():
|
||||
print("platform not supported")
|
||||
return
|
||||
|
||||
config.init()
|
||||
if not check_eula():
|
||||
return
|
||||
|
||||
splash = Splash()
|
||||
bot = EngineEventHandler(lambda: gui)
|
||||
gui = GUI(lambda: bot, lambda: on_gui_load(gui, splash, logger))
|
||||
finish_splash = splash.start()
|
||||
logger = GuiLogger()
|
||||
hotkey.init()
|
||||
config.start_backup_scheduler()
|
||||
active.init()
|
||||
hotkey.init()
|
||||
|
||||
try:
|
||||
config.init()
|
||||
if not check_eula():
|
||||
return
|
||||
def on_gui_load():
|
||||
finish_splash()
|
||||
update_dialog.check_update(gui)
|
||||
logger.connect(gui)
|
||||
|
||||
logging.info(f"Fishybot v{fishy.__version__}")
|
||||
window_to_hide = win32gui.GetForegroundWindow()
|
||||
|
||||
splash.start()
|
||||
config.start_backup_scheduler()
|
||||
bot = EngineEventHandler(lambda: gui)
|
||||
gui = GUI(lambda: bot, on_gui_load)
|
||||
|
||||
initialize()
|
||||
hotkey.start()
|
||||
|
||||
hotkey.start()
|
||||
gui.start()
|
||||
active.start()
|
||||
logging.info(f"Fishybot v{fishy.__version__}")
|
||||
initialize(window_to_hide)
|
||||
|
||||
bot.start_event_handler() # main thread loop
|
||||
except KeyboardInterrupt:
|
||||
print("caught KeyboardInterrupt, Stopping main thread")
|
||||
finally:
|
||||
gui.stop()
|
||||
hotkey.stop()
|
||||
active.stop()
|
||||
config.stop()
|
||||
bot.stop()
|
||||
gui.start()
|
||||
active.start()
|
||||
|
||||
bot.start_event_handler() # main thread loop
|
||||
|
||||
hotkey.stop()
|
||||
active.stop()
|
||||
config.stop()
|
||||
bot.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@ -1,17 +1,11 @@
|
||||
apiversion = 2
|
||||
|
||||
current_version_url = "https://raw.githubusercontent.com/fishyboteso/fishyboteso/main/fishy/version.txt"
|
||||
|
||||
# removed since 0.5.3
|
||||
chalutier = ("Chalutier", "https://cdn.esoui.com/downloads/file2934/Chalutier_1.3.zip", 130)
|
||||
chalutier = ("Chalutier", "https://www.esoui.com/downloads/dl2934/Chalutier_1.1.4.zip", 114)
|
||||
|
||||
# addons used
|
||||
lam2 = ("LibAddonMenu-2.0", "https://cdn.esoui.com/downloads/file7/LibAddonMenu-2.0r34.zip", 34)
|
||||
fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/releases/download/v1.8/FishyQR-1.8.zip", 180)
|
||||
fishyfsm = ("FishingStateMachine", "https://github.com/fishyboteso/FishingStateMachine/releases/download/fsm_v1.1/FishingStateMachine-1.1.zip", 110)
|
||||
libgps = ("LibGPS", "https://cdn.esoui.com/downloads/file601/LibGPS_v3.3.0.zip", 69)
|
||||
libmapping = ("LibMapPing", "https://cdn.esoui.com/downloads/file1302/LibMapPing_2_0_0.zip", 1236)
|
||||
libdl = ("LibDebugLogger", "https://cdn.esoui.com/downloads/file2275/LibDebugLogger_2_5_1.zip", 263)
|
||||
libchatmsg = ("LibChatMessage", "https://cdn.esoui.com/downloads/file2382/LibChatMessage_1_2_0.zip", 105)
|
||||
lam2 = ("LibAddonMenu-2.0", "https://www.esoui.com/downloads/dl7/LibAddonMenu-2.0r32.zip", 32)
|
||||
fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/releases/download/v1.2.0/FishyQR.zip", 120)
|
||||
libgps = ("LibGPS", "https://cdn.esoui.com/downloads/file601/LibGPS_3_0_3.zip", 30)
|
||||
|
||||
d3dshot_git = "git+https://github.com/fauskanger/D3DShot.git#egg=D3DShot"
|
||||
version = "0.5.12"
|
||||
|
@ -1,2 +1 @@
|
||||
from fishy.engine.semifisher.engine import SemiFisherEngine
|
||||
from fishy.engine.fullautofisher.engine import FullAuto
|
@ -65,7 +65,7 @@ class IEngine:
|
||||
# noinspection PyBroadException
|
||||
def _crash_safe(self):
|
||||
logging.debug(f"starting {self.name} engine thread")
|
||||
self.window = WindowClient()
|
||||
self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name=f"{self.name} debug")
|
||||
self.gui.bot_started(True)
|
||||
try:
|
||||
self.run()
|
||||
|
@ -3,37 +3,20 @@ import re
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from fishy.engine.common.window import WindowClient
|
||||
|
||||
detector = cv2.QRCodeDetector()
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def get_values(window: WindowClient):
|
||||
values = None
|
||||
for _ in range(6):
|
||||
img = window.processed_image()
|
||||
if img is None:
|
||||
logging.debug("Couldn't capture window.")
|
||||
continue
|
||||
|
||||
if not window.crop:
|
||||
window.crop = _get_qr_location(img)
|
||||
if not window.crop:
|
||||
logging.debug("FishyQR not found.")
|
||||
continue
|
||||
|
||||
values = _get_values_from_image(img)
|
||||
if not values:
|
||||
window.crop = None
|
||||
logging.debug("Values not able to read.")
|
||||
continue
|
||||
break
|
||||
|
||||
return values
|
||||
def image_pre_process(img):
|
||||
scale_percent = 100 # percent of original size
|
||||
width = int(img.shape[1] * scale_percent / 100)
|
||||
height = int(img.shape[0] * scale_percent / 100)
|
||||
dim = (width, height)
|
||||
img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
|
||||
return img
|
||||
|
||||
|
||||
def _get_qr_location(image):
|
||||
def get_qr_location(image):
|
||||
"""
|
||||
code from https://stackoverflow.com/a/45770227/4512396
|
||||
"""
|
||||
@ -46,15 +29,16 @@ def _get_qr_location(image):
|
||||
return [int(x) for x in [p[0][0], p[0][1], p[1][0], p[2][1]]]
|
||||
|
||||
|
||||
def _get_values_from_image(img):
|
||||
# noinspection PyBroadException
|
||||
def get_values_from_image(img):
|
||||
h, w = img.shape
|
||||
points = np.array([[(0, 0), (w, 0), (w, h), (0, h)]])
|
||||
code = detector.decode(img, points)[0]
|
||||
return _parse_qr_code(code)
|
||||
return parse_qr_code(code)
|
||||
|
||||
|
||||
# this needs to be updated each time qr code format is changed
|
||||
def _parse_qr_code(code):
|
||||
def parse_qr_code(code):
|
||||
if not code:
|
||||
return None
|
||||
match = re.match(r'^(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+),(\d+)$', code)
|
||||
|
@ -1,122 +0,0 @@
|
||||
import logging
|
||||
import subprocess
|
||||
import traceback
|
||||
from abc import ABC, abstractmethod
|
||||
from functools import partial
|
||||
from typing import Optional
|
||||
|
||||
import numpy as np
|
||||
from numpy import ndarray
|
||||
|
||||
from fishy import constants
|
||||
from fishy.helper.config import config
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
class IScreenShot(ABC):
|
||||
@abstractmethod
|
||||
def setup(self) -> bool:
|
||||
...
|
||||
|
||||
@abstractmethod
|
||||
def grab(self) -> ndarray:
|
||||
...
|
||||
|
||||
|
||||
def get_monitor_id(monitors_iterator, get_top_left) -> Optional[int]:
|
||||
monitor_rect = os_services.get_monitor_rect()
|
||||
if monitor_rect is None:
|
||||
logging.error("Game window not found")
|
||||
return None
|
||||
|
||||
for i, m in enumerate(monitors_iterator):
|
||||
top, left = get_top_left(m)
|
||||
if top == monitor_rect[1] and left == monitor_rect[0]:
|
||||
return i
|
||||
|
||||
return None
|
||||
|
||||
|
||||
class MSS(IScreenShot):
|
||||
def __init__(self):
|
||||
from mss import mss
|
||||
self.monitor_id = None
|
||||
self.sct = mss()
|
||||
|
||||
def setup(self) -> bool:
|
||||
self.monitor_id = get_monitor_id(self.sct.monitors, lambda m: (m["top"], m["left"]))
|
||||
return self.monitor_id is not None
|
||||
|
||||
# noinspection PyTypeChecker
|
||||
def grab(self) -> ndarray:
|
||||
sct_img = self.sct.grab(self.sct.monitors[self.monitor_id])
|
||||
return np.array(sct_img)
|
||||
|
||||
|
||||
class PyAutoGUI(IScreenShot):
|
||||
def __init__(self):
|
||||
self.monitor_rect = None
|
||||
|
||||
def setup(self) -> bool:
|
||||
from PIL import ImageGrab
|
||||
ImageGrab.grab = partial(ImageGrab.grab, all_screens=True)
|
||||
self.monitor_rect = os_services.get_monitor_rect()
|
||||
return True
|
||||
|
||||
def grab(self) -> ndarray:
|
||||
import pyautogui
|
||||
image = pyautogui.screenshot()
|
||||
img = np.array(image)
|
||||
crop = self.monitor_rect
|
||||
img = img[crop[1]:crop[3], crop[0]:crop[2]]
|
||||
return img
|
||||
|
||||
|
||||
class D3DShot(IScreenShot):
|
||||
# noinspection PyPackageRequirements
|
||||
def __init__(self):
|
||||
try:
|
||||
import d3dshot
|
||||
except ImportError:
|
||||
logging.info("Installing d3dshot please wait...")
|
||||
subprocess.call(["python", "-m", "pip", "install", constants.d3dshot_git])
|
||||
import d3dshot
|
||||
|
||||
self.d3 = d3dshot.create(capture_output="numpy")
|
||||
|
||||
def setup(self) -> bool:
|
||||
monitor_id = get_monitor_id(self.d3.displays, lambda m: (m.position["top"], m.position["left"]))
|
||||
if monitor_id is None:
|
||||
return False
|
||||
|
||||
self.d3.display = self.d3.displays[monitor_id]
|
||||
return True
|
||||
|
||||
def grab(self) -> ndarray:
|
||||
return self.d3.screenshot()
|
||||
|
||||
|
||||
LIBS = [PyAutoGUI, MSS, D3DShot]
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def create() -> Optional[IScreenShot]:
|
||||
# Initialize a variable to hold the preferred library index
|
||||
preferred_lib_index = config.get("sslib", 0)
|
||||
# Create a list of library indices to try, starting with the preferred one
|
||||
lib_indices = [preferred_lib_index] + [i for i in range(len(LIBS)) if i != preferred_lib_index]
|
||||
|
||||
for index in lib_indices:
|
||||
lib = LIBS[index]
|
||||
try:
|
||||
lib_instance = lib()
|
||||
if lib_instance.setup():
|
||||
# testing grab once
|
||||
ss = lib_instance.grab()
|
||||
if ss.shape:
|
||||
logging.debug(f"Using {lib.__name__} as the screenshot library.")
|
||||
return lib_instance
|
||||
except Exception:
|
||||
logging.warning(f"Setup failed for {lib.__name__} with error: {traceback.format_exc()}. Trying next library...")
|
||||
|
||||
return None
|
@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import List
|
||||
|
||||
import cv2
|
||||
@ -8,52 +7,26 @@ import imutils
|
||||
from fishy.engine.common import window_server
|
||||
from fishy.engine.common.window_server import Status, WindowServer
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
|
||||
|
||||
class WindowClient:
|
||||
clients: List['WindowClient'] = []
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, crop=None, color=None, scale=None, show_name=None):
|
||||
"""
|
||||
create a window instance with these pre process
|
||||
:param crop: [x1,y1,x2,y2] array defining the boundaries to crop
|
||||
:param color: color to use example cv2.COLOR_RGB2HSV
|
||||
:param scale: scaling the window
|
||||
"""
|
||||
self.crop = None
|
||||
self.scale = None
|
||||
self.show_name = f"window client {len(WindowClient.clients)}"
|
||||
self.color = color
|
||||
self.crop = crop
|
||||
self.scale = scale
|
||||
self.show_name = show_name
|
||||
|
||||
WindowClient.clients.append(self)
|
||||
if len(WindowClient.clients) > 0 and WindowServer.status != Status.RUNNING:
|
||||
if len(WindowClient.clients) == 0:
|
||||
window_server.start()
|
||||
|
||||
@staticmethod
|
||||
def running():
|
||||
return WindowServer.status == Status.RUNNING
|
||||
|
||||
def processed_image(self, func=None):
|
||||
"""
|
||||
processes the image using the function provided
|
||||
:param func: function to process image
|
||||
:return: processed image
|
||||
"""
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
return None
|
||||
|
||||
img = self._get_capture()
|
||||
|
||||
if img is None:
|
||||
return None
|
||||
|
||||
if func:
|
||||
img = func(img)
|
||||
|
||||
if config.get("show_grab", 0):
|
||||
self._show(img)
|
||||
|
||||
return img
|
||||
WindowClient.clients.append(self)
|
||||
|
||||
def destroy(self):
|
||||
if self in WindowClient.clients:
|
||||
@ -61,7 +34,11 @@ class WindowClient:
|
||||
if len(WindowClient.clients) == 0:
|
||||
window_server.stop()
|
||||
|
||||
def _get_capture(self):
|
||||
@staticmethod
|
||||
def running():
|
||||
return WindowServer.status == Status.RUNNING
|
||||
|
||||
def get_capture(self):
|
||||
"""
|
||||
copies the recorded screen and then pre processes its
|
||||
:return: game window image
|
||||
@ -79,7 +56,8 @@ class WindowClient:
|
||||
if temp_img is None or temp_img.size == 0:
|
||||
return None
|
||||
|
||||
temp_img = cv2.cvtColor(temp_img, cv2.COLOR_RGB2GRAY)
|
||||
if self.color is not None:
|
||||
temp_img = cv2.cvtColor(temp_img, self.color)
|
||||
|
||||
if self.crop is not None:
|
||||
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
|
||||
@ -87,18 +65,51 @@ class WindowClient:
|
||||
if self.scale is not None:
|
||||
temp_img = cv2.resize(temp_img, (self.scale[0], self.scale[1]), interpolation=cv2.INTER_AREA)
|
||||
|
||||
# need ot check again after crop/resize
|
||||
if temp_img.size == 0:
|
||||
return None
|
||||
|
||||
return temp_img
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def _show(self, img):
|
||||
def processed_image(self, func=None):
|
||||
"""
|
||||
processes the image using the function provided
|
||||
:param func: function to process image
|
||||
:return: processed image
|
||||
"""
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
return None
|
||||
|
||||
img = self.get_capture()
|
||||
|
||||
if img is None:
|
||||
return None
|
||||
|
||||
if func is None:
|
||||
return img
|
||||
|
||||
return func(img)
|
||||
|
||||
def show(self, to_show, resize=None, func=None):
|
||||
"""
|
||||
Displays the processed image for debugging purposes
|
||||
:param to_show: false to destroy the window
|
||||
:param resize: scale the image to make small images more visible
|
||||
:param func: function to process the image
|
||||
"""
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
return
|
||||
|
||||
helper.save_img(self.show_name, img, True)
|
||||
if not self.show_name:
|
||||
logging.warning("You need to assign a name first")
|
||||
return
|
||||
|
||||
if not to_show:
|
||||
cv2.destroyWindow(self.show_name)
|
||||
return
|
||||
|
||||
img = self.processed_image(func)
|
||||
|
||||
if img is None:
|
||||
return
|
||||
|
||||
if resize is not None:
|
||||
img = imutils.resize(img, width=resize)
|
||||
cv2.imshow(self.show_name, img)
|
||||
cv2.waitKey(25)
|
||||
|
@ -1,16 +1,15 @@
|
||||
import logging
|
||||
import math
|
||||
from enum import Enum
|
||||
from threading import Thread
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from mss.base import MSSBase
|
||||
import d3dshot
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32gui
|
||||
from ctypes import windll
|
||||
|
||||
from fishy.engine.common import screenshot
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.helper import print_exc
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
@ -23,11 +22,12 @@ class WindowServer:
|
||||
"""
|
||||
Records the game window, and allows to create instance to process it
|
||||
"""
|
||||
Screen: np.ndarray = None
|
||||
Screen = None
|
||||
windowOffset = None
|
||||
hwnd = None
|
||||
status = Status.STOPPED
|
||||
sslib = None
|
||||
crop = None
|
||||
d3: d3dshot.D3DShot = d3dshot.create(capture_output="numpy")
|
||||
monitor_top_left = None
|
||||
|
||||
|
||||
def init():
|
||||
@ -35,39 +35,23 @@ def init():
|
||||
Executed once before the main loop,
|
||||
Finds the game window, and calculates the offset to remove the title bar
|
||||
"""
|
||||
WindowServer.sslib = screenshot.create()
|
||||
# Check if the screenshot library was successfully created
|
||||
if WindowServer.sslib is None:
|
||||
logging.error("Failed to create screenshot library instance")
|
||||
# noinspection PyUnresolvedReferences
|
||||
try:
|
||||
WindowServer.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
|
||||
|
||||
monitor_id = windll.user32.MonitorFromWindow(WindowServer.hwnd, 2)
|
||||
WindowServer.monitor_top_left = win32api.GetMonitorInfo(monitor_id)["Monitor"][:2]
|
||||
|
||||
rect = win32gui.GetWindowRect(WindowServer.hwnd)
|
||||
client_rect = win32gui.GetClientRect(WindowServer.hwnd)
|
||||
WindowServer.windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2)
|
||||
WindowServer.status = Status.RUNNING
|
||||
|
||||
WindowServer.d3.display = next((m for m in WindowServer.d3.displays if m.hmonitor == monitor_id), None)
|
||||
|
||||
except pywintypes.error:
|
||||
logging.error("Game window not found")
|
||||
WindowServer.status = Status.CRASHED
|
||||
return
|
||||
|
||||
crop = os_services.get_game_window_rect()
|
||||
if crop is None or not WindowServer.sslib.setup():
|
||||
logging.error("Game window not found by window_server")
|
||||
WindowServer.status = Status.CRASHED
|
||||
return
|
||||
|
||||
WindowServer.crop = crop
|
||||
WindowServer.status = Status.RUNNING
|
||||
|
||||
|
||||
def get_cropped_screenshot():
|
||||
ss = WindowServer.sslib.grab()
|
||||
|
||||
if config.get("show_grab", 0):
|
||||
helper.save_img("full screen", ss)
|
||||
|
||||
crop = WindowServer.crop
|
||||
cropped_ss = ss[crop[1]:crop[3], crop[0]:crop[2]]
|
||||
|
||||
if cropped_ss.size == 0:
|
||||
return None
|
||||
|
||||
if config.get("show_grab", 0):
|
||||
helper.save_img("Game window", cropped_ss)
|
||||
|
||||
return cropped_ss
|
||||
|
||||
|
||||
def loop():
|
||||
@ -75,10 +59,25 @@ def loop():
|
||||
Executed in the start of the main loop
|
||||
finds the game window location and captures it
|
||||
"""
|
||||
WindowServer.Screen = get_cropped_screenshot()
|
||||
|
||||
if WindowServer.Screen is None:
|
||||
logging.error("Couldn't find the game window")
|
||||
temp_screen = WindowServer.d3.screenshot()
|
||||
|
||||
rect = win32gui.GetWindowRect(WindowServer.hwnd)
|
||||
client_rect = win32gui.GetClientRect(WindowServer.hwnd)
|
||||
|
||||
fullscreen = WindowServer.d3.display.resolution[1] == (rect[3] - rect[1])
|
||||
title_offset = ((rect[3] - rect[1]) - client_rect[3]) - WindowServer.windowOffset if not fullscreen else 0
|
||||
crop = (
|
||||
rect[0] + WindowServer.windowOffset - WindowServer.monitor_top_left[0],
|
||||
rect[1] + title_offset - WindowServer.monitor_top_left[1],
|
||||
rect[2] - WindowServer.windowOffset - WindowServer.monitor_top_left[0],
|
||||
rect[3] - WindowServer.windowOffset - WindowServer.monitor_top_left[1]
|
||||
)
|
||||
|
||||
WindowServer.Screen = temp_screen[crop[1]:crop[3], crop[0]:crop[2]] if not fullscreen else temp_screen
|
||||
|
||||
if WindowServer.Screen.size == 0:
|
||||
logging.error("Don't minimize or drag game window outside the screen")
|
||||
WindowServer.status = Status.CRASHED
|
||||
|
||||
|
||||
|
@ -3,7 +3,6 @@ import math
|
||||
import time
|
||||
from threading import Thread
|
||||
|
||||
from fishy.engine.common import qr_detection
|
||||
from pynput import keyboard, mouse
|
||||
|
||||
from fishy.engine import SemiFisherEngine
|
||||
@ -13,11 +12,12 @@ from fishy.engine.fullautofisher.mode.calibrator import Calibrator
|
||||
from fishy.engine.fullautofisher.mode.imode import FullAutoMode
|
||||
from fishy.engine.fullautofisher.mode.player import Player
|
||||
from fishy.engine.fullautofisher.mode.recorder import Recorder
|
||||
from fishy.engine.common.qr_detection import (get_qr_location,
|
||||
get_values_from_image, image_pre_process)
|
||||
from fishy.engine.semifisher import fishing_mode
|
||||
from fishy.engine.semifisher.fishing_mode import FishingMode
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.helper import wait_until, sign, print_exc
|
||||
from fishy.osservices.os_services import os_services
|
||||
from fishy.helper.helper import wait_until, is_eso_active, sign, print_exc
|
||||
|
||||
mse = mouse.Controller()
|
||||
kb = keyboard.Controller()
|
||||
@ -53,20 +53,14 @@ class FullAuto(IEngine):
|
||||
return
|
||||
|
||||
# block thread until game window becomes active
|
||||
if not os_services.is_eso_active():
|
||||
if not is_eso_active():
|
||||
logging.info("Waiting for eso window to be active...")
|
||||
wait_until(lambda: os_services.is_eso_active() or not self.start)
|
||||
wait_until(lambda: is_eso_active() or not self.start)
|
||||
if self.start:
|
||||
logging.info("starting in 2 secs...")
|
||||
time.sleep(2)
|
||||
|
||||
if not (type(self.mode) is Calibrator) and not self.calibrator.all_calibrated():
|
||||
logging.error("you need to calibrate first")
|
||||
return
|
||||
|
||||
if not qr_detection.get_values(self.window):
|
||||
logging.error("FishyQR not found, if its not hidden, try to drag it around, "
|
||||
"or increase/decrease its size and try again\nStopping engine...")
|
||||
if not self._pre_run_checks():
|
||||
return
|
||||
|
||||
if config.get("tabout_stop", 1):
|
||||
@ -79,11 +73,33 @@ class FullAuto(IEngine):
|
||||
logging.error("exception occurred while running full auto mode")
|
||||
print_exc()
|
||||
|
||||
def _pre_run_checks(self):
|
||||
if self.window.get_capture() is None:
|
||||
logging.error("Game window not found")
|
||||
return False
|
||||
|
||||
self.window.crop = get_qr_location(self.window.get_capture())
|
||||
if self.window.crop is None:
|
||||
logging.error("FishyQR not found, try to drag it around and try again")
|
||||
return False
|
||||
|
||||
if not (type(self.mode) is Calibrator) and not self.calibrator.all_calibrated():
|
||||
logging.error("you need to calibrate first")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def start_show(self):
|
||||
def func():
|
||||
while self.start and WindowClient.running():
|
||||
self.window.show(self.show_crop, func=image_pre_process)
|
||||
Thread(target=func).start()
|
||||
|
||||
def stop_on_inactive(self):
|
||||
def func():
|
||||
logging.debug("stop on inactive started")
|
||||
wait_until(lambda: not os_services.is_eso_active() or not self.start)
|
||||
if self.start and not os_services.is_eso_active():
|
||||
wait_until(lambda: not is_eso_active() or not self.start)
|
||||
if self.start and not is_eso_active():
|
||||
self.turn_off()
|
||||
logging.debug("stop on inactive stopped")
|
||||
Thread(target=func).start()
|
||||
@ -95,7 +111,8 @@ class FullAuto(IEngine):
|
||||
todo find a better way of handling None: switch from start bool to state which knows
|
||||
todo its waiting for qr which doesn't block the engine when commanded to close
|
||||
"""
|
||||
values = qr_detection.get_values(self.window)
|
||||
img = self.window.processed_image(func=image_pre_process)
|
||||
values = get_values_from_image(img)
|
||||
return values[:3] if values else None
|
||||
|
||||
def move_to(self, target) -> bool:
|
||||
@ -120,12 +137,9 @@ class FullAuto(IEngine):
|
||||
|
||||
walking_time = dist / self.calibrator.move_factor
|
||||
logging.debug(f"walking for {walking_time}")
|
||||
|
||||
forward_key = config.get("forward_key", 'w')
|
||||
kb.press(forward_key)
|
||||
kb.press('w')
|
||||
time.sleep(walking_time)
|
||||
kb.release(forward_key)
|
||||
|
||||
kb.release('w')
|
||||
logging.debug("done")
|
||||
# todo: maybe check if it reached the destination before returning true?
|
||||
return True
|
||||
@ -163,7 +177,7 @@ class FullAuto(IEngine):
|
||||
_hole_found_flag = FishingMode.CurrentMode in valid_states
|
||||
|
||||
# if vertical movement is disabled
|
||||
if not config.get("look_for_hole", 0):
|
||||
if not config.get("look_for_hole", 1):
|
||||
return _hole_found_flag
|
||||
|
||||
t = 0
|
||||
|
@ -20,6 +20,26 @@ kb = keyboard.Controller()
|
||||
offset = 0
|
||||
|
||||
|
||||
def get_crop_coords(window):
|
||||
img = window.get_capture()
|
||||
img = cv2.inRange(img, 0, 1)
|
||||
|
||||
cnt, h = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
"""
|
||||
code from https://stackoverflow.com/a/45770227/4512396
|
||||
"""
|
||||
for i in range(len(cnt)):
|
||||
area = cv2.contourArea(cnt[i])
|
||||
if 5000 < area < 100000:
|
||||
mask = np.zeros_like(img)
|
||||
cv2.drawContours(mask, cnt, i, 255, -1)
|
||||
x, y, w, h = cv2.boundingRect(cnt[i])
|
||||
return x, y + offset, x + w, y + h - offset
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _update_factor(key, value):
|
||||
full_auto_factors = config.get("full_auto_factors", {})
|
||||
full_auto_factors[key] = value
|
||||
@ -63,11 +83,9 @@ class Calibrator(IMode):
|
||||
|
||||
x1, y1, rot1 = coords
|
||||
|
||||
forward_key = config.get("forward_key", 'w')
|
||||
kb.press(forward_key)
|
||||
kb.press('w')
|
||||
time.sleep(walking_time)
|
||||
kb.release(forward_key)
|
||||
|
||||
kb.release('w')
|
||||
time.sleep(0.5)
|
||||
|
||||
coords = self.engine.get_coords()
|
||||
|
@ -99,7 +99,7 @@ class Recorder(IMode):
|
||||
|
||||
def _open_save_popup(self):
|
||||
top = PopUp(empty_function, self.engine.get_gui()._root, background=self.engine.get_gui()._root["background"])
|
||||
recorder_frame = ttk.Frame(top)
|
||||
controls_frame = ttk.Frame(top)
|
||||
top.title("Save Recording?")
|
||||
|
||||
button = [-1]
|
||||
@ -109,15 +109,14 @@ class Recorder(IMode):
|
||||
top.quit_top()
|
||||
|
||||
selected_text = f"\n\nSelected: {os.path.basename(config.get('full_auto_rec_file'))}" if config.get('edit_recorder_mode') else ""
|
||||
ttk.Label(recorder_frame, text=f"Do you want to save the recording?{selected_text}").grid(row=0, column=0, columnspan=3, pady=(0, 5))
|
||||
ttk.Label(controls_frame, text=f"Do you want to save the recording?{selected_text}").grid(row=0, column=0, columnspan=3, pady=(0, 5))
|
||||
|
||||
_overwrite = tk.NORMAL if config.get("edit_recorder_mode") else tk.DISABLED
|
||||
ttk.Button(recorder_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0, pady=(5, 0))
|
||||
ttk.Button(recorder_frame, text="Save As", command=lambda: button_pressed(1)).grid(row=1, column=1)
|
||||
ttk.Button(recorder_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2)
|
||||
ttk.Button(controls_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0, pady=(5, 0))
|
||||
ttk.Button(controls_frame, text="Save As", command=lambda: button_pressed(1)).grid(row=1, column=1)
|
||||
ttk.Button(controls_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2)
|
||||
|
||||
recorder_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||
recorder_frame.update()
|
||||
controls_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||
top.start()
|
||||
|
||||
return button[0]
|
||||
|
@ -4,11 +4,10 @@ import typing
|
||||
from threading import Thread
|
||||
from typing import Callable, Optional
|
||||
|
||||
from fishy.engine.common import qr_detection
|
||||
|
||||
from fishy.engine.semifisher.fishing_mode import FishingMode
|
||||
|
||||
from fishy.engine.common.IEngine import IEngine
|
||||
from fishy.engine.common.qr_detection import get_qr_location, get_values_from_image, image_pre_process
|
||||
from fishy.engine.common.window import WindowClient
|
||||
from fishy.engine.semifisher import fishing_event, fishing_mode
|
||||
from fishy.engine.semifisher.fishing_event import FishEvent
|
||||
@ -36,6 +35,15 @@ class SemiFisherEngine(IEngine):
|
||||
Thread(target=self._wait_and_check).start()
|
||||
|
||||
time.sleep(0.2)
|
||||
capture = self.window.get_capture()
|
||||
if capture is None:
|
||||
logging.error("couldn't get game capture")
|
||||
return
|
||||
|
||||
self.window.crop = get_qr_location(capture)
|
||||
if not self.window.crop:
|
||||
logging.error("FishyQR not found, try to drag it around and try again")
|
||||
return
|
||||
|
||||
fishing_event.init()
|
||||
# noinspection PyBroadException
|
||||
@ -51,9 +59,15 @@ class SemiFisherEngine(IEngine):
|
||||
def _engine_loop(self):
|
||||
skip_count = 0
|
||||
while self.state == 1 and WindowClient.running():
|
||||
# crop qr and get the values from it
|
||||
self.values = qr_detection.get_values(self.window)
|
||||
capture = self.window.processed_image(func=image_pre_process)
|
||||
|
||||
# if window server crashed
|
||||
if capture is None:
|
||||
logging.error("Couldn't capture window stopping engine")
|
||||
return
|
||||
|
||||
# crop qr and get the values from it
|
||||
self.values = get_values_from_image(capture)
|
||||
# if fishyqr fails to get read multiple times, stop the bot
|
||||
if not self.values:
|
||||
if skip_count >= 5:
|
||||
|
@ -12,10 +12,10 @@ from playsound import playsound
|
||||
|
||||
from fishy import web
|
||||
from fishy.engine.semifisher import fishing_mode
|
||||
from fishy.engine.semifisher.fishing_mode import State
|
||||
from fishy.engine.semifisher.fishing_mode import FishingMode, State
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
from fishy.osservices.os_services import os_services
|
||||
from fishy.helper.helper import is_eso_active
|
||||
|
||||
|
||||
class FishEvent:
|
||||
@ -44,7 +44,7 @@ def _fishing_sleep(waittime, lower_limit_ms=16, upper_limit_ms=1375):
|
||||
|
||||
def if_eso_is_focused(func):
|
||||
def wrapper():
|
||||
if not os_services.is_eso_active():
|
||||
if not is_eso_active():
|
||||
logging.warning("ESO window is not focused")
|
||||
return
|
||||
func()
|
||||
|
@ -13,20 +13,11 @@ from fishy.helper.config import config
|
||||
from fishy.helper.popup import PopUp
|
||||
|
||||
|
||||
def del_entry_key(event):
|
||||
event.widget.delete(0, "end")
|
||||
event.widget.insert(0, str(event.char))
|
||||
|
||||
|
||||
def start_fullfisher_config(gui: 'GUI' ):
|
||||
def save():
|
||||
gui.config.set("forward_key", forward_key_entry.get())
|
||||
|
||||
top = PopUp(save, gui._root, background=gui._root["background"])
|
||||
def start_fullfisher_config(gui: 'GUI'):
|
||||
top = PopUp(helper.empty_function, gui._root, background=gui._root["background"])
|
||||
controls_frame = ttk.Frame(top)
|
||||
top.title("Config")
|
||||
|
||||
|
||||
def file_name():
|
||||
file = config.get("full_auto_rec_file", None)
|
||||
if file is None:
|
||||
@ -57,7 +48,7 @@ def start_fullfisher_config(gui: 'GUI' ):
|
||||
mode_var = tk.IntVar(value=config.get("full_auto_mode", 0))
|
||||
edit_var = tk.IntVar(value=config.get("edit_recorder_mode", 0))
|
||||
tabout_var = tk.IntVar(value=config.get("tabout_stop", 1))
|
||||
look_for_hole = tk.IntVar(value=config.get("look_for_hole", 0))
|
||||
look_for_hole = tk.IntVar(value=config.get("look_for_hole", 1))
|
||||
row = 0
|
||||
|
||||
ttk.Label(controls_frame, text="Calibration: ").grid(row=row, column=0, pady=(5, 0))
|
||||
@ -72,14 +63,6 @@ def start_fullfisher_config(gui: 'GUI' ):
|
||||
|
||||
row += 1
|
||||
|
||||
ttk.Label(controls_frame, text="Forward key:").grid(row=row, column=0)
|
||||
forward_key_entry = ttk.Entry(controls_frame, justify=tk.CENTER)
|
||||
forward_key_entry.grid(row=row, column=1)
|
||||
forward_key_entry.insert(0, config.get("forward_key", "w"))
|
||||
forward_key_entry.bind("<KeyRelease>", del_entry_key)
|
||||
|
||||
row += 1
|
||||
|
||||
ttk.Label(controls_frame, text="Edit Mode: ").grid(row=row, column=0)
|
||||
edit_state = tk.NORMAL if config.get("full_auto_mode", 0) == FullAutoMode.Recorder.value else tk.DISABLED
|
||||
edit_cb = ttk.Checkbutton(controls_frame, variable=edit_var, state=edit_state, command=lambda: config.set("edit_recorder_mode", edit_var.get()))
|
||||
@ -107,8 +90,6 @@ def start_fullfisher_config(gui: 'GUI' ):
|
||||
ttk.Label(controls_frame, text="Use semi-fisher config for rest").grid(row=row, column=0, columnspan=2, pady=(20, 0))
|
||||
|
||||
controls_frame.pack(padx=(5, 5), pady=(5, 10))
|
||||
controls_frame.update()
|
||||
|
||||
top.start()
|
||||
|
||||
|
||||
@ -128,7 +109,9 @@ def start_semifisher_config(gui: 'GUI'):
|
||||
if web.sub():
|
||||
gui._notify.set(1)
|
||||
|
||||
|
||||
def del_entry_key(event):
|
||||
event.widget.delete(0, "end")
|
||||
event.widget.insert(0, str(event.char))
|
||||
|
||||
top = PopUp(save, gui._root, background=gui._root["background"])
|
||||
controls_frame = ttk.Frame(top)
|
||||
@ -136,7 +119,7 @@ def start_semifisher_config(gui: 'GUI'):
|
||||
|
||||
ttk.Label(controls_frame, text="Notification:").grid(row=0, column=0)
|
||||
|
||||
gui._notify = tk.IntVar()
|
||||
gui._notify = tk.IntVar(0)
|
||||
gui._notify_check = ttk.Checkbutton(controls_frame, command=toggle_sub, variable=gui._notify)
|
||||
gui._notify_check.grid(row=0, column=1)
|
||||
gui._notify_check['state'] = tk.DISABLED
|
||||
@ -166,8 +149,6 @@ def start_semifisher_config(gui: 'GUI'):
|
||||
jitter.grid(row=5, column=1)
|
||||
|
||||
controls_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||
controls_frame.update()
|
||||
|
||||
top.start()
|
||||
|
||||
|
||||
@ -175,5 +156,4 @@ if __name__ == '__main__':
|
||||
from fishy.gui import GUI
|
||||
gui = GUI(lambda: IEngineHandler())
|
||||
gui.call_in_thread(lambda: start_semifisher_config(gui))
|
||||
gui.call_in_thread(lambda: start_fullfisher_config(gui))
|
||||
gui.create()
|
||||
|
@ -69,9 +69,6 @@ class GUI:
|
||||
def create(self):
|
||||
main_gui._create(self)
|
||||
|
||||
def stop(self):
|
||||
self._destroyed = True
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
|
||||
|
@ -2,24 +2,19 @@ import logging
|
||||
import time
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
from tkinter import messagebox
|
||||
import typing
|
||||
from functools import partial
|
||||
import os
|
||||
|
||||
from fishy.gui import update_dialog
|
||||
from ttkthemes import ThemedTk
|
||||
|
||||
from fishy.helper import helper
|
||||
from fishy import helper
|
||||
from fishy.web import web
|
||||
|
||||
from ..constants import fishyqr
|
||||
from ..engine.common import screenshot
|
||||
from ..helper.config import config
|
||||
from .discord_login import discord_login
|
||||
from ..helper.hotkey.hotkey_process import hotkey
|
||||
from ..helper.hotkey.process import Key
|
||||
from ..osservices.os_services import os_services
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import GUI
|
||||
@ -51,7 +46,7 @@ def _create(gui: 'GUI'):
|
||||
gui.login.set(1 if login > 0 else 0)
|
||||
state = tk.DISABLED if login == -1 else tk.ACTIVE
|
||||
filemenu.add_checkbutton(label="Login", command=lambda: discord_login(gui), variable=gui.login, state=state)
|
||||
filemenu.add_command(label="Create Shortcut", command=lambda: os_services.create_shortcut(False))
|
||||
filemenu.add_command(label="Create Shortcut", command=lambda: helper.create_shortcut(False))
|
||||
# filemenu.add_command(label="Create Anti-Ghost Shortcut", command=lambda: helper.create_shortcut(True))
|
||||
|
||||
def _toggle_mode():
|
||||
@ -66,7 +61,6 @@ def _create(gui: 'GUI'):
|
||||
def update():
|
||||
config.delete("dont_ask_update")
|
||||
update_dialog.check_update(gui, True)
|
||||
|
||||
filemenu.add_command(label="Update", command=update)
|
||||
|
||||
def installer():
|
||||
@ -76,7 +70,6 @@ def _create(gui: 'GUI'):
|
||||
else:
|
||||
helper.install_required_addons(True)
|
||||
filemenu.entryconfigure(4, label="Remove FishyQR")
|
||||
|
||||
chaEntry = "Remove FishyQR" if helper.addon_exists(fishyqr[0]) else "Install FishyQR"
|
||||
filemenu.add_command(label=chaEntry, command=installer)
|
||||
menubar.add_cascade(label="Options", menu=filemenu)
|
||||
@ -85,47 +78,6 @@ def _create(gui: 'GUI'):
|
||||
debug_menu.add_command(label="Check QR Value",
|
||||
command=lambda: gui.engine.check_qr_val())
|
||||
|
||||
def toggle_show_grab():
|
||||
new_val = 1 - config.get("show_grab", 0)
|
||||
show_grab_var.set(new_val)
|
||||
config.set("show_grab", new_val)
|
||||
if new_val:
|
||||
logging.info(f"Screenshots taken by fishy will be saved in {helper.save_img_path()}")
|
||||
messagebox.showwarning("Warning", "Screenshots taken by Fishy will be saved in Documents.")
|
||||
logging.info(f"Screenshots taken by Fishy will be saved in {helper.save_img_path()}")
|
||||
else:
|
||||
delete_screenshots = messagebox.askyesno("Confirmation", "Do you want to delete the saved screenshots?")
|
||||
if delete_screenshots:
|
||||
# Delete the saved screenshots
|
||||
folder_path = helper.save_img_path()
|
||||
try:
|
||||
os.rmdir(folder_path) # Deletes the folder
|
||||
logging.info("Saved screenshots folder has been deleted.")
|
||||
except OSError as e:
|
||||
logging.error(f"Error occurred while deleting the folder: {e}")
|
||||
else:
|
||||
logging.info("Saved screenshots will be preserved.")
|
||||
|
||||
|
||||
show_grab_var = tk.IntVar()
|
||||
show_grab_var.set(config.get("show_grab", 0))
|
||||
debug_menu.add_checkbutton(label="Save Screenshots", variable=show_grab_var, command=lambda: toggle_show_grab(), onvalue=1)
|
||||
if config.get("show_grab", 0):
|
||||
logging.info(f"Save Screenshots is On, images will be saved in {helper.save_img_path()}")
|
||||
|
||||
|
||||
def select_sslib(selected_i):
|
||||
config.set("sslib", selected_i)
|
||||
sslib_var.set(selected_i)
|
||||
|
||||
sslib = tk.Menu(debug_menu, tearoff=False)
|
||||
sslib_var = tk.IntVar()
|
||||
sslib_var.set(config.get("sslib", 0))
|
||||
for i, lib in enumerate(screenshot.LIBS):
|
||||
sslib.add_checkbutton(label=lib.__name__, variable=sslib_var,
|
||||
command=partial(select_sslib, i), onvalue=i)
|
||||
debug_menu.add_cascade(label="Screenshot Lib", menu=sslib)
|
||||
|
||||
debug_var = tk.IntVar()
|
||||
debug_var.set(int(config.get('debug', False)))
|
||||
|
||||
@ -137,8 +89,7 @@ def _create(gui: 'GUI'):
|
||||
menubar.add_cascade(label="Debug", menu=debug_menu)
|
||||
|
||||
help_menu = tk.Menu(menubar, tearoff=0)
|
||||
help_menu.add_command(label="Need Help?",
|
||||
command=lambda: helper.open_web("https://github.com/fishyboteso/fishyboteso/wiki"))
|
||||
help_menu.add_command(label="Need Help?", command=lambda: helper.open_web("https://github.com/fishyboteso/fishyboteso/wiki"))
|
||||
help_menu.add_command(label="Donate", command=lambda: helper.open_web("https://paypal.me/AdamSaudagar"))
|
||||
menubar.add_cascade(label="Help", menu=help_menu)
|
||||
|
||||
@ -174,13 +125,9 @@ def _create(gui: 'GUI'):
|
||||
|
||||
_apply_theme(gui)
|
||||
gui._root.update()
|
||||
|
||||
gui._root.minsize(gui._root.winfo_width() + 10, gui._root.winfo_height() + 10)
|
||||
if config.get("win_loc") is not None:
|
||||
gui._root.geometry(config.get("win_loc").split(":")[-1])
|
||||
if config.get("win_loc").split(":")[0] == "zoomed":
|
||||
gui._root.update()
|
||||
gui._root.state("zoomed")
|
||||
gui._root.geometry(config.get("win_loc"))
|
||||
|
||||
hotkey.hook(Key.F9, gui.funcs.start_engine)
|
||||
|
||||
@ -190,13 +137,7 @@ def _create(gui: 'GUI'):
|
||||
if not tk.messagebox.askyesno(title="Quit?", message="Bot engine running. Quit Anyway?"):
|
||||
return
|
||||
|
||||
if gui._root.state() == "zoomed":
|
||||
# setting it to normal first is done to keep user-changed geometry values
|
||||
gui._root.state("normal")
|
||||
config.set("win_loc", "zoomed" + ":" + gui._root.geometry())
|
||||
else:
|
||||
config.set("win_loc", gui._root.state() + ":" + gui._root.geometry())
|
||||
|
||||
config.set("win_loc", gui._root.geometry())
|
||||
gui._destroyed = True
|
||||
|
||||
gui._root.protocol("WM_DELETE_WINDOW", set_destroy)
|
||||
|
@ -10,53 +10,54 @@ from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
|
||||
|
||||
class Splash:
|
||||
def __init__(self):
|
||||
self.q = Queue()
|
||||
self.process = Process(name=Splash.__name__, target=self.show, args=(config.get("win_loc"), self.q,))
|
||||
def show(win_loc, q):
|
||||
logging.debug("started splash process")
|
||||
dim = (300, 200)
|
||||
top = tk.Tk()
|
||||
|
||||
def finish(self):
|
||||
self.q.put("stop")
|
||||
top.overrideredirect(True)
|
||||
top.lift()
|
||||
top.attributes('-topmost', True)
|
||||
|
||||
def start(self):
|
||||
self.process.start()
|
||||
top.title("Loading...")
|
||||
top.resizable(False, False)
|
||||
top.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
def show(self, win_loc, q):
|
||||
logging.debug("started splash process")
|
||||
dim = (300, 200)
|
||||
top = tk.Tk()
|
||||
canvas = tk.Canvas(top, width=dim[0], height=dim[1], bg='white')
|
||||
canvas.pack()
|
||||
top.image = Image.open(helper.manifest_file('fishybot_logo.png')).resize(dim)
|
||||
top.image = ImageTk.PhotoImage(top.image)
|
||||
canvas.create_image(0, 0, anchor=tk.NW, image=top.image)
|
||||
|
||||
top.overrideredirect(True)
|
||||
top.lift()
|
||||
top.attributes('-topmost', True)
|
||||
# Position splash at the center of the main window
|
||||
|
||||
top.title("Loading...")
|
||||
top.resizable(False, False)
|
||||
top.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
default_loc = (str(top.winfo_reqwidth()) + "+" + str(top.winfo_reqheight()) + "+" + "0" + "0")
|
||||
loc = (win_loc or default_loc).split("+")[1:]
|
||||
top.geometry("{}x{}+{}+{}".format(dim[0], dim[1], int(loc[0]) + int(dim[0] / 2), int(loc[1]) + int(dim[1] / 2)))
|
||||
|
||||
canvas = tk.Canvas(top, width=dim[0], height=dim[1], bg='white')
|
||||
canvas.pack()
|
||||
top.image = Image.open(helper.manifest_file('fishybot_logo.png')).resize(dim)
|
||||
top.image = ImageTk.PhotoImage(top.image)
|
||||
canvas.create_image(0, 0, anchor=tk.NW, image=top.image)
|
||||
def waiting():
|
||||
q.get()
|
||||
time.sleep(0.2)
|
||||
running[0] = False
|
||||
Thread(target=waiting).start()
|
||||
|
||||
# Position splash at the center of the main window
|
||||
running = [True]
|
||||
while running[0]:
|
||||
top.update()
|
||||
time.sleep(0.1)
|
||||
|
||||
default_loc = (str(top.winfo_reqwidth()) + "+" + str(top.winfo_reqheight()) + "+" + "0" + "0")
|
||||
loc = (win_loc or default_loc).split(":")[-1].split("+")[1:]
|
||||
top.geometry("{}x{}+{}+{}".format(dim[0], dim[1], int(loc[0]) + int(dim[0] / 2), int(loc[1]) + int(dim[1] / 2)))
|
||||
top.destroy()
|
||||
logging.debug("ended splash process")
|
||||
|
||||
def waiting():
|
||||
q.get()
|
||||
time.sleep(0.2)
|
||||
running[0] = False
|
||||
|
||||
Thread(target=waiting).start()
|
||||
def create_finish(q):
|
||||
def finish():
|
||||
q.put("stop")
|
||||
|
||||
running = [True]
|
||||
while running[0]:
|
||||
top.update()
|
||||
time.sleep(0.1)
|
||||
return finish
|
||||
|
||||
top.destroy()
|
||||
logging.debug("ended splash process")
|
||||
|
||||
def start():
|
||||
q = Queue()
|
||||
Process(target=show, args=(config.get("win_loc"), q,)).start()
|
||||
return create_finish(q)
|
||||
|
@ -40,7 +40,7 @@ def _run_terms_window():
|
||||
root.image = ImageTk.PhotoImage(root.image)
|
||||
canvas.create_image(0, 0, anchor=tk.NW, image=root.image)
|
||||
|
||||
check_value = tk.IntVar()
|
||||
check_value = tk.IntVar(0)
|
||||
|
||||
g1 = ttk.Frame(f)
|
||||
ttk.Checkbutton(g1, command=disable_enable_button, variable=check_value).pack(side=tk.LEFT)
|
||||
|
@ -18,6 +18,7 @@ def _show(gui, currentversion, newversion, returns):
|
||||
|
||||
top = PopUp(helper.empty_function, gui._root)
|
||||
top.title("A wild fishy update appeared!")
|
||||
top.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
dialogLabel = tk.Label(top, text="There is a new fishy update available (" +
|
||||
currentversion + "->" + newversion + "). Do you want to update now?")
|
||||
@ -37,7 +38,6 @@ def _show(gui, currentversion, newversion, returns):
|
||||
width=buttonWidth, compound="c")
|
||||
dialogBtnYes.grid(row=2, column=1, padx=5, pady=5)
|
||||
dialogBtnYes.focus_set()
|
||||
dialogBtnYes.update()
|
||||
|
||||
top.protocol('WM_DELETE_WINDOW', _clickNo)
|
||||
top.start()
|
||||
|
@ -1,5 +1,5 @@
|
||||
from .config import Config
|
||||
from .helper import (addon_exists,
|
||||
from .helper import (addon_exists, create_shortcut, create_shortcut_first,
|
||||
get_addonversion, get_savedvarsdir,
|
||||
install_addon, install_thread_excepthook, manifest_file,
|
||||
not_implemented, open_web, playsound_multiple,
|
||||
|
@ -14,12 +14,12 @@ class active:
|
||||
return
|
||||
|
||||
active._scheduler = EventScheduler()
|
||||
active._scheduler.start()
|
||||
logging.debug("active scheduler initialized")
|
||||
|
||||
@staticmethod
|
||||
def start():
|
||||
web.ping()
|
||||
active._scheduler.start()
|
||||
active._scheduler.enter_recurring(60, 1, web.ping)
|
||||
logging.debug("active scheduler started")
|
||||
|
||||
|
@ -6,8 +6,14 @@ import logging
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
from os import execl
|
||||
from fishy.web import web
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
def _hr_version(v):
|
||||
return '.'.join([str(x) for x in v])
|
||||
|
||||
|
||||
def _normalize_version(v):
|
||||
@ -32,16 +38,46 @@ def _normalize_version(v):
|
||||
return rv
|
||||
|
||||
|
||||
def _get_highest_version(_index, _pkg):
|
||||
"""
|
||||
Crawls web for latest version name then returns latest version
|
||||
:param _index: website to check
|
||||
:param _pkg: package name
|
||||
:return: latest version normalized
|
||||
"""
|
||||
url = "{}/{}/".format(_index, _pkg)
|
||||
html = urllib.request.urlopen(url)
|
||||
if html.getcode() != 200:
|
||||
raise Exception # not found
|
||||
soup = BeautifulSoup(html.read(), "html.parser")
|
||||
_versions = []
|
||||
for link in soup.find_all('a'):
|
||||
text = link.get_text()
|
||||
try:
|
||||
version = re.search(_pkg + r'-(.*)\.tar\.gz', text).group(1)
|
||||
_versions.append(_normalize_version(version))
|
||||
except AttributeError:
|
||||
pass
|
||||
if len(_versions) == 0:
|
||||
raise Exception # no version
|
||||
return max(_versions)
|
||||
|
||||
|
||||
def _get_current_version():
|
||||
"""
|
||||
Gets the current version of the package installed
|
||||
:return: version normalized
|
||||
"""
|
||||
import fishy
|
||||
return fishy.__version__
|
||||
return _normalize_version(fishy.__version__)
|
||||
|
||||
|
||||
index = "https://pypi.python.org/simple"
|
||||
pkg = "fishy"
|
||||
|
||||
|
||||
def versions():
|
||||
return _get_current_version(), web.get_highest_version()
|
||||
return _hr_version(_get_current_version()), _hr_version(_get_highest_version(index, pkg))
|
||||
|
||||
|
||||
def upgrade_avail():
|
||||
@ -49,18 +85,14 @@ def upgrade_avail():
|
||||
Checks if update is available
|
||||
:return: boolean
|
||||
"""
|
||||
|
||||
highest_version_normalized = _normalize_version(web.get_highest_version())
|
||||
current_version_normalized = _normalize_version(_get_current_version())
|
||||
|
||||
return current_version_normalized < highest_version_normalized
|
||||
return _get_current_version() < _get_highest_version(index, pkg)
|
||||
|
||||
|
||||
def update_now(version):
|
||||
"""
|
||||
calling this function updates fishy,
|
||||
should be the last thing to be executed as this function will restart fishy
|
||||
the flaw is handed by `EngineEventHandler.update_flag` which is the last thing to be stopped
|
||||
public function,
|
||||
compares current version with the latest version (from web),
|
||||
if current version is older, then it updates and restarts the script
|
||||
"""
|
||||
logging.info(f"Updating to v{version}, Please Wait...")
|
||||
subprocess.call(["python", '-m', 'pip', 'install', '--upgrade', 'fishy', '--user'])
|
||||
|
@ -7,28 +7,18 @@ import logging
|
||||
import os
|
||||
# path to save the configuration file
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
|
||||
from event_scheduler import EventScheduler
|
||||
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
def filename():
|
||||
|
||||
if "--test-server" in sys.argv:
|
||||
name = "fishy_config_test.json"
|
||||
else:
|
||||
name = "fishy_config.json"
|
||||
|
||||
from fishy.helper.helper import get_documents
|
||||
name = "fishy_config.json"
|
||||
_filename = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"], "Documents", name)
|
||||
|
||||
if os.path.exists(_filename):
|
||||
return _filename
|
||||
|
||||
# fallback for OneDrive documents
|
||||
return os.path.join(os_services.get_documents_path(), name)
|
||||
return os.path.join(get_documents(), name)
|
||||
|
||||
|
||||
temp_file = os.path.join(os.environ["TEMP"], "fishy_config.BAK")
|
||||
|
@ -1,18 +0,0 @@
|
||||
"""
|
||||
no imports from fishy itself here, or anything which depends on fishy
|
||||
"""
|
||||
def singleton_proxy(instance_name):
|
||||
def decorator(root_cls):
|
||||
if not hasattr(root_cls, instance_name):
|
||||
raise AttributeError(f"{instance_name} not found in {root_cls}")
|
||||
|
||||
class SingletonProxy(type):
|
||||
def __getattr__(cls, name):
|
||||
return getattr(getattr(cls, instance_name), name)
|
||||
|
||||
class NewClass(root_cls, metaclass=SingletonProxy):
|
||||
...
|
||||
|
||||
return NewClass
|
||||
|
||||
return decorator
|
@ -7,21 +7,22 @@ import threading
|
||||
import time
|
||||
import traceback
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from hashlib import md5
|
||||
from io import BytesIO
|
||||
from threading import Thread
|
||||
from uuid import uuid1
|
||||
from zipfile import ZipFile
|
||||
|
||||
import cv2
|
||||
import requests
|
||||
import winshell
|
||||
from playsound import playsound
|
||||
from win32com.client import Dispatch
|
||||
from win32comext.shell import shell, shellcon
|
||||
from win32gui import GetForegroundWindow, GetWindowText
|
||||
|
||||
import fishy
|
||||
from fishy.constants import libgps, lam2, fishyqr, fishyfsm, libmapping, libdl, libchatmsg
|
||||
from fishy.constants import libgps, lam2, fishyqr
|
||||
from fishy.helper.config import config
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
def playsound_multiple(path, count=2):
|
||||
@ -109,14 +110,55 @@ def manifest_file(rel_path):
|
||||
return os.path.join(os.path.dirname(fishy.__file__), rel_path)
|
||||
|
||||
|
||||
def create_shortcut_first():
|
||||
from .config import config
|
||||
|
||||
if not config.get("shortcut_created", False):
|
||||
create_shortcut(False)
|
||||
config.set("shortcut_created", True)
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def create_shortcut(anti_ghosting: bool):
|
||||
"""
|
||||
creates a new shortcut on desktop
|
||||
"""
|
||||
try:
|
||||
desktop = winshell.desktop()
|
||||
path = os.path.join(desktop, "Fishybot ESO.lnk")
|
||||
|
||||
_shell = Dispatch('WScript.Shell')
|
||||
shortcut = _shell.CreateShortCut(path)
|
||||
|
||||
if anti_ghosting:
|
||||
shortcut.TargetPath = r"C:\Windows\System32\cmd.exe"
|
||||
python_dir = os.path.join(os.path.dirname(sys.executable), "pythonw.exe")
|
||||
shortcut.Arguments = f"/C start /affinity 1 /low {python_dir} -m fishy"
|
||||
else:
|
||||
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 Exception:
|
||||
print_exc()
|
||||
logging.error("Couldn't create shortcut")
|
||||
|
||||
|
||||
def get_savedvarsdir():
|
||||
eso_path = os_services.get_eso_config_path()
|
||||
return os.path.join(eso_path, "live", "SavedVariables")
|
||||
# noinspection PyUnresolvedReferences
|
||||
from win32com.shell import shell, shellcon
|
||||
documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
return os.path.join(documents, "Elder Scrolls Online", "live", "SavedVariables")
|
||||
|
||||
|
||||
def get_addondir():
|
||||
eso_path = os_services.get_eso_config_path()
|
||||
return os.path.join(eso_path, "live", "Addons")
|
||||
# noinspection PyUnresolvedReferences
|
||||
from win32com.shell import shell, shellcon
|
||||
documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
return os.path.join(documents, "Elder Scrolls Online", "live", "Addons")
|
||||
|
||||
|
||||
def addon_exists(name, url=None, v=None):
|
||||
@ -138,7 +180,7 @@ def get_addonversion(name, url=None, v=None):
|
||||
|
||||
|
||||
def install_required_addons(force=False):
|
||||
addons_req = [libgps, lam2, fishyqr, fishyfsm, libmapping, libdl, libchatmsg]
|
||||
addons_req = [libgps, lam2, fishyqr]
|
||||
addon_version = config.get("addon_version", {})
|
||||
installed = False
|
||||
for addon in addons_req:
|
||||
@ -180,11 +222,19 @@ def remove_addon(name, url=None, v=None):
|
||||
return 0
|
||||
|
||||
|
||||
def get_documents():
|
||||
return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
|
||||
|
||||
def log_raise(msg):
|
||||
logging.error(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
|
||||
def is_eso_active():
|
||||
return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online"
|
||||
|
||||
|
||||
# noinspection PyProtectedMember,PyUnresolvedReferences
|
||||
def _get_id(thread):
|
||||
# returns id of the respective thread
|
||||
@ -202,26 +252,8 @@ def kill_thread(thread):
|
||||
if res > 1:
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
|
||||
print('Exception raise failure')
|
||||
|
||||
|
||||
|
||||
|
||||
def print_exc():
|
||||
logging.error(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def save_img_path():
|
||||
return os.path.join(os_services.get_documents_path(), "fishy_debug", "imgs")
|
||||
|
||||
|
||||
def save_img(show_name, img, half=False):
|
||||
img_path = os.path.join(save_img_path(), show_name)
|
||||
if not os.path.exists(img_path):
|
||||
os.makedirs(img_path)
|
||||
|
||||
if half:
|
||||
img = cv2.resize(img, (0, 0), fx=0.5, fy=0.5)
|
||||
|
||||
t = time.strftime("%Y.%m.%d.%H.%M.%S")
|
||||
cv2.imwrite(
|
||||
os.path.join(img_path, f"{t}.jpg"),
|
||||
img)
|
||||
|
@ -7,7 +7,6 @@ from typing import Dict, Optional, Callable
|
||||
from playsound import playsound
|
||||
|
||||
from fishy import helper
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.hotkey import process
|
||||
from fishy.helper.hotkey.process import Key
|
||||
|
||||
@ -58,16 +57,13 @@ class HotKey:
|
||||
while True:
|
||||
key = self.outq.get()
|
||||
|
||||
if key == "stop":
|
||||
break
|
||||
|
||||
if key in Key:
|
||||
callback = self._hotkeys[key]
|
||||
if callback:
|
||||
if config.get("sound_notification", False):
|
||||
playsound(helper.manifest_file("beep.wav"), False)
|
||||
|
||||
playsound(helper.manifest_file("beep.wav"), False)
|
||||
callback()
|
||||
elif key == "stop":
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import logging
|
||||
|
||||
import fishy
|
||||
from fishy.helper.auto_update import _normalize_version
|
||||
|
||||
from fishy.constants import version
|
||||
from .config import config
|
||||
|
||||
|
||||
@ -14,14 +14,14 @@ class Migration:
|
||||
@staticmethod
|
||||
def migrate():
|
||||
prev_version = _normalize_version(config.get("prev_version", "0.0.0"))
|
||||
current_version = _normalize_version(fishy.__version__)
|
||||
current_version = _normalize_version(version)
|
||||
|
||||
if current_version > prev_version:
|
||||
for v, f in migration_code:
|
||||
if prev_version < _normalize_version(v) <= current_version:
|
||||
logging.info(f"running migration for {v}")
|
||||
f()
|
||||
config.set("prev_version", fishy.__version__)
|
||||
config.set("prev_version", version)
|
||||
|
||||
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
import time
|
||||
from tkinter import Toplevel
|
||||
from fishy import helper
|
||||
|
||||
|
||||
def center(win):
|
||||
@ -21,7 +20,6 @@ class PopUp(Toplevel):
|
||||
self.running = True
|
||||
self.quit_callback = quit_callback
|
||||
self.protocol("WM_DELETE_WINDOW", self.quit_top)
|
||||
self.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
def quit_top(self):
|
||||
self.quit_callback()
|
||||
@ -29,7 +27,6 @@ class PopUp(Toplevel):
|
||||
self.running = False
|
||||
|
||||
def start(self):
|
||||
self.minsize(self.winfo_width(), self.winfo_height())
|
||||
self.grab_set()
|
||||
center(self)
|
||||
while self.running:
|
||||
|
@ -1,29 +0,0 @@
|
||||
from typing import Tuple, Optional
|
||||
|
||||
from fishy.osservices.os_services import IOSServices
|
||||
|
||||
|
||||
class Linux(IOSServices):
|
||||
def hide_terminal(self):
|
||||
pass
|
||||
|
||||
def create_shortcut(self):
|
||||
pass
|
||||
|
||||
def get_documents_path(self) -> str:
|
||||
pass
|
||||
|
||||
def is_admin(self) -> bool:
|
||||
pass
|
||||
|
||||
def get_eso_config_path(self) -> str:
|
||||
pass
|
||||
|
||||
def is_eso_active(self) -> bool:
|
||||
pass
|
||||
|
||||
def get_monitor_rect(self):
|
||||
pass
|
||||
|
||||
def get_game_window_rect(self) -> Optional[Tuple[int, int, int, int]]:
|
||||
pass
|
@ -1,81 +0,0 @@
|
||||
import inspect
|
||||
import logging
|
||||
import re
|
||||
import sys
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Tuple, Optional
|
||||
import platform
|
||||
|
||||
from fishy.helper.depless import singleton_proxy
|
||||
|
||||
|
||||
class IOSServices(ABC):
|
||||
|
||||
@abstractmethod
|
||||
def hide_terminal(self):
|
||||
"""
|
||||
:return: hides the terminal used to launch fishy
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create_shortcut(self):
|
||||
"""
|
||||
creates a new shortcut on desktop
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_documents_path(self) -> str:
|
||||
"""
|
||||
:return: documents path to save config file
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def is_admin(self) -> bool:
|
||||
"""
|
||||
:return: true if has elevated rights
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_eso_config_path(self) -> str:
|
||||
"""
|
||||
:return: path location of the ElderScrollsOnline Folder (in documents) which has "live" folder in it
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def is_eso_active(self) -> bool:
|
||||
"""
|
||||
:return: true if eso is the active window
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_monitor_rect(self) -> Optional[Tuple[int, int]]:
|
||||
"""
|
||||
:return: [top, left, height, width] of monitor which has game running in it
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_game_window_rect(self) -> Optional[Tuple[int, int, int, int]]:
|
||||
"""
|
||||
:return: location of the game window without any frame
|
||||
"""
|
||||
|
||||
|
||||
@singleton_proxy("_instance")
|
||||
class os_services:
|
||||
_instance: Optional[IOSServices] = None
|
||||
|
||||
@staticmethod
|
||||
def init() -> bool:
|
||||
os_name = platform.system()
|
||||
if os_name == "Windows":
|
||||
from fishy.osservices.windows import Windows
|
||||
os_services._instance = Windows()
|
||||
return True
|
||||
|
||||
# todo uncomment after linux.py is implemented
|
||||
# if os_name == "Linux":
|
||||
# from fishy.osservices.linux import Linux
|
||||
# os_services._instance = Linux()
|
||||
# return True
|
||||
|
||||
return False
|
@ -1,112 +0,0 @@
|
||||
import ctypes
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import sys
|
||||
from typing import Tuple, Optional
|
||||
|
||||
import pywintypes
|
||||
import win32api
|
||||
import win32con
|
||||
import win32gui
|
||||
import winshell
|
||||
from win32com.client import Dispatch
|
||||
from win32comext.shell import shell, shellcon
|
||||
from win32gui import GetForegroundWindow, GetWindowText
|
||||
|
||||
|
||||
from ctypes import windll
|
||||
|
||||
from fishy.helper import manifest_file
|
||||
from fishy.osservices.os_services import IOSServices
|
||||
|
||||
|
||||
def _check_window_name(title):
|
||||
titles = ["Command Prompt", "PowerShell", "Fishy"]
|
||||
for t in titles:
|
||||
if t in title:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Windows(IOSServices):
|
||||
def is_admin(self) -> bool:
|
||||
try:
|
||||
is_admin = os.getuid() == 0
|
||||
except AttributeError:
|
||||
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
return is_admin
|
||||
|
||||
def is_eso_active(self) -> bool:
|
||||
return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online"
|
||||
|
||||
# noinspection PyBroadException
|
||||
def create_shortcut(self, anti_ghosting=False):
|
||||
try:
|
||||
desktop = winshell.desktop()
|
||||
path = os.path.join(desktop, "Fishybot ESO.lnk")
|
||||
_shell = Dispatch('WScript.Shell')
|
||||
shortcut = _shell.CreateShortCut(path)
|
||||
|
||||
if anti_ghosting:
|
||||
shortcut.TargetPath = r"C:\Windows\System32\cmd.exe"
|
||||
python_dir = os.path.join(os.path.dirname(sys.executable), "pythonw.exe")
|
||||
shortcut.Arguments = f"/C start /affinity 1 /low {python_dir} -m fishy"
|
||||
else:
|
||||
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 Exception:
|
||||
logging.error("Couldn't create shortcut")
|
||||
|
||||
def __init__(self):
|
||||
self.to_hide = win32gui.GetForegroundWindow()
|
||||
|
||||
def hide_terminal(self):
|
||||
if _check_window_name(win32gui.GetWindowText(self.to_hide)):
|
||||
win32gui.ShowWindow(self.to_hide, win32con.SW_HIDE)
|
||||
|
||||
def get_documents_path(self) -> str:
|
||||
return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
|
||||
def get_eso_config_path(self) -> str:
|
||||
# noinspection PyUnresolvedReferences
|
||||
from win32com.shell import shell, shellcon
|
||||
documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
return os.path.join(documents, "Elder Scrolls Online")
|
||||
|
||||
def get_monitor_rect(self):
|
||||
# noinspection PyUnresolvedReferences
|
||||
try:
|
||||
hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
|
||||
monitor = windll.user32.MonitorFromWindow(hwnd, 2)
|
||||
monitor_info = win32api.GetMonitorInfo(monitor)
|
||||
return monitor_info["Monitor"]
|
||||
except pywintypes.error:
|
||||
return None
|
||||
|
||||
def get_game_window_rect(self) -> Optional[Tuple[int, int, int, int]]:
|
||||
hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
|
||||
monitor_rect = self.get_monitor_rect()
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
try:
|
||||
rect = win32gui.GetWindowRect(hwnd)
|
||||
client_rect = win32gui.GetClientRect(hwnd)
|
||||
windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2)
|
||||
fullscreen = monitor_rect[3] == (rect[3] - rect[1])
|
||||
title_offset = ((rect[3] - rect[1]) - client_rect[3]) - windowOffset if not fullscreen else 0
|
||||
|
||||
game_rect = (
|
||||
rect[0] + windowOffset - monitor_rect[0],
|
||||
rect[1] + title_offset - monitor_rect[1],
|
||||
rect[2] - windowOffset - monitor_rect[0],
|
||||
rect[3] - windowOffset - monitor_rect[1]
|
||||
)
|
||||
return game_rect
|
||||
except pywintypes.error:
|
||||
return None
|
@ -1 +0,0 @@
|
||||
0.5.26
|
@ -1,3 +1,3 @@
|
||||
from .urls import get_notification_page, get_terms_page
|
||||
from .web import (get_session, is_subbed, _register_user, send_fish_caught,
|
||||
from .web import (get_session, is_subbed, register_user, send_fish_caught,
|
||||
send_notification, sub, unsub)
|
||||
|
@ -2,11 +2,8 @@ import logging
|
||||
import traceback
|
||||
from functools import wraps
|
||||
|
||||
from fishy.web import web
|
||||
|
||||
|
||||
def uses_session(f):
|
||||
"""directly returns none if it couldn't get session, instead of running the function"""
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
from .web import get_session
|
||||
@ -24,9 +21,6 @@ def fallback(default):
|
||||
# noinspection PyBroadException
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not web.is_online():
|
||||
return default
|
||||
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except Exception:
|
||||
|
@ -3,9 +3,9 @@ import sys
|
||||
if "--local-server" in sys.argv:
|
||||
domain = "http://127.0.0.1:5000"
|
||||
elif "--test-server" in sys.argv:
|
||||
domain = "https://fishyeso-test.definex.in"
|
||||
domain = "https://fishyeso-test.herokuapp.com"
|
||||
else:
|
||||
domain = "https://fishyeso.definex.in"
|
||||
domain = "https://fishyeso.herokuapp.com"
|
||||
|
||||
user = domain + "/api/user"
|
||||
notify = domain + "/api/notify"
|
||||
|
@ -1,7 +1,6 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from fishy import constants
|
||||
from whatsmyip.ip import get_ip
|
||||
from whatsmyip.providers import GoogleDnsProvider
|
||||
|
||||
@ -11,11 +10,6 @@ from . import urls
|
||||
from .decorators import fallback, uses_session
|
||||
|
||||
_session_id = None
|
||||
_online = True
|
||||
|
||||
|
||||
def is_online():
|
||||
return _online
|
||||
|
||||
|
||||
@fallback(-1)
|
||||
@ -24,7 +18,7 @@ def is_logged_in():
|
||||
return -1
|
||||
|
||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||
response = requests.get(urls.discord, json=body)
|
||||
response = requests.get(urls.discord, params=body)
|
||||
logged_in = response.json()["discord_login"]
|
||||
return 1 if logged_in else 0
|
||||
|
||||
@ -50,10 +44,10 @@ def logout():
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def _register_user():
|
||||
def register_user():
|
||||
ip = get_ip(GoogleDnsProvider)
|
||||
body = {"ip": ip, "apiversion": apiversion}
|
||||
response = requests.post(urls.user, json=body, timeout=10)
|
||||
response = requests.post(urls.user, json=body)
|
||||
result = response.json()
|
||||
return result["uid"]
|
||||
|
||||
@ -99,7 +93,7 @@ def is_subbed():
|
||||
return False, False
|
||||
|
||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||
response = requests.get(urls.subscription, json=body)
|
||||
response = requests.get(urls.subscription, params=body)
|
||||
|
||||
if response.status_code != 200:
|
||||
return False, False
|
||||
@ -117,12 +111,7 @@ def unsub():
|
||||
|
||||
|
||||
def get_session(lazy=True):
|
||||
"""
|
||||
this doesn't have @fallback as this doesn't actually make any web calls directly
|
||||
this web call needs to be the first thing to be called, as it sets the online status
|
||||
todo maybe shift this to web.init() or something to signify that
|
||||
"""
|
||||
global _session_id, _online
|
||||
global _session_id
|
||||
|
||||
# lazy loading logic
|
||||
if lazy and _session_id is not None:
|
||||
@ -133,22 +122,22 @@ def get_session(lazy=True):
|
||||
|
||||
# then create session
|
||||
if uid:
|
||||
_session_id, _online = _create_new_session(uid)
|
||||
_session_id, online = _create_new_session(uid)
|
||||
# if not, create new id then try creating session again
|
||||
else:
|
||||
uid = _register_user()
|
||||
uid = register_user()
|
||||
config.set("uid", uid, True)
|
||||
logging.debug(f"New User, generated new uid: {uid}")
|
||||
if uid:
|
||||
_session_id, _online = _create_new_session(uid)
|
||||
_session_id, online = _create_new_session(uid)
|
||||
else:
|
||||
_online = False
|
||||
online = False
|
||||
|
||||
# when the user is already registered but session is not created as uid is not found by the server
|
||||
if _online and not _session_id:
|
||||
# when the user is already registered but session is not created as uid is not found
|
||||
if online and not _session_id:
|
||||
logging.error("user not found, generating new uid.. contact dev if you don't want to loose data")
|
||||
new_uid = _register_user()
|
||||
_session_id, _online = _create_new_session(new_uid)
|
||||
new_uid = register_user()
|
||||
_session_id, online = _create_new_session(new_uid)
|
||||
config.set("uid", new_uid, True)
|
||||
config.set("old_uid", uid, True)
|
||||
|
||||
@ -158,7 +147,7 @@ def get_session(lazy=True):
|
||||
@fallback((None, False))
|
||||
def _create_new_session(uid):
|
||||
body = {"uid": uid, "apiversion": apiversion}
|
||||
response = requests.post(urls.session, json=body, timeout=10)
|
||||
response = requests.post(urls.session, params=body)
|
||||
|
||||
if response.status_code == 405:
|
||||
return None, True
|
||||
@ -169,7 +158,7 @@ def _create_new_session(uid):
|
||||
@fallback(False)
|
||||
def has_beta():
|
||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||
response = requests.get(urls.beta, json=body)
|
||||
response = requests.get(urls.beta, params=body)
|
||||
result = response.json()
|
||||
|
||||
if not result["success"]:
|
||||
@ -181,11 +170,5 @@ def has_beta():
|
||||
@fallback(None)
|
||||
def ping():
|
||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||
response = requests.post(urls.ping, json=body)
|
||||
response = requests.post(urls.ping, params=body)
|
||||
logging.debug(f"ping response: {response.json()}")
|
||||
|
||||
|
||||
@fallback("0.5.21")
|
||||
def get_highest_version():
|
||||
response = requests.get(constants.current_version_url)
|
||||
return response.content.decode()
|
@ -1,17 +1,17 @@
|
||||
urllib3
|
||||
winshell
|
||||
imutils
|
||||
numpy
|
||||
opencv_python
|
||||
numpy!=1.19.4
|
||||
opencv_python==4.5.2.54
|
||||
Pillow
|
||||
pypiwin32 ; platform_system=="Windows"
|
||||
winshell ; platform_system=="Windows"
|
||||
pypiwin32
|
||||
ttkthemes
|
||||
requests
|
||||
beautifulsoup4
|
||||
whatsmyip
|
||||
pynput
|
||||
keyboard
|
||||
playsound==1.2.2
|
||||
event-scheduler
|
||||
mouse
|
||||
pyautogui
|
||||
mss
|
||||
d3dshot
|
Loading…
x
Reference in New Issue
Block a user