Files
hanfuit 0ced464168 启动器新增游戏设置页(频道开关+活动开关),窗口标题改名
- 服务端新增活动开关运行时配置与 GM 接口(GET/POST /dnf/gm/activity-switches),
  持久化到 Settings,接入月签到/周签到/PC房↔累计在线的实时下发
- 服务端新增频道开关 GM 接口(channel-switches),目录生成时过滤禁用频道
- channelcatalog/channelinfo 支持按禁用频道 ID 过滤目录与客户端脚本
- 启动器新增「游戏设置」页面:频道开关 Tab + 活动开关 Tab
- native.py/server.py 新增对应转发 API
- 窗口标题/页面 title/侧边栏「90轮回」统一改为「DNF90 灵狐小竹整合单机版」
2026-11-04 20:08:50 +08:00

644 lines
29 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""Native API (127.0.0.1:18111) client for the DNF90 GM tool.
本文件依据服务端源码 (go-server/internal/app/dnf90/gm_tool_admin.go、
gm_admin.go、gm_tool_compat.go 以及 internal/services/dnfbridge/*) 精确实现
全部 GM 端点,字段名/类型/校验规则均与源码一致。
两类端点:
- 读端点 GET /dnf/gm/... (返回 {"success":true,"data":...})
- 写端点 POST /dnf/gm/... 或 /local/dnf-gm/... (需要 X-Admin-Confirm)
认证头:
X-Admin-Token : adminToken(来自 instance.json)
X-Admin-Confirm: 写操作设为 "1"
"""
import json
import os
import uuid
import urllib.request
import urllib.error
from urllib.parse import urlencode
class NativeError(Exception):
pass
class NativeClient(object):
def __init__(self, base_url="http://127.0.0.1:18111", token=""):
self.base_url = base_url.rstrip("/")
self.token = token
# ---- low-level HTTP ------------------------------------------
def _request(self, method, path, body=None, confirm=False):
url = self.base_url + path
data = None
headers = {"Accept": "application/json"}
if self.token:
headers["X-Admin-Token"] = self.token
if confirm:
headers["X-Admin-Confirm"] = "1"
headers["X-Admin-Idempotency-Key"] = uuid.uuid4().hex
if body is not None:
data = json.dumps(body, ensure_ascii=False).encode("utf-8")
headers["Content-Type"] = "application/json"
req = urllib.request.Request(url, data=data, headers=headers, method=method)
try:
with urllib.request.urlopen(req, timeout=15) as resp:
raw = resp.read()
if not raw:
return {}
return json.loads(raw.decode("utf-8"))
except urllib.error.HTTPError as e:
raw = e.read().decode("utf-8", "replace")
# 尝试提取服务端返回的 error 字段,给出更友好的提示
try:
parsed = json.loads(raw)
msg = parsed.get("error") or raw[:200]
except Exception:
msg = raw[:200]
raise NativeError("HTTP %d: %s" % (e.code, msg))
except urllib.error.URLError as e:
raise NativeError("连接失败: %s" % e.reason)
def _get(self, path):
return self._request("GET", path)
def _post(self, path, body=None, confirm=True):
return self._request("POST", path, body=body, confirm=confirm)
def _unwrap(self, raw):
"""取服务端统一响应里的 data 字段。"""
if isinstance(raw, dict) and "data" in raw:
return raw["data"]
return raw
# ---- 状态 / 账号 / 角色 --------------------------------------
def ping(self):
return self._get("/dnf/gm/status")
def list_accounts(self, query="", limit=50):
qs = urlencode({"q": query, "limit": limit})
try:
return self._unwrap(self._get("/dnf/gm/accounts?" + qs)) or []
except NativeError:
return []
def get_account(self, account_id):
return self._unwrap(self._get("/dnf/gm/accounts/%s" % account_id))
def set_account_value(self, account_id, field, value):
"""POST /dnf/gm/accounts/{account_id}/value {field, value:uint64}"""
return self._unwrap(self._post(
"/dnf/gm/accounts/%s/value" % account_id,
{"field": field, "value": int(value)}))
def list_characters(self, account_id=None):
"""角色按账号枚举:/dnf/gm/accounts/{id}/characters。
无 account_id 时遍历所有账号。"""
account_ids = []
if account_id:
account_ids = [account_id]
else:
accs = self.list_accounts()
account_ids = [a.get("account_id") for a in accs
if isinstance(a, dict) and a.get("account_id")]
out = []
for aid in account_ids:
try:
raw = self._unwrap(self._get("/dnf/gm/accounts/%s/characters" % aid))
except NativeError:
continue
if not isinstance(raw, list):
raw = [raw] if raw else []
for c in raw:
if not isinstance(c, dict):
continue
cid = c.get("character_id") or c.get("id")
out.append({
"character_id": str(cid),
"account_id": c.get("account_id", aid),
"name": c.get("name", ""),
"job": c.get("job", "0"),
"level": c.get("level", 0),
"gold": c.get("gold", 0),
"profession_name": c.get("profession_name", ""),
"grow_type": c.get("grow_type", 0),
"first_grow": c.get("first_grow", 0),
"awakening": c.get("awakening", 0),
"online": c.get("online", False),
"raw": c,
})
return out
def get_character(self, character_id):
raw = self._unwrap(self._get("/dnf/gm/characters/%s" % character_id))
if not isinstance(raw, dict):
return raw
stats = {k: v for k, v in raw.items()
if k not in ("character_id", "account_id", "name", "job",
"level", "slot", "profession_name", "grow_type",
"first_grow", "awakening", "online", "gold", "cera")}
if "gold" in raw:
stats["gold"] = raw["gold"]
out = {
"character_id": str(raw.get("character_id") or character_id),
"account_id": raw.get("account_id", ""),
"name": raw.get("name", ""),
"job": raw.get("job", "0"),
"level": raw.get("level", 0),
"gold": raw.get("gold", 0),
"profession_name": raw.get("profession_name", ""),
"grow_type": raw.get("grow_type", 0),
"first_grow": raw.get("first_grow", 0),
"awakening": raw.get("awakening", 0),
"online": raw.get("online", False),
"stats": stats,
}
return out
def get_character_detail(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/detail" % character_id))
def get_inventory(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/inventory" % character_id))
def get_equipment(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/equipment-slots" % character_id))
def get_mailbox(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/mailbox" % character_id))
def get_account_cubes(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/account-cubes" % character_id))
# ---- 物品目录 / 搜索 -----------------------------------------
def get_item_detail(self, item_id):
return self._unwrap(self._get("/dnf/gm/items/%s/detail" % item_id))
def get_item_mail_options(self, item_id, mode="", query="", rarity=-1, limit=40, offset=0):
qs = urlencode({"mode": mode, "q": query, "rarity": rarity,
"limit": limit, "offset": offset})
return self._unwrap(self._get("/dnf/gm/items/%s/mail-options?%s" % (item_id, qs)))
def get_item_categories(self):
return self._unwrap(self._get("/dnf/gm/items/categories"))
def search_items(self, query="", kind="", tag="", segment="", group="", job="",
min_level=0, max_level=0, rarity=-1, expiration="",
offset=0, limit=50):
"""GET /dnf/gm/items/page —— 完整筛选参数(与源码 GMItemQuery 一致)。
返回归一化结构 {items:[...], total, limit, offset}。"""
params = {
"q": query, "kind": kind, "tag": tag, "segment": segment,
"group": group, "job": job, "min_level": min_level,
"max_level": max_level, "rarity": rarity, "expiration": expiration,
"offset": offset, "limit": limit,
}
# 去掉空值参数,避免干扰
params = {k: v for k, v in params.items() if v not in ("", -1, 0) or k in ("offset", "limit", "q", "rarity")}
qs = urlencode(params)
raw = self._unwrap(self._get("/dnf/gm/items/page?" + qs))
if isinstance(raw, dict) and "items" in raw:
return raw
return {"items": raw if isinstance(raw, list) else [], "total": 0,
"limit": limit, "offset": offset}
def search_avatar_sets(self, query="", job="", limit=24, offset=0):
qs = urlencode({"q": query, "job": job, "limit": limit, "offset": offset})
return self._unwrap(self._get("/dnf/gm/avatar-sets?" + qs))
# ---- 物品发放 -------------------------------------------------
# 注意:服务端有两个发放端点:
# 1) POST /local/dnf-gm/item/grant —— 直接进背包(服务端决定格位)
# 请求体 {character_id:string, item_id:uint32, count:uint32}
# 2) POST /local/dnf-gm/item/grant-options —— 带强化/增幅/锻造/品质/耐久/附魔/徽章
# 3) POST /local/dnf-gm/item/grant-with-emblems —— 带附魔宝珠 + 徽章(装备/时装)
# 4) POST /dnf/gm/characters/{id}/mail —— 邮件发放(带完整选项)
def grant_item(self, character_id, item_id, count=1):
body = {"character_id": str(character_id),
"item_id": int(item_id),
"count": int(count)}
return self._unwrap(self._post("/local/dnf-gm/item/grant", body))
def grant_item_options(self, character_id, item_id, count=1, reinforce=0,
amplify_type=0, forging=0, equipment_quality="",
durability=None, expire_days=0, emblem_item_ids=None,
enchant_bead_item_id=0):
"""带强化/增幅/锻造/品质/耐久/附魔的发放。
校验范围(源码 gm_tool_items_extra.go):
reinforce 0..31, amplify_type 0..4, forging 0..8, expire_days 0..3650"""
body = {"character_id": str(character_id), "item_id": int(item_id),
"count": int(count)}
if reinforce:
body["reinforce"] = int(reinforce)
if amplify_type:
body["amplify_type"] = int(amplify_type)
if forging:
body["forging"] = int(forging)
if equipment_quality:
body["equipment_quality"] = equipment_quality
if durability is not None:
body["durability"] = int(durability)
if expire_days:
body["expire_days"] = int(expire_days)
if emblem_item_ids:
body["emblem_item_ids"] = [int(x) for x in emblem_item_ids]
if enchant_bead_item_id:
body["enchant_bead_item_id"] = int(enchant_bead_item_id)
return self._unwrap(self._post("/local/dnf-gm/item/grant-options", body))
def grant_item_with_emblems(self, character_id, item_id, enchant_bead_item_id=0,
enchant_upgrade_count=None, emblems=None, open_sockets=None):
"""带附魔宝珠 + 徽章发放(仅装备/时装)。
emblems: [{socket_index:int, emblem_item_id:int, emblem_slot:int}, ...]"""
body = {"character_id": str(character_id), "item_id": int(item_id)}
if enchant_bead_item_id:
body["enchant_bead_item_id"] = int(enchant_bead_item_id)
if enchant_upgrade_count is not None:
body["enchant_upgrade_count"] = int(enchant_upgrade_count)
if emblems:
body["emblems"] = emblems
if open_sockets is not None:
body["open_sockets"] = int(open_sockets)
return self._unwrap(self._post("/local/dnf-gm/item/grant-with-emblems", body))
def send_item_mail(self, character_id, item_id, count=1, title="", body="",
expire_days=0, reinforce=0, amplify_type=0,
equipment_quality="", forging=0, emblem_item_ids=None,
enchant_bead_item_id=0):
"""POST /dnf/gm/characters/{id}/mail —— 邮件发放(完整选项)。"""
req = {"item_id": int(item_id), "count": int(count)}
if title:
req["title"] = title
if body:
req["body"] = body
if expire_days:
req["expire_days"] = int(expire_days)
if reinforce:
req["reinforce"] = int(reinforce)
if amplify_type:
req["amplify_type"] = int(amplify_type)
if equipment_quality:
req["equipment_quality"] = equipment_quality
if forging:
req["forging"] = int(forging)
if emblem_item_ids:
req["emblem_item_ids"] = [int(x) for x in emblem_item_ids]
if enchant_bead_item_id:
req["enchant_bead_item_id"] = int(enchant_bead_item_id)
return self._unwrap(self._post(
"/dnf/gm/characters/%s/mail" % character_id, req))
def delete_item(self, character_id, collection="", list_type=None, slot=None,
entry_key=""):
"""POST /local/dnf-gm/item/delete —— 删除背包行或穿戴装备行。
collection: "slots"(背包,默认) 或 "wear"(穿戴装备)"""
req = {"character_id": str(character_id)}
if collection:
req["collection"] = collection
if entry_key:
req["entry_key"] = entry_key
else:
req["list_type"] = int(list_type)
req["slot"] = int(slot)
return self._unwrap(self._post("/local/dnf-gm/item/delete", req))
# ---- 属性修改 -------------------------------------------------
def set_gold(self, character_id, amount):
"""POST /dnf/gm/characters/{id}/gold {value:int64}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/gold" % str(character_id), {"value": int(amount)}))
def set_level(self, character_id, level):
"""POST /dnf/gm/characters/{id}/level {level:int}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/level" % str(character_id), {"level": int(level)}))
def set_skill_bonus(self, character_id, bonus_sp, bonus_tp):
"""POST /dnf/gm/characters/{id}/skill-points {bonus_sp, bonus_tp:int64}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/skill-points" % str(character_id),
{"bonus_sp": int(bonus_sp), "bonus_tp": int(bonus_tp)}))
def get_career(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/career" % character_id))
def set_career(self, character_id, job, first, awakening):
"""POST /dnf/gm/characters/{id}/career {job:int, first:int, awakening:int}
这是修改职业/转职的正确接口(旧 /local/dnf-gm/character/job 语义不同)。"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/career" % str(character_id),
{"job": int(job), "first": int(first), "awakening": int(awakening)}))
def set_cera(self, account_id, amount=None, value=None, currency=""):
"""POST /local/dnf-gm/account/cera
{account_id, currency, amount(增量) 或 value(覆写)}
currency: cera / token_cera / happy_token_cera,默认 cera"""
req = {"account_id": str(account_id)}
if currency:
req["currency"] = currency
if amount is not None:
req["amount"] = int(amount)
elif value is not None:
req["value"] = int(value)
return self._unwrap(self._post("/local/dnf-gm/account/cera", req))
def adjust_revive_coin(self, character_id, amount=None, value=None, source=""):
"""POST /local/dnf-gm/character/revive-coin
amount(增量) 或 value(覆写),二者互斥"""
req = {"character_id": str(character_id)}
if amount is not None:
req["amount"] = int(amount)
elif value is not None:
req["value"] = int(value)
if source:
req["source"] = source
return self._unwrap(self._post("/local/dnf-gm/character/revive-coin", req))
def query_wallet(self, character_id="", character_name="", currency=""):
"""POST /local/dnf-gm/wallet/query"""
req = {}
if character_id:
req["character_id"] = str(character_id)
if character_name:
req["character_name"] = character_name
if currency:
req["currency"] = currency
return self._unwrap(self._post("/local/dnf-gm/wallet/query", req))
def update_wallet(self, character_id="", character_name="", currency="",
mode="", amount=0):
"""POST /local/dnf-gm/wallet/update
currency + mode + amount;mode 语义见源码 gm_admin_state.go"""
req = {}
if character_id:
req["character_id"] = str(character_id)
if character_name:
req["character_name"] = character_name
if currency:
req["currency"] = currency
if mode:
req["mode"] = mode
if amount:
req["amount"] = int(amount)
return self._unwrap(self._post("/local/dnf-gm/wallet/update", req))
# ---- 背包 / 装备 ---------------------------------------------
def inventory_list(self, character_id):
"""GET /local/dnf-gm/inventory/list —— 列角色背包全部行 + 穿戴装备行。
返回 {"character_id":..., "rows":[{collection,entry_key,item_id,count,bind,item_name},...]}"""
return self._unwrap(self._get(
"/local/dnf-gm/inventory/list?character_id=%s" % character_id))
def delete_inventory_item(self, character_id, container, key, count):
"""POST /dnf/gm/characters/{id}/inventory/delete {container, key, count}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/inventory/delete" % str(character_id),
{"container": container, "key": key, "count": int(count)}))
def clear_inventory(self, character_id, container="", keep_wallet=True, collection=""):
"""POST /local/dnf-gm/inventory/clear {character_id, collection, keep_wallet}
工具端版本会保留主背包 0:0~0:2 的固定钱包槽(金币/复活币/点券载体)。"""
req = {"character_id": str(character_id)}
if collection:
req["collection"] = collection
req["keep_wallet"] = bool(keep_wallet)
return self._unwrap(self._post("/local/dnf-gm/inventory/clear", req))
def unequip_equipment(self, character_id, key):
"""POST /dnf/gm/characters/{id}/equipment/unequip {key}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/equipment/unequip" % str(character_id), {"key": key}))
def delete_equipment(self, character_id, key):
"""POST /dnf/gm/characters/{id}/equipment/delete {key}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/equipment/delete" % str(character_id), {"key": key}))
def unlock_equip_slots(self, character_id, slots):
"""POST /dnf/gm/characters/{id}/equipment-slots/unlock {slots:[]string}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/equipment-slots/unlock" % str(character_id),
{"slots": slots}))
def set_account_cube(self, character_id, selection, count):
"""POST /dnf/gm/characters/{id}/account-cubes {selection:int, count:int64}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/account-cubes" % str(character_id),
{"selection": int(selection), "count": int(count)}))
# ---- 邮件 -----------------------------------------------------
def send_system_mail(self, character_id="", character_name="", title="",
body="", gold=0, attachments=None):
"""POST /local/dnf-gm/mail {character_id/name, title, body, gold, attachments}
attachments: [{item_id, count, bind, reinforce_level, amplify_level, amplify_type}]"""
req = {}
if character_id:
req["character_id"] = str(character_id)
if character_name:
req["character_name"] = character_name
if title:
req["title"] = title
if body:
req["body"] = body
if gold:
req["gold"] = int(gold)
if attachments:
req["attachments"] = attachments
return self._unwrap(self._post("/local/dnf-gm/mail", req))
def clear_mailbox(self, character_id="", character_name="", confirm=True):
"""POST /local/dnf-gm/mail/clear {character_id/name, confirm}"""
req = {"confirm": bool(confirm)}
if character_id:
req["character_id"] = str(character_id)
if character_name:
req["character_name"] = character_name
return self._unwrap(self._post("/local/dnf-gm/mail/clear", req))
def delete_mailbox_mails(self, character_id, mail_ids, force=False):
"""POST /dnf/gm/characters/{id}/mailbox/delete {mail_ids, force}"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/mailbox/delete" % str(character_id),
{"mail_ids": mail_ids, "force": bool(force)}))
# ---- 拍卖行(服务端 GM 接口,线上上架/下架)------------------
def auction_listings(self):
"""GET /local/dnf-gm/auction/listings —— 列出所有挂单(含过期)。
返回 {"listings":[...], "escrow_gold":int}。"""
return self._unwrap(self._get("/local/dnf-gm/auction/listings"))
def auction_list(self, seller_id, item_id, count, unit_price,
start_price=None, buyout_price=None, duration_hours=24,
grant_if_missing=False):
"""POST /local/dnf-gm/auction/list —— 上架。
服务端 GMAuctionListRequest 字段:
seller_id / seller_name / item_id / count / unit_price /
start_price(每件起拍,-1=不设) / buyout_price(每件一口价,-1=不设) /
duration_hours(24 或 48) / grant_if_missing(背包不足自动发放差额)"""
req = {
"seller_id": str(seller_id),
"item_id": int(item_id),
"count": int(count),
"unit_price": int(unit_price),
}
# start/buyout:None 或空 -> -1(服务端约定 -1 表示不设)
req["start_price"] = int(start_price) if start_price not in (None, "") else -1
req["buyout_price"] = int(buyout_price) if buyout_price not in (None, "") else -1
req["duration_hours"] = int(duration_hours) if duration_hours else 24
if grant_if_missing:
req["grant_if_missing"] = True
return self._unwrap(self._post("/local/dnf-gm/auction/list", req))
def auction_cancel(self, listing_id):
"""POST /local/dnf-gm/auction/cancel —— 下架。
GMAuctionCancelRequest: {listing_id:uint64}。"""
return self._unwrap(self._post(
"/local/dnf-gm/auction/cancel", {"listing_id": int(listing_id)}))
# ---- 任务 -----------------------------------------------------
def get_quest_overview(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/quests" % character_id))
def search_quests(self, character_id, query, limit=40):
qs = urlencode({"q": query, "limit": limit})
return self._unwrap(self._get(
"/dnf/gm/characters/%s/quests/search?%s" % (character_id, qs)))
def get_quest_detail(self, character_id, quest_id):
return self._unwrap(self._get(
"/dnf/gm/characters/%s/quests/%s/detail" % (character_id, quest_id)))
def _quest_mutate(self, character_id, quest_id, action):
return self._unwrap(self._post(
"/dnf/gm/characters/%s/quests/%s/%s" % (character_id, quest_id, action), {}))
def accept_quest(self, character_id, quest_id):
return self._quest_mutate(character_id, quest_id, "accept")
def mark_quest_ready(self, character_id, quest_id):
return self._quest_mutate(character_id, quest_id, "ready")
def give_up_quest(self, character_id, quest_id):
return self._quest_mutate(character_id, quest_id, "give-up")
def complete_quest(self, character_id, quest_id):
return self._quest_mutate(character_id, quest_id, "complete")
def uncomplete_quest(self, character_id, quest_id):
return self._quest_mutate(character_id, quest_id, "uncomplete")
def complete_quest_batch(self, character_id, quest_ids):
return self._unwrap(self._post(
"/dnf/gm/characters/%s/quests/complete-batch" % character_id,
{"quest_ids": [int(x) for x in quest_ids]}))
# ---- 副本 -----------------------------------------------------
def get_dungeons(self, character_id):
return self._unwrap(self._get("/dnf/gm/characters/%s/dungeons" % character_id))
def unlock_dungeon_difficulties(self, character_id):
return self._unwrap(self._post(
"/dnf/gm/characters/%s/dungeons/unlock-difficulties" % character_id, {}))
def unlock_hell_challenges(self, character_id):
return self._unwrap(self._post(
"/dnf/gm/characters/%s/dungeons/unlock-hell" % character_id, {}))
# ---- 黑钻 -----------------------------------------------------
def get_black_diamond(self, character_id="", character_name=""):
qs = urlencode({"id": character_id, "name": character_name})
return self._unwrap(self._get("/local/dnf-gm/character/black-diamond?" + qs))
def activate_black_diamond(self, character_id, days=0):
req = {"character_id": str(character_id)}
if days:
req["days"] = int(days)
return self._unwrap(self._post("/local/dnf-gm/character/black-diamond", req))
# ---- 运营参数 / 爆率(Native,服务端在线) ----------------------
def get_gameplay_settings(self):
"""GET /dnf/gm/gameplay -> GMGameplaySettings(深渊史诗掉率等运营参数)"""
return self._unwrap(self._get("/dnf/gm/gameplay"))
def set_hell_drop_rate(self, percent):
"""POST /dnf/gm/gameplay/hell-drop-rate {percent:int}(深渊史诗掉落率百分比)"""
return self._unwrap(self._post("/dnf/gm/gameplay/hell-drop-rate",
{"percent": int(percent)}))
def set_epic_potion_multiplier(self, percent):
"""POST /dnf/gm/gameplay/epic-potion-test-multiplier {percent:int}
城镇增益药剂测试倍率,0 = 关闭按 PVF 表走。"""
return self._unwrap(self._post(
"/dnf/gm/gameplay/epic-potion-test-multiplier",
{"percent": int(percent)}))
def set_fatigue_mode(self, mode):
"""POST /dnf/gm/gameplay/fatigue-mode {mode:str}
mode: none(正常)/ free(无限不扣)/ lock(锁死高值)/ refill(一键回满)。"""
return self._unwrap(self._post("/dnf/gm/gameplay/fatigue-mode",
{"mode": str(mode)}))
def set_burning(self, enabled):
"""POST /dnf/gm/gameplay/burning {enabled:bool}
燃烧疲劳:双倍消耗 + 通关经验翻倍。"""
return self._unwrap(self._post("/dnf/gm/gameplay/burning",
{"enabled": bool(enabled)}))
def refill_fatigue(self, character_id):
"""POST /dnf/gm/characters/{id}/fatigue/refill 一键回满角色疲劳。"""
return self._unwrap(self._post(
"/dnf/gm/characters/%s/fatigue/refill" % str(character_id), {}))
# ---- 宠物 -----------------------------------------------------
def update_pet(self, character_id, pet_key="", level=None, exp=None,
satiety=None, clear_satiety=False, pet_index=None):
req = {"character_id": str(character_id)}
if pet_key:
req["pet_key"] = pet_key
if level is not None:
req["level"] = int(level)
if exp is not None:
req["exp"] = int(exp)
if satiety is not None:
req["satiety"] = int(satiety)
if clear_satiety:
req["clear_satiety"] = True
if pet_index is not None:
req["pet_index"] = int(pet_index)
return self._unwrap(self._post("/local/dnf-gm/pet/update", req))
# ---- 启动器工具扩展(换装 / 服务端工具 / 支援兵)----------------
def loadout_characters(self):
return self._unwrap(self._get("/local/dnf-gm/characters")) or []
def loadout_equipment(self, character_id):
qs = urlencode({"character_id": str(character_id)})
return self._unwrap(self._get("/local/dnf-gm/loadout/equipment?" + qs))
def loadout_apply(self, character_id, slots):
return self._unwrap(self._post("/local/dnf-gm/loadout/apply", {
"character_id": str(character_id), "slots": slots}))
# ---- 游戏设置:活动开关 / 频道开关(Native,服务端在线)--------
def get_activity_switches(self):
"""GET /dnf/gm/activity-switches -> GMActivitySwitches"""
return self._unwrap(self._get("/dnf/gm/activity-switches"))
def set_activity_switch(self, key, enabled):
"""POST /dnf/gm/activity-switches {key:str, enabled:bool}"""
return self._unwrap(self._post("/dnf/gm/activity-switches", {
"key": str(key), "enabled": bool(enabled)}))
def get_channel_switches(self):
"""GET /dnf/gm/channel-switches -> GMChannelSwitches"""
return self._unwrap(self._get("/dnf/gm/channel-switches"))
def set_channel_switch(self, channel_id, enabled):
"""POST /dnf/gm/channel-switch {channel_id:int, enabled:bool}"""
return self._unwrap(self._post("/dnf/gm/channel-switch", {
"channel_id": int(channel_id), "enabled": bool(enabled)}))