mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
Compare commits
90 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
aedd048a34 | ||
|
d262885afa | ||
|
773f05ebae | ||
|
0efa8138da | ||
|
106eca4980 | ||
|
8a9d621086 | ||
|
a16474f613 | ||
|
270abc5167 | ||
|
f6f6bfad70 | ||
|
820bdfdd06 | ||
|
cc36abc605 | ||
|
0fdb285aea | ||
|
04e30c2f1c | ||
|
afb82d0562 | ||
|
e80f0fabdc | ||
|
a59909de9d | ||
|
d29b801657 | ||
|
43651b81fd | ||
|
c67a40a7d6 | ||
|
91a97af5d9 | ||
![]() |
0b17e0dd88 | ||
|
8b21b722f2 | ||
|
cbb37e8f0b | ||
|
dd404741fe | ||
|
28b7cfeb8c | ||
|
cab56da6e4 | ||
|
ad77ac85b9 | ||
![]() |
5fe4c235ac | ||
|
ecf5b3524c | ||
|
cc61caf12d | ||
|
0c3b5da26b | ||
|
3354de4772 | ||
|
9942d0533f | ||
|
4add028ff6 | ||
|
70af635025 | ||
|
19a5fe9b7f | ||
|
5141ae9c2d | ||
|
df4deda1f2 | ||
|
2ca4107595 | ||
|
7bf4567395 | ||
|
e1257aeda0 | ||
|
f520004c11 | ||
|
8634bac19c | ||
|
199fed6682 | ||
|
5dc7c09cb7 | ||
|
788e78b9bb | ||
|
5fb58d9998 | ||
|
bc491c8cb0 | ||
|
063c1e5481 | ||
|
2118e10d5d | ||
|
4dec07d27e | ||
|
47c0ce7413 | ||
|
901ce6c346 | ||
|
a5499475f6 | ||
|
4f90df9079 | ||
|
c5d1cb67cf | ||
|
0de6b54777 | ||
|
6000e9022e | ||
|
455976a018 | ||
|
4e55c88629 | ||
|
b6e543a9e3 | ||
|
3567f062b0 | ||
|
fb54ca4826 | ||
|
ebbce458cf | ||
|
cd32b8926d | ||
|
0827bd9f3b | ||
|
339abba4c4 | ||
|
ca06141386 | ||
|
0fef4aa22c | ||
|
cc926bf5fd | ||
|
f93ea04d42 | ||
|
5acc7863bc | ||
|
7331bc7824 | ||
|
fe6cd012f5 | ||
|
20c920adc9 | ||
![]() |
e0204ad205 | ||
![]() |
74e96e4439 | ||
![]() |
f925997731 | ||
![]() |
70e4fad167 | ||
|
9e4c17f035 | ||
|
dfc3c14c2c | ||
|
131d6bbf3c | ||
|
ca771811b7 | ||
|
016e378fdd | ||
|
62a531c381 | ||
|
17a03c7142 | ||
|
a5af567e14 | ||
|
621d8d3549 | ||
|
a8ff0c5bc8 | ||
|
f1f565c628 |
46
.github/workflows/python-publish.yml
vendored
Normal file
46
.github/workflows/python-publish.yml
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
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,6 +1,7 @@
|
|||||||
include LICENSE
|
include LICENSE
|
||||||
include README.md
|
include README.md
|
||||||
include requirements.txt
|
include requirements.txt
|
||||||
|
include fishy/version.txt
|
||||||
include fishy/icon.ico
|
include fishy/icon.ico
|
||||||
include fishy/fishybot_logo.png
|
include fishy/fishybot_logo.png
|
||||||
include fishy/sound.mp3
|
include fishy/sound.mp3
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Fishybot ESO 🎣
|
# 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 😉
|
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 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.**
|
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.**
|
||||||
|
|
||||||
- Check out the [Showcase Video](https://www.youtube.com/watch?v=THQ66lG4ImU).
|
- Check out the [Showcase Video](https://www.youtube.com/watch?v=THQ66lG4ImU).
|
||||||
- [How to Install ?](https://github.com/fishyboteso/fishyboteso/wiki/Installation)
|
- [How to Install ?](https://github.com/fishyboteso/fishyboteso/wiki/Installation)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from fishy import constants
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
# this prevents importing from package while setup
|
# this prevents importing from package while setup
|
||||||
@ -7,4 +8,4 @@ def main():
|
|||||||
actual_main()
|
actual_main()
|
||||||
|
|
||||||
|
|
||||||
__version__ = constants.version
|
__version__ = (Path(os.path.dirname(__file__)) / "version.txt").read_text()
|
||||||
|
@ -1,36 +1,27 @@
|
|||||||
import ctypes
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import win32con
|
|
||||||
import win32gui
|
|
||||||
|
|
||||||
import fishy
|
import fishy
|
||||||
from fishy.gui import GUI, splash, update_dialog, check_eula
|
from fishy.gui import GUI, update_dialog, check_eula
|
||||||
from fishy import helper, web
|
from fishy import helper, web
|
||||||
from fishy.engine.common.event_handler import EngineEventHandler
|
from fishy.engine.common.event_handler import EngineEventHandler
|
||||||
from fishy.gui.log_config import GuiLogger
|
from fishy.gui.log_config import GuiLogger
|
||||||
|
from fishy.gui.splash import Splash
|
||||||
from fishy.helper import hotkey
|
from fishy.helper import hotkey
|
||||||
from fishy.helper.active_poll import active
|
from fishy.helper.active_poll import active
|
||||||
from fishy.helper.config import config
|
from fishy.helper.config import config
|
||||||
from fishy.helper.hotkey.hotkey_process import hotkey
|
from fishy.helper.hotkey.hotkey_process import hotkey
|
||||||
from fishy.helper.migration import Migration
|
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
|
# noinspection PyBroadException
|
||||||
def initialize(window_to_hide):
|
def initialize():
|
||||||
Migration.migrate()
|
Migration.migrate()
|
||||||
|
|
||||||
helper.create_shortcut_first()
|
if not config.get("shortcut_created", False):
|
||||||
|
os_services.create_shortcut(False)
|
||||||
|
config.set("shortcut_created", True)
|
||||||
|
|
||||||
new_session = web.get_session()
|
new_session = web.get_session()
|
||||||
|
|
||||||
@ -38,58 +29,66 @@ def initialize(window_to_hide):
|
|||||||
logging.error("Couldn't create a session, some features might not work")
|
logging.error("Couldn't create a session, some features might not work")
|
||||||
logging.debug(f"created session {new_session}")
|
logging.debug(f"created session {new_session}")
|
||||||
|
|
||||||
try:
|
if os_services.is_admin():
|
||||||
is_admin = os.getuid() == 0
|
|
||||||
except AttributeError:
|
|
||||||
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
|
|
||||||
if is_admin:
|
|
||||||
logging.info("Running with admin privileges")
|
logging.info("Running with admin privileges")
|
||||||
|
|
||||||
if not config.get("debug", False) and check_window_name(win32gui.GetWindowText(window_to_hide)):
|
if not config.get("debug", False):
|
||||||
win32gui.ShowWindow(window_to_hide, win32con.SW_HIDE)
|
os_services.hide_terminal()
|
||||||
helper.install_thread_excepthook()
|
helper.install_thread_excepthook()
|
||||||
sys.excepthook = helper.unhandled_exception_logging
|
sys.excepthook = helper.unhandled_exception_logging
|
||||||
|
|
||||||
helper.install_required_addons()
|
helper.install_required_addons()
|
||||||
|
|
||||||
|
|
||||||
|
def on_gui_load(gui, splash, logger):
|
||||||
|
splash.finish()
|
||||||
|
update_dialog.check_update(gui)
|
||||||
|
logger.connect(gui)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
print("launching please wait...")
|
print("launching please wait...")
|
||||||
|
|
||||||
|
if not os_services.init():
|
||||||
|
print("platform not supported")
|
||||||
|
return
|
||||||
|
|
||||||
config.init()
|
config.init()
|
||||||
if not check_eula():
|
if not check_eula():
|
||||||
return
|
return
|
||||||
|
|
||||||
finish_splash = splash.start()
|
splash = Splash()
|
||||||
logger = GuiLogger()
|
|
||||||
config.start_backup_scheduler()
|
|
||||||
active.init()
|
|
||||||
hotkey.init()
|
|
||||||
|
|
||||||
def on_gui_load():
|
|
||||||
finish_splash()
|
|
||||||
update_dialog.check_update(gui)
|
|
||||||
logger.connect(gui)
|
|
||||||
|
|
||||||
window_to_hide = win32gui.GetForegroundWindow()
|
|
||||||
|
|
||||||
bot = EngineEventHandler(lambda: gui)
|
bot = EngineEventHandler(lambda: gui)
|
||||||
gui = GUI(lambda: bot, on_gui_load)
|
gui = GUI(lambda: bot, lambda: on_gui_load(gui, splash, logger))
|
||||||
|
logger = GuiLogger()
|
||||||
|
hotkey.init()
|
||||||
|
active.init()
|
||||||
|
|
||||||
hotkey.start()
|
try:
|
||||||
|
config.init()
|
||||||
|
if not check_eula():
|
||||||
|
return
|
||||||
|
|
||||||
logging.info(f"Fishybot v{fishy.__version__}")
|
logging.info(f"Fishybot v{fishy.__version__}")
|
||||||
initialize(window_to_hide)
|
|
||||||
|
|
||||||
gui.start()
|
splash.start()
|
||||||
active.start()
|
config.start_backup_scheduler()
|
||||||
|
|
||||||
bot.start_event_handler() # main thread loop
|
initialize()
|
||||||
|
|
||||||
hotkey.stop()
|
hotkey.start()
|
||||||
active.stop()
|
gui.start()
|
||||||
config.stop()
|
active.start()
|
||||||
bot.stop()
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
apiversion = 2
|
apiversion = 2
|
||||||
|
|
||||||
|
current_version_url = "https://raw.githubusercontent.com/fishyboteso/fishyboteso/main/fishy/version.txt"
|
||||||
|
|
||||||
# removed since 0.5.3
|
# removed since 0.5.3
|
||||||
chalutier = ("Chalutier", "https://www.esoui.com/downloads/dl2934/Chalutier_1.1.4.zip", 114)
|
chalutier = ("Chalutier", "https://cdn.esoui.com/downloads/file2934/Chalutier_1.3.zip", 130)
|
||||||
|
|
||||||
# addons used
|
# addons used
|
||||||
lam2 = ("LibAddonMenu-2.0", "https://www.esoui.com/downloads/dl7/LibAddonMenu-2.0r32.zip", 32)
|
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.2.0/FishyQR.zip", 120)
|
fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/releases/download/v1.8/FishyQR-1.8.zip", 180)
|
||||||
libgps = ("LibGPS", "https://cdn.esoui.com/downloads/file601/LibGPS_3_0_3.zip", 30)
|
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)
|
||||||
|
|
||||||
version = "0.5.12"
|
d3dshot_git = "git+https://github.com/fauskanger/D3DShot.git#egg=D3DShot"
|
||||||
|
@ -1 +1,2 @@
|
|||||||
from fishy.engine.semifisher.engine import SemiFisherEngine
|
from fishy.engine.semifisher.engine import SemiFisherEngine
|
||||||
|
from fishy.engine.fullautofisher.engine import FullAuto
|
@ -65,7 +65,7 @@ class IEngine:
|
|||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
def _crash_safe(self):
|
def _crash_safe(self):
|
||||||
logging.debug(f"starting {self.name} engine thread")
|
logging.debug(f"starting {self.name} engine thread")
|
||||||
self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name=f"{self.name} debug")
|
self.window = WindowClient()
|
||||||
self.gui.bot_started(True)
|
self.gui.bot_started(True)
|
||||||
try:
|
try:
|
||||||
self.run()
|
self.run()
|
||||||
|
@ -3,20 +3,37 @@ import re
|
|||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from fishy.engine.common.window import WindowClient
|
||||||
|
|
||||||
detector = cv2.QRCodeDetector()
|
detector = cv2.QRCodeDetector()
|
||||||
|
|
||||||
|
|
||||||
def image_pre_process(img):
|
# noinspection PyBroadException
|
||||||
scale_percent = 100 # percent of original size
|
def get_values(window: WindowClient):
|
||||||
width = int(img.shape[1] * scale_percent / 100)
|
values = None
|
||||||
height = int(img.shape[0] * scale_percent / 100)
|
for _ in range(6):
|
||||||
dim = (width, height)
|
img = window.processed_image()
|
||||||
img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
|
if img is None:
|
||||||
return img
|
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 get_qr_location(image):
|
def _get_qr_location(image):
|
||||||
"""
|
"""
|
||||||
code from https://stackoverflow.com/a/45770227/4512396
|
code from https://stackoverflow.com/a/45770227/4512396
|
||||||
"""
|
"""
|
||||||
@ -29,16 +46,15 @@ def get_qr_location(image):
|
|||||||
return [int(x) for x in [p[0][0], p[0][1], p[1][0], p[2][1]]]
|
return [int(x) for x in [p[0][0], p[0][1], p[1][0], p[2][1]]]
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyBroadException
|
def _get_values_from_image(img):
|
||||||
def get_values_from_image(img):
|
|
||||||
h, w = img.shape
|
h, w = img.shape
|
||||||
points = np.array([[(0, 0), (w, 0), (w, h), (0, h)]])
|
points = np.array([[(0, 0), (w, 0), (w, h), (0, h)]])
|
||||||
code = detector.decode(img, points)[0]
|
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
|
# 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:
|
if not code:
|
||||||
return None
|
return None
|
||||||
match = re.match(r'^(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+),(\d+)$', code)
|
match = re.match(r'^(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+),(\d+)$', code)
|
||||||
|
122
fishy/engine/common/screenshot.py
Normal file
122
fishy/engine/common/screenshot.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
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,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
import uuid
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
import cv2
|
import cv2
|
||||||
@ -7,26 +8,52 @@ import imutils
|
|||||||
from fishy.engine.common import window_server
|
from fishy.engine.common import window_server
|
||||||
from fishy.engine.common.window_server import Status, WindowServer
|
from fishy.engine.common.window_server import Status, WindowServer
|
||||||
from fishy.helper import helper
|
from fishy.helper import helper
|
||||||
|
from fishy.helper.config import config
|
||||||
|
|
||||||
|
|
||||||
class WindowClient:
|
class WindowClient:
|
||||||
clients: List['WindowClient'] = []
|
clients: List['WindowClient'] = []
|
||||||
|
|
||||||
def __init__(self, crop=None, color=None, scale=None, show_name=None):
|
def __init__(self):
|
||||||
"""
|
"""
|
||||||
create a window instance with these pre process
|
create a window instance with these pre process
|
||||||
:param crop: [x1,y1,x2,y2] array defining the boundaries to crop
|
:param crop: [x1,y1,x2,y2] array defining the boundaries to crop
|
||||||
:param color: color to use example cv2.COLOR_RGB2HSV
|
:param color: color to use example cv2.COLOR_RGB2HSV
|
||||||
:param scale: scaling the window
|
:param scale: scaling the window
|
||||||
"""
|
"""
|
||||||
self.color = color
|
self.crop = None
|
||||||
self.crop = crop
|
self.scale = None
|
||||||
self.scale = scale
|
self.show_name = f"window client {len(WindowClient.clients)}"
|
||||||
self.show_name = show_name
|
|
||||||
|
|
||||||
if len(WindowClient.clients) == 0:
|
|
||||||
window_server.start()
|
|
||||||
WindowClient.clients.append(self)
|
WindowClient.clients.append(self)
|
||||||
|
if len(WindowClient.clients) > 0 and WindowServer.status != Status.RUNNING:
|
||||||
|
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
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
if self in WindowClient.clients:
|
if self in WindowClient.clients:
|
||||||
@ -34,11 +61,7 @@ class WindowClient:
|
|||||||
if len(WindowClient.clients) == 0:
|
if len(WindowClient.clients) == 0:
|
||||||
window_server.stop()
|
window_server.stop()
|
||||||
|
|
||||||
@staticmethod
|
def _get_capture(self):
|
||||||
def running():
|
|
||||||
return WindowServer.status == Status.RUNNING
|
|
||||||
|
|
||||||
def get_capture(self):
|
|
||||||
"""
|
"""
|
||||||
copies the recorded screen and then pre processes its
|
copies the recorded screen and then pre processes its
|
||||||
:return: game window image
|
:return: game window image
|
||||||
@ -56,8 +79,7 @@ class WindowClient:
|
|||||||
if temp_img is None or temp_img.size == 0:
|
if temp_img is None or temp_img.size == 0:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.color is not None:
|
temp_img = cv2.cvtColor(temp_img, cv2.COLOR_RGB2GRAY)
|
||||||
temp_img = cv2.cvtColor(temp_img, self.color)
|
|
||||||
|
|
||||||
if self.crop is not None:
|
if self.crop is not None:
|
||||||
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
|
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
|
||||||
@ -65,51 +87,18 @@ class WindowClient:
|
|||||||
if self.scale is not None:
|
if self.scale is not None:
|
||||||
temp_img = cv2.resize(temp_img, (self.scale[0], self.scale[1]), interpolation=cv2.INTER_AREA)
|
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
|
return temp_img
|
||||||
|
|
||||||
def processed_image(self, func=None):
|
# noinspection PyUnresolvedReferences
|
||||||
"""
|
def _show(self, img):
|
||||||
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
|
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:
|
if WindowServer.status == Status.CRASHED:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not self.show_name:
|
helper.save_img(self.show_name, img, True)
|
||||||
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,15 +1,16 @@
|
|||||||
import logging
|
import logging
|
||||||
import math
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import d3dshot
|
import cv2
|
||||||
import pywintypes
|
import numpy as np
|
||||||
import win32api
|
from mss.base import MSSBase
|
||||||
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.helper.helper import print_exc
|
||||||
|
from fishy.osservices.os_services import os_services
|
||||||
|
|
||||||
|
|
||||||
class Status(Enum):
|
class Status(Enum):
|
||||||
@ -22,12 +23,11 @@ class WindowServer:
|
|||||||
"""
|
"""
|
||||||
Records the game window, and allows to create instance to process it
|
Records the game window, and allows to create instance to process it
|
||||||
"""
|
"""
|
||||||
Screen = None
|
Screen: np.ndarray = None
|
||||||
windowOffset = None
|
windowOffset = None
|
||||||
hwnd = None
|
|
||||||
status = Status.STOPPED
|
status = Status.STOPPED
|
||||||
d3: d3dshot.D3DShot = d3dshot.create(capture_output="numpy")
|
sslib = None
|
||||||
monitor_top_left = None
|
crop = None
|
||||||
|
|
||||||
|
|
||||||
def init():
|
def init():
|
||||||
@ -35,23 +35,39 @@ def init():
|
|||||||
Executed once before the main loop,
|
Executed once before the main loop,
|
||||||
Finds the game window, and calculates the offset to remove the title bar
|
Finds the game window, and calculates the offset to remove the title bar
|
||||||
"""
|
"""
|
||||||
# noinspection PyUnresolvedReferences
|
WindowServer.sslib = screenshot.create()
|
||||||
try:
|
# Check if the screenshot library was successfully created
|
||||||
WindowServer.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
|
if WindowServer.sslib is None:
|
||||||
|
logging.error("Failed to create screenshot library instance")
|
||||||
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
|
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():
|
def loop():
|
||||||
@ -59,25 +75,10 @@ def loop():
|
|||||||
Executed in the start of the main loop
|
Executed in the start of the main loop
|
||||||
finds the game window location and captures it
|
finds the game window location and captures it
|
||||||
"""
|
"""
|
||||||
|
WindowServer.Screen = get_cropped_screenshot()
|
||||||
|
|
||||||
temp_screen = WindowServer.d3.screenshot()
|
if WindowServer.Screen is None:
|
||||||
|
logging.error("Couldn't find the game window")
|
||||||
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
|
WindowServer.status = Status.CRASHED
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import math
|
|||||||
import time
|
import time
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
|
from fishy.engine.common import qr_detection
|
||||||
from pynput import keyboard, mouse
|
from pynput import keyboard, mouse
|
||||||
|
|
||||||
from fishy.engine import SemiFisherEngine
|
from fishy.engine import SemiFisherEngine
|
||||||
@ -12,12 +13,11 @@ from fishy.engine.fullautofisher.mode.calibrator import Calibrator
|
|||||||
from fishy.engine.fullautofisher.mode.imode import FullAutoMode
|
from fishy.engine.fullautofisher.mode.imode import FullAutoMode
|
||||||
from fishy.engine.fullautofisher.mode.player import Player
|
from fishy.engine.fullautofisher.mode.player import Player
|
||||||
from fishy.engine.fullautofisher.mode.recorder import Recorder
|
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 import fishing_mode
|
||||||
from fishy.engine.semifisher.fishing_mode import FishingMode
|
from fishy.engine.semifisher.fishing_mode import FishingMode
|
||||||
from fishy.helper.config import config
|
from fishy.helper.config import config
|
||||||
from fishy.helper.helper import wait_until, is_eso_active, sign, print_exc
|
from fishy.helper.helper import wait_until, sign, print_exc
|
||||||
|
from fishy.osservices.os_services import os_services
|
||||||
|
|
||||||
mse = mouse.Controller()
|
mse = mouse.Controller()
|
||||||
kb = keyboard.Controller()
|
kb = keyboard.Controller()
|
||||||
@ -53,14 +53,20 @@ class FullAuto(IEngine):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# block thread until game window becomes active
|
# block thread until game window becomes active
|
||||||
if not is_eso_active():
|
if not os_services.is_eso_active():
|
||||||
logging.info("Waiting for eso window to be active...")
|
logging.info("Waiting for eso window to be active...")
|
||||||
wait_until(lambda: is_eso_active() or not self.start)
|
wait_until(lambda: os_services.is_eso_active() or not self.start)
|
||||||
if self.start:
|
if self.start:
|
||||||
logging.info("starting in 2 secs...")
|
logging.info("starting in 2 secs...")
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
||||||
if not self._pre_run_checks():
|
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...")
|
||||||
return
|
return
|
||||||
|
|
||||||
if config.get("tabout_stop", 1):
|
if config.get("tabout_stop", 1):
|
||||||
@ -73,33 +79,11 @@ class FullAuto(IEngine):
|
|||||||
logging.error("exception occurred while running full auto mode")
|
logging.error("exception occurred while running full auto mode")
|
||||||
print_exc()
|
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 stop_on_inactive(self):
|
||||||
def func():
|
def func():
|
||||||
logging.debug("stop on inactive started")
|
logging.debug("stop on inactive started")
|
||||||
wait_until(lambda: not is_eso_active() or not self.start)
|
wait_until(lambda: not os_services.is_eso_active() or not self.start)
|
||||||
if self.start and not is_eso_active():
|
if self.start and not os_services.is_eso_active():
|
||||||
self.turn_off()
|
self.turn_off()
|
||||||
logging.debug("stop on inactive stopped")
|
logging.debug("stop on inactive stopped")
|
||||||
Thread(target=func).start()
|
Thread(target=func).start()
|
||||||
@ -111,8 +95,7 @@ class FullAuto(IEngine):
|
|||||||
todo find a better way of handling None: switch from start bool to state which knows
|
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
|
todo its waiting for qr which doesn't block the engine when commanded to close
|
||||||
"""
|
"""
|
||||||
img = self.window.processed_image(func=image_pre_process)
|
values = qr_detection.get_values(self.window)
|
||||||
values = get_values_from_image(img)
|
|
||||||
return values[:3] if values else None
|
return values[:3] if values else None
|
||||||
|
|
||||||
def move_to(self, target) -> bool:
|
def move_to(self, target) -> bool:
|
||||||
@ -137,9 +120,12 @@ class FullAuto(IEngine):
|
|||||||
|
|
||||||
walking_time = dist / self.calibrator.move_factor
|
walking_time = dist / self.calibrator.move_factor
|
||||||
logging.debug(f"walking for {walking_time}")
|
logging.debug(f"walking for {walking_time}")
|
||||||
kb.press('w')
|
|
||||||
|
forward_key = config.get("forward_key", 'w')
|
||||||
|
kb.press(forward_key)
|
||||||
time.sleep(walking_time)
|
time.sleep(walking_time)
|
||||||
kb.release('w')
|
kb.release(forward_key)
|
||||||
|
|
||||||
logging.debug("done")
|
logging.debug("done")
|
||||||
# todo: maybe check if it reached the destination before returning true?
|
# todo: maybe check if it reached the destination before returning true?
|
||||||
return True
|
return True
|
||||||
@ -177,7 +163,7 @@ class FullAuto(IEngine):
|
|||||||
_hole_found_flag = FishingMode.CurrentMode in valid_states
|
_hole_found_flag = FishingMode.CurrentMode in valid_states
|
||||||
|
|
||||||
# if vertical movement is disabled
|
# if vertical movement is disabled
|
||||||
if not config.get("look_for_hole", 1):
|
if not config.get("look_for_hole", 0):
|
||||||
return _hole_found_flag
|
return _hole_found_flag
|
||||||
|
|
||||||
t = 0
|
t = 0
|
||||||
|
@ -20,26 +20,6 @@ kb = keyboard.Controller()
|
|||||||
offset = 0
|
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):
|
def _update_factor(key, value):
|
||||||
full_auto_factors = config.get("full_auto_factors", {})
|
full_auto_factors = config.get("full_auto_factors", {})
|
||||||
full_auto_factors[key] = value
|
full_auto_factors[key] = value
|
||||||
@ -83,9 +63,11 @@ class Calibrator(IMode):
|
|||||||
|
|
||||||
x1, y1, rot1 = coords
|
x1, y1, rot1 = coords
|
||||||
|
|
||||||
kb.press('w')
|
forward_key = config.get("forward_key", 'w')
|
||||||
|
kb.press(forward_key)
|
||||||
time.sleep(walking_time)
|
time.sleep(walking_time)
|
||||||
kb.release('w')
|
kb.release(forward_key)
|
||||||
|
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
coords = self.engine.get_coords()
|
coords = self.engine.get_coords()
|
||||||
|
@ -99,7 +99,7 @@ class Recorder(IMode):
|
|||||||
|
|
||||||
def _open_save_popup(self):
|
def _open_save_popup(self):
|
||||||
top = PopUp(empty_function, self.engine.get_gui()._root, background=self.engine.get_gui()._root["background"])
|
top = PopUp(empty_function, self.engine.get_gui()._root, background=self.engine.get_gui()._root["background"])
|
||||||
controls_frame = ttk.Frame(top)
|
recorder_frame = ttk.Frame(top)
|
||||||
top.title("Save Recording?")
|
top.title("Save Recording?")
|
||||||
|
|
||||||
button = [-1]
|
button = [-1]
|
||||||
@ -109,14 +109,15 @@ class Recorder(IMode):
|
|||||||
top.quit_top()
|
top.quit_top()
|
||||||
|
|
||||||
selected_text = f"\n\nSelected: {os.path.basename(config.get('full_auto_rec_file'))}" if config.get('edit_recorder_mode') else ""
|
selected_text = f"\n\nSelected: {os.path.basename(config.get('full_auto_rec_file'))}" if config.get('edit_recorder_mode') else ""
|
||||||
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))
|
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))
|
||||||
|
|
||||||
_overwrite = tk.NORMAL if config.get("edit_recorder_mode") else tk.DISABLED
|
_overwrite = tk.NORMAL if config.get("edit_recorder_mode") else tk.DISABLED
|
||||||
ttk.Button(controls_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0, pady=(5, 0))
|
ttk.Button(recorder_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(recorder_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)
|
ttk.Button(recorder_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2)
|
||||||
|
|
||||||
controls_frame.pack(padx=(5, 5), pady=(5, 5))
|
recorder_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||||
|
recorder_frame.update()
|
||||||
top.start()
|
top.start()
|
||||||
|
|
||||||
return button[0]
|
return button[0]
|
||||||
|
@ -4,10 +4,11 @@ import typing
|
|||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Callable, Optional
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
from fishy.engine.common import qr_detection
|
||||||
|
|
||||||
from fishy.engine.semifisher.fishing_mode import FishingMode
|
from fishy.engine.semifisher.fishing_mode import FishingMode
|
||||||
|
|
||||||
from fishy.engine.common.IEngine import IEngine
|
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.common.window import WindowClient
|
||||||
from fishy.engine.semifisher import fishing_event, fishing_mode
|
from fishy.engine.semifisher import fishing_event, fishing_mode
|
||||||
from fishy.engine.semifisher.fishing_event import FishEvent
|
from fishy.engine.semifisher.fishing_event import FishEvent
|
||||||
@ -35,15 +36,6 @@ class SemiFisherEngine(IEngine):
|
|||||||
Thread(target=self._wait_and_check).start()
|
Thread(target=self._wait_and_check).start()
|
||||||
|
|
||||||
time.sleep(0.2)
|
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()
|
fishing_event.init()
|
||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
@ -59,15 +51,9 @@ class SemiFisherEngine(IEngine):
|
|||||||
def _engine_loop(self):
|
def _engine_loop(self):
|
||||||
skip_count = 0
|
skip_count = 0
|
||||||
while self.state == 1 and WindowClient.running():
|
while self.state == 1 and WindowClient.running():
|
||||||
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
|
# crop qr and get the values from it
|
||||||
self.values = get_values_from_image(capture)
|
self.values = qr_detection.get_values(self.window)
|
||||||
|
|
||||||
# if fishyqr fails to get read multiple times, stop the bot
|
# if fishyqr fails to get read multiple times, stop the bot
|
||||||
if not self.values:
|
if not self.values:
|
||||||
if skip_count >= 5:
|
if skip_count >= 5:
|
||||||
|
@ -12,10 +12,10 @@ from playsound import playsound
|
|||||||
|
|
||||||
from fishy import web
|
from fishy import web
|
||||||
from fishy.engine.semifisher import fishing_mode
|
from fishy.engine.semifisher import fishing_mode
|
||||||
from fishy.engine.semifisher.fishing_mode import FishingMode, State
|
from fishy.engine.semifisher.fishing_mode import State
|
||||||
from fishy.helper import helper
|
from fishy.helper import helper
|
||||||
from fishy.helper.config import config
|
from fishy.helper.config import config
|
||||||
from fishy.helper.helper import is_eso_active
|
from fishy.osservices.os_services import os_services
|
||||||
|
|
||||||
|
|
||||||
class FishEvent:
|
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 if_eso_is_focused(func):
|
||||||
def wrapper():
|
def wrapper():
|
||||||
if not is_eso_active():
|
if not os_services.is_eso_active():
|
||||||
logging.warning("ESO window is not focused")
|
logging.warning("ESO window is not focused")
|
||||||
return
|
return
|
||||||
func()
|
func()
|
||||||
|
@ -13,11 +13,20 @@ from fishy.helper.config import config
|
|||||||
from fishy.helper.popup import PopUp
|
from fishy.helper.popup import PopUp
|
||||||
|
|
||||||
|
|
||||||
def start_fullfisher_config(gui: 'GUI'):
|
def del_entry_key(event):
|
||||||
top = PopUp(helper.empty_function, gui._root, background=gui._root["background"])
|
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"])
|
||||||
controls_frame = ttk.Frame(top)
|
controls_frame = ttk.Frame(top)
|
||||||
top.title("Config")
|
top.title("Config")
|
||||||
|
|
||||||
|
|
||||||
def file_name():
|
def file_name():
|
||||||
file = config.get("full_auto_rec_file", None)
|
file = config.get("full_auto_rec_file", None)
|
||||||
if file is None:
|
if file is None:
|
||||||
@ -48,7 +57,7 @@ def start_fullfisher_config(gui: 'GUI'):
|
|||||||
mode_var = tk.IntVar(value=config.get("full_auto_mode", 0))
|
mode_var = tk.IntVar(value=config.get("full_auto_mode", 0))
|
||||||
edit_var = tk.IntVar(value=config.get("edit_recorder_mode", 0))
|
edit_var = tk.IntVar(value=config.get("edit_recorder_mode", 0))
|
||||||
tabout_var = tk.IntVar(value=config.get("tabout_stop", 1))
|
tabout_var = tk.IntVar(value=config.get("tabout_stop", 1))
|
||||||
look_for_hole = tk.IntVar(value=config.get("look_for_hole", 1))
|
look_for_hole = tk.IntVar(value=config.get("look_for_hole", 0))
|
||||||
row = 0
|
row = 0
|
||||||
|
|
||||||
ttk.Label(controls_frame, text="Calibration: ").grid(row=row, column=0, pady=(5, 0))
|
ttk.Label(controls_frame, text="Calibration: ").grid(row=row, column=0, pady=(5, 0))
|
||||||
@ -63,6 +72,14 @@ def start_fullfisher_config(gui: 'GUI'):
|
|||||||
|
|
||||||
row += 1
|
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)
|
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_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()))
|
edit_cb = ttk.Checkbutton(controls_frame, variable=edit_var, state=edit_state, command=lambda: config.set("edit_recorder_mode", edit_var.get()))
|
||||||
@ -90,6 +107,8 @@ 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))
|
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.pack(padx=(5, 5), pady=(5, 10))
|
||||||
|
controls_frame.update()
|
||||||
|
|
||||||
top.start()
|
top.start()
|
||||||
|
|
||||||
|
|
||||||
@ -109,9 +128,7 @@ def start_semifisher_config(gui: 'GUI'):
|
|||||||
if web.sub():
|
if web.sub():
|
||||||
gui._notify.set(1)
|
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"])
|
top = PopUp(save, gui._root, background=gui._root["background"])
|
||||||
controls_frame = ttk.Frame(top)
|
controls_frame = ttk.Frame(top)
|
||||||
@ -119,7 +136,7 @@ def start_semifisher_config(gui: 'GUI'):
|
|||||||
|
|
||||||
ttk.Label(controls_frame, text="Notification:").grid(row=0, column=0)
|
ttk.Label(controls_frame, text="Notification:").grid(row=0, column=0)
|
||||||
|
|
||||||
gui._notify = tk.IntVar(0)
|
gui._notify = tk.IntVar()
|
||||||
gui._notify_check = ttk.Checkbutton(controls_frame, command=toggle_sub, variable=gui._notify)
|
gui._notify_check = ttk.Checkbutton(controls_frame, command=toggle_sub, variable=gui._notify)
|
||||||
gui._notify_check.grid(row=0, column=1)
|
gui._notify_check.grid(row=0, column=1)
|
||||||
gui._notify_check['state'] = tk.DISABLED
|
gui._notify_check['state'] = tk.DISABLED
|
||||||
@ -149,6 +166,8 @@ def start_semifisher_config(gui: 'GUI'):
|
|||||||
jitter.grid(row=5, column=1)
|
jitter.grid(row=5, column=1)
|
||||||
|
|
||||||
controls_frame.pack(padx=(5, 5), pady=(5, 5))
|
controls_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||||
|
controls_frame.update()
|
||||||
|
|
||||||
top.start()
|
top.start()
|
||||||
|
|
||||||
|
|
||||||
@ -156,4 +175,5 @@ if __name__ == '__main__':
|
|||||||
from fishy.gui import GUI
|
from fishy.gui import GUI
|
||||||
gui = GUI(lambda: IEngineHandler())
|
gui = GUI(lambda: IEngineHandler())
|
||||||
gui.call_in_thread(lambda: start_semifisher_config(gui))
|
gui.call_in_thread(lambda: start_semifisher_config(gui))
|
||||||
|
gui.call_in_thread(lambda: start_fullfisher_config(gui))
|
||||||
gui.create()
|
gui.create()
|
||||||
|
@ -69,6 +69,9 @@ class GUI:
|
|||||||
def create(self):
|
def create(self):
|
||||||
main_gui._create(self)
|
main_gui._create(self)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
self._destroyed = True
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self._thread.start()
|
self._thread.start()
|
||||||
|
|
||||||
|
@ -2,19 +2,24 @@ import logging
|
|||||||
import time
|
import time
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
import tkinter.ttk as ttk
|
import tkinter.ttk as ttk
|
||||||
|
from tkinter import messagebox
|
||||||
import typing
|
import typing
|
||||||
|
from functools import partial
|
||||||
|
import os
|
||||||
|
|
||||||
from fishy.gui import update_dialog
|
from fishy.gui import update_dialog
|
||||||
from ttkthemes import ThemedTk
|
from ttkthemes import ThemedTk
|
||||||
|
|
||||||
from fishy import helper
|
from fishy.helper import helper
|
||||||
from fishy.web import web
|
from fishy.web import web
|
||||||
|
|
||||||
from ..constants import fishyqr
|
from ..constants import fishyqr
|
||||||
|
from ..engine.common import screenshot
|
||||||
from ..helper.config import config
|
from ..helper.config import config
|
||||||
from .discord_login import discord_login
|
from .discord_login import discord_login
|
||||||
from ..helper.hotkey.hotkey_process import hotkey
|
from ..helper.hotkey.hotkey_process import hotkey
|
||||||
from ..helper.hotkey.process import Key
|
from ..helper.hotkey.process import Key
|
||||||
|
from ..osservices.os_services import os_services
|
||||||
|
|
||||||
if typing.TYPE_CHECKING:
|
if typing.TYPE_CHECKING:
|
||||||
from . import GUI
|
from . import GUI
|
||||||
@ -46,7 +51,7 @@ def _create(gui: 'GUI'):
|
|||||||
gui.login.set(1 if login > 0 else 0)
|
gui.login.set(1 if login > 0 else 0)
|
||||||
state = tk.DISABLED if login == -1 else tk.ACTIVE
|
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_checkbutton(label="Login", command=lambda: discord_login(gui), variable=gui.login, state=state)
|
||||||
filemenu.add_command(label="Create Shortcut", command=lambda: helper.create_shortcut(False))
|
filemenu.add_command(label="Create Shortcut", command=lambda: os_services.create_shortcut(False))
|
||||||
# filemenu.add_command(label="Create Anti-Ghost Shortcut", command=lambda: helper.create_shortcut(True))
|
# filemenu.add_command(label="Create Anti-Ghost Shortcut", command=lambda: helper.create_shortcut(True))
|
||||||
|
|
||||||
def _toggle_mode():
|
def _toggle_mode():
|
||||||
@ -61,6 +66,7 @@ def _create(gui: 'GUI'):
|
|||||||
def update():
|
def update():
|
||||||
config.delete("dont_ask_update")
|
config.delete("dont_ask_update")
|
||||||
update_dialog.check_update(gui, True)
|
update_dialog.check_update(gui, True)
|
||||||
|
|
||||||
filemenu.add_command(label="Update", command=update)
|
filemenu.add_command(label="Update", command=update)
|
||||||
|
|
||||||
def installer():
|
def installer():
|
||||||
@ -70,6 +76,7 @@ def _create(gui: 'GUI'):
|
|||||||
else:
|
else:
|
||||||
helper.install_required_addons(True)
|
helper.install_required_addons(True)
|
||||||
filemenu.entryconfigure(4, label="Remove FishyQR")
|
filemenu.entryconfigure(4, label="Remove FishyQR")
|
||||||
|
|
||||||
chaEntry = "Remove FishyQR" if helper.addon_exists(fishyqr[0]) else "Install FishyQR"
|
chaEntry = "Remove FishyQR" if helper.addon_exists(fishyqr[0]) else "Install FishyQR"
|
||||||
filemenu.add_command(label=chaEntry, command=installer)
|
filemenu.add_command(label=chaEntry, command=installer)
|
||||||
menubar.add_cascade(label="Options", menu=filemenu)
|
menubar.add_cascade(label="Options", menu=filemenu)
|
||||||
@ -78,6 +85,47 @@ def _create(gui: 'GUI'):
|
|||||||
debug_menu.add_command(label="Check QR Value",
|
debug_menu.add_command(label="Check QR Value",
|
||||||
command=lambda: gui.engine.check_qr_val())
|
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 = tk.IntVar()
|
||||||
debug_var.set(int(config.get('debug', False)))
|
debug_var.set(int(config.get('debug', False)))
|
||||||
|
|
||||||
@ -89,7 +137,8 @@ def _create(gui: 'GUI'):
|
|||||||
menubar.add_cascade(label="Debug", menu=debug_menu)
|
menubar.add_cascade(label="Debug", menu=debug_menu)
|
||||||
|
|
||||||
help_menu = tk.Menu(menubar, tearoff=0)
|
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"))
|
help_menu.add_command(label="Donate", command=lambda: helper.open_web("https://paypal.me/AdamSaudagar"))
|
||||||
menubar.add_cascade(label="Help", menu=help_menu)
|
menubar.add_cascade(label="Help", menu=help_menu)
|
||||||
|
|
||||||
@ -125,9 +174,13 @@ def _create(gui: 'GUI'):
|
|||||||
|
|
||||||
_apply_theme(gui)
|
_apply_theme(gui)
|
||||||
gui._root.update()
|
gui._root.update()
|
||||||
|
|
||||||
gui._root.minsize(gui._root.winfo_width() + 10, gui._root.winfo_height() + 10)
|
gui._root.minsize(gui._root.winfo_width() + 10, gui._root.winfo_height() + 10)
|
||||||
if config.get("win_loc") is not None:
|
if config.get("win_loc") is not None:
|
||||||
gui._root.geometry(config.get("win_loc"))
|
gui._root.geometry(config.get("win_loc").split(":")[-1])
|
||||||
|
if config.get("win_loc").split(":")[0] == "zoomed":
|
||||||
|
gui._root.update()
|
||||||
|
gui._root.state("zoomed")
|
||||||
|
|
||||||
hotkey.hook(Key.F9, gui.funcs.start_engine)
|
hotkey.hook(Key.F9, gui.funcs.start_engine)
|
||||||
|
|
||||||
@ -137,7 +190,13 @@ def _create(gui: 'GUI'):
|
|||||||
if not tk.messagebox.askyesno(title="Quit?", message="Bot engine running. Quit Anyway?"):
|
if not tk.messagebox.askyesno(title="Quit?", message="Bot engine running. Quit Anyway?"):
|
||||||
return
|
return
|
||||||
|
|
||||||
config.set("win_loc", gui._root.geometry())
|
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())
|
||||||
|
|
||||||
gui._destroyed = True
|
gui._destroyed = True
|
||||||
|
|
||||||
gui._root.protocol("WM_DELETE_WINDOW", set_destroy)
|
gui._root.protocol("WM_DELETE_WINDOW", set_destroy)
|
||||||
|
@ -10,54 +10,53 @@ from fishy.helper import helper
|
|||||||
from fishy.helper.config import config
|
from fishy.helper.config import config
|
||||||
|
|
||||||
|
|
||||||
def show(win_loc, q):
|
class Splash:
|
||||||
logging.debug("started splash process")
|
def __init__(self):
|
||||||
dim = (300, 200)
|
self.q = Queue()
|
||||||
top = tk.Tk()
|
self.process = Process(name=Splash.__name__, target=self.show, args=(config.get("win_loc"), self.q,))
|
||||||
|
|
||||||
top.overrideredirect(True)
|
def finish(self):
|
||||||
top.lift()
|
self.q.put("stop")
|
||||||
top.attributes('-topmost', True)
|
|
||||||
|
|
||||||
top.title("Loading...")
|
def start(self):
|
||||||
top.resizable(False, False)
|
self.process.start()
|
||||||
top.iconbitmap(helper.manifest_file('icon.ico'))
|
|
||||||
|
|
||||||
canvas = tk.Canvas(top, width=dim[0], height=dim[1], bg='white')
|
def show(self, win_loc, q):
|
||||||
canvas.pack()
|
logging.debug("started splash process")
|
||||||
top.image = Image.open(helper.manifest_file('fishybot_logo.png')).resize(dim)
|
dim = (300, 200)
|
||||||
top.image = ImageTk.PhotoImage(top.image)
|
top = tk.Tk()
|
||||||
canvas.create_image(0, 0, anchor=tk.NW, image=top.image)
|
|
||||||
|
|
||||||
# Position splash at the center of the main window
|
top.overrideredirect(True)
|
||||||
|
top.lift()
|
||||||
|
top.attributes('-topmost', True)
|
||||||
|
|
||||||
default_loc = (str(top.winfo_reqwidth()) + "+" + str(top.winfo_reqheight()) + "+" + "0" + "0")
|
top.title("Loading...")
|
||||||
loc = (win_loc or default_loc).split("+")[1:]
|
top.resizable(False, False)
|
||||||
top.geometry("{}x{}+{}+{}".format(dim[0], dim[1], int(loc[0]) + int(dim[0] / 2), int(loc[1]) + int(dim[1] / 2)))
|
top.iconbitmap(helper.manifest_file('icon.ico'))
|
||||||
|
|
||||||
def waiting():
|
canvas = tk.Canvas(top, width=dim[0], height=dim[1], bg='white')
|
||||||
q.get()
|
canvas.pack()
|
||||||
time.sleep(0.2)
|
top.image = Image.open(helper.manifest_file('fishybot_logo.png')).resize(dim)
|
||||||
running[0] = False
|
top.image = ImageTk.PhotoImage(top.image)
|
||||||
Thread(target=waiting).start()
|
canvas.create_image(0, 0, anchor=tk.NW, image=top.image)
|
||||||
|
|
||||||
running = [True]
|
# Position splash at the center of the main window
|
||||||
while running[0]:
|
|
||||||
top.update()
|
|
||||||
time.sleep(0.1)
|
|
||||||
|
|
||||||
top.destroy()
|
default_loc = (str(top.winfo_reqwidth()) + "+" + str(top.winfo_reqheight()) + "+" + "0" + "0")
|
||||||
logging.debug("ended splash process")
|
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)))
|
||||||
|
|
||||||
|
def waiting():
|
||||||
|
q.get()
|
||||||
|
time.sleep(0.2)
|
||||||
|
running[0] = False
|
||||||
|
|
||||||
def create_finish(q):
|
Thread(target=waiting).start()
|
||||||
def finish():
|
|
||||||
q.put("stop")
|
|
||||||
|
|
||||||
return finish
|
running = [True]
|
||||||
|
while running[0]:
|
||||||
|
top.update()
|
||||||
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
top.destroy()
|
||||||
def start():
|
logging.debug("ended splash process")
|
||||||
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)
|
root.image = ImageTk.PhotoImage(root.image)
|
||||||
canvas.create_image(0, 0, anchor=tk.NW, image=root.image)
|
canvas.create_image(0, 0, anchor=tk.NW, image=root.image)
|
||||||
|
|
||||||
check_value = tk.IntVar(0)
|
check_value = tk.IntVar()
|
||||||
|
|
||||||
g1 = ttk.Frame(f)
|
g1 = ttk.Frame(f)
|
||||||
ttk.Checkbutton(g1, command=disable_enable_button, variable=check_value).pack(side=tk.LEFT)
|
ttk.Checkbutton(g1, command=disable_enable_button, variable=check_value).pack(side=tk.LEFT)
|
||||||
|
@ -18,7 +18,6 @@ def _show(gui, currentversion, newversion, returns):
|
|||||||
|
|
||||||
top = PopUp(helper.empty_function, gui._root)
|
top = PopUp(helper.empty_function, gui._root)
|
||||||
top.title("A wild fishy update appeared!")
|
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 (" +
|
dialogLabel = tk.Label(top, text="There is a new fishy update available (" +
|
||||||
currentversion + "->" + newversion + "). Do you want to update now?")
|
currentversion + "->" + newversion + "). Do you want to update now?")
|
||||||
@ -38,6 +37,7 @@ def _show(gui, currentversion, newversion, returns):
|
|||||||
width=buttonWidth, compound="c")
|
width=buttonWidth, compound="c")
|
||||||
dialogBtnYes.grid(row=2, column=1, padx=5, pady=5)
|
dialogBtnYes.grid(row=2, column=1, padx=5, pady=5)
|
||||||
dialogBtnYes.focus_set()
|
dialogBtnYes.focus_set()
|
||||||
|
dialogBtnYes.update()
|
||||||
|
|
||||||
top.protocol('WM_DELETE_WINDOW', _clickNo)
|
top.protocol('WM_DELETE_WINDOW', _clickNo)
|
||||||
top.start()
|
top.start()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from .config import Config
|
from .config import Config
|
||||||
from .helper import (addon_exists, create_shortcut, create_shortcut_first,
|
from .helper import (addon_exists,
|
||||||
get_addonversion, get_savedvarsdir,
|
get_addonversion, get_savedvarsdir,
|
||||||
install_addon, install_thread_excepthook, manifest_file,
|
install_addon, install_thread_excepthook, manifest_file,
|
||||||
not_implemented, open_web, playsound_multiple,
|
not_implemented, open_web, playsound_multiple,
|
||||||
|
@ -14,12 +14,12 @@ class active:
|
|||||||
return
|
return
|
||||||
|
|
||||||
active._scheduler = EventScheduler()
|
active._scheduler = EventScheduler()
|
||||||
active._scheduler.start()
|
|
||||||
logging.debug("active scheduler initialized")
|
logging.debug("active scheduler initialized")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def start():
|
def start():
|
||||||
web.ping()
|
web.ping()
|
||||||
|
active._scheduler.start()
|
||||||
active._scheduler.enter_recurring(60, 1, web.ping)
|
active._scheduler.enter_recurring(60, 1, web.ping)
|
||||||
logging.debug("active scheduler started")
|
logging.debug("active scheduler started")
|
||||||
|
|
||||||
|
@ -6,14 +6,8 @@ import logging
|
|||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import urllib.request
|
|
||||||
from os import execl
|
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):
|
def _normalize_version(v):
|
||||||
@ -38,46 +32,16 @@ def _normalize_version(v):
|
|||||||
return rv
|
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():
|
def _get_current_version():
|
||||||
"""
|
"""
|
||||||
Gets the current version of the package installed
|
Gets the current version of the package installed
|
||||||
:return: version normalized
|
|
||||||
"""
|
"""
|
||||||
import fishy
|
import fishy
|
||||||
return _normalize_version(fishy.__version__)
|
return fishy.__version__
|
||||||
|
|
||||||
|
|
||||||
index = "https://pypi.python.org/simple"
|
|
||||||
pkg = "fishy"
|
|
||||||
|
|
||||||
|
|
||||||
def versions():
|
def versions():
|
||||||
return _hr_version(_get_current_version()), _hr_version(_get_highest_version(index, pkg))
|
return _get_current_version(), web.get_highest_version()
|
||||||
|
|
||||||
|
|
||||||
def upgrade_avail():
|
def upgrade_avail():
|
||||||
@ -85,14 +49,18 @@ def upgrade_avail():
|
|||||||
Checks if update is available
|
Checks if update is available
|
||||||
:return: boolean
|
:return: boolean
|
||||||
"""
|
"""
|
||||||
return _get_current_version() < _get_highest_version(index, pkg)
|
|
||||||
|
highest_version_normalized = _normalize_version(web.get_highest_version())
|
||||||
|
current_version_normalized = _normalize_version(_get_current_version())
|
||||||
|
|
||||||
|
return current_version_normalized < highest_version_normalized
|
||||||
|
|
||||||
|
|
||||||
def update_now(version):
|
def update_now(version):
|
||||||
"""
|
"""
|
||||||
public function,
|
calling this function updates fishy,
|
||||||
compares current version with the latest version (from web),
|
should be the last thing to be executed as this function will restart fishy
|
||||||
if current version is older, then it updates and restarts the script
|
the flaw is handed by `EngineEventHandler.update_flag` which is the last thing to be stopped
|
||||||
"""
|
"""
|
||||||
logging.info(f"Updating to v{version}, Please Wait...")
|
logging.info(f"Updating to v{version}, Please Wait...")
|
||||||
subprocess.call(["python", '-m', 'pip', 'install', '--upgrade', 'fishy', '--user'])
|
subprocess.call(["python", '-m', 'pip', 'install', '--upgrade', 'fishy', '--user'])
|
||||||
|
@ -7,18 +7,28 @@ import logging
|
|||||||
import os
|
import os
|
||||||
# path to save the configuration file
|
# path to save the configuration file
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
from event_scheduler import EventScheduler
|
from event_scheduler import EventScheduler
|
||||||
|
|
||||||
|
from fishy.osservices.os_services import os_services
|
||||||
|
|
||||||
|
|
||||||
def filename():
|
def filename():
|
||||||
from fishy.helper.helper import get_documents
|
|
||||||
name = "fishy_config.json"
|
if "--test-server" in sys.argv:
|
||||||
|
name = "fishy_config_test.json"
|
||||||
|
else:
|
||||||
|
name = "fishy_config.json"
|
||||||
|
|
||||||
_filename = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"], "Documents", name)
|
_filename = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"], "Documents", name)
|
||||||
|
|
||||||
if os.path.exists(_filename):
|
if os.path.exists(_filename):
|
||||||
return _filename
|
return _filename
|
||||||
|
|
||||||
return os.path.join(get_documents(), name)
|
# fallback for OneDrive documents
|
||||||
|
return os.path.join(os_services.get_documents_path(), name)
|
||||||
|
|
||||||
|
|
||||||
temp_file = os.path.join(os.environ["TEMP"], "fishy_config.BAK")
|
temp_file = os.path.join(os.environ["TEMP"], "fishy_config.BAK")
|
||||||
|
18
fishy/helper/depless.py
Normal file
18
fishy/helper/depless.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
"""
|
||||||
|
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,22 +7,21 @@ import threading
|
|||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
import webbrowser
|
import webbrowser
|
||||||
|
from datetime import datetime
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from uuid import uuid1
|
from uuid import uuid1
|
||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
|
import cv2
|
||||||
import requests
|
import requests
|
||||||
import winshell
|
|
||||||
from playsound import playsound
|
from playsound import playsound
|
||||||
from win32com.client import Dispatch
|
|
||||||
from win32comext.shell import shell, shellcon
|
|
||||||
from win32gui import GetForegroundWindow, GetWindowText
|
|
||||||
|
|
||||||
import fishy
|
import fishy
|
||||||
from fishy.constants import libgps, lam2, fishyqr
|
from fishy.constants import libgps, lam2, fishyqr, fishyfsm, libmapping, libdl, libchatmsg
|
||||||
from fishy.helper.config import config
|
from fishy.helper.config import config
|
||||||
|
from fishy.osservices.os_services import os_services
|
||||||
|
|
||||||
|
|
||||||
def playsound_multiple(path, count=2):
|
def playsound_multiple(path, count=2):
|
||||||
@ -110,55 +109,14 @@ def manifest_file(rel_path):
|
|||||||
return os.path.join(os.path.dirname(fishy.__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():
|
def get_savedvarsdir():
|
||||||
# noinspection PyUnresolvedReferences
|
eso_path = os_services.get_eso_config_path()
|
||||||
from win32com.shell import shell, shellcon
|
return os.path.join(eso_path, "live", "SavedVariables")
|
||||||
documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
|
||||||
return os.path.join(documents, "Elder Scrolls Online", "live", "SavedVariables")
|
|
||||||
|
|
||||||
|
|
||||||
def get_addondir():
|
def get_addondir():
|
||||||
# noinspection PyUnresolvedReferences
|
eso_path = os_services.get_eso_config_path()
|
||||||
from win32com.shell import shell, shellcon
|
return os.path.join(eso_path, "live", "Addons")
|
||||||
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):
|
def addon_exists(name, url=None, v=None):
|
||||||
@ -180,7 +138,7 @@ def get_addonversion(name, url=None, v=None):
|
|||||||
|
|
||||||
|
|
||||||
def install_required_addons(force=False):
|
def install_required_addons(force=False):
|
||||||
addons_req = [libgps, lam2, fishyqr]
|
addons_req = [libgps, lam2, fishyqr, fishyfsm, libmapping, libdl, libchatmsg]
|
||||||
addon_version = config.get("addon_version", {})
|
addon_version = config.get("addon_version", {})
|
||||||
installed = False
|
installed = False
|
||||||
for addon in addons_req:
|
for addon in addons_req:
|
||||||
@ -222,19 +180,11 @@ def remove_addon(name, url=None, v=None):
|
|||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def get_documents():
|
|
||||||
return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
|
||||||
|
|
||||||
|
|
||||||
def log_raise(msg):
|
def log_raise(msg):
|
||||||
logging.error(msg)
|
logging.error(msg)
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
|
|
||||||
|
|
||||||
def is_eso_active():
|
|
||||||
return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online"
|
|
||||||
|
|
||||||
|
|
||||||
# noinspection PyProtectedMember,PyUnresolvedReferences
|
# noinspection PyProtectedMember,PyUnresolvedReferences
|
||||||
def _get_id(thread):
|
def _get_id(thread):
|
||||||
# returns id of the respective thread
|
# returns id of the respective thread
|
||||||
@ -252,8 +202,26 @@ def kill_thread(thread):
|
|||||||
if res > 1:
|
if res > 1:
|
||||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
|
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
|
||||||
print('Exception raise failure')
|
print('Exception raise failure')
|
||||||
|
|
||||||
|
|
||||||
def print_exc():
|
def print_exc():
|
||||||
logging.error(traceback.format_exc())
|
logging.error(traceback.format_exc())
|
||||||
traceback.print_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,6 +7,7 @@ from typing import Dict, Optional, Callable
|
|||||||
from playsound import playsound
|
from playsound import playsound
|
||||||
|
|
||||||
from fishy import helper
|
from fishy import helper
|
||||||
|
from fishy.helper.config import config
|
||||||
from fishy.helper.hotkey import process
|
from fishy.helper.hotkey import process
|
||||||
from fishy.helper.hotkey.process import Key
|
from fishy.helper.hotkey.process import Key
|
||||||
|
|
||||||
@ -57,13 +58,16 @@ class HotKey:
|
|||||||
while True:
|
while True:
|
||||||
key = self.outq.get()
|
key = self.outq.get()
|
||||||
|
|
||||||
|
if key == "stop":
|
||||||
|
break
|
||||||
|
|
||||||
if key in Key:
|
if key in Key:
|
||||||
callback = self._hotkeys[key]
|
callback = self._hotkeys[key]
|
||||||
if callback:
|
if callback:
|
||||||
playsound(helper.manifest_file("beep.wav"), False)
|
if config.get("sound_notification", False):
|
||||||
|
playsound(helper.manifest_file("beep.wav"), False)
|
||||||
|
|
||||||
callback()
|
callback()
|
||||||
elif key == "stop":
|
|
||||||
break
|
|
||||||
|
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import fishy
|
||||||
from fishy.helper.auto_update import _normalize_version
|
from fishy.helper.auto_update import _normalize_version
|
||||||
|
|
||||||
from fishy.constants import version
|
|
||||||
from .config import config
|
from .config import config
|
||||||
|
|
||||||
|
|
||||||
@ -14,14 +14,14 @@ class Migration:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def migrate():
|
def migrate():
|
||||||
prev_version = _normalize_version(config.get("prev_version", "0.0.0"))
|
prev_version = _normalize_version(config.get("prev_version", "0.0.0"))
|
||||||
current_version = _normalize_version(version)
|
current_version = _normalize_version(fishy.__version__)
|
||||||
|
|
||||||
if current_version > prev_version:
|
if current_version > prev_version:
|
||||||
for v, f in migration_code:
|
for v, f in migration_code:
|
||||||
if prev_version < _normalize_version(v) <= current_version:
|
if prev_version < _normalize_version(v) <= current_version:
|
||||||
logging.info(f"running migration for {v}")
|
logging.info(f"running migration for {v}")
|
||||||
f()
|
f()
|
||||||
config.set("prev_version", version)
|
config.set("prev_version", fishy.__version__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
from tkinter import Toplevel
|
from tkinter import Toplevel
|
||||||
|
from fishy import helper
|
||||||
|
|
||||||
|
|
||||||
def center(win):
|
def center(win):
|
||||||
@ -20,6 +21,7 @@ class PopUp(Toplevel):
|
|||||||
self.running = True
|
self.running = True
|
||||||
self.quit_callback = quit_callback
|
self.quit_callback = quit_callback
|
||||||
self.protocol("WM_DELETE_WINDOW", self.quit_top)
|
self.protocol("WM_DELETE_WINDOW", self.quit_top)
|
||||||
|
self.iconbitmap(helper.manifest_file('icon.ico'))
|
||||||
|
|
||||||
def quit_top(self):
|
def quit_top(self):
|
||||||
self.quit_callback()
|
self.quit_callback()
|
||||||
@ -27,6 +29,7 @@ class PopUp(Toplevel):
|
|||||||
self.running = False
|
self.running = False
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
|
self.minsize(self.winfo_width(), self.winfo_height())
|
||||||
self.grab_set()
|
self.grab_set()
|
||||||
center(self)
|
center(self)
|
||||||
while self.running:
|
while self.running:
|
||||||
|
0
fishy/osservices/__init__.py
Normal file
0
fishy/osservices/__init__.py
Normal file
29
fishy/osservices/linux.py
Normal file
29
fishy/osservices/linux.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
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
|
81
fishy/osservices/os_services.py
Normal file
81
fishy/osservices/os_services.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
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
|
112
fishy/osservices/windows.py
Normal file
112
fishy/osservices/windows.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
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
fishy/version.txt
Normal file
1
fishy/version.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.5.26
|
@ -1,3 +1,3 @@
|
|||||||
from .urls import get_notification_page, get_terms_page
|
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)
|
send_notification, sub, unsub)
|
||||||
|
@ -2,8 +2,11 @@ import logging
|
|||||||
import traceback
|
import traceback
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
from fishy.web import web
|
||||||
|
|
||||||
|
|
||||||
def uses_session(f):
|
def uses_session(f):
|
||||||
|
"""directly returns none if it couldn't get session, instead of running the function"""
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
from .web import get_session
|
from .web import get_session
|
||||||
@ -21,6 +24,9 @@ def fallback(default):
|
|||||||
# noinspection PyBroadException
|
# noinspection PyBroadException
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
|
if not web.is_online():
|
||||||
|
return default
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -3,9 +3,9 @@ import sys
|
|||||||
if "--local-server" in sys.argv:
|
if "--local-server" in sys.argv:
|
||||||
domain = "http://127.0.0.1:5000"
|
domain = "http://127.0.0.1:5000"
|
||||||
elif "--test-server" in sys.argv:
|
elif "--test-server" in sys.argv:
|
||||||
domain = "https://fishyeso-test.herokuapp.com"
|
domain = "https://fishyeso-test.definex.in"
|
||||||
else:
|
else:
|
||||||
domain = "https://fishyeso.herokuapp.com"
|
domain = "https://fishyeso.definex.in"
|
||||||
|
|
||||||
user = domain + "/api/user"
|
user = domain + "/api/user"
|
||||||
notify = domain + "/api/notify"
|
notify = domain + "/api/notify"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
from fishy import constants
|
||||||
from whatsmyip.ip import get_ip
|
from whatsmyip.ip import get_ip
|
||||||
from whatsmyip.providers import GoogleDnsProvider
|
from whatsmyip.providers import GoogleDnsProvider
|
||||||
|
|
||||||
@ -10,6 +11,11 @@ from . import urls
|
|||||||
from .decorators import fallback, uses_session
|
from .decorators import fallback, uses_session
|
||||||
|
|
||||||
_session_id = None
|
_session_id = None
|
||||||
|
_online = True
|
||||||
|
|
||||||
|
|
||||||
|
def is_online():
|
||||||
|
return _online
|
||||||
|
|
||||||
|
|
||||||
@fallback(-1)
|
@fallback(-1)
|
||||||
@ -18,7 +24,7 @@ def is_logged_in():
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||||
response = requests.get(urls.discord, params=body)
|
response = requests.get(urls.discord, json=body)
|
||||||
logged_in = response.json()["discord_login"]
|
logged_in = response.json()["discord_login"]
|
||||||
return 1 if logged_in else 0
|
return 1 if logged_in else 0
|
||||||
|
|
||||||
@ -44,10 +50,10 @@ def logout():
|
|||||||
|
|
||||||
|
|
||||||
@fallback(None)
|
@fallback(None)
|
||||||
def register_user():
|
def _register_user():
|
||||||
ip = get_ip(GoogleDnsProvider)
|
ip = get_ip(GoogleDnsProvider)
|
||||||
body = {"ip": ip, "apiversion": apiversion}
|
body = {"ip": ip, "apiversion": apiversion}
|
||||||
response = requests.post(urls.user, json=body)
|
response = requests.post(urls.user, json=body, timeout=10)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
return result["uid"]
|
return result["uid"]
|
||||||
|
|
||||||
@ -93,7 +99,7 @@ def is_subbed():
|
|||||||
return False, False
|
return False, False
|
||||||
|
|
||||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||||
response = requests.get(urls.subscription, params=body)
|
response = requests.get(urls.subscription, json=body)
|
||||||
|
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
return False, False
|
return False, False
|
||||||
@ -111,7 +117,12 @@ def unsub():
|
|||||||
|
|
||||||
|
|
||||||
def get_session(lazy=True):
|
def get_session(lazy=True):
|
||||||
global _session_id
|
"""
|
||||||
|
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
|
||||||
|
|
||||||
# lazy loading logic
|
# lazy loading logic
|
||||||
if lazy and _session_id is not None:
|
if lazy and _session_id is not None:
|
||||||
@ -122,22 +133,22 @@ def get_session(lazy=True):
|
|||||||
|
|
||||||
# then create session
|
# then create session
|
||||||
if uid:
|
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
|
# if not, create new id then try creating session again
|
||||||
else:
|
else:
|
||||||
uid = register_user()
|
uid = _register_user()
|
||||||
config.set("uid", uid, True)
|
config.set("uid", uid, True)
|
||||||
logging.debug(f"New User, generated new uid: {uid}")
|
logging.debug(f"New User, generated new uid: {uid}")
|
||||||
if uid:
|
if uid:
|
||||||
_session_id, online = _create_new_session(uid)
|
_session_id, _online = _create_new_session(uid)
|
||||||
else:
|
else:
|
||||||
online = False
|
_online = False
|
||||||
|
|
||||||
# when the user is already registered but session is not created as uid is not found
|
# 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:
|
if _online and not _session_id:
|
||||||
logging.error("user not found, generating new uid.. contact dev if you don't want to loose data")
|
logging.error("user not found, generating new uid.. contact dev if you don't want to loose data")
|
||||||
new_uid = register_user()
|
new_uid = _register_user()
|
||||||
_session_id, online = _create_new_session(new_uid)
|
_session_id, _online = _create_new_session(new_uid)
|
||||||
config.set("uid", new_uid, True)
|
config.set("uid", new_uid, True)
|
||||||
config.set("old_uid", uid, True)
|
config.set("old_uid", uid, True)
|
||||||
|
|
||||||
@ -147,7 +158,7 @@ def get_session(lazy=True):
|
|||||||
@fallback((None, False))
|
@fallback((None, False))
|
||||||
def _create_new_session(uid):
|
def _create_new_session(uid):
|
||||||
body = {"uid": uid, "apiversion": apiversion}
|
body = {"uid": uid, "apiversion": apiversion}
|
||||||
response = requests.post(urls.session, params=body)
|
response = requests.post(urls.session, json=body, timeout=10)
|
||||||
|
|
||||||
if response.status_code == 405:
|
if response.status_code == 405:
|
||||||
return None, True
|
return None, True
|
||||||
@ -158,7 +169,7 @@ def _create_new_session(uid):
|
|||||||
@fallback(False)
|
@fallback(False)
|
||||||
def has_beta():
|
def has_beta():
|
||||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||||
response = requests.get(urls.beta, params=body)
|
response = requests.get(urls.beta, json=body)
|
||||||
result = response.json()
|
result = response.json()
|
||||||
|
|
||||||
if not result["success"]:
|
if not result["success"]:
|
||||||
@ -170,5 +181,11 @@ def has_beta():
|
|||||||
@fallback(None)
|
@fallback(None)
|
||||||
def ping():
|
def ping():
|
||||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||||
response = requests.post(urls.ping, params=body)
|
response = requests.post(urls.ping, json=body)
|
||||||
logging.debug(f"ping response: {response.json()}")
|
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
|
urllib3
|
||||||
winshell
|
|
||||||
imutils
|
imutils
|
||||||
numpy!=1.19.4
|
numpy
|
||||||
opencv_python==4.5.2.54
|
opencv_python
|
||||||
Pillow
|
Pillow
|
||||||
pypiwin32
|
pypiwin32 ; platform_system=="Windows"
|
||||||
|
winshell ; platform_system=="Windows"
|
||||||
ttkthemes
|
ttkthemes
|
||||||
requests
|
requests
|
||||||
beautifulsoup4
|
|
||||||
whatsmyip
|
whatsmyip
|
||||||
pynput
|
pynput
|
||||||
keyboard
|
keyboard
|
||||||
playsound==1.2.2
|
playsound==1.2.2
|
||||||
event-scheduler
|
event-scheduler
|
||||||
mouse
|
mouse
|
||||||
d3dshot
|
pyautogui
|
||||||
|
mss
|
Loading…
x
Reference in New Issue
Block a user