mirror of
https://github.com/fishyboteso/fishyboteso.git
synced 2024-08-30 18:32:13 +00:00
Compare commits
152 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 | ||
|
3b2b23b8d9 | ||
|
3fbc67c49b | ||
|
72e65c0f8e | ||
|
1fb475794f | ||
|
39a12a0797 | ||
|
84e8150dd7 | ||
|
5f040aafd9 | ||
|
8b2ba7a600 | ||
|
fb08e29ae6 | ||
|
a6d7a2ce27 | ||
|
35e27f277b | ||
|
b67c047f0c | ||
|
241728d75c | ||
|
e1c7bd626d | ||
|
14e3c9aa84 | ||
|
13319e2606 | ||
|
a8a41a1660 | ||
|
a80e1ee84b | ||
|
3761ad813d | ||
|
fe4eab3076 | ||
|
a6ec33f30f | ||
|
4b2364818c | ||
|
0fd8a22e02 | ||
|
6c4b00775e | ||
|
c0690ae7fa | ||
|
8d41616720 | ||
|
fd7237161b | ||
|
d4a5297a97 | ||
|
2893465571 | ||
|
76e17c4502 | ||
|
c624557a41 | ||
|
608a8548fb | ||
|
fc3c8746c8 | ||
|
a12c397357 | ||
|
d22a4e79e5 | ||
|
c9c2982403 | ||
|
572604ff36 | ||
|
17c014d690 | ||
|
4ea27ae7da | ||
|
9bcde7e922 | ||
|
245493fbc4 | ||
|
a5236e9b30 | ||
|
0f35faa59f | ||
|
b79a7ed076 | ||
|
b0fb23a684 | ||
|
e0223a0902 | ||
|
3d393cef93 | ||
|
b745fd915b | ||
|
6326a83434 | ||
|
f77478d52b | ||
|
30411e785e | ||
|
f31fa11804 | ||
|
814ca1e0d5 | ||
|
d09dd42666 | ||
|
bde31fce85 | ||
|
6bb02778f9 | ||
|
9f0974abb3 | ||
|
8ae46dd5a5 | ||
|
4167ffcfac | ||
|
8054128664 | ||
|
5d18b8538e | ||
|
40451b1867 |
13
.github/FUNDING.yml
vendored
Normal file
13
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: AdamSaudagar
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://www.paypal.com/paypalme/AdamSaudagar']
|
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 README.md
|
||||
include requirements.txt
|
||||
include fishy/version.txt
|
||||
include fishy/icon.ico
|
||||
include fishy/fishybot_logo.png
|
||||
include fishy/sound.mp3
|
||||
|
19
README.md
19
README.md
@ -1,20 +1,11 @@
|
||||
# Fishybot ESO
|
||||
Auto fishing bot for Elder Scrolls Online. The Bot automatically fishes until the fishing hole disappears. It can also send a notification to the users phone with the statistics of that fishing hole.
|
||||
# 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 😉
|
||||
|
||||
It's not a fully automated bot, it does fishing on its own but you will have to move from one hole to another manually (although I was developing a fully automated bot, I didn't get a positive feedback from the community so I discontinued it).
|
||||
|
||||
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).
|
||||
- [How to Install ?](https://github.com/fishyboteso/fishyboteso/wiki/Installation)
|
||||
- Chat with us on [Discord](https://discord.gg/V6e2fpc).
|
||||
- Support us via [PayPal](https://www.paypal.me/AdamSaudagar) or [Patreon](https://www.patreon.com/AdamSaudagar).
|
||||
|
||||
### How to Install?
|
||||
|
||||
- Install [Python v3.7.3](https://www.python.org/downloads/release/python-373/) (make sure you tick, `Add Python to PATH`)
|
||||
- Then open PowerShell and type these commands, one by one,
|
||||
```
|
||||
python -m pip install pip --upgrade
|
||||
pip install fishy
|
||||
python -m fishy
|
||||
```
|
||||
For more Info, please refer our [Wiki](https://github.com/fishyboteso/fishyboteso/wiki).
|
||||
|
@ -1,2 +1,11 @@
|
||||
from fishy.__main__ import main
|
||||
__version__ = "0.5.0"
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# this prevents importing from package while setup
|
||||
def main():
|
||||
from fishy.__main__ import main as actual_main
|
||||
actual_main()
|
||||
|
||||
|
||||
__version__ = (Path(os.path.dirname(__file__)) / "version.txt").read_text()
|
||||
|
@ -1,104 +1,94 @@
|
||||
import ctypes
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
import win32con
|
||||
import win32gui
|
||||
|
||||
import fishy
|
||||
from fishy import gui, helper, web
|
||||
from fishy.constants import chalutier, lam2
|
||||
from fishy.gui import GUI, update_dialog, check_eula
|
||||
from fishy import helper, web
|
||||
from fishy.engine.common.event_handler import EngineEventHandler
|
||||
from fishy.gui import GUI, splash, update_dialog
|
||||
from fishy.gui.log_config import GuiLogger
|
||||
from fishy.gui.splash import Splash
|
||||
from fishy.helper import hotkey
|
||||
from fishy.helper.active_poll import active
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.hotkey.hotkey_process import hotkey
|
||||
|
||||
|
||||
def check_window_name(title):
|
||||
titles = ["Command Prompt", "PowerShell", "Fishy"]
|
||||
for t in titles:
|
||||
if t in title:
|
||||
return True
|
||||
return False
|
||||
from fishy.helper.migration import Migration
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def initialize(window_to_hide):
|
||||
helper.create_shortcut_first()
|
||||
helper.initialize_uid()
|
||||
def initialize():
|
||||
Migration.migrate()
|
||||
|
||||
if not config.get("shortcut_created", False):
|
||||
os_services.create_shortcut(False)
|
||||
config.set("shortcut_created", True)
|
||||
|
||||
new_session = web.get_session()
|
||||
|
||||
if new_session is None:
|
||||
logging.error("Couldn't create a session, some features might not work")
|
||||
print(f"created session {new_session}")
|
||||
logging.debug(f"created session {new_session}")
|
||||
|
||||
try:
|
||||
is_admin = os.getuid() == 0
|
||||
except AttributeError:
|
||||
is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
|
||||
if is_admin:
|
||||
if os_services.is_admin():
|
||||
logging.info("Running with admin privileges")
|
||||
|
||||
try:
|
||||
if helper.upgrade_avail() and not config.get("dont_ask_update", False):
|
||||
cv, hv = helper.versions()
|
||||
update_now, dont_ask_update = update_dialog.start(cv, hv)
|
||||
if dont_ask_update:
|
||||
config.set("dont_ask_update", dont_ask_update)
|
||||
else:
|
||||
config.delete("dont_ask_update")
|
||||
|
||||
if update_now:
|
||||
helper.auto_upgrade()
|
||||
except Exception:
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
if not config.get("debug", False) and check_window_name(win32gui.GetWindowText(window_to_hide)):
|
||||
win32gui.ShowWindow(window_to_hide, win32con.SW_HIDE)
|
||||
if not config.get("debug", False):
|
||||
os_services.hide_terminal()
|
||||
helper.install_thread_excepthook()
|
||||
sys.excepthook = helper.unhandled_exception_logging
|
||||
|
||||
if not config.get("addoninstalled", 0) or helper.get_addonversion(chalutier[0]) < chalutier[2]:
|
||||
helper.install_addon(*chalutier)
|
||||
helper.install_addon(*lam2)
|
||||
config.set("addoninstalled", helper.get_addonversion(chalutier[0]))
|
||||
helper.install_required_addons()
|
||||
|
||||
|
||||
def on_gui_load(gui, splash, logger):
|
||||
splash.finish()
|
||||
update_dialog.check_update(gui)
|
||||
logger.connect(gui)
|
||||
|
||||
|
||||
def main():
|
||||
active.init()
|
||||
config.init()
|
||||
splash.start()
|
||||
hotkey.init()
|
||||
|
||||
print("launching please wait...")
|
||||
|
||||
pil_logger = logging.getLogger('PIL')
|
||||
pil_logger.setLevel(logging.INFO)
|
||||
|
||||
window_to_hide = win32gui.GetForegroundWindow()
|
||||
|
||||
if not gui.check_eula():
|
||||
if not os_services.init():
|
||||
print("platform not supported")
|
||||
return
|
||||
|
||||
bot = EngineEventHandler(lambda: gui_window)
|
||||
gui_window = GUI(lambda: bot)
|
||||
config.init()
|
||||
if not check_eula():
|
||||
return
|
||||
|
||||
hotkey.start()
|
||||
splash = Splash()
|
||||
bot = EngineEventHandler(lambda: gui)
|
||||
gui = GUI(lambda: bot, lambda: on_gui_load(gui, splash, logger))
|
||||
logger = GuiLogger()
|
||||
hotkey.init()
|
||||
active.init()
|
||||
|
||||
logging.info(f"Fishybot v{fishy.__version__}")
|
||||
initialize(window_to_hide)
|
||||
try:
|
||||
config.init()
|
||||
if not check_eula():
|
||||
return
|
||||
|
||||
gui_window.start()
|
||||
active.start()
|
||||
logging.info(f"Fishybot v{fishy.__version__}")
|
||||
|
||||
bot.start_event_handler()
|
||||
config.stop()
|
||||
hotkey.stop()
|
||||
active.stop()
|
||||
splash.start()
|
||||
config.start_backup_scheduler()
|
||||
|
||||
initialize()
|
||||
|
||||
hotkey.start()
|
||||
gui.start()
|
||||
active.start()
|
||||
|
||||
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__":
|
||||
|
@ -1,6 +1,17 @@
|
||||
apiversion = 2
|
||||
chalutier = ("Chalutier", "https://www.esoui.com/downloads/dl2934/Chalutier_1.1.4.zip", 114)
|
||||
lam2 = ("LibAddonMenu-2.0", "https://www.esoui.com/downloads/dl7/LibAddonMenu-2.0r32.zip", 32)
|
||||
|
||||
fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/files/6329586/FishyQR.zip", 100)
|
||||
libgps = ("LibGPS", "https://cdn.esoui.com/downloads/file601/LibGPS_3_0_3.zip", 30)
|
||||
current_version_url = "https://raw.githubusercontent.com/fishyboteso/fishyboteso/main/fishy/version.txt"
|
||||
|
||||
# removed since 0.5.3
|
||||
chalutier = ("Chalutier", "https://cdn.esoui.com/downloads/file2934/Chalutier_1.3.zip", 130)
|
||||
|
||||
# addons used
|
||||
lam2 = ("LibAddonMenu-2.0", "https://cdn.esoui.com/downloads/file7/LibAddonMenu-2.0r34.zip", 34)
|
||||
fishyqr = ("FishyQR", "https://github.com/fishyboteso/FishyQR/releases/download/v1.8/FishyQR-1.8.zip", 180)
|
||||
fishyfsm = ("FishingStateMachine", "https://github.com/fishyboteso/FishingStateMachine/releases/download/fsm_v1.1/FishingStateMachine-1.1.zip", 110)
|
||||
libgps = ("LibGPS", "https://cdn.esoui.com/downloads/file601/LibGPS_v3.3.0.zip", 69)
|
||||
libmapping = ("LibMapPing", "https://cdn.esoui.com/downloads/file1302/LibMapPing_2_0_0.zip", 1236)
|
||||
libdl = ("LibDebugLogger", "https://cdn.esoui.com/downloads/file2275/LibDebugLogger_2_5_1.zip", 263)
|
||||
libchatmsg = ("LibChatMessage", "https://cdn.esoui.com/downloads/file2382/LibChatMessage_1_2_0.zip", 105)
|
||||
|
||||
d3dshot_git = "git+https://github.com/fauskanger/D3DShot.git#egg=D3DShot"
|
||||
|
@ -1 +1,2 @@
|
||||
from fishy.engine.semifisher.engine import SemiFisherEngine
|
||||
from fishy.engine.fullautofisher.engine import FullAuto
|
@ -1,21 +1,27 @@
|
||||
import logging
|
||||
import typing
|
||||
from abc import ABC, abstractmethod
|
||||
from threading import Thread
|
||||
from typing import Callable
|
||||
|
||||
import cv2
|
||||
|
||||
from fishy.engine.common.window import WindowClient
|
||||
from fishy.gui.funcs import GUIFuncsMock
|
||||
from fishy.helper.helper import print_exc
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from fishy.gui import GUI
|
||||
|
||||
|
||||
class IEngine(ABC):
|
||||
class IEngine:
|
||||
|
||||
def __init__(self, gui_ref: 'Callable[[], GUI]'):
|
||||
self.get_gui = gui_ref
|
||||
self.start = False
|
||||
# 0 - off, 1 - running, 2 - quitting
|
||||
self.state = 0
|
||||
self.window = None
|
||||
self.thread = None
|
||||
self.name = "default"
|
||||
|
||||
@property
|
||||
def gui(self):
|
||||
@ -24,10 +30,52 @@ class IEngine(ABC):
|
||||
|
||||
return self.get_gui().funcs
|
||||
|
||||
@abstractmethod
|
||||
def toggle_start(self):
|
||||
...
|
||||
@property
|
||||
def start(self):
|
||||
return self.state == 1
|
||||
|
||||
def toggle_start(self):
|
||||
if self.state == 0:
|
||||
self.turn_on()
|
||||
else:
|
||||
self.turn_off()
|
||||
|
||||
def turn_on(self):
|
||||
self.state = 1
|
||||
self.thread = Thread(target=self._crash_safe)
|
||||
self.thread.start()
|
||||
|
||||
def join(self):
|
||||
if self.thread:
|
||||
logging.debug(f"waiting for {self.name} engine")
|
||||
self.thread.join()
|
||||
|
||||
def turn_off(self):
|
||||
"""
|
||||
this method only signals the thread to close using start flag,
|
||||
its the responsibility of the thread to shut turn itself off
|
||||
"""
|
||||
if self.state == 1:
|
||||
logging.debug(f"sending turn off signal to {self.name} engine")
|
||||
self.state = 2
|
||||
else:
|
||||
logging.debug(f"{self.name} engine already signaled to turn off ")
|
||||
# todo: implement force turn off on repeated calls
|
||||
|
||||
# noinspection PyBroadException
|
||||
def _crash_safe(self):
|
||||
logging.debug(f"starting {self.name} engine thread")
|
||||
self.window = WindowClient()
|
||||
self.gui.bot_started(True)
|
||||
try:
|
||||
self.run()
|
||||
except Exception:
|
||||
logging.error(f"Unhandled exception occurred while running {self.name} engine")
|
||||
print_exc()
|
||||
self.state = 0
|
||||
self.gui.bot_started(False)
|
||||
self.window.destroy()
|
||||
logging.debug(f"{self.name} engine thread safely exiting")
|
||||
|
||||
@abstractmethod
|
||||
def run(self):
|
||||
...
|
||||
raise NotImplementedError
|
||||
|
@ -1,10 +1,13 @@
|
||||
import logging
|
||||
import time
|
||||
|
||||
from fishy.helper import auto_update
|
||||
|
||||
from fishy.engine import SemiFisherEngine
|
||||
from fishy.engine.fullautofisher.engine import FullAuto
|
||||
|
||||
|
||||
# to test only gui without engine code interfering
|
||||
class IEngineHandler:
|
||||
def __init__(self):
|
||||
...
|
||||
@ -18,10 +21,13 @@ class IEngineHandler:
|
||||
def toggle_fullfisher(self):
|
||||
...
|
||||
|
||||
def check_pixel_val(self):
|
||||
def check_qr_val(self):
|
||||
...
|
||||
|
||||
def quit(self):
|
||||
def set_update(self, version):
|
||||
...
|
||||
|
||||
def quit_me(self):
|
||||
...
|
||||
|
||||
|
||||
@ -31,6 +37,9 @@ class EngineEventHandler(IEngineHandler):
|
||||
self.event_handler_running = True
|
||||
self.event = []
|
||||
|
||||
self.update_flag = False
|
||||
self.to_version = ""
|
||||
|
||||
self.semi_fisher_engine = SemiFisherEngine(gui_ref)
|
||||
self.full_fisher_engine = FullAuto(gui_ref)
|
||||
|
||||
@ -47,18 +56,33 @@ class EngineEventHandler(IEngineHandler):
|
||||
def toggle_fullfisher(self):
|
||||
self.event.append(self.full_fisher_engine.toggle_start)
|
||||
|
||||
def check_pixel_val(self):
|
||||
def check_qr_val(self):
|
||||
def func():
|
||||
if self.semi_fisher_engine.start:
|
||||
self.semi_fisher_engine.show_pixel_vals()
|
||||
self.semi_fisher_engine.show_qr_vals()
|
||||
else:
|
||||
logging.debug("Start the engine first before running this command")
|
||||
|
||||
self.event.append(func)
|
||||
|
||||
def quit(self):
|
||||
def set_update(self, version):
|
||||
self.to_version = version
|
||||
self.update_flag = True
|
||||
self.quit_me()
|
||||
|
||||
def stop(self):
|
||||
self.semi_fisher_engine.join()
|
||||
self.full_fisher_engine.join()
|
||||
if self.update_flag:
|
||||
auto_update.update_now(self.to_version)
|
||||
|
||||
def quit_me(self):
|
||||
def func():
|
||||
self.semi_fisher_engine.start = False
|
||||
if self.semi_fisher_engine.start:
|
||||
self.semi_fisher_engine.turn_off()
|
||||
if self.full_fisher_engine.start:
|
||||
self.semi_fisher_engine.turn_off()
|
||||
|
||||
self.event_handler_running = False
|
||||
|
||||
self.event.append(func)
|
||||
|
64
fishy/engine/common/qr_detection.py
Normal file
64
fishy/engine/common/qr_detection.py
Normal file
@ -0,0 +1,64 @@
|
||||
import logging
|
||||
import re
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from fishy.engine.common.window import WindowClient
|
||||
|
||||
detector = cv2.QRCodeDetector()
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def get_values(window: WindowClient):
|
||||
values = None
|
||||
for _ in range(6):
|
||||
img = window.processed_image()
|
||||
if img is None:
|
||||
logging.debug("Couldn't capture window.")
|
||||
continue
|
||||
|
||||
if not window.crop:
|
||||
window.crop = _get_qr_location(img)
|
||||
if not window.crop:
|
||||
logging.debug("FishyQR not found.")
|
||||
continue
|
||||
|
||||
values = _get_values_from_image(img)
|
||||
if not values:
|
||||
window.crop = None
|
||||
logging.debug("Values not able to read.")
|
||||
continue
|
||||
break
|
||||
|
||||
return values
|
||||
|
||||
|
||||
def _get_qr_location(image):
|
||||
"""
|
||||
code from https://stackoverflow.com/a/45770227/4512396
|
||||
"""
|
||||
success, points = detector.detect(image)
|
||||
if not success:
|
||||
return None
|
||||
|
||||
p = points[0]
|
||||
# (x, y, x + w, y + h)
|
||||
return [int(x) for x in [p[0][0], p[0][1], p[1][0], p[2][1]]]
|
||||
|
||||
|
||||
def _get_values_from_image(img):
|
||||
h, w = img.shape
|
||||
points = np.array([[(0, 0), (w, 0), (w, h), (0, h)]])
|
||||
code = detector.decode(img, points)[0]
|
||||
return _parse_qr_code(code)
|
||||
|
||||
|
||||
# this needs to be updated each time qr code format is changed
|
||||
def _parse_qr_code(code):
|
||||
if not code:
|
||||
return None
|
||||
match = re.match(r'^(-?\d+\.\d+),(-?\d+\.\d+),(-?\d+),(\d+)$', code)
|
||||
if not match:
|
||||
logging.warning(f"qr code is not what was expected {code}")
|
||||
return None
|
||||
return [float(match.group(1)), float(match.group(2)), int(match.group(3)), int(match.group(4))]
|
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 uuid
|
||||
from typing import List
|
||||
|
||||
import cv2
|
||||
@ -7,66 +8,31 @@ import imutils
|
||||
from fishy.engine.common import window_server
|
||||
from fishy.engine.common.window_server import Status, WindowServer
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
|
||||
|
||||
class WindowClient:
|
||||
clients: List['WindowClient'] = []
|
||||
|
||||
def __init__(self, crop=None, color=None, scale=None, show_name=None):
|
||||
def __init__(self):
|
||||
"""
|
||||
create a window instance with these pre process
|
||||
:param crop: [x1,y1,x2,y2] array defining the boundaries to crop
|
||||
:param color: color to use example cv2.COLOR_RGB2HSV
|
||||
:param scale: scaling the window
|
||||
"""
|
||||
self.color = color
|
||||
self.crop = crop
|
||||
self.scale = scale
|
||||
self.show_name = show_name
|
||||
self.crop = None
|
||||
self.scale = None
|
||||
self.show_name = f"window client {len(WindowClient.clients)}"
|
||||
|
||||
if len(WindowClient.clients) == 0:
|
||||
window_server.start()
|
||||
WindowClient.clients.append(self)
|
||||
|
||||
def destory(self):
|
||||
if self in WindowClient.clients:
|
||||
WindowClient.clients.remove(self)
|
||||
if len(WindowClient.clients) == 0:
|
||||
window_server.stop()
|
||||
if len(WindowClient.clients) > 0 and WindowServer.status != Status.RUNNING:
|
||||
window_server.start()
|
||||
|
||||
@staticmethod
|
||||
def running():
|
||||
return WindowServer.status == Status.RUNNING
|
||||
|
||||
def get_capture(self):
|
||||
"""
|
||||
copies the recorded screen and then pre processes its
|
||||
:return: game window image
|
||||
"""
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
return None
|
||||
|
||||
if not window_server.screen_ready():
|
||||
print("waiting for screen...")
|
||||
helper.wait_until(window_server.screen_ready)
|
||||
print("screen ready, continuing...")
|
||||
|
||||
temp_img = WindowServer.Screen
|
||||
|
||||
if temp_img is None or temp_img.size == 0:
|
||||
return None
|
||||
|
||||
if self.color is not None:
|
||||
temp_img = cv2.cvtColor(temp_img, self.color)
|
||||
|
||||
if self.crop is not None:
|
||||
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
|
||||
|
||||
if self.scale is not None:
|
||||
temp_img = cv2.resize(temp_img, (self.scale[0], self.scale[1]), interpolation=cv2.INTER_AREA)
|
||||
|
||||
return temp_img
|
||||
|
||||
def processed_image(self, func=None):
|
||||
"""
|
||||
processes the image using the function provided
|
||||
@ -76,40 +42,63 @@ class WindowClient:
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
return None
|
||||
|
||||
img = self.get_capture()
|
||||
img = self._get_capture()
|
||||
|
||||
if img is None:
|
||||
return None
|
||||
|
||||
if func is None:
|
||||
return img
|
||||
else:
|
||||
return func(img)
|
||||
if func:
|
||||
img = func(img)
|
||||
|
||||
def show(self, to_show, resize=None, func=None):
|
||||
if config.get("show_grab", 0):
|
||||
self._show(img)
|
||||
|
||||
return img
|
||||
|
||||
def destroy(self):
|
||||
if self in WindowClient.clients:
|
||||
WindowClient.clients.remove(self)
|
||||
if len(WindowClient.clients) == 0:
|
||||
window_server.stop()
|
||||
|
||||
def _get_capture(self):
|
||||
"""
|
||||
copies the recorded screen and then pre processes its
|
||||
:return: game window image
|
||||
"""
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
return None
|
||||
|
||||
if not window_server.screen_ready():
|
||||
logging.debug("waiting for screen...")
|
||||
helper.wait_until(window_server.screen_ready)
|
||||
logging.debug("screen ready, continuing...")
|
||||
|
||||
temp_img = WindowServer.Screen
|
||||
|
||||
if temp_img is None or temp_img.size == 0:
|
||||
return None
|
||||
|
||||
temp_img = cv2.cvtColor(temp_img, cv2.COLOR_RGB2GRAY)
|
||||
|
||||
if self.crop is not None:
|
||||
temp_img = temp_img[self.crop[1]:self.crop[3], self.crop[0]:self.crop[2]]
|
||||
|
||||
if self.scale is not None:
|
||||
temp_img = cv2.resize(temp_img, (self.scale[0], self.scale[1]), interpolation=cv2.INTER_AREA)
|
||||
|
||||
# need ot check again after crop/resize
|
||||
if temp_img.size == 0:
|
||||
return None
|
||||
|
||||
return temp_img
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def _show(self, img):
|
||||
"""
|
||||
Displays the processed image for debugging purposes
|
||||
:param ready_img: send ready image, just show the `ready_img` directly
|
||||
:param resize: scale the image to make small images more visible
|
||||
:param func: function to process the image
|
||||
"""
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
return
|
||||
|
||||
if not self.show_name:
|
||||
logging.warning("You need to assign a name first")
|
||||
return
|
||||
|
||||
if not to_show:
|
||||
cv2.destroyWindow(self.show_name)
|
||||
return
|
||||
|
||||
img = self.processed_image(func)
|
||||
|
||||
if img is None:
|
||||
return
|
||||
|
||||
if resize is not None:
|
||||
img = imutils.resize(img, width=resize)
|
||||
cv2.imshow(self.show_name, img)
|
||||
cv2.waitKey(25)
|
||||
helper.save_img(self.show_name, img, True)
|
||||
|
@ -1,16 +1,16 @@
|
||||
import logging
|
||||
import math
|
||||
from enum import Enum
|
||||
from threading import Thread
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import pywintypes
|
||||
import win32gui
|
||||
from PIL import ImageGrab
|
||||
from win32api import GetSystemMetrics
|
||||
from mss.base import MSSBase
|
||||
|
||||
from fishy.engine.common import screenshot
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.helper import print_exc
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
@ -23,10 +23,11 @@ class WindowServer:
|
||||
"""
|
||||
Records the game window, and allows to create instance to process it
|
||||
"""
|
||||
Screen = None
|
||||
Screen: np.ndarray = None
|
||||
windowOffset = None
|
||||
hwnd = None
|
||||
status = Status.STOPPED
|
||||
sslib = None
|
||||
crop = None
|
||||
|
||||
|
||||
def init():
|
||||
@ -34,15 +35,39 @@ def init():
|
||||
Executed once before the main loop,
|
||||
Finds the game window, and calculates the offset to remove the title bar
|
||||
"""
|
||||
try:
|
||||
WindowServer.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
|
||||
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
|
||||
except pywintypes.error:
|
||||
logging.error("Game window not found")
|
||||
WindowServer.sslib = screenshot.create()
|
||||
# Check if the screenshot library was successfully created
|
||||
if WindowServer.sslib is None:
|
||||
logging.error("Failed to create screenshot library instance")
|
||||
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():
|
||||
@ -50,36 +75,28 @@ def loop():
|
||||
Executed in the start of the main loop
|
||||
finds the game window location and captures it
|
||||
"""
|
||||
bbox = (0, 0, GetSystemMetrics(0), GetSystemMetrics(1))
|
||||
WindowServer.Screen = get_cropped_screenshot()
|
||||
|
||||
temp_screen = np.array(ImageGrab.grab(bbox=bbox))
|
||||
|
||||
rect = win32gui.GetWindowRect(WindowServer.hwnd)
|
||||
client_rect = win32gui.GetClientRect(WindowServer.hwnd)
|
||||
|
||||
fullscreen = GetSystemMetrics(1) == (rect[3] - rect[1])
|
||||
titleOffset = ((rect[3] - rect[1]) - client_rect[3]) - WindowServer.windowOffset if not fullscreen else 0
|
||||
|
||||
crop = (
|
||||
rect[0] + WindowServer.windowOffset, rect[1] + titleOffset, rect[2] - WindowServer.windowOffset,
|
||||
rect[3] - WindowServer.windowOffset)
|
||||
|
||||
WindowServer.Screen = temp_screen[crop[1]:crop[3], crop[0]:crop[2]]
|
||||
|
||||
if WindowServer.Screen.size == 0:
|
||||
logging.error("Don't minimize or drag game window outside the screen")
|
||||
if WindowServer.Screen is None:
|
||||
logging.error("Couldn't find the game window")
|
||||
WindowServer.status = Status.CRASHED
|
||||
|
||||
|
||||
def loop_end():
|
||||
cv2.waitKey(25)
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def run():
|
||||
# todo use config
|
||||
logging.debug("window server started")
|
||||
while WindowServer.status == Status.RUNNING:
|
||||
loop()
|
||||
loop_end()
|
||||
try:
|
||||
loop()
|
||||
except Exception:
|
||||
print_exc()
|
||||
WindowServer.status = Status.CRASHED
|
||||
|
||||
if WindowServer.status == Status.CRASHED:
|
||||
logging.debug("window server crashed")
|
||||
elif WindowServer.status == Status.STOPPED:
|
||||
logging.debug("window server stopped")
|
||||
|
||||
|
||||
def start():
|
||||
|
@ -4,6 +4,8 @@ from pynput.keyboard import Key
|
||||
|
||||
from fishy.helper import hotkey
|
||||
|
||||
# todo: unused code remove it
|
||||
|
||||
|
||||
def get_controls(controls: 'Controls'):
|
||||
controls = [
|
||||
|
@ -1,14 +1,11 @@
|
||||
import math
|
||||
import logging
|
||||
import math
|
||||
import time
|
||||
import traceback
|
||||
from threading import Thread
|
||||
|
||||
import cv2
|
||||
from fishy.engine.common import qr_detection
|
||||
from pynput import keyboard, mouse
|
||||
|
||||
from fishy.constants import fishyqr, lam2, libgps
|
||||
from fishy.engine import SemiFisherEngine
|
||||
from fishy.engine.common.IEngine import IEngine
|
||||
from fishy.engine.common.window import WindowClient
|
||||
@ -16,28 +13,16 @@ from fishy.engine.fullautofisher.mode.calibrator import Calibrator
|
||||
from fishy.engine.fullautofisher.mode.imode import FullAutoMode
|
||||
from fishy.engine.fullautofisher.mode.player import Player
|
||||
from fishy.engine.fullautofisher.mode.recorder import Recorder
|
||||
from fishy.engine.fullautofisher.qr_detection import (get_qr_location,
|
||||
get_values_from_image)
|
||||
from fishy.engine.semifisher import fishing_event, fishing_mode
|
||||
from fishy.engine.semifisher import fishing_mode
|
||||
from fishy.engine.semifisher.fishing_mode import FishingMode
|
||||
from fishy.helper import helper, hotkey
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.helper import log_raise, wait_until, is_eso_active
|
||||
from fishy.helper.helper import sign
|
||||
from fishy.helper.helper import wait_until, sign, print_exc
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
mse = mouse.Controller()
|
||||
kb = keyboard.Controller()
|
||||
|
||||
|
||||
def image_pre_process(img):
|
||||
scale_percent = 100 # percent of original size
|
||||
width = int(img.shape[1] * scale_percent / 100)
|
||||
height = int(img.shape[0] * scale_percent / 100)
|
||||
dim = (width, height)
|
||||
img = cv2.resize(img, dim, interpolation=cv2.INTER_AREA)
|
||||
return img
|
||||
|
||||
|
||||
class FullAuto(IEngine):
|
||||
rotate_by = 30
|
||||
|
||||
@ -45,7 +30,7 @@ class FullAuto(IEngine):
|
||||
from fishy.engine.fullautofisher.test import Test
|
||||
|
||||
super().__init__(gui_ref)
|
||||
self._hole_found_flag = False
|
||||
self.name = "FullAuto"
|
||||
self._curr_rotate_y = 0
|
||||
|
||||
self.fisher = SemiFisherEngine(None)
|
||||
@ -56,15 +41,6 @@ class FullAuto(IEngine):
|
||||
self.mode = None
|
||||
|
||||
def run(self):
|
||||
|
||||
addons_req = [libgps, lam2, fishyqr]
|
||||
for addon in addons_req:
|
||||
if not helper.addon_exists(*addon):
|
||||
helper.install_addon(*addon)
|
||||
|
||||
self.gui.bot_started(True)
|
||||
self.window = WindowClient(color=cv2.COLOR_RGB2GRAY, show_name="Full auto debug")
|
||||
|
||||
self.mode = None
|
||||
if config.get("calibrate", False):
|
||||
self.mode = Calibrator(self)
|
||||
@ -72,56 +48,44 @@ class FullAuto(IEngine):
|
||||
self.mode = Player(self)
|
||||
elif FullAutoMode(config.get("full_auto_mode", 0)) == FullAutoMode.Recorder:
|
||||
self.mode = Recorder(self)
|
||||
else:
|
||||
logging.error("not a valid mode selected")
|
||||
return
|
||||
|
||||
if not is_eso_active():
|
||||
# block thread until game window becomes active
|
||||
if not os_services.is_eso_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:
|
||||
logging.info("starting in 2 secs...")
|
||||
time.sleep(2)
|
||||
|
||||
if not (type(self.mode) is Calibrator) and not self.calibrator.all_calibrated():
|
||||
logging.error("you need to calibrate first")
|
||||
return
|
||||
|
||||
if not qr_detection.get_values(self.window):
|
||||
logging.error("FishyQR not found, if its not hidden, try to drag it around, "
|
||||
"or increase/decrease its size and try again\nStopping engine...")
|
||||
return
|
||||
|
||||
if config.get("tabout_stop", 1):
|
||||
self.stop_on_inactive()
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
if self.window.get_capture() is None:
|
||||
log_raise("Game window not found")
|
||||
|
||||
self.window.crop = get_qr_location(self.window.get_capture())
|
||||
if self.window.crop is None:
|
||||
log_raise("FishyQR not found")
|
||||
|
||||
if not (type(self.mode) is Calibrator) and not self.calibrator.all_calibrated():
|
||||
log_raise("you need to calibrate first")
|
||||
|
||||
self.fisher.toggle_start()
|
||||
fishing_event.unsubscribe()
|
||||
if self.show_crop:
|
||||
self.start_show()
|
||||
|
||||
if config.get("tabout_stop", 1):
|
||||
self.stop_on_inactive()
|
||||
|
||||
self.mode.run()
|
||||
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
self.start = False
|
||||
|
||||
self.gui.bot_started(False)
|
||||
self.window.show(False)
|
||||
logging.info("Quitting")
|
||||
self.window.destory()
|
||||
self.fisher.toggle_start()
|
||||
|
||||
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()
|
||||
logging.error("exception occurred while running full auto mode")
|
||||
print_exc()
|
||||
|
||||
def stop_on_inactive(self):
|
||||
def func():
|
||||
wait_until(lambda: not is_eso_active())
|
||||
self.start = False
|
||||
logging.debug("stop on inactive started")
|
||||
wait_until(lambda: not os_services.is_eso_active() or not self.start)
|
||||
if self.start and not os_services.is_eso_active():
|
||||
self.turn_off()
|
||||
logging.debug("stop on inactive stopped")
|
||||
Thread(target=func).start()
|
||||
|
||||
def get_coords(self):
|
||||
@ -131,21 +95,21 @@ class FullAuto(IEngine):
|
||||
todo find a better way of handling None: switch from start bool to state which knows
|
||||
todo its waiting for qr which doesn't block the engine when commanded to close
|
||||
"""
|
||||
img = self.window.processed_image(func=image_pre_process)
|
||||
return get_values_from_image(img)
|
||||
values = qr_detection.get_values(self.window)
|
||||
return values[:3] if values else None
|
||||
|
||||
def move_to(self, target) -> bool:
|
||||
current = self.get_coords()
|
||||
if not current:
|
||||
return False
|
||||
|
||||
print(f"Moving from {(current[0], current[1])} to {target}")
|
||||
logging.debug(f"Moving from {(current[0], current[1])} to {target}")
|
||||
move_vec = target[0] - current[0], target[1] - current[1]
|
||||
|
||||
dist = math.sqrt(move_vec[0] ** 2 + move_vec[1] ** 2)
|
||||
print(f"distance: {dist}")
|
||||
logging.debug(f"distance: {dist}")
|
||||
if dist < 5e-05:
|
||||
print("distance very small skipping")
|
||||
logging.debug("distance very small skipping")
|
||||
return True
|
||||
|
||||
target_angle = math.degrees(math.atan2(-move_vec[1], move_vec[0])) + 90
|
||||
@ -155,11 +119,15 @@ class FullAuto(IEngine):
|
||||
return False
|
||||
|
||||
walking_time = dist / self.calibrator.move_factor
|
||||
print(f"walking for {walking_time}")
|
||||
kb.press('w')
|
||||
logging.debug(f"walking for {walking_time}")
|
||||
|
||||
forward_key = config.get("forward_key", 'w')
|
||||
kb.press(forward_key)
|
||||
time.sleep(walking_time)
|
||||
kb.release('w')
|
||||
print("done")
|
||||
kb.release(forward_key)
|
||||
|
||||
logging.debug("done")
|
||||
# todo: maybe check if it reached the destination before returning true?
|
||||
return True
|
||||
|
||||
def rotate_to(self, target_angle, from_angle=None) -> bool:
|
||||
@ -173,7 +141,7 @@ class FullAuto(IEngine):
|
||||
target_angle = 360 + target_angle
|
||||
while target_angle > 360:
|
||||
target_angle -= 360
|
||||
print(f"Rotating from {from_angle} to {target_angle}")
|
||||
logging.debug(f"Rotating from {from_angle} to {target_angle}")
|
||||
|
||||
angle_diff = target_angle - from_angle
|
||||
|
||||
@ -182,7 +150,7 @@ class FullAuto(IEngine):
|
||||
|
||||
rotate_times = int(angle_diff / self.calibrator.rot_factor) * -1
|
||||
|
||||
print(f"rotate_times: {rotate_times}")
|
||||
logging.debug(f"rotate_times: {rotate_times}")
|
||||
|
||||
for _ in range(abs(rotate_times)):
|
||||
mse.move(sign(rotate_times) * FullAuto.rotate_by * -1, 0)
|
||||
@ -191,30 +159,23 @@ class FullAuto(IEngine):
|
||||
return True
|
||||
|
||||
def look_for_hole(self) -> bool:
|
||||
self._hole_found_flag = False
|
||||
valid_states = [fishing_mode.State.LOOKING, fishing_mode.State.FISHING]
|
||||
_hole_found_flag = FishingMode.CurrentMode in valid_states
|
||||
|
||||
if FishingMode.CurrentMode == fishing_mode.State.LOOKING:
|
||||
return True
|
||||
|
||||
def found_hole(e):
|
||||
if e == fishing_mode.State.LOOKING:
|
||||
self._hole_found_flag = True
|
||||
|
||||
fishing_mode.subscribers.append(found_hole)
|
||||
# if vertical movement is disabled
|
||||
if not config.get("look_for_hole", 0):
|
||||
return _hole_found_flag
|
||||
|
||||
t = 0
|
||||
while not self._hole_found_flag and t <= 1.25:
|
||||
mse.move(0, FullAuto.rotate_by)
|
||||
while not _hole_found_flag and t <= 2.5:
|
||||
direction = -1 if t > 1.25 else 1
|
||||
mse.move(0, FullAuto.rotate_by*direction)
|
||||
time.sleep(0.05)
|
||||
t += 0.05
|
||||
while not self._hole_found_flag and t > 0:
|
||||
mse.move(0, -FullAuto.rotate_by)
|
||||
time.sleep(0.05)
|
||||
t -= 0.05
|
||||
_hole_found_flag = FishingMode.CurrentMode in valid_states
|
||||
|
||||
self._curr_rotate_y = t
|
||||
fishing_mode.subscribers.remove(found_hole)
|
||||
return self._hole_found_flag
|
||||
return _hole_found_flag
|
||||
|
||||
def rotate_back(self):
|
||||
while self._curr_rotate_y > 0.01:
|
||||
@ -222,16 +183,8 @@ class FullAuto(IEngine):
|
||||
time.sleep(0.05)
|
||||
self._curr_rotate_y -= 0.05
|
||||
|
||||
def toggle_start(self):
|
||||
self.start = not self.start
|
||||
if self.start:
|
||||
self.thread = Thread(target=self.run)
|
||||
self.thread.start()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.getLogger("").setLevel(logging.DEBUG)
|
||||
hotkey.initalize()
|
||||
# noinspection PyTypeChecker
|
||||
bot = FullAuto(None)
|
||||
bot.toggle_start()
|
||||
|
@ -20,24 +20,6 @@ kb = keyboard.Controller()
|
||||
offset = 0
|
||||
|
||||
|
||||
def get_crop_coods(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
|
||||
|
||||
|
||||
def _update_factor(key, value):
|
||||
full_auto_factors = config.get("full_auto_factors", {})
|
||||
full_auto_factors[key] = value
|
||||
@ -75,25 +57,27 @@ class Calibrator(IMode):
|
||||
def _walk_calibrate(self):
|
||||
walking_time = 3
|
||||
|
||||
coods = self.engine.get_coords()
|
||||
if coods is None:
|
||||
coords = self.engine.get_coords()
|
||||
if coords is None:
|
||||
return
|
||||
|
||||
x1, y1, rot1 = coods
|
||||
x1, y1, rot1 = coords
|
||||
|
||||
kb.press('w')
|
||||
forward_key = config.get("forward_key", 'w')
|
||||
kb.press(forward_key)
|
||||
time.sleep(walking_time)
|
||||
kb.release('w')
|
||||
kb.release(forward_key)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
coods = self.engine.get_coords()
|
||||
if coods is None:
|
||||
coords = self.engine.get_coords()
|
||||
if coords is None:
|
||||
return
|
||||
x2, y2, rot2 = coods
|
||||
x2, y2, rot2 = coords
|
||||
|
||||
move_factor = math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / walking_time
|
||||
_update_factor("move_factor", move_factor)
|
||||
logging.info("walk calibrate done")
|
||||
logging.info(f"walk calibrate done, move_factor: {move_factor}")
|
||||
|
||||
def _rotate_calibrate(self):
|
||||
from fishy.engine.fullautofisher.engine import FullAuto
|
||||
@ -119,7 +103,7 @@ class Calibrator(IMode):
|
||||
|
||||
rot_factor = (rot3 - rot2) / rotate_times
|
||||
_update_factor("rot_factor", rot_factor)
|
||||
logging.info("rotate calibrate done")
|
||||
logging.info(f"rotate calibrate done, rot_factor: {rot_factor}")
|
||||
|
||||
def run(self):
|
||||
self._walk_calibrate()
|
||||
|
@ -2,14 +2,11 @@ import logging
|
||||
import math
|
||||
import pickle
|
||||
import time
|
||||
from pprint import pprint
|
||||
|
||||
import typing
|
||||
from threading import Thread
|
||||
|
||||
from fishy.engine.fullautofisher.mode.imode import IMode
|
||||
from fishy.engine.semifisher import fishing_event, fishing_mode
|
||||
from fishy.helper.helper import log_raise, wait_until, kill_thread
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from fishy.engine.fullautofisher.engine import FullAuto
|
||||
@ -56,23 +53,29 @@ class Player(IMode):
|
||||
self.timeline = None
|
||||
|
||||
def run(self):
|
||||
self._init()
|
||||
if not self._init():
|
||||
return
|
||||
|
||||
while self.engine.start:
|
||||
self._loop()
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.info("player stopped")
|
||||
|
||||
def _init(self):
|
||||
def _init(self) -> bool:
|
||||
self.timeline = get_rec_file()
|
||||
if not self.timeline:
|
||||
log_raise("data not found, can't start")
|
||||
logging.info("starting player")
|
||||
logging.error("data not found, can't start")
|
||||
return False
|
||||
|
||||
coords = self.engine.get_coords()
|
||||
if not coords:
|
||||
log_raise("QR not found")
|
||||
logging.error("QR not found")
|
||||
return False
|
||||
|
||||
self.i = find_nearest(self.timeline, coords)[0]
|
||||
logging.info("starting player")
|
||||
return True
|
||||
|
||||
def _loop(self):
|
||||
action = self.timeline[self.i]
|
||||
@ -87,21 +90,23 @@ class Player(IMode):
|
||||
if not self.engine.rotate_to(action[1][2]):
|
||||
return
|
||||
|
||||
fishing_mode.subscribers.append(self._hole_complete_callback)
|
||||
fishing_event.subscribe()
|
||||
self.engine.fisher.turn_on()
|
||||
helper.wait_until(lambda: self.engine.fisher.first_loop_done)
|
||||
# scan for fish hole
|
||||
logging.info("scanning")
|
||||
# if found start fishing and wait for hole to complete
|
||||
if self.engine.look_for_hole():
|
||||
logging.info("starting fishing")
|
||||
fishing_mode.subscribers.append(self._hole_complete_callback)
|
||||
self.hole_complete_flag = False
|
||||
helper.wait_until(lambda: self.hole_complete_flag or not self.engine.start)
|
||||
fishing_mode.subscribers.remove(self._hole_complete_callback)
|
||||
|
||||
self.engine.rotate_back()
|
||||
else:
|
||||
logging.info("no hole found")
|
||||
# continue when hole completes
|
||||
fishing_mode.subscribers.remove(self._hole_complete_callback)
|
||||
fishing_event.unsubscribe()
|
||||
self.engine.fisher.turn_off()
|
||||
|
||||
self.next()
|
||||
|
||||
|
@ -9,20 +9,18 @@ import typing
|
||||
from tkinter.filedialog import asksaveasfile
|
||||
|
||||
from fishy.engine.fullautofisher.mode import player
|
||||
from fishy.helper import helper
|
||||
|
||||
from fishy.helper.helper import empty_function, log_raise
|
||||
from fishy.helper.helper import empty_function
|
||||
from fishy.helper.hotkey.process import Key
|
||||
|
||||
from fishy.helper.popup import PopUp
|
||||
from playsound import playsound
|
||||
|
||||
from fishy.helper.config import config
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from fishy.engine.fullautofisher.engine import FullAuto
|
||||
from fishy.engine.fullautofisher.mode.imode import IMode
|
||||
from fishy.helper.hotkey.hotkey_process import HotKey, hotkey
|
||||
from fishy.helper.hotkey.hotkey_process import hotkey
|
||||
|
||||
|
||||
class Recorder(IMode):
|
||||
@ -49,48 +47,50 @@ class Recorder(IMode):
|
||||
|
||||
old_timeline = player.get_rec_file()
|
||||
if not old_timeline:
|
||||
log_raise("Edit mode selected, but no fishy file selected")
|
||||
logging.error("Edit mode selected, but no fishy file selected")
|
||||
return
|
||||
|
||||
coords = self.engine.get_coords()
|
||||
if not coords:
|
||||
log_raise("QR not found")
|
||||
logging.error("QR not found")
|
||||
return
|
||||
|
||||
start_from = player.find_nearest(old_timeline, coords)
|
||||
if not self.engine.move_to(start_from[2]):
|
||||
log_raise("QR not found")
|
||||
logging.error("QR not found")
|
||||
return
|
||||
|
||||
logging.info("starting, press LMB to mark hole")
|
||||
hotkey.hook(Key.LMB, self._mark_hole)
|
||||
|
||||
self.timeline = []
|
||||
|
||||
last_coord = None
|
||||
while self.engine.start:
|
||||
start_time = time.time()
|
||||
coods = self.engine.get_coords()
|
||||
if not coods:
|
||||
coords = self.engine.get_coords()
|
||||
if not coords:
|
||||
logging.warning("missed a frame, as qr not be read properly...")
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
self.timeline.append(("move_to", (coods[0], coods[1])))
|
||||
self.timeline.append(("move_to", (coords[0], coords[1])))
|
||||
|
||||
# maintaining constant frequency for recording
|
||||
time_took = time.time() - start_time
|
||||
if time_took <= Recorder.recording_fps:
|
||||
time.sleep(Recorder.recording_fps - time_took)
|
||||
else:
|
||||
logging.warning("Took too much time to record")
|
||||
last_coord = coords
|
||||
|
||||
hotkey.free(Key.LMB)
|
||||
|
||||
if config.get("edit_recorder_mode"):
|
||||
logging.info("moving to nearest coord in recording")
|
||||
|
||||
# todo allow the user the chance to wait for qr
|
||||
coords = self.engine.get_coords()
|
||||
if not coords:
|
||||
log_raise("QR not found")
|
||||
|
||||
end = player.find_nearest(old_timeline, coords)
|
||||
end = player.find_nearest(old_timeline, last_coord)
|
||||
self.engine.move_to(end[2])
|
||||
|
||||
# recording stitching
|
||||
part1 = old_timeline[:start_from[0]]
|
||||
part2 = old_timeline[end[0]:]
|
||||
self.timeline = part1 + self.timeline + part2
|
||||
@ -99,7 +99,7 @@ class Recorder(IMode):
|
||||
|
||||
def _open_save_popup(self):
|
||||
top = PopUp(empty_function, self.engine.get_gui()._root, background=self.engine.get_gui()._root["background"])
|
||||
controls_frame = ttk.Frame(top)
|
||||
recorder_frame = ttk.Frame(top)
|
||||
top.title("Save Recording?")
|
||||
|
||||
button = [-1]
|
||||
@ -109,14 +109,15 @@ class Recorder(IMode):
|
||||
top.quit_top()
|
||||
|
||||
selected_text = f"\n\nSelected: {os.path.basename(config.get('full_auto_rec_file'))}" if config.get('edit_recorder_mode') else ""
|
||||
ttk.Label(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
|
||||
ttk.Button(controls_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0, pady=(5, 0))
|
||||
ttk.Button(controls_frame, text="Save As", command=lambda: button_pressed(1)).grid(row=1, column=1)
|
||||
ttk.Button(controls_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2)
|
||||
ttk.Button(recorder_frame, text="Overwrite", command=lambda: button_pressed(0), state=_overwrite).grid(row=1, column=0, pady=(5, 0))
|
||||
ttk.Button(recorder_frame, text="Save As", command=lambda: button_pressed(1)).grid(row=1, column=1)
|
||||
ttk.Button(recorder_frame, text="Cancel", command=lambda: button_pressed(2)).grid(row=1, column=2)
|
||||
|
||||
controls_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||
recorder_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||
recorder_frame.update()
|
||||
top.start()
|
||||
|
||||
return button[0]
|
||||
|
@ -1,50 +0,0 @@
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from pyzbar.pyzbar import decode
|
||||
|
||||
from fishy.helper.helper import get_documents
|
||||
|
||||
|
||||
def get_qr_location(og_img):
|
||||
"""
|
||||
code from https://stackoverflow.com/a/45770227/4512396
|
||||
"""
|
||||
gray = cv2.bilateralFilter(og_img, 11, 17, 17)
|
||||
kernel = np.ones((5, 5), np.uint8)
|
||||
erosion = cv2.erode(gray, kernel, iterations=2)
|
||||
kernel = np.ones((4, 4), np.uint8)
|
||||
img = cv2.dilate(erosion, kernel, iterations=2)
|
||||
|
||||
cnt, h = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
|
||||
|
||||
valid_crops = []
|
||||
for i in range(len(cnt)):
|
||||
area = cv2.contourArea(cnt[i])
|
||||
if 500 < area < 100000:
|
||||
mask = np.zeros_like(img)
|
||||
cv2.drawContours(mask, cnt, i, 255, -1)
|
||||
x, y, w, h = cv2.boundingRect(cnt[i])
|
||||
qr_result = decode(og_img[y:h + y, x:w + x])
|
||||
if qr_result:
|
||||
valid_crops.append(((x, y, x + w, y + h), area))
|
||||
|
||||
return min(valid_crops, key=lambda c: c[1])[0] if valid_crops else None
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def get_values_from_image(img):
|
||||
try:
|
||||
for qr in decode(img):
|
||||
vals = qr.data.decode('utf-8').split(",")
|
||||
return float(vals[0]), float(vals[1]), float(vals[2])
|
||||
|
||||
logging.error("FishyQR not found")
|
||||
return None
|
||||
except Exception:
|
||||
logging.error("Couldn't read coods, make sure 'crop' calibration is correct")
|
||||
cv2.imwrite(os.path.join(get_documents(), "fishy_failed_reads", f"{datetime.now()}.jpg"), img)
|
||||
return None
|
@ -9,7 +9,7 @@ class Test:
|
||||
self.target = None
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
def print_coods(self):
|
||||
def print_coords(self):
|
||||
logging.info(self.engine.get_coords())
|
||||
|
||||
def set_target(self):
|
||||
|
@ -4,16 +4,15 @@ import typing
|
||||
from threading import Thread
|
||||
from typing import Callable, Optional
|
||||
|
||||
from playsound import playsound
|
||||
from fishy.engine.common import qr_detection
|
||||
|
||||
from fishy.engine.semifisher.fishing_mode import FishingMode
|
||||
|
||||
from fishy.engine.common.IEngine import IEngine
|
||||
from fishy.engine.common.window import WindowClient
|
||||
from fishy.engine.semifisher import fishing_event, fishing_mode
|
||||
from fishy.engine.semifisher.fishing_event import FishEvent
|
||||
from fishy.engine.semifisher.fishing_mode import Colors, FishingMode
|
||||
from fishy.engine.semifisher.pixel_loc import PixelLoc
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.luaparser import sv_color_extract
|
||||
from fishy.helper.helper import print_exc
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from fishy.gui import GUI
|
||||
@ -22,71 +21,75 @@ if typing.TYPE_CHECKING:
|
||||
class SemiFisherEngine(IEngine):
|
||||
def __init__(self, gui_ref: Optional['Callable[[], GUI]']):
|
||||
super().__init__(gui_ref)
|
||||
self.fishPixWindow = None
|
||||
self.window = None
|
||||
self.values = None
|
||||
self.name = "SemiFisher"
|
||||
self.first_loop_done = False
|
||||
|
||||
def run(self):
|
||||
"""
|
||||
Starts the fishing
|
||||
code explained in comments in detail
|
||||
"""
|
||||
fishing_event.init()
|
||||
self.fishPixWindow = WindowClient()
|
||||
|
||||
# check for game window and stuff
|
||||
self.gui.bot_started(True)
|
||||
|
||||
sv_color_extract(Colors)
|
||||
|
||||
if self.get_gui:
|
||||
logging.info("Starting the bot engine, look at the fishing hole to start fishing")
|
||||
Thread(target=self._wait_and_check).start()
|
||||
|
||||
while self.start and WindowClient.running():
|
||||
capture = self.fishPixWindow.get_capture()
|
||||
time.sleep(0.2)
|
||||
|
||||
if capture is None:
|
||||
# if window server crashed
|
||||
self.gui.bot_started(False)
|
||||
self.toggle_start()
|
||||
continue
|
||||
fishing_event.init()
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self._engine_loop()
|
||||
except Exception:
|
||||
logging.error("exception occurred while running engine loop")
|
||||
print_exc()
|
||||
|
||||
self.fishPixWindow.crop = PixelLoc.val
|
||||
fishing_mode.loop(capture[0][0])
|
||||
time.sleep(0.1)
|
||||
|
||||
logging.info("Fishing engine stopped")
|
||||
self.gui.bot_started(False)
|
||||
fishing_event.unsubscribe()
|
||||
self.fishPixWindow.destory()
|
||||
self.first_loop_done = False
|
||||
|
||||
def _engine_loop(self):
|
||||
skip_count = 0
|
||||
while self.state == 1 and WindowClient.running():
|
||||
# crop qr and get the values from it
|
||||
self.values = qr_detection.get_values(self.window)
|
||||
|
||||
# if fishyqr fails to get read multiple times, stop the bot
|
||||
if not self.values:
|
||||
if skip_count >= 5:
|
||||
logging.error("Couldn't read values from FishyQR, Stopping engine...")
|
||||
return
|
||||
skip_count += 1
|
||||
time.sleep(0.1)
|
||||
else:
|
||||
skip_count = 0
|
||||
|
||||
if self.values:
|
||||
fishing_mode.loop(self.values[3])
|
||||
self.first_loop_done = True
|
||||
time.sleep(0.1)
|
||||
|
||||
def _wait_and_check(self):
|
||||
time.sleep(10)
|
||||
if not FishEvent.FishingStarted and self.start:
|
||||
if not FishEvent.FishingStarted and self.state == 1:
|
||||
logging.warning("Doesn't look like fishing has started \n"
|
||||
"Check out #read-me-first on our discord channel to troubleshoot the issue")
|
||||
"Check out #faqs on our discord channel to troubleshoot the issue")
|
||||
|
||||
def show_pixel_vals(self):
|
||||
# TODO: remove this, no longer needed
|
||||
def show_qr_vals(self):
|
||||
def show():
|
||||
freq = 0.5
|
||||
t = 0
|
||||
while t < 10.0:
|
||||
while t < 25.0:
|
||||
t += freq
|
||||
logging.debug(str(FishingMode.CurrentMode) + ":" + str(self.fishPixWindow.get_capture()[0][0]))
|
||||
logging.info(str(self.values))
|
||||
time.sleep(freq)
|
||||
logging.info("Displaying QR values stopped")
|
||||
|
||||
logging.debug("Will display pixel values for 10 seconds")
|
||||
logging.info("Will display QR values for 25 seconds")
|
||||
time.sleep(5)
|
||||
Thread(target=show, args=()).start()
|
||||
|
||||
def toggle_start(self):
|
||||
self.start = not self.start
|
||||
if self.start:
|
||||
self.thread = Thread(target=self.run)
|
||||
self.thread.start()
|
||||
playsound(helper.manifest_file("beep.wav"), False)
|
||||
else:
|
||||
helper.playsound_multiple(helper.manifest_file("beep.wav"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.getLogger("").setLevel(logging.DEBUG)
|
||||
|
@ -12,10 +12,10 @@ from playsound import playsound
|
||||
|
||||
from fishy import web
|
||||
from fishy.engine.semifisher import fishing_mode
|
||||
from fishy.engine.semifisher.fishing_mode import FishingMode, State
|
||||
from fishy.engine.semifisher.fishing_mode import State
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.helper import is_eso_active
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
class FishEvent:
|
||||
@ -34,7 +34,7 @@ class FishEvent:
|
||||
sound = False
|
||||
|
||||
|
||||
def _fishing_sleep(waittime, lower_limit_ms=16, upper_limit_ms=2500):
|
||||
def _fishing_sleep(waittime, lower_limit_ms=16, upper_limit_ms=1375):
|
||||
reaction = 0.0
|
||||
if FishEvent.jitter and upper_limit_ms > lower_limit_ms:
|
||||
reaction = float(random.randrange(lower_limit_ms, upper_limit_ms)) / 1000.0
|
||||
@ -44,7 +44,7 @@ def _fishing_sleep(waittime, lower_limit_ms=16, upper_limit_ms=2500):
|
||||
|
||||
def if_eso_is_focused(func):
|
||||
def wrapper():
|
||||
if not is_eso_active():
|
||||
if not os_services.is_eso_active():
|
||||
logging.warning("ESO window is not focused")
|
||||
return
|
||||
func()
|
||||
@ -78,9 +78,6 @@ def subscribe():
|
||||
if fisher_callback not in fishing_mode.subscribers:
|
||||
fishing_mode.subscribers.append(fisher_callback)
|
||||
|
||||
if FishingMode.CurrentMode == State.LOOKING:
|
||||
fisher_callback(FishingMode.CurrentMode)
|
||||
|
||||
|
||||
def fisher_callback(event: State):
|
||||
callbacks_map = {
|
||||
@ -107,7 +104,10 @@ def fisher_callback(event: State):
|
||||
|
||||
|
||||
def on_idle():
|
||||
if FishEvent.previousState in (State.FISHING, State.REELIN):
|
||||
if FishEvent.previousState == State.REELIN:
|
||||
logging.info("HOLE DEPLETED")
|
||||
_sound_and_send_fishy_data()
|
||||
elif FishEvent.previousState == State.FISHING:
|
||||
logging.info("FISHING INTERRUPTED")
|
||||
_sound_and_send_fishy_data()
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
from enum import Enum
|
||||
from time import time, sleep
|
||||
|
||||
subscribers = []
|
||||
|
||||
checkpoint = 0
|
||||
|
||||
class State(Enum):
|
||||
IDLE = 0
|
||||
@ -17,21 +18,6 @@ class State(Enum):
|
||||
DEAD = 15
|
||||
|
||||
|
||||
Colors = {
|
||||
State.IDLE : [255, 255, 255],
|
||||
State.LOOKAWAY : [ 76, 0, 76],
|
||||
State.LOOKING : [101, 69, 0],
|
||||
State.DEPLETED : [ 0, 76, 76],
|
||||
State.NOBAIT : [255, 204, 0],
|
||||
State.FISHING : [ 75, 156, 213],
|
||||
State.REELIN : [ 0, 204, 0],
|
||||
State.LOOT : [ 0, 0, 204],
|
||||
State.INVFULL : [ 0, 0, 51],
|
||||
State.FIGHT : [204, 0, 0],
|
||||
State.DEAD : [ 51, 51, 51]
|
||||
}
|
||||
|
||||
|
||||
def _notify(event):
|
||||
for subscriber in subscribers:
|
||||
subscriber(event)
|
||||
@ -42,19 +28,22 @@ class FishingMode:
|
||||
PrevMode = State.IDLE
|
||||
|
||||
|
||||
def loop(rgb):
|
||||
def loop(state_num: int):
|
||||
"""
|
||||
Executed in the start of the main loop in fishy.py
|
||||
Changes modes, calls mode events (callbacks) when mode is changed
|
||||
|
||||
:param rgb: rgb read by the bot
|
||||
"""
|
||||
FishingMode.CurrentMode = State.IDLE
|
||||
for s in State:
|
||||
if all(rgb == Colors[s]):
|
||||
FishingMode.CurrentMode = s
|
||||
global checkpoint
|
||||
FishingMode.CurrentMode = State(state_num)
|
||||
|
||||
if FishingMode.CurrentMode != FishingMode.PrevMode:
|
||||
checkpoint = time()
|
||||
_notify(FishingMode.CurrentMode)
|
||||
elif FishingMode.CurrentMode == State.LOOKING:
|
||||
if time() - checkpoint > 5:
|
||||
_notify(FishingMode.CurrentMode)
|
||||
checkpoint = time()
|
||||
else:
|
||||
sleep(0.5)
|
||||
|
||||
FishingMode.PrevMode = FishingMode.CurrentMode
|
||||
|
@ -1,66 +0,0 @@
|
||||
import cv2
|
||||
|
||||
|
||||
def get_keypoint_from_image(img):
|
||||
"""
|
||||
convert image int hsv
|
||||
creates a mask for brown color
|
||||
uses blob detection to find a blob in the mask
|
||||
filter the blobs to find the correct one
|
||||
|
||||
:param img: rgb image
|
||||
:return: location of the pixel which is used to detect different fishing states
|
||||
"""
|
||||
|
||||
# Setup SimpleBlobDetector parameters.
|
||||
hsv_img = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
|
||||
lower = (99, 254, 100)
|
||||
upper = (100, 255, 101)
|
||||
mask = cv2.inRange(hsv_img, lower, upper)
|
||||
|
||||
# Setup SimpleBlobDetector parameters.
|
||||
params = cv2.SimpleBlobDetector_Params()
|
||||
|
||||
# Change thresholds
|
||||
params.minThreshold = 10
|
||||
params.maxThreshold = 255
|
||||
|
||||
params.filterByColor = True
|
||||
params.blobColor = 255
|
||||
|
||||
params.filterByCircularity = False
|
||||
params.filterByConvexity = False
|
||||
params.filterByInertia = False
|
||||
|
||||
params.filterByArea = True
|
||||
params.minArea = 10.0
|
||||
|
||||
detector = cv2.SimpleBlobDetector_create(params)
|
||||
|
||||
# Detect blobs.
|
||||
key_points = detector.detect(mask)
|
||||
|
||||
if len(key_points) <= 0:
|
||||
return None
|
||||
|
||||
return int(key_points[0].pt[0]), int(key_points[0].pt[1])
|
||||
|
||||
|
||||
class PixelLoc:
|
||||
"""
|
||||
finds the pixel loc and store it
|
||||
"""
|
||||
|
||||
val = None
|
||||
|
||||
@staticmethod
|
||||
def config():
|
||||
"""
|
||||
Uses the game window to get an image of the game screen
|
||||
then uses `GetKeypointFromImage()` to find the Chalutier pixel location
|
||||
:return: false if pixel loc not found
|
||||
"""
|
||||
|
||||
PixelLoc.val = (0, 0, 1, 1)
|
||||
|
||||
return True
|
@ -2,27 +2,31 @@ import logging
|
||||
import os
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
import typing
|
||||
from tkinter.filedialog import askopenfilename
|
||||
|
||||
from fishy.engine.common.event_handler import IEngineHandler
|
||||
from fishy.engine.fullautofisher.mode.imode import FullAutoMode
|
||||
from fishy.helper import helper
|
||||
|
||||
from fishy import web
|
||||
from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.popup import PopUp
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from fishy.gui import GUI
|
||||
|
||||
def del_entry_key(event):
|
||||
event.widget.delete(0, "end")
|
||||
event.widget.insert(0, str(event.char))
|
||||
|
||||
|
||||
def start_fullfisher_config(gui: 'GUI'):
|
||||
top = PopUp(helper.empty_function, gui._root, background=gui._root["background"])
|
||||
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)
|
||||
top.title("Config")
|
||||
|
||||
|
||||
def file_name():
|
||||
file = config.get("full_auto_rec_file", None)
|
||||
if file is None:
|
||||
@ -48,10 +52,12 @@ def start_fullfisher_config(gui: 'GUI'):
|
||||
config.set("full_auto_mode", mode_var.get())
|
||||
edit_cb['state'] = "normal" if config.get("full_auto_mode", 0) == FullAutoMode.Recorder.value else "disable"
|
||||
|
||||
# todo repetitive code fix
|
||||
file_name_label = tk.StringVar(value=file_name())
|
||||
mode_var = tk.IntVar(value=config.get("full_auto_mode", 0))
|
||||
edit_var = tk.IntVar(value=config.get("edit_recorder_mode", 0))
|
||||
tabout_var = tk.IntVar(value=config.get("tabout_stop", 1))
|
||||
look_for_hole = tk.IntVar(value=config.get("look_for_hole", 0))
|
||||
row = 0
|
||||
|
||||
ttk.Label(controls_frame, text="Calibration: ").grid(row=row, column=0, pady=(5, 0))
|
||||
@ -66,6 +72,14 @@ def start_fullfisher_config(gui: 'GUI'):
|
||||
|
||||
row += 1
|
||||
|
||||
ttk.Label(controls_frame, text="Forward key:").grid(row=row, column=0)
|
||||
forward_key_entry = ttk.Entry(controls_frame, justify=tk.CENTER)
|
||||
forward_key_entry.grid(row=row, column=1)
|
||||
forward_key_entry.insert(0, config.get("forward_key", "w"))
|
||||
forward_key_entry.bind("<KeyRelease>", del_entry_key)
|
||||
|
||||
row += 1
|
||||
|
||||
ttk.Label(controls_frame, text="Edit Mode: ").grid(row=row, column=0)
|
||||
edit_state = tk.NORMAL if config.get("full_auto_mode", 0) == FullAutoMode.Recorder.value else tk.DISABLED
|
||||
edit_cb = ttk.Checkbutton(controls_frame, variable=edit_var, state=edit_state, command=lambda: config.set("edit_recorder_mode", edit_var.get()))
|
||||
@ -78,6 +92,11 @@ def start_fullfisher_config(gui: 'GUI'):
|
||||
|
||||
row += 1
|
||||
|
||||
ttk.Label(controls_frame, text="Look for hole: ").grid(row=row, column=0)
|
||||
ttk.Checkbutton(controls_frame, variable=look_for_hole, command=lambda: config.set("look_for_hole", look_for_hole.get())).grid(row=row, column=1, pady=(5, 0))
|
||||
|
||||
row += 1
|
||||
|
||||
ttk.Label(controls_frame, text="Fishy file: ").grid(row=row, column=0, rowspan=2)
|
||||
ttk.Button(controls_frame, text="Select", command=select_file).grid(row=row, column=1, pady=(5, 0))
|
||||
row += 1
|
||||
@ -88,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))
|
||||
|
||||
controls_frame.pack(padx=(5, 5), pady=(5, 10))
|
||||
controls_frame.update()
|
||||
|
||||
top.start()
|
||||
|
||||
|
||||
@ -107,9 +128,7 @@ def start_semifisher_config(gui: 'GUI'):
|
||||
if web.sub():
|
||||
gui._notify.set(1)
|
||||
|
||||
def del_entry_key(event):
|
||||
event.widget.delete(0, "end")
|
||||
event.widget.insert(0, str(event.char))
|
||||
|
||||
|
||||
top = PopUp(save, gui._root, background=gui._root["background"])
|
||||
controls_frame = ttk.Frame(top)
|
||||
@ -117,7 +136,7 @@ def start_semifisher_config(gui: 'GUI'):
|
||||
|
||||
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.grid(row=0, column=1)
|
||||
gui._notify_check['state'] = tk.DISABLED
|
||||
@ -147,6 +166,8 @@ def start_semifisher_config(gui: 'GUI'):
|
||||
jitter.grid(row=5, column=1)
|
||||
|
||||
controls_frame.pack(padx=(5, 5), pady=(5, 5))
|
||||
controls_frame.update()
|
||||
|
||||
top.start()
|
||||
|
||||
|
||||
@ -154,4 +175,5 @@ if __name__ == '__main__':
|
||||
from fishy.gui import GUI
|
||||
gui = GUI(lambda: IEngineHandler())
|
||||
gui.call_in_thread(lambda: start_semifisher_config(gui))
|
||||
gui.call_in_thread(lambda: start_fullfisher_config(gui))
|
||||
gui.create()
|
||||
|
@ -26,6 +26,7 @@ def discord_login(gui: 'GUI'):
|
||||
top.destroy()
|
||||
top_running[0] = False
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def check():
|
||||
code = int(login_code.get()) if login_code.get().isdigit() else 0
|
||||
if web.login(config.get("uid"), code):
|
||||
|
@ -1,4 +1,3 @@
|
||||
import logging
|
||||
import queue
|
||||
import threading
|
||||
import tkinter as tk
|
||||
@ -8,15 +7,13 @@ from dataclasses import dataclass
|
||||
|
||||
from ttkthemes import ThemedTk
|
||||
|
||||
from fishy.engine.common.event_handler import EngineEventHandler, IEngineHandler
|
||||
from fishy.engine.common.event_handler import IEngineHandler
|
||||
from fishy.gui import config_top
|
||||
from fishy.gui.funcs import GUIFuncs
|
||||
from fishy.web import web
|
||||
|
||||
from ..helper.config import config
|
||||
from ..helper.helper import wait_until
|
||||
from . import main_gui
|
||||
from .log_config import GUIStreamHandler
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -26,9 +23,10 @@ class EngineRunner:
|
||||
|
||||
|
||||
class GUI:
|
||||
def __init__(self, get_engine: Callable[[], IEngineHandler]):
|
||||
def __init__(self, get_engine: Callable[[], IEngineHandler], on_ready: Callable):
|
||||
self.funcs = GUIFuncs(self)
|
||||
self.get_engine = get_engine
|
||||
self.on_ready = on_ready
|
||||
|
||||
self.config = config
|
||||
self._start_restart = False
|
||||
@ -52,12 +50,6 @@ class GUI:
|
||||
self._notify = None
|
||||
self.login = None
|
||||
|
||||
root_logger = logging.getLogger('')
|
||||
root_logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger('urllib3').setLevel(logging.WARNING)
|
||||
new_console = GUIStreamHandler(self)
|
||||
root_logger.addHandler(new_console)
|
||||
|
||||
@property
|
||||
def engine(self):
|
||||
return self.get_engine()
|
||||
@ -77,6 +69,9 @@ class GUI:
|
||||
def create(self):
|
||||
main_gui._create(self)
|
||||
|
||||
def stop(self):
|
||||
self._destroyed = True
|
||||
|
||||
def start(self):
|
||||
self._thread.start()
|
||||
|
||||
@ -99,3 +94,17 @@ class GUI:
|
||||
|
||||
def _get_start_stop_text(self):
|
||||
return "STOP (F9)" if self._bot_running else "START (F9)"
|
||||
|
||||
def write_to_console(self, msg):
|
||||
if not self._console:
|
||||
return
|
||||
|
||||
numlines = self._console.index('end - 1 line').split('.')[0]
|
||||
self._console['state'] = 'normal'
|
||||
if int(numlines) >= 50: # delete old lines
|
||||
self._console.delete(1.0, 2.0)
|
||||
if self._console.index('end-1c') != '1.0': # new line for each log
|
||||
self._console.insert('end', '\n')
|
||||
self._console.insert('end', msg)
|
||||
self._console.see("end") # scroll to bottom
|
||||
self._console['state'] = 'disabled'
|
||||
|
@ -1,27 +1,36 @@
|
||||
import typing
|
||||
from logging import StreamHandler
|
||||
import logging
|
||||
from logging import StreamHandler, Formatter
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import GUI
|
||||
from fishy.helper.config import config
|
||||
|
||||
|
||||
class GUIStreamHandler(StreamHandler):
|
||||
def __init__(self, gui):
|
||||
class GuiLogger(StreamHandler):
|
||||
def __init__(self):
|
||||
StreamHandler.__init__(self)
|
||||
self.gui = gui
|
||||
|
||||
self.renderer = None
|
||||
self._temp_buffer = []
|
||||
|
||||
formatter = Formatter('%(levelname)s - %(message)s')
|
||||
self.setFormatter(formatter)
|
||||
logging_config = {"comtypes": logging.INFO,
|
||||
"PIL": logging.INFO,
|
||||
"urllib3": logging.WARNING,
|
||||
"": logging.DEBUG}
|
||||
for name, level in logging_config.items():
|
||||
_logger = logging.getLogger(name)
|
||||
_logger.setLevel(level)
|
||||
self.setLevel(logging.DEBUG if config.get("debug", False) else logging.INFO)
|
||||
logging.getLogger("").addHandler(self)
|
||||
|
||||
def emit(self, record):
|
||||
msg = self.format(record)
|
||||
self.gui.call_in_thread(lambda: _write_to_console(self.gui, msg))
|
||||
if self.renderer:
|
||||
self.renderer(msg)
|
||||
else:
|
||||
self._temp_buffer.append(msg)
|
||||
|
||||
|
||||
def _write_to_console(root: 'GUI', msg):
|
||||
numlines = root._console.index('end - 1 line').split('.')[0]
|
||||
root._console['state'] = 'normal'
|
||||
if int(numlines) >= 50: # delete old lines
|
||||
root._console.delete(1.0, 2.0)
|
||||
if root._console.index('end-1c') != '1.0': # new line for each log
|
||||
root._console.insert('end', '\n')
|
||||
root._console.insert('end', msg)
|
||||
root._console.see("end") # scroll to bottom
|
||||
root._console['state'] = 'disabled'
|
||||
def connect(self, gui):
|
||||
self.renderer = lambda m: gui.call_in_thread(lambda: gui.write_to_console(m))
|
||||
while self._temp_buffer:
|
||||
self.renderer(self._temp_buffer.pop(0))
|
||||
|
@ -2,18 +2,24 @@ import logging
|
||||
import time
|
||||
import tkinter as tk
|
||||
import tkinter.ttk as ttk
|
||||
from tkinter import messagebox
|
||||
import typing
|
||||
from functools import partial
|
||||
import os
|
||||
|
||||
from fishy.gui import update_dialog
|
||||
from ttkthemes import ThemedTk
|
||||
|
||||
from fishy import helper
|
||||
from fishy.helper import helper
|
||||
from fishy.web import web
|
||||
|
||||
from ..constants import chalutier, lam2
|
||||
from ..constants import fishyqr
|
||||
from ..engine.common import screenshot
|
||||
from ..helper.config import config
|
||||
from .discord_login import discord_login
|
||||
from ..helper.hotkey.hotkey_process import hotkey
|
||||
from ..helper.hotkey.process import Key
|
||||
from ..osservices.os_services import os_services
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from . import GUI
|
||||
@ -31,6 +37,7 @@ def _create(gui: 'GUI'):
|
||||
engines = gui.engines
|
||||
|
||||
gui._root = ThemedTk(theme="equilux", background=True)
|
||||
gui._root.attributes('-alpha', 0.0)
|
||||
gui._root.title("Fishybot for Elder Scrolls Online")
|
||||
gui._root.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
@ -44,7 +51,7 @@ def _create(gui: 'GUI'):
|
||||
gui.login.set(1 if login > 0 else 0)
|
||||
state = tk.DISABLED if login == -1 else tk.ACTIVE
|
||||
filemenu.add_checkbutton(label="Login", command=lambda: discord_login(gui), variable=gui.login, state=state)
|
||||
filemenu.add_command(label="Create Shortcut", command=lambda: 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))
|
||||
|
||||
def _toggle_mode():
|
||||
@ -55,25 +62,69 @@ def _create(gui: 'GUI'):
|
||||
dark_mode_var.set(int(config.get('dark_mode', True)))
|
||||
filemenu.add_checkbutton(label="Dark Mode", command=_toggle_mode,
|
||||
variable=dark_mode_var)
|
||||
if config.get("dont_ask_update", False):
|
||||
filemenu.add_command(label="Update", command=helper.update)
|
||||
|
||||
def update():
|
||||
config.delete("dont_ask_update")
|
||||
update_dialog.check_update(gui, True)
|
||||
|
||||
filemenu.add_command(label="Update", command=update)
|
||||
|
||||
def installer():
|
||||
if filemenu.entrycget(4, 'label') == "Remove Chalutier":
|
||||
if helper.remove_addon(chalutier[0]) == 0:
|
||||
filemenu.entryconfigure(4, label="Install Chalutier")
|
||||
if filemenu.entrycget(4, 'label') == "Remove FishyQR":
|
||||
if helper.remove_addon(fishyqr[0]) == 0:
|
||||
filemenu.entryconfigure(4, label="Install FishyQR")
|
||||
else:
|
||||
r = helper.install_addon(*chalutier)
|
||||
r += helper.install_addon(*lam2)
|
||||
if r == 0:
|
||||
filemenu.entryconfigure(4, label="Remove Chalutier")
|
||||
chaEntry = "Remove Chalutier" if helper.addon_exists(chalutier[0]) else "Install Chalutier"
|
||||
helper.install_required_addons(True)
|
||||
filemenu.entryconfigure(4, label="Remove FishyQR")
|
||||
|
||||
chaEntry = "Remove FishyQR" if helper.addon_exists(fishyqr[0]) else "Install FishyQR"
|
||||
filemenu.add_command(label=chaEntry, command=installer)
|
||||
menubar.add_cascade(label="Options", menu=filemenu)
|
||||
|
||||
debug_menu = tk.Menu(menubar, tearoff=0)
|
||||
debug_menu.add_command(label="Check PixelVal",
|
||||
command=lambda: gui.engine.check_pixel_val())
|
||||
debug_menu.add_command(label="Check QR Value",
|
||||
command=lambda: gui.engine.check_qr_val())
|
||||
|
||||
def toggle_show_grab():
|
||||
new_val = 1 - config.get("show_grab", 0)
|
||||
show_grab_var.set(new_val)
|
||||
config.set("show_grab", new_val)
|
||||
if new_val:
|
||||
logging.info(f"Screenshots taken by fishy will be saved in {helper.save_img_path()}")
|
||||
messagebox.showwarning("Warning", "Screenshots taken by Fishy will be saved in Documents.")
|
||||
logging.info(f"Screenshots taken by Fishy will be saved in {helper.save_img_path()}")
|
||||
else:
|
||||
delete_screenshots = messagebox.askyesno("Confirmation", "Do you want to delete the saved screenshots?")
|
||||
if delete_screenshots:
|
||||
# Delete the saved screenshots
|
||||
folder_path = helper.save_img_path()
|
||||
try:
|
||||
os.rmdir(folder_path) # Deletes the folder
|
||||
logging.info("Saved screenshots folder has been deleted.")
|
||||
except OSError as e:
|
||||
logging.error(f"Error occurred while deleting the folder: {e}")
|
||||
else:
|
||||
logging.info("Saved screenshots will be preserved.")
|
||||
|
||||
|
||||
show_grab_var = tk.IntVar()
|
||||
show_grab_var.set(config.get("show_grab", 0))
|
||||
debug_menu.add_checkbutton(label="Save Screenshots", variable=show_grab_var, command=lambda: toggle_show_grab(), onvalue=1)
|
||||
if config.get("show_grab", 0):
|
||||
logging.info(f"Save Screenshots is On, images will be saved in {helper.save_img_path()}")
|
||||
|
||||
|
||||
def select_sslib(selected_i):
|
||||
config.set("sslib", selected_i)
|
||||
sslib_var.set(selected_i)
|
||||
|
||||
sslib = tk.Menu(debug_menu, tearoff=False)
|
||||
sslib_var = tk.IntVar()
|
||||
sslib_var.set(config.get("sslib", 0))
|
||||
for i, lib in enumerate(screenshot.LIBS):
|
||||
sslib.add_checkbutton(label=lib.__name__, variable=sslib_var,
|
||||
command=partial(select_sslib, i), onvalue=i)
|
||||
debug_menu.add_cascade(label="Screenshot Lib", menu=sslib)
|
||||
|
||||
debug_var = tk.IntVar()
|
||||
debug_var.set(int(config.get('debug', False)))
|
||||
@ -83,11 +134,11 @@ def _create(gui: 'GUI'):
|
||||
logging.debug("Restart to update the changes")
|
||||
|
||||
debug_menu.add_checkbutton(label="Keep Console", command=keep_console, variable=debug_var)
|
||||
debug_menu.add_command(label="Restart", command=helper.restart)
|
||||
menubar.add_cascade(label="Debug", menu=debug_menu)
|
||||
|
||||
help_menu = tk.Menu(menubar, tearoff=0)
|
||||
help_menu.add_command(label="Need Help?", command=lambda: helper.open_web("https://github.com/fishyboteso/fishyboteso/wiki"))
|
||||
help_menu.add_command(label="Need Help?",
|
||||
command=lambda: helper.open_web("https://github.com/fishyboteso/fishyboteso/wiki"))
|
||||
help_menu.add_command(label="Donate", command=lambda: helper.open_web("https://paypal.me/AdamSaudagar"))
|
||||
menubar.add_cascade(label="Help", menu=help_menu)
|
||||
|
||||
@ -123,24 +174,38 @@ def _create(gui: 'GUI'):
|
||||
|
||||
_apply_theme(gui)
|
||||
gui._root.update()
|
||||
|
||||
gui._root.minsize(gui._root.winfo_width() + 10, gui._root.winfo_height() + 10)
|
||||
if config.get("win_loc") is not None:
|
||||
gui._root.geometry(config.get("win_loc"))
|
||||
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)
|
||||
|
||||
# noinspection PyProtectedMember
|
||||
# noinspection PyProtectedMember,PyUnresolvedReferences
|
||||
def set_destroy():
|
||||
if gui._bot_running:
|
||||
if not tk.messagebox.askyesno(title="Quit?", message="Bot engine running. Quit Anyway?"):
|
||||
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._root.protocol("WM_DELETE_WINDOW", set_destroy)
|
||||
gui._destroyed = False
|
||||
|
||||
gui._root.update()
|
||||
gui._clear_function_queue()
|
||||
gui._root.after(0, gui._root.attributes, "-alpha", 1.0)
|
||||
gui.on_ready()
|
||||
while True:
|
||||
gui._root.update()
|
||||
gui._clear_function_queue()
|
||||
@ -150,6 +215,6 @@ def _create(gui: 'GUI'):
|
||||
gui._start_restart = False
|
||||
gui.create()
|
||||
if gui._destroyed:
|
||||
gui.engine.quit()
|
||||
gui.engine.quit_me()
|
||||
break
|
||||
time.sleep(0.01)
|
||||
|
@ -1,6 +1,8 @@
|
||||
import logging
|
||||
import time
|
||||
import tkinter as tk
|
||||
from multiprocessing import Process
|
||||
from multiprocessing import Process, Queue
|
||||
from threading import Thread
|
||||
|
||||
from PIL import Image, ImageTk
|
||||
|
||||
@ -8,33 +10,53 @@ from fishy.helper import helper
|
||||
from fishy.helper.config import config
|
||||
|
||||
|
||||
def show(win_loc):
|
||||
dim = (300, 200)
|
||||
top = tk.Tk()
|
||||
class Splash:
|
||||
def __init__(self):
|
||||
self.q = Queue()
|
||||
self.process = Process(name=Splash.__name__, target=self.show, args=(config.get("win_loc"), self.q,))
|
||||
|
||||
top.overrideredirect(True)
|
||||
top.lift()
|
||||
def finish(self):
|
||||
self.q.put("stop")
|
||||
|
||||
top.title("Loading...")
|
||||
top.resizable(False, False)
|
||||
top.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
def start(self):
|
||||
self.process.start()
|
||||
|
||||
canvas = tk.Canvas(top, width=dim[0], height=dim[1], bg='white')
|
||||
canvas.pack()
|
||||
top.image = Image.open(helper.manifest_file('fishybot_logo.png')).resize(dim)
|
||||
top.image = ImageTk.PhotoImage(top.image)
|
||||
canvas.create_image(0, 0, anchor=tk.NW, image=top.image)
|
||||
def show(self, win_loc, q):
|
||||
logging.debug("started splash process")
|
||||
dim = (300, 200)
|
||||
top = tk.Tk()
|
||||
|
||||
# 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")
|
||||
loc = (win_loc or default_loc).split("+")[1:]
|
||||
top.geometry("{}x{}+{}+{}".format(dim[0], dim[1], int(loc[0]) + int(dim[0] / 2), int(loc[1]) + int(dim[1] / 2)))
|
||||
top.title("Loading...")
|
||||
top.resizable(False, False)
|
||||
top.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
top.update()
|
||||
time.sleep(3)
|
||||
top.destroy()
|
||||
canvas = tk.Canvas(top, width=dim[0], height=dim[1], bg='white')
|
||||
canvas.pack()
|
||||
top.image = Image.open(helper.manifest_file('fishybot_logo.png')).resize(dim)
|
||||
top.image = ImageTk.PhotoImage(top.image)
|
||||
canvas.create_image(0, 0, anchor=tk.NW, image=top.image)
|
||||
|
||||
# Position splash at the center of the main window
|
||||
|
||||
def start():
|
||||
Process(target=show, args=(config.get("win_loc"),)).start()
|
||||
default_loc = (str(top.winfo_reqwidth()) + "+" + str(top.winfo_reqheight()) + "+" + "0" + "0")
|
||||
loc = (win_loc or default_loc).split(":")[-1].split("+")[1:]
|
||||
top.geometry("{}x{}+{}+{}".format(dim[0], dim[1], int(loc[0]) + int(dim[0] / 2), int(loc[1]) + int(dim[1] / 2)))
|
||||
|
||||
def waiting():
|
||||
q.get()
|
||||
time.sleep(0.2)
|
||||
running[0] = False
|
||||
|
||||
Thread(target=waiting).start()
|
||||
|
||||
running = [True]
|
||||
while running[0]:
|
||||
top.update()
|
||||
time.sleep(0.1)
|
||||
|
||||
top.destroy()
|
||||
logging.debug("ended splash process")
|
||||
|
@ -40,7 +40,7 @@ def _run_terms_window():
|
||||
root.image = ImageTk.PhotoImage(root.image)
|
||||
canvas.create_image(0, 0, anchor=tk.NW, image=root.image)
|
||||
|
||||
check_value = tk.IntVar(0)
|
||||
check_value = tk.IntVar()
|
||||
|
||||
g1 = ttk.Frame(f)
|
||||
ttk.Checkbutton(g1, command=disable_enable_button, variable=check_value).pack(side=tk.LEFT)
|
||||
|
@ -1,13 +1,23 @@
|
||||
import logging
|
||||
import tkinter as tk
|
||||
from multiprocessing import Manager, Process
|
||||
|
||||
from fishy import helper
|
||||
from fishy.helper import helper, auto_update
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.popup import PopUp
|
||||
|
||||
|
||||
def show(currentversion, newversion, returns):
|
||||
top = tk.Tk()
|
||||
def _show(gui, currentversion, newversion, returns):
|
||||
|
||||
def _clickYes():
|
||||
returns[0], returns[1] = True, False
|
||||
top.quit_top()
|
||||
|
||||
def _clickNo():
|
||||
returns[0], returns[1] = False, bool(cbVar.get())
|
||||
top.quit_top()
|
||||
|
||||
top = PopUp(helper.empty_function, gui._root)
|
||||
top.title("A wild fishy update appeared!")
|
||||
top.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
dialogLabel = tk.Label(top, text="There is a new fishy update available (" +
|
||||
currentversion + "->" + newversion + "). Do you want to update now?")
|
||||
@ -19,14 +29,6 @@ def show(currentversion, newversion, returns):
|
||||
top.update()
|
||||
buttonWidth = int(dialogLabel.winfo_width() / 2) - 20
|
||||
|
||||
def _clickYes():
|
||||
returns[0], returns[1] = True, False
|
||||
top.destroy()
|
||||
|
||||
def _clickNo():
|
||||
returns[0], returns[1] = False, bool(cbVar.get())
|
||||
top.destroy()
|
||||
|
||||
pixelVirtual = tk.PhotoImage(width=1, height=1) # trick to use buttonWidth as pixels, not #symbols
|
||||
dialogBtnNo = tk.Button(top, text="No " + str(chr(10005)), fg='red4', command=_clickNo, image=pixelVirtual,
|
||||
width=buttonWidth, compound="c")
|
||||
@ -35,16 +37,26 @@ def show(currentversion, newversion, returns):
|
||||
width=buttonWidth, compound="c")
|
||||
dialogBtnYes.grid(row=2, column=1, padx=5, pady=5)
|
||||
dialogBtnYes.focus_set()
|
||||
dialogBtnYes.update()
|
||||
|
||||
top.protocol('WM_DELETE_WINDOW', _clickNo)
|
||||
|
||||
top.update()
|
||||
top.mainloop()
|
||||
top.start()
|
||||
|
||||
|
||||
def start(currentversion, newversion):
|
||||
returns = Manager().dict()
|
||||
p = Process(target=show, args=(currentversion, newversion, returns))
|
||||
p.start()
|
||||
p.join()
|
||||
return returns[0], returns[1]
|
||||
def check_update(gui, manual_check=False):
|
||||
if not auto_update.upgrade_avail() or config.get("dont_ask_update", False):
|
||||
if manual_check:
|
||||
logging.info("No update is available.")
|
||||
return
|
||||
|
||||
cv, hv = auto_update.versions()
|
||||
returns = [None, None]
|
||||
_show(gui, cv, hv, returns)
|
||||
[update_now, dont_ask_update] = returns
|
||||
if dont_ask_update:
|
||||
config.set("dont_ask_update", dont_ask_update)
|
||||
else:
|
||||
config.delete("dont_ask_update")
|
||||
|
||||
if update_now:
|
||||
gui.engine.set_update(hv)
|
||||
|
@ -1,9 +1,8 @@
|
||||
from .auto_update import auto_upgrade, upgrade_avail, versions
|
||||
from .config import Config
|
||||
from .helper import (addon_exists, create_shortcut, create_shortcut_first,
|
||||
get_addonversion, get_savedvarsdir, initialize_uid,
|
||||
from .helper import (addon_exists,
|
||||
get_addonversion, get_savedvarsdir,
|
||||
install_addon, install_thread_excepthook, manifest_file,
|
||||
not_implemented, open_web, playsound_multiple,
|
||||
remove_addon, restart, unhandled_exception_logging,
|
||||
update)
|
||||
remove_addon, unhandled_exception_logging,
|
||||
install_required_addons)
|
||||
from .luaparser import sv_color_extract
|
||||
|
@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from event_scheduler import EventScheduler
|
||||
from fishy.web import web
|
||||
|
||||
@ -12,12 +14,16 @@ class active:
|
||||
return
|
||||
|
||||
active._scheduler = EventScheduler()
|
||||
active._scheduler.start()
|
||||
logging.debug("active scheduler initialized")
|
||||
|
||||
@staticmethod
|
||||
def start():
|
||||
web.ping()
|
||||
active._scheduler.start()
|
||||
active._scheduler.enter_recurring(60, 1, web.ping)
|
||||
logging.debug("active scheduler started")
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
active._scheduler.stop(hard_stop=True)
|
||||
logging.debug("active scheduler stopped")
|
||||
|
@ -6,14 +6,8 @@ import logging
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.request
|
||||
from os import execl
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
def _hr_version(v):
|
||||
return '.'.join([str(x) for x in v])
|
||||
from fishy.web import web
|
||||
|
||||
|
||||
def _normalize_version(v):
|
||||
@ -38,46 +32,16 @@ def _normalize_version(v):
|
||||
return rv
|
||||
|
||||
|
||||
def _get_highest_version(index, pkg):
|
||||
"""
|
||||
Crawls web for latest version name then returns latest version
|
||||
:param index: website to check
|
||||
:param pkg: package name
|
||||
:return: latest version normalized
|
||||
"""
|
||||
url = "{}/{}/".format(index, pkg)
|
||||
html = urllib.request.urlopen(url)
|
||||
if html.getcode() != 200:
|
||||
raise Exception # not found
|
||||
soup = BeautifulSoup(html.read(), "html.parser")
|
||||
versions = []
|
||||
for link in soup.find_all('a'):
|
||||
text = link.get_text()
|
||||
try:
|
||||
version = re.search(pkg + r'-(.*)\.tar\.gz', text).group(1)
|
||||
versions.append(_normalize_version(version))
|
||||
except AttributeError:
|
||||
pass
|
||||
if len(versions) == 0:
|
||||
raise Exception # no version
|
||||
return max(versions)
|
||||
|
||||
|
||||
def _get_current_version():
|
||||
"""
|
||||
Gets the current version of the package installed
|
||||
:return: version normalized
|
||||
"""
|
||||
import fishy
|
||||
return _normalize_version(fishy.__version__)
|
||||
|
||||
|
||||
index = "https://pypi.python.org/simple"
|
||||
pkg = "fishy"
|
||||
return fishy.__version__
|
||||
|
||||
|
||||
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():
|
||||
@ -85,16 +49,19 @@ def upgrade_avail():
|
||||
Checks if update is available
|
||||
: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 auto_upgrade():
|
||||
def update_now(version):
|
||||
"""
|
||||
public function,
|
||||
compares current version with the latest version (from web),
|
||||
if current version is older, then it updates and restarts the script
|
||||
calling this function updates fishy,
|
||||
should be the last thing to be executed as this function will restart fishy
|
||||
the flaw is handed by `EngineEventHandler.update_flag` which is the last thing to be stopped
|
||||
"""
|
||||
version = _hr_version(_get_highest_version(index, pkg))
|
||||
logging.info(f"Updating to v{version}, Please Wait...")
|
||||
subprocess.call(["python", '-m', 'pip', 'install', '--upgrade', 'fishy', '--user'])
|
||||
execl(sys.executable, *([sys.executable, '-m', 'fishy'] + sys.argv[1:]))
|
||||
|
@ -3,21 +3,32 @@ config.py
|
||||
Saves configuration in file as json file
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
# path to save the configuration file
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
|
||||
from event_scheduler import EventScheduler
|
||||
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
def filename():
|
||||
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)
|
||||
|
||||
if os.path.exists(_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")
|
||||
@ -46,28 +57,32 @@ class Config:
|
||||
self._config_dict = json.loads(open(filename()).read())
|
||||
except json.JSONDecodeError:
|
||||
try:
|
||||
print("Config file got corrupted, trying to restore backup")
|
||||
logging.warning("Config file got corrupted, trying to restore backup")
|
||||
self._config_dict = json.loads(open(temp_file).read())
|
||||
self.save_config()
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
print("couldn't restore, creating new")
|
||||
logging.warning("couldn't restore, creating new")
|
||||
os.remove(filename())
|
||||
self._config_dict = dict()
|
||||
|
||||
else:
|
||||
self._config_dict = dict()
|
||||
logging.debug("config initialized")
|
||||
|
||||
def start_backup_scheduler(self):
|
||||
self._create_backup()
|
||||
self._scheduler.start()
|
||||
self._scheduler.enter_recurring(5 * 60, 1, self._create_backup)
|
||||
logging.debug("scheduler started")
|
||||
|
||||
def stop(self):
|
||||
self._scheduler.stop(True)
|
||||
logging.debug("config stopped")
|
||||
|
||||
def _create_backup(self):
|
||||
with open(temp_file, 'w') as f:
|
||||
f.write(json.dumps(self._config_dict))
|
||||
print("created backup")
|
||||
logging.debug("created backup")
|
||||
|
||||
def _sort_dict(self):
|
||||
tmpdict = dict()
|
||||
@ -90,8 +105,13 @@ class config:
|
||||
|
||||
@staticmethod
|
||||
def init():
|
||||
config._instance = Config()
|
||||
config._instance.initialize()
|
||||
if not config._instance:
|
||||
config._instance = Config()
|
||||
config._instance.initialize()
|
||||
|
||||
@staticmethod
|
||||
def start_backup_scheduler():
|
||||
config._instance.start_backup_scheduler()
|
||||
|
||||
@staticmethod
|
||||
def stop():
|
||||
|
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,21 +7,21 @@ import threading
|
||||
import time
|
||||
import traceback
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from hashlib import md5
|
||||
from io import BytesIO
|
||||
from threading import Thread
|
||||
from uuid import uuid1
|
||||
from zipfile import ZipFile
|
||||
|
||||
import cv2
|
||||
import requests
|
||||
import winshell
|
||||
from playsound import playsound
|
||||
from win32com.client import Dispatch
|
||||
from win32comext.shell import shell, shellcon
|
||||
from win32gui import GetForegroundWindow, GetWindowText
|
||||
|
||||
import fishy
|
||||
from fishy import web
|
||||
from fishy.constants import libgps, lam2, fishyqr, fishyfsm, libmapping, libdl, libchatmsg
|
||||
from fishy.helper.config import config
|
||||
from fishy.osservices.os_services import os_services
|
||||
|
||||
|
||||
def playsound_multiple(path, count=2):
|
||||
@ -64,19 +64,6 @@ def open_web(website):
|
||||
Thread(target=lambda: webbrowser.open(website, new=2)).start()
|
||||
|
||||
|
||||
def initialize_uid():
|
||||
from .config import config
|
||||
|
||||
if config.get("uid") is not None:
|
||||
return
|
||||
|
||||
new_uid = web.register_user()
|
||||
if new_uid is not None:
|
||||
config.set("uid", new_uid)
|
||||
else:
|
||||
logging.error("Couldn't register uid, some features might not work")
|
||||
|
||||
|
||||
def _create_new_uid():
|
||||
"""
|
||||
Creates a unique id for user
|
||||
@ -122,55 +109,14 @@ def manifest_file(rel_path):
|
||||
return os.path.join(os.path.dirname(fishy.__file__), rel_path)
|
||||
|
||||
|
||||
def create_shortcut_first():
|
||||
from .config import config
|
||||
|
||||
if not config.get("shortcut_created", False):
|
||||
create_shortcut(False)
|
||||
config.set("shortcut_created", True)
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def create_shortcut(anti_ghosting: bool):
|
||||
"""
|
||||
creates a new shortcut on desktop
|
||||
"""
|
||||
try:
|
||||
desktop = winshell.desktop()
|
||||
path = os.path.join(desktop, "Fishybot ESO.lnk")
|
||||
|
||||
shell = Dispatch('WScript.Shell')
|
||||
shortcut = shell.CreateShortCut(path)
|
||||
|
||||
if anti_ghosting:
|
||||
shortcut.TargetPath = r"C:\Windows\System32\cmd.exe"
|
||||
python_dir = os.path.join(os.path.dirname(sys.executable), "pythonw.exe")
|
||||
shortcut.Arguments = f"/C start /affinity 1 /low {python_dir} -m fishy"
|
||||
else:
|
||||
shortcut.TargetPath = os.path.join(os.path.dirname(sys.executable), "python.exe")
|
||||
shortcut.Arguments = "-m fishy"
|
||||
|
||||
shortcut.IconLocation = manifest_file("icon.ico")
|
||||
shortcut.save()
|
||||
|
||||
logging.info("Shortcut created")
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
logging.error("Couldn't create shortcut")
|
||||
|
||||
|
||||
def get_savedvarsdir():
|
||||
# noinspection PyUnresolvedReferences
|
||||
from win32com.shell import shell, shellcon
|
||||
documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
return os.path.join(documents, "Elder Scrolls Online", "live", "SavedVariables")
|
||||
eso_path = os_services.get_eso_config_path()
|
||||
return os.path.join(eso_path, "live", "SavedVariables")
|
||||
|
||||
|
||||
def get_addondir():
|
||||
# noinspection PyUnresolvedReferences
|
||||
from win32com.shell import shell, shellcon
|
||||
documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
return os.path.join(documents, "Elder Scrolls Online", "live", "Addons")
|
||||
eso_path = os_services.get_eso_config_path()
|
||||
return os.path.join(eso_path, "live", "Addons")
|
||||
|
||||
|
||||
def addon_exists(name, url=None, v=None):
|
||||
@ -180,6 +126,7 @@ def addon_exists(name, url=None, v=None):
|
||||
def get_addonversion(name, url=None, v=None):
|
||||
if addon_exists(name):
|
||||
txt = name + ".txt"
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
with open(os.path.join(get_addondir(), name, txt)) as f:
|
||||
for line in f:
|
||||
@ -190,17 +137,34 @@ def get_addonversion(name, url=None, v=None):
|
||||
return 0
|
||||
|
||||
|
||||
def install_required_addons(force=False):
|
||||
addons_req = [libgps, lam2, fishyqr, fishyfsm, libmapping, libdl, libchatmsg]
|
||||
addon_version = config.get("addon_version", {})
|
||||
installed = False
|
||||
for addon in addons_req:
|
||||
if force or (addon_exists(*addon) and
|
||||
(addon[0] not in addon_version or (
|
||||
addon[0] in addon_version and addon_version[addon[0]] < addon[2]))):
|
||||
remove_addon(*addon)
|
||||
install_addon(*addon)
|
||||
addon_version[addon[0]] = addon[2]
|
||||
installed = True
|
||||
config.set("addon_version", addon_version)
|
||||
if installed:
|
||||
logging.info("Please make sure to enable \"Allow outdated addons\" in ESO")
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
def install_addon(name, url, v=None):
|
||||
try:
|
||||
r = requests.get(url, stream=True)
|
||||
z = ZipFile(BytesIO(r.content))
|
||||
z.extractall(path=get_addondir())
|
||||
logging.info("Add-On " + name +
|
||||
" installed successfully!\nPlease make sure to enable \"Allow outdated addons\" in ESO")
|
||||
logging.info("Add-On " + name + " installed successfully!")
|
||||
return 0
|
||||
except Exception:
|
||||
logging.error("Could not install Add-On " + name + ", try doing it manually")
|
||||
print_exc()
|
||||
return 1
|
||||
|
||||
|
||||
@ -216,38 +180,19 @@ def remove_addon(name, url=None, v=None):
|
||||
return 0
|
||||
|
||||
|
||||
def get_documents():
|
||||
return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
|
||||
|
||||
|
||||
def restart():
|
||||
os.execl(sys.executable, *([sys.executable] + sys.argv))
|
||||
|
||||
|
||||
def log_raise(msg):
|
||||
logging.error(msg)
|
||||
raise Exception(msg)
|
||||
|
||||
|
||||
def update():
|
||||
from .config import config
|
||||
|
||||
config.delete("dont_ask_update")
|
||||
restart()
|
||||
|
||||
|
||||
def is_eso_active():
|
||||
return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online"
|
||||
|
||||
|
||||
# noinspection PyProtectedMember,PyUnresolvedReferences
|
||||
def _get_id(thread):
|
||||
# returns id of the respective thread
|
||||
if hasattr(thread, '_thread_id'):
|
||||
return thread._thread_id
|
||||
for id, thread in threading._active.items():
|
||||
for _id, thread in threading._active.items():
|
||||
if thread is thread:
|
||||
return id
|
||||
return _id
|
||||
|
||||
|
||||
def kill_thread(thread):
|
||||
@ -257,3 +202,26 @@ def kill_thread(thread):
|
||||
if res > 1:
|
||||
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
|
||||
print('Exception raise failure')
|
||||
|
||||
|
||||
def print_exc():
|
||||
logging.error(traceback.format_exc())
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def save_img_path():
|
||||
return os.path.join(os_services.get_documents_path(), "fishy_debug", "imgs")
|
||||
|
||||
|
||||
def save_img(show_name, img, half=False):
|
||||
img_path = os.path.join(save_img_path(), show_name)
|
||||
if not os.path.exists(img_path):
|
||||
os.makedirs(img_path)
|
||||
|
||||
if half:
|
||||
img = cv2.resize(img, (0, 0), fx=0.5, fy=0.5)
|
||||
|
||||
t = time.strftime("%Y.%m.%d.%H.%M.%S")
|
||||
cv2.imwrite(
|
||||
os.path.join(img_path, f"{t}.jpg"),
|
||||
img)
|
||||
|
@ -1,3 +1,4 @@
|
||||
import logging
|
||||
import time
|
||||
from multiprocessing import Process, Queue
|
||||
from threading import Thread
|
||||
@ -6,6 +7,7 @@ from typing import Dict, Optional, Callable
|
||||
from playsound import playsound
|
||||
|
||||
from fishy import helper
|
||||
from fishy.helper.config import config
|
||||
from fishy.helper.hotkey import process
|
||||
from fishy.helper.hotkey.process import Key
|
||||
|
||||
@ -56,23 +58,27 @@ class HotKey:
|
||||
while True:
|
||||
key = self.outq.get()
|
||||
|
||||
if key == "stop":
|
||||
break
|
||||
|
||||
if key in Key:
|
||||
callback = self._hotkeys[key]
|
||||
if callback:
|
||||
playsound(helper.manifest_file("beep.wav"), False)
|
||||
if config.get("sound_notification", False):
|
||||
playsound(helper.manifest_file("beep.wav"), False)
|
||||
|
||||
callback()
|
||||
elif key == "stop":
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
def start(self):
|
||||
self.process.start()
|
||||
self.event.start()
|
||||
logging.debug("hotkey process started")
|
||||
|
||||
def stop(self):
|
||||
self.inq.put("stop")
|
||||
self.outq.put("stop")
|
||||
self.process.join()
|
||||
self.event.join()
|
||||
print("hotkey process ended")
|
||||
logging.debug("hotkey process ended")
|
||||
|
31
fishy/helper/migration.py
Normal file
31
fishy/helper/migration.py
Normal file
@ -0,0 +1,31 @@
|
||||
import logging
|
||||
|
||||
import fishy
|
||||
from fishy.helper.auto_update import _normalize_version
|
||||
|
||||
from .config import config
|
||||
|
||||
|
||||
class Migration:
|
||||
@staticmethod
|
||||
def up_to_0_5_3():
|
||||
config.delete("addoninstalled")
|
||||
|
||||
@staticmethod
|
||||
def migrate():
|
||||
prev_version = _normalize_version(config.get("prev_version", "0.0.0"))
|
||||
current_version = _normalize_version(fishy.__version__)
|
||||
|
||||
if current_version > prev_version:
|
||||
for v, f in migration_code:
|
||||
if prev_version < _normalize_version(v) <= current_version:
|
||||
logging.info(f"running migration for {v}")
|
||||
f()
|
||||
config.set("prev_version", fishy.__version__)
|
||||
|
||||
|
||||
|
||||
migration_code = [
|
||||
# version, upgrade_code
|
||||
("0.5.3", Migration.up_to_0_5_3)
|
||||
]
|
@ -1,5 +1,6 @@
|
||||
import time
|
||||
from tkinter import Toplevel
|
||||
from fishy import helper
|
||||
|
||||
|
||||
def center(win):
|
||||
@ -19,6 +20,8 @@ class PopUp(Toplevel):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.running = True
|
||||
self.quit_callback = quit_callback
|
||||
self.protocol("WM_DELETE_WINDOW", self.quit_top)
|
||||
self.iconbitmap(helper.manifest_file('icon.ico'))
|
||||
|
||||
def quit_top(self):
|
||||
self.quit_callback()
|
||||
@ -26,7 +29,7 @@ class PopUp(Toplevel):
|
||||
self.running = False
|
||||
|
||||
def start(self):
|
||||
self.protocol("WM_DELETE_WINDOW", self.quit_top)
|
||||
self.minsize(self.winfo_width(), self.winfo_height())
|
||||
self.grab_set()
|
||||
center(self)
|
||||
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 .web import (get_session, is_subbed, register_user, send_fish_caught,
|
||||
from .web import (get_session, is_subbed, _register_user, send_fish_caught,
|
||||
send_notification, sub, unsub)
|
||||
|
@ -2,8 +2,11 @@ import logging
|
||||
import traceback
|
||||
from functools import wraps
|
||||
|
||||
from fishy.web import web
|
||||
|
||||
|
||||
def uses_session(f):
|
||||
"""directly returns none if it couldn't get session, instead of running the function"""
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
from .web import get_session
|
||||
@ -21,6 +24,9 @@ def fallback(default):
|
||||
# noinspection PyBroadException
|
||||
@wraps(f)
|
||||
def wrapper(*args, **kwargs):
|
||||
if not web.is_online():
|
||||
return default
|
||||
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except Exception:
|
||||
|
@ -3,9 +3,9 @@ import sys
|
||||
if "--local-server" in sys.argv:
|
||||
domain = "http://127.0.0.1:5000"
|
||||
elif "--test-server" in sys.argv:
|
||||
domain = "https://fishyeso-test.herokuapp.com"
|
||||
domain = "https://fishyeso-test.definex.in"
|
||||
else:
|
||||
domain = "https://fishyeso.herokuapp.com"
|
||||
domain = "https://fishyeso.definex.in"
|
||||
|
||||
user = domain + "/api/user"
|
||||
notify = domain + "/api/notify"
|
||||
|
@ -1,15 +1,21 @@
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from fishy import constants
|
||||
from whatsmyip.ip import get_ip
|
||||
from whatsmyip.providers import GoogleDnsProvider
|
||||
|
||||
from fishy import helper
|
||||
|
||||
from ..constants import apiversion
|
||||
from ..helper.config import config
|
||||
from . import urls
|
||||
from .decorators import fallback, uses_session
|
||||
|
||||
_session_id = None
|
||||
_online = True
|
||||
|
||||
|
||||
def is_online():
|
||||
return _online
|
||||
|
||||
|
||||
@fallback(-1)
|
||||
@ -18,7 +24,7 @@ def is_logged_in():
|
||||
return -1
|
||||
|
||||
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"]
|
||||
return 1 if logged_in else 0
|
||||
|
||||
@ -44,10 +50,10 @@ def logout():
|
||||
|
||||
|
||||
@fallback(None)
|
||||
def register_user():
|
||||
def _register_user():
|
||||
ip = get_ip(GoogleDnsProvider)
|
||||
body = {"ip": ip, "apiversion": apiversion}
|
||||
response = requests.post(urls.user, json=body)
|
||||
response = requests.post(urls.user, json=body, timeout=10)
|
||||
result = response.json()
|
||||
return result["uid"]
|
||||
|
||||
@ -86,8 +92,6 @@ def sub():
|
||||
@fallback((False, False))
|
||||
def is_subbed():
|
||||
"""
|
||||
:param uid:
|
||||
:param lazy:
|
||||
:return: Tuple[is_subbed, success]
|
||||
"""
|
||||
|
||||
@ -95,13 +99,13 @@ def is_subbed():
|
||||
return False, False
|
||||
|
||||
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:
|
||||
return False, False
|
||||
|
||||
is_subbed = response.json()["subbed"]
|
||||
return is_subbed, True
|
||||
_is_subbed = response.json()["subbed"]
|
||||
return _is_subbed, True
|
||||
|
||||
|
||||
@fallback(None)
|
||||
@ -112,29 +116,60 @@ def unsub():
|
||||
return result["success"]
|
||||
|
||||
|
||||
@fallback(None)
|
||||
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
|
||||
if lazy and _session_id is not None:
|
||||
return _session_id
|
||||
|
||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||
response = requests.post(urls.session, params=body)
|
||||
# check if user has uid
|
||||
uid = config.get("uid")
|
||||
|
||||
# then create session
|
||||
if uid:
|
||||
_session_id, _online = _create_new_session(uid)
|
||||
# if not, create new id then try creating session again
|
||||
else:
|
||||
uid = _register_user()
|
||||
config.set("uid", uid, True)
|
||||
logging.debug(f"New User, generated new uid: {uid}")
|
||||
if uid:
|
||||
_session_id, _online = _create_new_session(uid)
|
||||
else:
|
||||
_online = False
|
||||
|
||||
# when the user is already registered but session is not created as uid is not found by the server
|
||||
if _online and not _session_id:
|
||||
logging.error("user not found, generating new uid.. contact dev if you don't want to loose data")
|
||||
new_uid = _register_user()
|
||||
_session_id, _online = _create_new_session(new_uid)
|
||||
config.set("uid", new_uid, True)
|
||||
config.set("old_uid", uid, True)
|
||||
|
||||
return _session_id
|
||||
|
||||
|
||||
@fallback((None, False))
|
||||
def _create_new_session(uid):
|
||||
body = {"uid": uid, "apiversion": apiversion}
|
||||
response = requests.post(urls.session, json=body, timeout=10)
|
||||
|
||||
if response.status_code == 405:
|
||||
config.delete("uid")
|
||||
helper.restart()
|
||||
return None
|
||||
return None, True
|
||||
|
||||
_session_id = response.json()["session_id"]
|
||||
return _session_id
|
||||
return response.json()["session_id"], True
|
||||
|
||||
|
||||
@fallback(False)
|
||||
def has_beta():
|
||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||
response = requests.get(urls.beta, params=body)
|
||||
response = requests.get(urls.beta, json=body)
|
||||
result = response.json()
|
||||
|
||||
if not result["success"]:
|
||||
@ -146,4 +181,11 @@ def has_beta():
|
||||
@fallback(None)
|
||||
def ping():
|
||||
body = {"uid": config.get("uid"), "apiversion": apiversion}
|
||||
requests.post(urls.ping, params=body)
|
||||
response = requests.post(urls.ping, json=body)
|
||||
logging.debug(f"ping response: {response.json()}")
|
||||
|
||||
|
||||
@fallback("0.5.21")
|
||||
def get_highest_version():
|
||||
response = requests.get(constants.current_version_url)
|
||||
return response.content.decode()
|
@ -1,17 +1,17 @@
|
||||
urllib3
|
||||
winshell
|
||||
imutils
|
||||
numpy!=1.19.4
|
||||
numpy
|
||||
opencv_python
|
||||
Pillow
|
||||
pypiwin32
|
||||
pypiwin32 ; platform_system=="Windows"
|
||||
winshell ; platform_system=="Windows"
|
||||
ttkthemes
|
||||
requests
|
||||
beautifulsoup4
|
||||
whatsmyip
|
||||
pynput
|
||||
keyboard
|
||||
playsound
|
||||
playsound==1.2.2
|
||||
event-scheduler
|
||||
pyzbar
|
||||
mouse
|
||||
pyautogui
|
||||
mss
|
Loading…
x
Reference in New Issue
Block a user