Merge branch 'main' into wip/keyboard-interup

# Conflicts:
#	fishy/__main__.py
This commit is contained in:
Adam Saudagar 2023-03-07 00:07:36 +05:30
commit 063c1e5481
13 changed files with 313 additions and 137 deletions

View File

@ -1,11 +1,6 @@
import ctypes
import logging
import os
import sys
import win32con
import win32gui
import fishy
from fishy.gui import GUI, update_dialog, check_eula
from fishy import helper, web
@ -17,21 +12,16 @@ from fishy.helper.active_poll import active
from fishy.helper.config import config
from fishy.helper.hotkey.hotkey_process import hotkey
from fishy.helper.migration import Migration
def check_window_name(title):
titles = ["Command Prompt", "PowerShell", "Fishy"]
for t in titles:
if t in title:
return True
return False
from fishy.osservices.os_services import os_services
# noinspection PyBroadException
def initialize(window_to_hide):
def initialize():
Migration.migrate()
helper.create_shortcut_first()
if not config.get("shortcut_created", False):
os_services.create_shortcut(False)
config.set("shortcut_created", True)
new_session = web.get_session()
@ -39,15 +29,11 @@ def initialize(window_to_hide):
logging.error("Couldn't create a session, some features might not work")
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")
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
@ -62,9 +48,17 @@ def on_gui_load(gui, splash, logger):
def main():
print("launching please wait...")
if not os_services.init():
print("platform not supported")
return
config.init()
if not check_eula():
return
bot = EngineEventHandler(lambda: gui)
gui = GUI(lambda: bot, lambda: on_gui_load(gui, splash, logger))
window_to_hide = win32gui.GetForegroundWindow()
logger = GuiLogger()
hotkey.init()
active.init()
@ -79,7 +73,7 @@ def main():
splash = Splash().start()
config.start_backup_scheduler()
initialize(window_to_hide)
initialize()
hotkey.start()
gui.start()

View File

@ -1,18 +1,14 @@
import logging
import math
from enum import Enum
from threading import Thread
import numpy as np
import pywintypes
import win32api
import win32gui
from ctypes import windll
from mss import mss
from mss.base import MSSBase
from fishy.helper.helper import print_exc
from fishy.osservices.os_services import os_services
class Status(Enum):
@ -27,10 +23,11 @@ class WindowServer:
"""
Screen: np.ndarray = None
windowOffset = None
hwnd = None
status = Status.STOPPED
sct: MSSBase = None
monitor_top_left = None
crop = None
monitor_id = -1
def init():
@ -38,22 +35,31 @@ def init():
Executed once before the main loop,
Finds the game window, and calculates the offset to remove the title bar
"""
# noinspection PyUnresolvedReferences
try:
WindowServer.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online")
WindowServer.status = Status.RUNNING
WindowServer.sct = mss()
monitor_id = windll.user32.MonitorFromWindow(WindowServer.hwnd, 2)
WindowServer.monitor_top_left = win32api.GetMonitorInfo(monitor_id)["Monitor"][:2]
WindowServer.crop = os_services.get_game_window_rect()
monitor_rect = os_services.get_monitor_rect()
rect = win32gui.GetWindowRect(WindowServer.hwnd)
client_rect = win32gui.GetClientRect(WindowServer.hwnd)
WindowServer.windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2)
WindowServer.status = Status.RUNNING
WindowServer.sct = mss()
except pywintypes.error:
if monitor_rect is None or WindowServer.crop is None:
logging.error("Game window not found")
WindowServer.status = Status.CRASHED
return
for i, m in enumerate(WindowServer.sct.monitors):
if m["top"] == monitor_rect[0] and m["left"] == monitor_rect[1]:
WindowServer.monitor_id = i
def get_cropped_screenshot():
sct_img = WindowServer.sct.grab(WindowServer.sct.monitors[WindowServer.monitor_id])
# noinspection PyTypeChecker
ss = np.array(sct_img)
crop = WindowServer.crop
cropped_ss = ss[crop[1]:crop[3], crop[0]:crop[2]]
if cropped_ss.size == 0:
return None
return cropped_ss
def loop():
@ -61,27 +67,10 @@ def loop():
Executed in the start of the main loop
finds the game window location and captures it
"""
WindowServer.Screen = get_cropped_screenshot()
sct_img = WindowServer.sct.grab(WindowServer.sct.monitors[1])
# noinspection PyTypeChecker
temp_screen = np.array(sct_img)
rect = win32gui.GetWindowRect(WindowServer.hwnd)
client_rect = win32gui.GetClientRect(WindowServer.hwnd)
fullscreen = sct_img.size.height == (rect[3] - rect[1])
title_offset = ((rect[3] - rect[1]) - client_rect[3]) - WindowServer.windowOffset if not fullscreen else 0
crop = (
rect[0] + WindowServer.windowOffset - WindowServer.monitor_top_left[0],
rect[1] + title_offset - WindowServer.monitor_top_left[1],
rect[2] - WindowServer.windowOffset - WindowServer.monitor_top_left[0],
rect[3] - WindowServer.windowOffset - WindowServer.monitor_top_left[1]
)
WindowServer.Screen = temp_screen[crop[1]:crop[3], crop[0]:crop[2]] if not fullscreen else temp_screen
if WindowServer.Screen.size == 0:
logging.error("Don't minimize or drag game window outside the screen")
if WindowServer.Screen is None:
logging.error("Couldn't find the game window")
WindowServer.status = Status.CRASHED

View File

@ -16,7 +16,8 @@ from fishy.engine.fullautofisher.mode.recorder import Recorder
from fishy.engine.semifisher import fishing_mode
from fishy.engine.semifisher.fishing_mode import FishingMode
from fishy.helper.config import config
from fishy.helper.helper import wait_until, is_eso_active, sign, print_exc
from fishy.helper.helper import wait_until, sign, print_exc
from fishy.osservices.os_services import os_services
mse = mouse.Controller()
kb = keyboard.Controller()
@ -52,9 +53,9 @@ class FullAuto(IEngine):
return
# block thread until game window becomes active
if not is_eso_active():
if not os_services.is_eso_active():
logging.info("Waiting for eso window to be active...")
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)
@ -87,8 +88,8 @@ class FullAuto(IEngine):
def stop_on_inactive(self):
def func():
logging.debug("stop on inactive started")
wait_until(lambda: not is_eso_active() or not self.start)
if self.start and not is_eso_active():
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()

View File

@ -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:
@ -44,7 +44,7 @@ def _fishing_sleep(waittime, lower_limit_ms=16, upper_limit_ms=1375):
def if_eso_is_focused(func):
def wrapper():
if not is_eso_active():
if not os_services.is_eso_active():
logging.warning("ESO window is not focused")
return
func()

View File

@ -15,6 +15,7 @@ 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
@ -46,7 +47,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():

View File

@ -1,5 +1,5 @@
from .config import Config
from .helper import (addon_exists, create_shortcut, create_shortcut_first,
from .helper import (addon_exists,
get_addonversion, get_savedvarsdir,
install_addon, install_thread_excepthook, manifest_file,
not_implemented, open_web, playsound_multiple,

View File

@ -10,15 +10,17 @@ from typing import Optional
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"
_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(), name)
temp_file = os.path.join(os.environ["TEMP"], "fishy_config.BAK")

View File

@ -14,15 +14,13 @@ from uuid import uuid1
from zipfile import ZipFile
import requests
import winshell
from playsound import playsound
from win32com.client import Dispatch
from win32comext.shell import shell, shellcon
from win32gui import GetForegroundWindow, GetWindowText
import fishy
from fishy.constants import libgps, lam2, fishyqr, libmapping, libdl, libchatmsg
from fishy.helper.config import config
from fishy.osservices.os_services import os_services
def playsound_multiple(path, count=2):
@ -110,55 +108,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:
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):
@ -222,19 +179,10 @@ def remove_addon(name, url=None, v=None):
return 0
def get_documents():
return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0)
def log_raise(msg):
logging.error(msg)
raise Exception(msg)
def is_eso_active():
return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online"
# noinspection PyProtectedMember,PyUnresolvedReferences
def _get_id(thread):
# returns id of the respective thread
@ -252,8 +200,8 @@ def kill_thread(thread):
if res > 1:
ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
print('Exception raise failure')
def print_exc():
logging.error(traceback.format_exc())
traceback.print_exc()

View File

29
fishy/osservices/linux.py Normal file
View 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

View File

@ -0,0 +1,100 @@
import inspect
import logging
import re
import sys
from abc import ABC, abstractmethod
from typing import Tuple, Optional
import platform
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
"""
# todo move this into helper and use for config and similar places
# but make sure other fishy stuff is not imported while importing helper
# to do that, move everything which uses fishy stuff into a different helper script
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
@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
View 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

View File

@ -1,10 +1,10 @@
urllib3
winshell
imutils
numpy
opencv_python
Pillow
pypiwin32
pypiwin32 ; platform_system=="Windows"
winshell ; platform_system=="Windows"
ttkthemes
requests
beautifulsoup4