Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

新增了代理功能 && 新增加了一站式服务 #9

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
新增了代理功能 && 新增加了一站式服务
bilibili12433014 committed Jun 23, 2024
commit bef87514c3e08e474269877a3185a2736694a73d
160 changes: 160 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# VS
.vs


config
mahjong-helper.exe
log
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -94,3 +94,24 @@
- 如果自动更新失败,可以在[AutoLiqi > Releases](https://github.com/Avenshy/AutoLiqi/releases/latest)下载,并手动替换`./proto`文件夹下的同名文件
2. 还有其它问题?
在上方加入我们的[Telegram群](https://github.com/Avenshy/MajsoulMax?tab=readme-ov-file#%EF%B8%8Ftelegram%E9%A2%91%E9%81%93%E4%BA%A4%E6%B5%81%E7%BE%A4)

## 代理
如果下载失败或,可以使用代理,代理配置文件在`config/settings.proxy.yaml`将其中`null`改为你的代理地址格式是`http://host:port`

## 一站式服务

1. 此功能涉及到的文件位于`START.py`和`auto_play`文件夹下
2. 该功能目前正在开发中,已经能够实现自定义端口(方便多开)以及浏览器自动启动(只支持chrome)
3. 启动文件为 `START.py`, 请确保你的电脑上有python环境
4. 启动命令为 `python START.py -mit_port port1 -helper_port port2 [-proxy http_proxy]`
5. `port1`是mitmproxy的端口,`port2`是mahjong-helper的端口,`http_proxy`是http代理,可选,格式是`http://host:port`
6. TODO:
- 自动选择端口
- 端口重复提示
- 自动出牌
- 自动胡牌
- 自动开始开始下一局
7. 此功能由bilibili12433014开发,不得用于商业用途,否则后果自负
8. 本功能完全免费、开源,如果您为此付费,说明您被骗了!
8. 警告:
> 雀魂游戏官方可能会检测并封号!<br />如产生任何后果与作者无关!<br />使用本功能则表示同意此条款!
40 changes: 40 additions & 0 deletions START.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import argparse
import yaml
from auto_play.MahjongHelper import MahjongHelper
from auto_play.MajSoulMitmdump import MajSoulMitmdump
from auto_play.MajSoulWeb import MajSoulWeb

def update_helper_config(helper_port):
config_data = {
'config': {
'api_url': f'https://localhost:{helper_port}/'
}
}

with open('config/settings.helper.yaml', 'w') as file:
yaml.dump(config_data, file, default_flow_style=False)

def update_proxy_config(proxy):
config_data = {
'config': {
'proxy': proxy
}
}

with open('config/settings.proxy.yaml', 'w') as file:
yaml.dump(config_data, file, default_flow_style=False)

parser = argparse.ArgumentParser(description='设置代理和端口配置')
parser.add_argument('-proxy', type=str, default=None, help='指定代理上网,流量出接口,http://host:port')
parser.add_argument('-mit_port', type=int, default=23410, help='指定中间代理监听端口')
parser.add_argument('-helper_port', type=int, default=12121, help='指定小助手端监听口')

args = parser.parse_args()

update_helper_config(args.helper_port)
if args.proxy:
update_proxy_config(args.proxy)

helper = MahjongHelper(port=args.helper_port)
web = MajSoulWeb(proxy_port=args.mit_port)
mitmdump = MajSoulMitmdump(proxy=args.proxy,port=args.mit_port)
84 changes: 84 additions & 0 deletions auto_play/MahjongHelper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import subprocess
import threading
import queue
import os
import zipfile
import atexit
from auto_play.RequestsProxy import proxy as requests


def download_and_extract(url, extract_to="."):
local_filename = url.split("/")[-1]
if not os.path.exists("mahjong-helper.exe"):
with requests.get(url, stream=True) as r:
r.raise_for_status()
with open(local_filename, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)

with zipfile.ZipFile(local_filename, "r") as zip_ref:
zip_ref.extractall(extract_to)

os.remove(local_filename)
else:
print(f"mahjong-helper.exe already exists, skipping download.")


url = "https://github.com/EndlessCheng/mahjong-helper/releases/download/v0.2.8/mahjong-helper-v0.2.8-win64-x64.zip"
download_and_extract(url)


class MahjongHelper:
def __init__(self, executable_path="mahjong-helper.exe", port=12121):
# os.system("taskkill /f /pid mahjong-helper.exe")
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
self.process = subprocess.Popen(
executable_path + f" -port {port}",
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8", # 使用utf-8编码
errors="ignore", # 忽略解码错误
startupinfo=startupinfo, # 隐藏窗口
)
self.output_queue = queue.Queue()
self.output_thread = threading.Thread(target=self._read_output)
self.output_thread.daemon = True
self.output_thread.start()
self.set_input("1")
atexit.register(self.close)

def _read_output(self):
while True:
output_line = self.process.stdout.readline()
if output_line:
print(output_line)
self.output_queue.put(output_line)
else:
break

def set_input(self, data):
self.process.stdin.write(data + "\n")
self.process.stdin.flush()

def get_output(self):
if not self.output_queue.empty():
return self.output_queue.get()
return None

def close(self):
self.process.terminate()
self.process.wait()

def __del__(self):
self.close()


if __name__ == "__main__":
helper = MahjongHelper("../mahjong-helper.exe")

while True:
if output := helper.get_output():
print(output)
18 changes: 18 additions & 0 deletions auto_play/MajSoulMitmdump.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from mitmproxy.tools.main import mitmdump

class MajSoulMitmdump:
def __init__(
self,
proxy:str|None=None,
port:int|str=23410,
):
options=[]
if proxy:
options += [
'--mode', 'upstream:'+proxy, # ���ö��δ���
]
options += [
'-p', str(port), # ָ���˿�
'-s', 'addons.py' # ָ���ű�
]
mitmdump(options)
93 changes: 93 additions & 0 deletions auto_play/MajSoulWeb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from PIL import Image
import numpy as np
from io import BytesIO
import cv2
import threading
import time


class MajSoulWeb:
def __init__(
self,
proxy_port:str|int|None="23410",
chrome_path="C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
):
self.proxy_server = "127.0.0.1:"+str(proxy_port) if None else None
self.chrome_path = chrome_path
self.driver = self._setup_driver()
threading.Thread(target=self.main).start()

def _setup_driver(self):
options = webdriver.ChromeOptions()
if self.proxy_server:
options.add_argument(f"--proxy-server={self.proxy_server}")
options.add_argument("--ignore-certificate-errors")
options.binary_location = self.chrome_path

driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install()), options=options
)
return driver

def open_game_page(self, url="https://game.maj-soul.com/1/"):
self.driver.get(url)
WebDriverWait(self.driver, 10).until(
EC.presence_of_element_located((By.ID, "layaCanvas"))
)

def get_image(self):
screenshot = self.driver.get_screenshot_as_png()
image = Image.open(BytesIO(screenshot))
image_array = np.array(image)
return image_array[:, :, ::-1]

def get_size(self, image: np.array) -> tuple:
return tuple(reversed(image.shape[:2]))

def get_padding(self, size: tuple) -> tuple:
width, height = size
target_ratio = 16 / 9

if width / height > target_ratio:
rect_height = height
rect_width = int(height * target_ratio)
else:
rect_width = width
rect_height = int(width / target_ratio)

x_padding = (width - rect_width) // 2
y_padding = (height - rect_height) // 2

return (rect_width, rect_height), (x_padding, y_padding)

def cut_image(self, image: np.ndarray, padding: tuple) -> np.ndarray:
x_padding, y_padding = padding
height, width = image.shape[:2]

x_start = x_padding
x_end = width - x_padding
y_start = y_padding
y_end = height - y_padding

cut_img = image[y_start:y_end, x_start:x_end]

return cut_img

def process_and_display(self):
image = self.get_image()
size = self.get_size(image)
rect, padding = self.get_padding(size)
image = self.cut_image(image, padding)
cv2.imshow("maj-soul", image)
cv2.waitKey(0)
cv2.destroyAllWindows()

def main(self):
time.sleep(10)
self.open_game_page()
50 changes: 50 additions & 0 deletions auto_play/RequestsProxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import requests
import yaml
def init_proxy_config():
config_data = {
'config': {
'proxy': None
}
}

with open('config/settings.proxy.yaml', 'w') as file:
yaml.dump(config_data, file, default_flow_style=False)

def get_proxy():
try:
with open('config/settings.proxy.yaml', 'r') as file:
proxy = yaml.load(file, Loader=yaml.FullLoader)["config"]["proxy"]
return proxy
except:
init_proxy_config()
return None

class RequestsProxy:
def __init__(self):
proxy = get_proxy()
if proxy:
self.proxies = {
'http': proxy,
'https': proxy,
}
else:
self.proxies = None

def get(self, url, **kwargs):
kwargs['proxies'] = self.proxies
return requests.get(url, **kwargs)

def post(self, url, data=None, json=None, **kwargs):
kwargs['proxies'] = self.proxies
return requests.post(url, data=data, json=json, **kwargs)

def put(self, url, data=None, **kwargs):
kwargs['proxies'] = self.proxies
return requests.put(url, data=data, **kwargs)

def delete(self, url, **kwargs):
kwargs['proxies'] = self.proxies
return requests.delete(url, **kwargs)

# 创建一个RequestsProxy的实例并赋值给requests
proxy = RequestsProxy()
Binary file added auto_play/requirements.txt
Binary file not shown.
2 changes: 1 addition & 1 deletion plugin/mod.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from ruamel.yaml import YAML
from loguru import logger
import liqi_new
import requests
from struct import unpack
from proto import liqi_pb2, config_pb2, sheets_pb2, basic_pb2
from google.protobuf import json_format
from .update_liqi import get_version
from auto_play.RequestsProxy import proxy as requests


class mod:
2 changes: 1 addition & 1 deletion plugin/update_liqi.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import requests
from auto_play.RequestsProxy import proxy as requests
from loguru import logger


2 changes: 1 addition & 1 deletion proto/liqi.json

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions proto/liqi.proto
Original file line number Diff line number Diff line change
@@ -576,6 +576,7 @@ message AccountUpdate {
AccountABMatchUpdate ab_match = 14;
lq.AccountActivityUpdate activity = 15;
SegmentTaskUpdate activity_segment_task = 16;
MonthTicketUpdate month_ticket = 17;

message NumericalUpdate {

@@ -648,6 +649,12 @@ message AccountUpdate {
repeated lq.SegmentTaskProgress progresses = 1;
repeated uint32 task_list = 2;
}

message MonthTicketUpdate {

uint32 end_time = 1;
uint32 last_pay_time = 2;
}
}

message GameMetaData {
@@ -817,6 +824,7 @@ message Room {
uint32 robot_count = 9;
uint32 tournament_id = 10;
uint32 seq = 11;
string pre_rule = 12;
}

message GameEndResult {
@@ -2986,6 +2994,7 @@ message ReqCreateRoom {
GameMode mode = 2;
bool public_live = 3;
string client_version_string = 4;
string pre_rule = 5;
}

message ResCreateRoom {
@@ -3637,6 +3646,7 @@ message ReqRollingNotice {
message ResServerTime {

uint32 server_time = 1;
Error error = 2;
}

message ReqPlatformBillingProducts {
3,170 changes: 1,586 additions & 1,584 deletions proto/liqi_pb2.py

Large diffs are not rendered by default.

Binary file modified proto/lqc.lqbin
Binary file not shown.