🎨 一些代码调整

This commit is contained in:
目棃
2024-12-06 14:36:30 +08:00
parent f4678be198
commit dd65bb565d
32 changed files with 401 additions and 589 deletions

View File

@@ -3,7 +3,9 @@
<TSidebar v-if="isMain" />
<v-main>
<v-container :fluid="true" class="app-container">
<Suspense>
<router-view />
</Suspense>
</v-container>
</v-main>
<TBackTop />
@@ -11,9 +13,9 @@
</template>
<script lang="ts" setup>
import { app, event, core, webviewWindow, window as TauriWindow } from "@tauri-apps/api";
import { app, core, event, webviewWindow, window as TauriWindow } from "@tauri-apps/api";
import { PhysicalSize } from "@tauri-apps/api/dpi";
import { UnlistenFn, Event } from "@tauri-apps/api/event";
import { Event, UnlistenFn } from "@tauri-apps/api/event";
import { mkdir } from "@tauri-apps/plugin-fs";
import { storeToRefs } from "pinia";
import { computed, onBeforeMount, onMounted, onUnmounted, ref } from "vue";

View File

@@ -6,10 +6,10 @@
</transition>
</template>
<script lang="ts" setup>
import { ref, onMounted, onUnmounted } from "vue";
import { onMounted, onUnmounted, ref } from "vue";
const scrollTop = ref(0); // 滚动条距离顶部的距离
const canTop = ref(false); // 默认不显示
const scrollTop = ref<number>(0); // 滚动条距离顶部的距离
const canTop = ref<boolean>(false); // 默认不显示
// 监听滚动事件
function handleScroll(): void {
@@ -40,15 +40,8 @@ function handleScrollTop(): void {
});
}
// 监听滚动事件
onMounted(() => {
window.addEventListener("scroll", handleScroll);
});
// 销毁监听
onUnmounted(() => {
window.removeEventListener("scroll", handleScroll);
});
onMounted(() => window.addEventListener("scroll", handleScroll));
onUnmounted(() => window.removeEventListener("scroll", handleScroll));
</script>
<style scoped>

View File

@@ -12,7 +12,7 @@
</template>
<script lang="ts" setup>
import { emit } from "@tauri-apps/api/event";
import { computed, onMounted, ref, watch } from "vue";
import { computed, onMounted, ref, shallowRef, watch } from "vue";
import Mys from "../../plugins/Mys/index.js";
import { useAppStore } from "../../store/modules/app.js";
@@ -25,17 +25,12 @@ import showSnackbar from "../func/snackbar.js";
import ToLivecode from "./to-livecode.vue";
interface TGameNavProps {
modelValue: number;
}
const props = withDefaults(defineProps<TGameNavProps>(), {
modelValue: 2,
});
type TGameNavProps = { modelValue: number };
const props = withDefaults(defineProps<TGameNavProps>(), { modelValue: 2 });
const appStore = useAppStore();
const nav = ref<TGApp.BBS.Navigator.Navigator[]>([]);
const codeData = ref<TGApp.BBS.Navigator.CodeData[]>([]);
const nav = shallowRef<TGApp.BBS.Navigator.Navigator[]>([]);
const codeData = shallowRef<TGApp.BBS.Navigator.CodeData[]>([]);
const showOverlay = ref<boolean>(false);
const actId = ref<string>();

View File

@@ -8,23 +8,13 @@
</transition>
</template>
<script lang="ts" setup>
// vue
import { ref, watch } from "vue";
interface TolProps {
modelValue: boolean;
toClick?: () => void;
blurVal: string;
hide?: true;
}
type TolProps = { modelValue: boolean; toClick?: () => void; blurVal: string; hide?: true };
const props = withDefaults(defineProps<TolProps>(), {
modelValue: false,
blurVal: "20px",
});
const showTolo = ref(!props.hide);
const showToli = ref(!props.hide);
const props = withDefaults(defineProps<TolProps>(), { modelValue: false, blurVal: "20px" });
const showTolo = ref<boolean>(!props.hide);
const showToli = ref<boolean>(!props.hide);
watch(
() => props.modelValue,
@@ -32,14 +22,10 @@ watch(
if (props.modelValue) {
showTolo.value = true;
showToli.value = true;
} else {
setTimeout(() => {
showToli.value = false;
}, 100);
setTimeout(() => {
showTolo.value = false;
}, 300);
return;
}
setTimeout(() => (showToli.value = false), 100);
setTimeout(() => (showTolo.value = false), 300);
},
);
</script>

View File

@@ -15,10 +15,10 @@
</div>
<div class="tpc-title" :title="card.title" @click="shareCard">{{ card.title }}</div>
</div>
<div class="tpc-mid" v-if="card.user">
<div class="tpc-mid" v-if="card.user !== null">
<TpAvatar :data="card.user" position="left" />
</div>
<div class="tpc-bottom" v-if="card.data">
<div class="tpc-bottom" v-if="card.data !== null">
<div class="tpc-tags">
<div v-for="topic in card.topics" :key="topic.id" class="tpc-tag" @click="toTopic(topic)">
<v-icon size="10">mdi-tag</v-icon>
@@ -50,7 +50,7 @@
</div>
<div
class="tpc-forum"
v-if="card.forum && card.forum.name !== ''"
v-if="card.forum !== null && card.forum.name !== ''"
:title="`频道: ${card.forum.name}`"
@click="toForum(card.forum)"
>
@@ -68,27 +68,20 @@
</template>
<script lang="ts" setup>
import { emit } from "@tauri-apps/api/event";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
import { generateShareImg, saveImgLocal } from "../../utils/TGShare.js";
import { createPost } from "../../utils/TGWindow.js";
import showSnackbar from "../func/snackbar.js";
import TpAvatar from "../viewPost/tp-avatar.vue";
interface TPostCardProps {
modelValue: TGApp.Plugins.Mys.Post.FullData;
selectMode?: boolean;
}
type TPostCardProps = { modelValue: TGApp.Plugins.Mys.Post.FullData; selectMode?: boolean };
type TPostCardEmits = (e: "onSelected", v: string) => void;
interface TPostCardEmits {
(e: "onSelected", value: string): void;
}
const props = withDefaults(defineProps<TPostCardProps>(), {
selectMode: false,
});
const props = withDefaults(defineProps<TPostCardProps>(), { selectMode: false });
const emits = defineEmits<TPostCardEmits>();
const isAct = ref<boolean>(false);
const card = ref<TGApp.Plugins.Mys.News.RenderCard>();
const card = shallowRef<TGApp.Plugins.Mys.News.RenderCard>();
const localCover = ref<string>();
const cardBg = computed<string>(() => {
@@ -106,10 +99,9 @@ async function reload(data: TGApp.Plugins.Mys.Post.FullData): Promise<void> {
localCover.value = undefined;
}
card.value = getPostCard(data);
if (card.value && card.value.cover !== "") {
if (card.value && card.value.cover !== "")
localCover.value = await saveImgLocal(card.value.cover);
}
}
onUnmounted(() => {
if (localCover.value) {
@@ -186,32 +178,30 @@ function getPostCover(item: TGApp.Plugins.Mys.Post.FullData): string {
* @returns {TGApp.Plugins.Mys.News.RenderCard} 渲染用咨讯列表项
*/
function getCommonCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.News.RenderCard {
let forum: TGApp.Plugins.Mys.News.RenderForum | null = null;
let data: TGApp.Plugins.Mys.News.RenderData | null = null;
if (item.forum !== null) {
forum = {
name: item.forum.name,
icon: item.forum.icon,
id: item.forum.id,
};
}
if (item.stat !== null) {
data = {
mark: item.stat.bookmark_num,
forward: item.stat.forward_num,
like: item.stat.like_num,
reply: item.stat.reply_num,
view: item.stat.view_num,
};
}
return {
title: item.post.subject,
cover: getPostCover(item),
postId: Number(item.post.post_id),
subtitle: item.post.post_id,
user: item.user,
forum: forum,
data: data,
forum:
item.forum !== null
? {
name: item.forum.name,
icon: item.forum.icon,
id: item.forum.id,
}
: null,
data:
item.stat !== null
? {
mark: item.stat.bookmark_num,
forward: item.stat.forward_num,
like: item.stat.like_num,
reply: item.stat.reply_num,
view: item.stat.view_num,
}
: null,
topics: item.topics,
};
}
@@ -235,9 +225,13 @@ function getPostCard(item: TGApp.Plugins.Mys.Post.FullData): TGApp.Plugins.Mys.N
async function shareCard(): Promise<void> {
if (!card.value) return;
const dom = <HTMLDivElement>document.querySelector(`#post-card-${card.value.postId}`);
const shareDom = document.querySelector<HTMLDivElement>(`#post-card-${card.value.postId}`);
if (shareDom === null) {
showSnackbar.error("分享内容不存在", 3000);
return;
}
const fileName = `PostCard_${card.value.postId}`;
await generateShareImg(fileName, dom, 2.5);
await generateShareImg(fileName, shareDom, 2.5);
}
async function toTopic(topic: TGApp.Plugins.Mys.Topic.Info): Promise<void> {

View File

@@ -9,31 +9,28 @@
import TGLogger from "../../utils/TGLogger.js";
import { generateShareImg } from "../../utils/TGShare.js";
import showLoading from "../func/loading.js";
import showSnackbar from "../func/snackbar.js";
interface TShareBtnProps {
modelValue: HTMLElement;
title: string;
}
type TShareBtnProps = { selector: string; title: string };
const props = defineProps<TShareBtnProps>();
async function shareContent(): Promise<void> {
showLoading.start("正在生成分享图片", props.title);
await TGLogger.Info("[TShareBtn][shareContent] 开始生成分享图片");
props.modelValue.querySelectorAll("details").forEach((item) => {
if (item.open) {
item.setAttribute("details-open", "");
} else {
item.open = true;
const shareDom = document.querySelector<HTMLElement>(props.selector);
if (shareDom === null) {
showSnackbar.error("分享内容不存在", 3000);
showLoading.end();
return;
}
shareDom.querySelectorAll("details").forEach((item) => {
if (item.open) item.setAttribute("details-open", "");
else item.open = true;
});
await generateShareImg(props.title, props.modelValue);
props.modelValue.querySelectorAll("details").forEach((item) => {
if (item.hasAttribute("details-open")) {
item.removeAttribute("details-open");
} else {
item.open = false;
}
await generateShareImg(props.title, shareDom);
shareDom.querySelectorAll("details").forEach((item) => {
if (item.hasAttribute("details-open")) item.removeAttribute("details-open");
else item.open = false;
});
showLoading.end();
await TGLogger.Info("[TShareBtn][shareContent] 生成分享图片完成");

View File

@@ -2,7 +2,7 @@
<v-navigation-drawer :permanent="true" :rail="rail" class="tsb-box">
<v-list class="side-list" density="compact" :nav="true">
<!-- 负责收缩侧边栏 -->
<v-list-item @click="collapse()">
<v-list-item @click="rail = !rail">
<template v-if="rail" #prepend>
<v-list-item-action>
<v-icon>mdi-chevron-right</v-icon>
@@ -202,7 +202,7 @@
class="side-item-menu"
title="登录"
@click="login"
v-show="userStore.cookie.value?.stoken === ''"
v-show="cookie?.stoken === ''"
>
<template #prepend>
<img src="/source/UI/lumine.webp" class="side-icon-menu" alt="login" />
@@ -231,71 +231,46 @@
<script lang="ts" setup>
import { event, webviewWindow } from "@tauri-apps/api";
import { UnlistenFn, Event } from "@tauri-apps/api/event";
import { Event, UnlistenFn } from "@tauri-apps/api/event";
import { storeToRefs } from "pinia";
import { computed, onMounted, onUnmounted, ref, watch } from "vue";
import { computed, onMounted, onUnmounted, shallowRef } from "vue";
import { useAppStore } from "../../store/modules/app.js";
import { useUserStore } from "../../store/modules/user.js";
import mhyClient from "../../utils/TGClient.js";
import TGLogger from "../../utils/TGLogger.js";
import showSnackbar from "../func/snackbar.js";
const appStore = useAppStore();
const userStore = storeToRefs(useUserStore());
const { cookie, briefInfo } = storeToRefs(useUserStore());
const isDevEnv = ref<boolean>(import.meta.env.MODE === "development");
const userInfo = ref({
// @ts-expect-error The import.meta meta-property is not allowed in files which will build into CommonJS output.
const isDevEnv = import.meta.env.MODE === "development";
const themeListener = shallowRef<UnlistenFn | null>(null);
const rail = computed<boolean>({
get: () => appStore.sidebar.collapse,
set: (v: boolean) => (appStore.sidebar.collapse = v),
});
const userInfo = computed<TGApp.App.Account.BriefInfo>(() => {
if (briefInfo.value && briefInfo.value.nickname) return briefInfo.value;
return {
nickname: "未登录",
uid: "-1",
desc: "请扫码登录",
avatar: "/source/UI/lumine.webp",
};
});
watch(userStore.briefInfo, (val) => {
if (val && val.nickname) {
userInfo.value = val;
}
const themeGet = computed<string>({
get: () => appStore.theme,
set: (v: string) => (appStore.theme = v),
});
const rail = ref(appStore.sidebar.collapse);
// theme
const themeGet = computed({
get() {
return appStore.theme;
},
set(value: string) {
appStore.theme = value;
},
});
const themeTitle = computed(() => {
return themeGet.value === "default" ? "夜间模式" : "日间模式";
});
watch(themeTitle, async (val) => {
const themeNow = val === "夜间模式" ? "浅色模式" : "深色模式";
await TGLogger.Info(`[App][theme] 已切换到${themeNow}`);
});
function collapse(): void {
rail.value = !rail.value;
appStore.sidebar.collapse = rail.value;
}
let themeListener: UnlistenFn;
const themeTitle = computed<string>(() => (themeGet.value === "default" ? "夜间模式" : "日间模式"));
onMounted(async () => {
themeListener = await event.listen("readTheme", (e: Event<string>) => {
themeListener.value = await event.listen("readTheme", (e: Event<string>) => {
const theme = e.payload;
themeGet.value = theme === "default" ? "default" : "dark";
});
if (webviewWindow.getCurrentWebviewWindow().label === "TeyvatGuide") {
await mhyClient.run();
}
if (userStore.briefInfo.value && userStore.briefInfo.value.nickname) {
userInfo.value = userStore.briefInfo.value;
}
if (webviewWindow.getCurrentWebviewWindow().label === "TeyvatGuide") await mhyClient.run();
});
async function switchTheme(): Promise<void> {
@@ -303,21 +278,16 @@ async function switchTheme(): Promise<void> {
}
async function openClient(func: string): Promise<void> {
if (appStore.isLogin) {
await mhyClient.open(func);
} else {
login();
}
if (appStore.isLogin) await mhyClient.open(func);
else showSnackbar.warn("请前往设置页面登录!");
}
function login(): void {
showSnackbar({
text: "请前往设置页面登录!",
color: "warn",
onUnmounted(() => {
if (themeListener.value !== null) {
themeListener.value();
themeListener.value = null;
}
});
}
onUnmounted(() => themeListener());
</script>
<style lang="css" scoped>

View File

@@ -1,13 +1,11 @@
<template>
<div class="tsl-box">
<img src="../../assets/icons/arrow-right.svg" alt="right" />
<slot>{{ props.title }}</slot>
<slot>{{ title }}</slot>
</div>
</template>
<script lang="ts" setup>
const props = defineProps<{
title?: string;
}>();
defineProps<{ title?: string }>();
</script>
<style lang="css" scoped>
.tsl-box {

View File

@@ -9,25 +9,19 @@
</template>
<script lang="ts" setup>
import { event } from "@tauri-apps/api";
import { UnlistenFn, Event } from "@tauri-apps/api/event";
import { computed, onMounted, onUnmounted } from "vue";
import { Event, UnlistenFn } from "@tauri-apps/api/event";
import { computed, onMounted, onUnmounted, shallowRef } from "vue";
import { useAppStore } from "../../store/modules/app.js";
const appStore = useAppStore();
const themeGet = computed({
get() {
return appStore.theme;
},
set(value: string) {
appStore.theme = value;
},
const themeGet = computed<string>({
get: () => appStore.theme,
set: (v: string) => (appStore.theme = v),
});
let themeListener: UnlistenFn | null = null;
const themeListener = shallowRef<UnlistenFn | null>(null);
onMounted(async () => {
themeListener = await listenOnTheme();
});
onMounted(async () => (themeListener.value = await listenOnTheme()));
async function switchTheme(): Promise<void> {
appStore.changeTheme();
@@ -42,9 +36,9 @@ async function listenOnTheme(): Promise<UnlistenFn> {
}
onUnmounted(() => {
if (themeListener !== null) {
themeListener();
themeListener = null;
if (themeListener.value !== null) {
themeListener.value();
themeListener.value = null;
}
});
</script>

View File

@@ -64,16 +64,14 @@ interface ToLiveCodeProps {
modelValue: boolean;
}
type ToLiveCodeEmits = (e: "update:modelValue", value: boolean) => void;
type ToLiveCodeEmits = (e: "update:modelValue", v: boolean) => void;
const props = withDefaults(defineProps<ToLiveCodeProps>(), { modelValue: false });
const emits = defineEmits<ToLiveCodeEmits>();
const visible = computed<boolean>({
get: () => props.modelValue,
set: (value) => {
emits("update:modelValue", value);
},
set: (v: boolean) => emits("update:modelValue", v),
});
function onCancel(): void {
@@ -86,7 +84,11 @@ function copy(code: string): void {
}
async function shareImg(): Promise<void> {
const element = <HTMLElement>document.querySelector(".tolc-box");
const element = document.querySelector<HTMLElement>(".tolc-box");
if (element === null) {
showSnackbar.error("未获取到分享内容");
return;
}
const fileName = `LiveCode_${props.actId}_${new Date().getTime()}`;
await generateShareImg(fileName, element, 2);
}

View File

@@ -1,5 +1,5 @@
<template>
<v-list class="top-nc-box" @click="toNameCard(props.data)">
<v-list class="top-nc-box" @click="emit('selected', props.data)">
<v-list-item :title="props.data.name">
<template #subtitle>
<span :title="props.data.desc">{{ props.data.desc }}</span>
@@ -13,13 +13,8 @@
<script lang="ts" setup>
import { computed } from "vue";
interface TopNameCardProps {
data: TGApp.App.NameCard.Item;
}
interface TopNameCardEmits {
(e: "selected", data: TGApp.App.NameCard.Item): void;
}
type TopNameCardProps = { data: TGApp.App.NameCard.Item };
type TopNameCardEmits = (e: "selected", v: TGApp.App.NameCard.Item) => void;
const props = defineProps<TopNameCardProps>();
const emit = defineEmits<TopNameCardEmits>();
@@ -28,10 +23,6 @@ const bgImage = computed<string>(() => {
if (props.data.name === "原神·印象") return "none;";
return `url("${props.data.bg}")`;
});
function toNameCard(item: TGApp.App.NameCard.Item) {
emit("selected", item);
}
</script>
<style lang="css" scoped>
.top-nc-box {

View File

@@ -23,7 +23,7 @@
</transition>
</template>
<script lang="ts" setup>
import { reactive, ref, watch, onMounted, toRaw } from "vue";
import { ref, watch, onMounted, toRaw } from "vue";
import { LoadingParams } from "./loading.js";
@@ -32,7 +32,7 @@ const showOuter = ref<boolean>(false);
const showInner = ref<boolean>(false);
const props = defineProps<LoadingParams>();
const data = reactive<LoadingParams>(toRaw(props));
const data = ref<LoadingParams>(toRaw(props));
watch(
() => showBox.value,
@@ -53,15 +53,15 @@ function displayBox(params: LoadingParams): void {
if (!params.show) {
showBox.value = false;
setTimeout(() => {
data.title = "";
data.subtitle = "";
data.empty = false;
data.value.title = "";
data.value.subtitle = "";
data.value.empty = false;
}, 500);
return;
}
data.title = params.title;
data.subtitle = params.subtitle;
data.empty = params.empty;
data.value.title = params.title;
data.value.subtitle = params.subtitle;
data.value.empty = params.empty;
showBox.value = true;
}
@@ -180,7 +180,6 @@ defineExpose({ displayBox });
.loading-circle > div {
position: relative;
box-sizing: border-box;
box-sizing: border-box;
}
.loading-circle {

View File

@@ -59,6 +59,7 @@ import { AbyssDataItem } from "../../pages/WIKI/Abyss.vue";
import { generateShareImg } from "../../utils/TGShare.js";
import { timestampToDate } from "../../utils/toolFunc.js";
import TOverlay from "../app/t-overlay.vue";
import showSnackbar from "../func/snackbar.js";
import HtaOverviewLine from "./hta-overview-line.vue";
@@ -93,7 +94,12 @@ function onCancel(): void {
async function share(): Promise<void> {
loadShare.value = true;
const shareEl = <HTMLElement>document.querySelector(".hta-oo-box");
const shareEl = document.querySelector<HTMLElement>(".hta-oo-box");
if (shareEl === null) {
showSnackbar.warn("分享失败");
loadShare.value = false;
return;
}
const fileName = `深渊数据统计_${timestampToDate(dataCur.value.Timestamp)}.png`;
await generateShareImg(fileName, shareEl, 2);
loadShare.value = false;

View File

@@ -12,10 +12,7 @@
</div>
</template>
<script lang="ts" setup>
interface THomeCardProps {
append?: boolean;
}
type THomeCardProps = { append?: boolean };
const props = defineProps<THomeCardProps>();
</script>
<style lang="css" scoped>

View File

@@ -11,7 +11,7 @@ import TGHttp from "../../../utils/TGHttp.js";
import { getDeviceInfo } from "../../../utils/toolFunc.js";
import TGConstant from "../../../web/constant/TGConstant.js";
const PUB_KEY_STR = `-----BEGIN PUBLIC KEY-----
const PUB_KEY_STR: Readonly<string> = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDvekdPMHN3AYhm/vktJT+YJr7cI5DcsNKqdsx5DZX0gDuWFuIjzdwButrIYPNmRJ1G8ybDIF7oDW2eEpm5sMbL9zs
9ExXCdvqrn51qELbqj0XxtMTIpaCHFSI50PfPpTFV9Xt/hmyVwokoOXFlAEgCn+Q
CgGs52bFoYMtyi+xEQIDAQAB
@@ -55,7 +55,7 @@ export async function getCaptcha(
"x-rpc-aigis": aigis || "",
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-client_type": "2",
"x-rpc-app_id": TGConstant.BBS.APP_ID,
"x-rpc-app_id": "bll8iq97cem8",
"x-rpc-device_fp": device_fp,
"x-rpc-device_name": device_name,
"x-rpc-device_id": device_id,
@@ -63,7 +63,7 @@ export async function getCaptcha(
"user-agent": TGConstant.BBS.UA_MOBILE,
"content-type": "application/json",
referer: "https://user.miyoushe.com/",
"x-rpc-game_biz": TGConstant.GAME_BIZ,
"x-rpc-game_biz": "hk4e_cn",
};
const resp = await TGHttp<
TGApp.Plugins.Mys.CaptchaLogin.CaptchaResponse | TGApp.BBS.Response.Base
@@ -117,7 +117,7 @@ export async function doCaptchaLogin(
"x-rpc-aigis": aigis || "",
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-client_type": "2",
"x-rpc-app_id": TGConstant.BBS.APP_ID,
"x-rpc-app_id": "bll8iq97cem8",
"x-rpc-device_fp": device_fp,
"x-rpc-device_name": device_name,
"x-rpc-device_id": device_id,

View File

@@ -18,15 +18,15 @@ const APP_ID = 8;
export async function getLoginQr(): Promise<
TGApp.Plugins.Mys.GameLogin.GetLoginQrData | TGApp.BBS.Response.Base
> {
const url = "https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/fetch";
const device = getDeviceInfo("device_id");
const data = { app_id: APP_ID, device };
const header = getRequestHeader({}, "POST", data, "common");
const data: Record<string, string | number> = {
app_id: APP_ID,
device: getDeviceInfo("device_id"),
};
const resp = await TGHttp<
TGApp.Plugins.Mys.GameLogin.GetLoginQrResponse | TGApp.BBS.Response.Base
>(url, {
>("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/fetch", {
method: "POST",
headers: header,
headers: getRequestHeader({}, "POST", data),
body: JSON.stringify(data),
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
@@ -42,15 +42,16 @@ export async function getLoginQr(): Promise<
export async function getLoginStatus(
ticket: string,
): Promise<TGApp.Plugins.Mys.GameLogin.GetLoginStatusData | TGApp.BBS.Response.Base> {
const url = "https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query";
const device = getDeviceInfo("device_id");
const data = { app_id: APP_ID, device, ticket };
const header = getRequestHeader({}, "POST", data, "common");
const data: Record<string, string | number> = {
app_id: APP_ID,
device: getDeviceInfo("device_id"),
ticket,
};
const resp = await TGHttp<
TGApp.Plugins.Mys.GameLogin.GetLoginStatusResponse | TGApp.BBS.Response.Base
>(url, {
>("https://hk4e-sdk.mihoyo.com/hk4e_cn/combo/panda/qrcode/query", {
method: "POST",
headers: header,
headers: getRequestHeader({}, "POST", data),
body: JSON.stringify(data),
});
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;

View File

@@ -4,9 +4,8 @@
* @since Beta v0.6.3
*/
import { event, core, webviewWindow } from "@tauri-apps/api";
import type { Event } from "@tauri-apps/api/event";
import type { UnlistenFn } from "@tauri-apps/api/event";
import { core, event, webviewWindow } from "@tauri-apps/api";
import type { Event, UnlistenFn } from "@tauri-apps/api/event";
import showSnackbar from "../components/func/snackbar.js";
import TGSqlite from "../plugins/Sqlite/index.js";
@@ -615,7 +614,7 @@ class TGClient {
* @returns {void} - 无返回值
*/
async getDS(arg: TGApp.Plugins.JSBridge.NullArg): Promise<void> {
const data = { DS: getDS4JS("lk2", 1, undefined, undefined) };
const data = { DS: getDS4JS("LK2", 1, undefined, undefined) };
await this.callback(arg.callback, data);
}
@@ -630,7 +629,7 @@ class TGClient {
arg: TGApp.Plugins.JSBridge.Arg<TGApp.Plugins.JSBridge.GetDS2Payload>,
): Promise<void> {
const data = {
DS: getDS4JS("common", 2, arg.payload.body, arg.payload.query),
DS: getDS4JS("X4", 2, arg.payload.body, arg.payload.query),
};
await this.callback(arg.callback, data);
}

View File

@@ -8,7 +8,7 @@
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { onMounted, shallowRef } from "vue";
import JsonViewer from "vue-json-viewer";
import { useRoute } from "vue-router";
@@ -21,8 +21,8 @@ const route = useRoute();
const annoId = Number(route.params.anno_id);
const region = <AnnoServer>route.params.region;
const lang = <AnnoLang>route.params.lang;
const jsonList = ref<TGApp.BBS.Announcement.AnnoSingle>();
const jsonContent = ref<TGApp.BBS.Announcement.ContentItem>();
const jsonList = shallowRef<TGApp.BBS.Announcement.AnnoSingle>();
const jsonContent = shallowRef<TGApp.BBS.Announcement.ContentItem>();
onMounted(async () => {
showLoading.start("正在获取公告数据...");
@@ -32,11 +32,14 @@ onMounted(async () => {
}
showLoading.update("正在获取数据...", `公告ID: ${annoId}`);
const listData = await Hk4eApi.anno.list(region, lang);
listData.list.map((item: TGApp.BBS.Announcement.ListItem) => {
return item.list.map((single: TGApp.BBS.Announcement.AnnoSingle) => {
return single.ann_id === annoId ? (jsonList.value = single) : null;
});
});
for (const listItem of listData.list) {
for (const single of listItem.list) {
if (single.ann_id === annoId) {
jsonList.value = single;
break;
}
}
}
jsonContent.value = await Hk4eApi.anno.content(annoId, region, lang);
showLoading.end();
});

View File

@@ -1,7 +1,7 @@
<template>
<TSwitchTheme />
<TPinWin />
<TShareBtn v-model="annoRef" :title="annoTitle" />
<TShareBtn selector=".anno-body" :title="`Anno_${route.params.anno_id}`" />
<div class="anno-body" v-if="annoData">
<div class="anno-info">AnnoID: {{ annoId }} | Render by TeyvatGuide v{{ appVersion }}</div>
<div class="anno-title">{{ annoData.title }}</div>
@@ -13,7 +13,7 @@
</template>
<script lang="ts" setup>
import { app, webviewWindow } from "@tauri-apps/api";
import { ref, onMounted } from "vue";
import { onMounted, ref, shallowRef } from "vue";
import { useRoute } from "vue-router";
import TPinWin from "../components/app/t-pinWin.vue";
@@ -26,16 +26,12 @@ import TGLogger from "../utils/TGLogger.js";
import { createTGWindow } from "../utils/TGWindow.js";
import Hk4eApi, { AnnoLang, AnnoServer } from "../web/request/hk4eReq.js";
const annoRef = ref<HTMLElement>(<HTMLElement>{});
const annoTitle = ref<string>("");
// 数据
const route = useRoute();
const annoId = Number(route.params.anno_id);
const region = <AnnoServer>route.params.region;
const lang = <AnnoLang>route.params.lang;
const appVersion = ref<string>();
const annoData = ref<TGApp.BBS.Announcement.ContentItem | undefined>();
const annoData = shallowRef<TGApp.BBS.Announcement.ContentItem | undefined>();
onMounted(async () => {
showLoading.start("正在加载公告数据...");
@@ -49,11 +45,9 @@ onMounted(async () => {
try {
annoData.value = await Hk4eApi.anno.content(annoId, region, lang);
showLoading.update("正在渲染数据...", `公告ID${annoId}`);
annoTitle.value = `Anno_${annoId}`;
await webviewWindow
.getCurrentWebviewWindow()
.setTitle(`Anno_${annoId} ${annoData.value.title}`);
annoRef.value = <HTMLElement>document.querySelector(".anno-body");
} catch (error) {
if (error instanceof Error)
await TGLogger.Error(`[t-anno.vue][${annoId}] ${error.name}${error.message}`);

View File

@@ -8,7 +8,7 @@
</div>
</template>
<script lang="ts" setup>
import { onMounted, ref } from "vue";
import { onMounted, ref, shallowRef } from "vue";
import JsonViewer from "vue-json-viewer";
import { useRoute } from "vue-router";
@@ -19,8 +19,8 @@ import Mys from "../plugins/Mys/index.js";
import TGLogger from "../utils/TGLogger.js";
const postId = Number(useRoute().params.post_id);
const jsonData = ref<TGApp.Plugins.Mys.Post.FullData>();
const parseData = ref<TGApp.Plugins.Mys.SctPost.Base[]>();
const jsonData = shallowRef<TGApp.Plugins.Mys.Post.FullData>();
const parseData = shallowRef<TGApp.Plugins.Mys.SctPost.Base[]>();
const isEmpty = ref<boolean>(false);
onMounted(async () => {

View File

@@ -2,7 +2,7 @@
<TSwitchTheme />
<TPinWin />
<VpBtnCollect :model-value="postId" :data="postData" />
<TShareBtn v-model="postRef" :title="shareTitle" />
<TShareBtn selector=".tp-post-body" :title="`Post_${postId}`" />
<VpBtnReply :gid="postData.post.game_id" :post-id="postData.post.post_id" v-if="postData" />
<div class="tp-post-body" v-if="postData">
<div class="tp-post-info">
@@ -83,10 +83,9 @@
/>
</template>
<script lang="ts" setup>
import { app } from "@tauri-apps/api";
import { webviewWindow } from "@tauri-apps/api";
import { app, webviewWindow } from "@tauri-apps/api";
import { emit } from "@tauri-apps/api/event";
import { nextTick, onMounted, onUnmounted, ref } from "vue";
import { nextTick, onMounted, onUnmounted, ref, shallowRef } from "vue";
import { useRoute } from "vue-router";
import TPinWin from "../components/app/t-pinWin.vue";
@@ -106,21 +105,14 @@ import TGLogger from "../utils/TGLogger.js";
import { createTGWindow } from "../utils/TGWindow.js";
import TGConstant from "../web/constant/TGConstant.js";
// share
const postRef = ref<HTMLElement>(<HTMLElement>{});
const shareTitle = ref<string>("");
// 数据
const appVersion = ref<string>();
const appVersion = await app.getVersion();
const postId = Number(useRoute().params.post_id);
const renderPost = ref<TGApp.Plugins.Mys.SctPost.Base[]>([]);
const postData = ref<TGApp.Plugins.Mys.Post.FullData>();
// 当前时间,秒级时间戳
const renderPost = shallowRef<TGApp.Plugins.Mys.SctPost.Base[]>([]);
const postData = shallowRef<TGApp.Plugins.Mys.Post.FullData>();
const showCollection = ref<boolean>(false);
const shareTime = ref<number>(Math.floor(Date.now() / 1000));
// eslint-disable-next-line no-undef
const shareTimeTimer = ref<NodeJS.Timeout | undefined>(undefined);
// 合集
const showCollection = ref<boolean>(false);
function getGameIcon(gameId: number): string {
const find = TGConstant.BBS.CHANNELS.find((item) => item.gid === gameId.toString());
@@ -130,7 +122,6 @@ function getGameIcon(gameId: number): string {
onMounted(async () => {
showLoading.start(`正在加载帖子数据...`);
appVersion.value = await app.getVersion();
// 检查数据
if (!postId) {
showLoading.empty("未找到数据", "PostID 不存在");
@@ -150,7 +141,6 @@ onMounted(async () => {
postData.value = resp;
showLoading.update("正在渲染数据...", `帖子ID: ${postId}`);
renderPost.value = getRenderPost(postData.value);
shareTitle.value = `Post_${postId}`;
await webviewWindow
.getCurrentWebviewWindow()
.setTitle(`Post_${postId} ${postData.value.post.subject}`);
@@ -167,7 +157,6 @@ onMounted(async () => {
shareTimeTimer.value = undefined;
}
shareTimeTimer.value = setInterval(() => (shareTime.value = Math.floor(Date.now() / 1000)), 1000);
postRef.value = <HTMLElement>document.querySelector(".tp-post-body");
showLoading.end();
});

View File

@@ -4,26 +4,14 @@
* @since Beta v0.6.3
*/
import {
BBS_APP_ID,
BBS_SALT,
BBS_UA_MOBILE,
BBS_UA_PC,
BBS_VERSION,
CHANNEL_LIST,
} from "./bbs.js";
import { GAME_BIZ } from "./utils.js";
import { BBS_UA_MOBILE, BBS_VERSION, CHANNEL_LIST } from "./bbs.js";
const TGConstant = {
BBS: {
VERSION: BBS_VERSION,
UA_PC: BBS_UA_PC,
UA_MOBILE: BBS_UA_MOBILE,
APP_ID: BBS_APP_ID,
CHANNELS: CHANNEL_LIST,
},
Salt: BBS_SALT,
GAME_BIZ,
};
export default TGConstant;

View File

@@ -5,22 +5,7 @@
*/
export const BBS_VERSION = "2.78.1";
export const BBS_UA_PC = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/${BBS_VERSION}`;
export const BBS_UA_MOBILE = `Mozilla/5.0 (Linux; Android 12) Mobile miHoYoBBS/${BBS_VERSION}`;
export const BBS_APP_ID = "bll8iq97cem8";
/**
* @description salt 值
* @version 2.78.1
* @since Beta v0.6.5
*/
export const BBS_SALT = {
K2: "GuODIETRPuJxpiUQoZairQxHtmzZKYFl",
LK2: "ACDpsiiEFSqqLiEpzXMuXNsLNqGkrIQc",
X4: "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
X6: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
PROD: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
};
/**
* @description 频道列表
@@ -44,7 +29,7 @@ export interface ToChannelItem {
* @since Beta v0.6.5
* @type {Array<ToChannelItem>}
*/
export const CHANNEL_LIST: ToChannelItem[] = [
export const CHANNEL_LIST: Readonly<Array<ToChannelItem>> = [
{
title: "原神",
icon: "/platforms/mhy/ys.webp",

View File

@@ -1,9 +0,0 @@
/**
* @file web constant utils.ts
* @description 一些杂项的常量
* @author BTMuli<bt-muli@outlook.com>
* @since Alpha v0.2.0
*/
// game_biz
export const GAME_BIZ = "hk4e_cn";

View File

@@ -16,15 +16,12 @@ import { getRequestHeader } from "../utils/getRequestHeader.js";
async function getUserFullInfo(
cookie: TGApp.App.Account.Cookie,
): Promise<TGApp.BBS.Response.Base | TGApp.Plugins.Mys.User.Info> {
const url = "https://bbs-api.miyoushe.com/user/wapi/getUserFullInfo";
const ck = { cookie_token: cookie.cookie_token, account_id: cookie.account_id };
const params = { gids: "2" };
const header = getRequestHeader(ck, "GET", params, "common", true);
const resp = await TGHttp<TGApp.Plugins.Mys.User.HomeResponse | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
query: params,
});
const resp = await TGHttp<TGApp.Plugins.Mys.User.HomeResponse | TGApp.BBS.Response.Base>(
"https://bbs-api.miyoushe.com/user/wapi/getUserFullInfo",
{ method: "GET", headers: getRequestHeader(ck, "GET", params, "X4", true), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.user_info;
}
@@ -42,22 +39,16 @@ async function userFavouritePost(
uid: string,
offset: string = "",
): Promise<TGApp.BBS.Collection.PostRespData | TGApp.BBS.Response.Base> {
const url = "https://bbs-api.miyoushe.com/post/wapi/userFavouritePost";
const ck = { cookie_token: cookie.cookie_token, account_id: cookie.account_id };
const params = { size: "20", uid, offset };
const header = getRequestHeader(ck, "GET", params, "common");
const resp = await TGHttp<TGApp.BBS.Collection.PostResponse | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
query: params,
});
const resp = await TGHttp<TGApp.BBS.Collection.PostResponse | TGApp.BBS.Response.Base>(
"https://bbs-api.miyoushe.com/post/wapi/userFavouritePost",
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
const BBSApi = {
userInfo: getUserFullInfo,
lovePost: userFavouritePost,
};
const BBSApi = { userInfo: getUserFullInfo, lovePost: userFavouritePost };
export default BBSApi;

View File

@@ -17,8 +17,29 @@ export enum AnnoServer {
export type AnnoLang = "zh-cn" | "zh-tw" | "en" | "ja";
const AnnoApi = "https://hk4e-ann-api.mihoyo.com/common/hk4e_cn/announcement/api";
const AnnoApiGlobal = "https://sg-hk4e-api.hoyoverse.com/common/hk4e_global/announcement/api";
const AnnoApi: Readonly<string> = "https://hk4e-ann-api.mihoyo.com/common/hk4e_cn/announcement/api";
const AnnoApiGlobal: Readonly<string> =
"https://sg-hk4e-api.hoyoverse.com/common/hk4e_global/announcement/api";
/**
* @description 判断是否为国内服务器
* @since Beta v0.6.5
* @param {AnnoServer} region 服务器
* @returns {boolean} 是否为国内服务器
*/
function isCN(region: AnnoServer): boolean {
return region === AnnoServer.CN_ISLAND || region === AnnoServer.CN_TREE;
}
/**
* @description 根据服务器获取公告地址
* @since Beta v0.6.5
* @param {AnnoServer} region 服务器
* @returns {string} 公告地址
*/
function getAnnoApi(region: AnnoServer): string {
return isCN(region) ? AnnoApi : AnnoApiGlobal;
}
/**
* @description 获取游戏内公告参数
@@ -31,22 +52,16 @@ function getAnnoParams(
region: AnnoServer = AnnoServer.CN_ISLAND,
lang: AnnoLang = "zh-cn",
): TGApp.BBS.Announcement.Params {
const params: TGApp.BBS.Announcement.Params = {
return {
game: "hk4e",
game_biz: "hk4e_cn",
game_biz: isCN(region) ? "hk4e_cn" : "hk4e_global",
lang,
bundle_id: "hk4e_cn",
bundle_id: isCN(region) ? "hk4e_cn" : "hk4e_global",
platform: "pc",
region,
level: "55",
uid: "100000000",
};
if (region === AnnoServer.CN_ISLAND || region === AnnoServer.CN_TREE) {
return params;
}
params.game_biz = "hk4e_global";
params.bundle_id = "hk4e_global";
return params;
}
/**
@@ -60,15 +75,10 @@ async function getAnnoList(
region: AnnoServer = AnnoServer.CN_ISLAND,
lang: AnnoLang = "zh-cn",
): Promise<TGApp.BBS.Announcement.ListData> {
const params: TGApp.BBS.Announcement.Params = getAnnoParams(region, lang);
let url = `${AnnoApi}/getAnnList`;
if (region !== AnnoServer.CN_ISLAND && region !== AnnoServer.CN_TREE) {
url = `${AnnoApiGlobal}/getAnnList`;
}
const resp = await TGHttp<TGApp.BBS.Announcement.ListResponse>(url, {
method: "GET",
query: params,
});
const resp = await TGHttp<TGApp.BBS.Announcement.ListResponse>(
`${getAnnoApi(region)}/getAnnList`,
{ method: "GET", query: getAnnoParams(region, lang) },
);
return resp.data;
}
@@ -85,21 +95,15 @@ async function getAnnoContent(
region: AnnoServer = AnnoServer.CN_ISLAND,
lang: AnnoLang = "zh-cn",
): Promise<TGApp.BBS.Announcement.ContentItem> {
const params: TGApp.BBS.Announcement.Params = getAnnoParams(region, lang);
let url = `${AnnoApi}/getAnnContent`;
if (region !== AnnoServer.CN_ISLAND && region !== AnnoServer.CN_TREE) {
url = `${AnnoApiGlobal}/getAnnContent`;
}
const annoResp = await TGHttp<TGApp.BBS.Announcement.ContentResponse>(url, {
method: "GET",
query: params,
});
const annoResp = await TGHttp<TGApp.BBS.Announcement.ContentResponse>(
`${getAnnoApi(region)}/getAnnContent`,
{ method: "GET", query: getAnnoParams(region, lang) },
);
const annoContent = annoResp.data.list.find((item) => item.ann_id === annId);
if (annoContent != null) {
return annoContent;
} else {
if (annoContent === undefined) {
throw new Error("公告内容不存在");
}
return annoContent;
}
/**
@@ -115,7 +119,6 @@ async function getGachaLog(
gachaType: string,
endId: string = "0",
): Promise<TGApp.Game.Gacha.GachaItem[] | TGApp.BBS.Response.Base> {
const url = "https://public-operation-hk4e.mihoyo.com/gacha_info/api/getGachaLog";
const params = {
lang: "zh-cn",
auth_appid: "webview_gacha",
@@ -126,19 +129,16 @@ async function getGachaLog(
size: "20",
end_id: endId,
};
const resp = await TGHttp<TGApp.Game.Gacha.GachaLogResponse | TGApp.BBS.Response.Base>(url, {
method: "GET",
query: params,
});
const resp = await TGHttp<TGApp.Game.Gacha.GachaLogResponse | TGApp.BBS.Response.Base>(
"https://public-operation-hk4e.mihoyo.com/gacha_info/api/getGachaLog",
{ method: "GET", query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.list;
}
const Hk4eApi = {
anno: {
list: getAnnoList,
content: getAnnoContent,
},
anno: { list: getAnnoList, content: getAnnoContent },
gacha: getGachaLog,
};

View File

@@ -19,7 +19,7 @@ async function getDeviceFp(
Info?: TGApp.App.Device.DeviceInfo,
): Promise<TGApp.App.Device.DeviceInfo> {
const info = Info ?? getInitDeviceInfo();
const deviceFPHeader = {
const deviceFPHeader: Record<string, string | number> = {
proxyStatus: 0,
isRoot: 0,
romCapacity: "512",
@@ -74,8 +74,7 @@ async function getDeviceFp(
hasKeyboard: 0,
board: "taro",
};
const url = "https://public-data-api.mihoyo.com/device-fp/api/getFp";
const data = {
const data: Record<string, string> = {
device_id: info.device_id,
seed_id: info.seed_id,
platform: "2",
@@ -85,24 +84,20 @@ async function getDeviceFp(
bbs_device_id: info.device_id,
device_fp: info.device_fp,
};
const header = {
"User-Agent": TGConstant.BBS.UA_MOBILE,
const header: Record<string, string> = {
"user-agent": TGConstant.BBS.UA_MOBILE,
"x-rpc-app_version": TGConstant.BBS.VERSION,
"x-rpc-client_type": "5",
"x-requested-with": "com.mihoyo.hyperion",
Referer: "https://webstatic.mihoyo.com/",
};
try {
const resp = await TGHttp<TGApp.BBS.Response.getDeviceFp>(url, {
method: "POST",
body: JSON.stringify(data),
headers: header,
});
if (resp.retcode !== 0) {
info.device_fp = "0000000000000";
} else {
info.device_fp = resp.data.device_fp;
}
const resp = await TGHttp<TGApp.BBS.Response.getDeviceFp>(
"https://public-data-api.mihoyo.com/device-fp/api/getFp",
{ method: "POST", body: JSON.stringify(data), headers: header },
);
if (resp.retcode !== 0) info.device_fp = "0000000000000";
else info.device_fp = resp.data.device_fp;
} catch (error) {
info.device_fp = "0000000000000";
await TGLogger.Error(`获取设备指纹失败: ${error}`);
@@ -119,19 +114,14 @@ async function getDeviceFp(
async function refreshCode(
actId: string,
): Promise<TGApp.BBS.Navigator.CodeData[] | TGApp.BBS.Response.Base> {
const url = "https://api-takumi-static.mihoyo.com/event/miyolive/refreshCode";
const header = { "x-rpc-act_id": actId };
const res = await TGHttp<TGApp.BBS.Navigator.CodeResponse | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
});
const res = await TGHttp<TGApp.BBS.Navigator.CodeResponse | TGApp.BBS.Response.Base>(
"https://api-takumi-static.mihoyo.com/event/miyolive/refreshCode",
{ method: "GET", headers: { "x-rpc-act_id": actId } },
);
if (res.retcode !== 0) return <TGApp.BBS.Response.Base>res;
return res.data.code_list;
}
const OtherApi = {
code: refreshCode,
fp: getDeviceFp,
};
const OtherApi = { code: refreshCode, fp: getDeviceFp };
export default OtherApi;

View File

@@ -7,9 +7,9 @@ import TGHttp from "../../utils/TGHttp.js";
import { getRequestHeader } from "../utils/getRequestHeader.js";
// PassportApiBaseUrl => pAbu
const pAbu = "https://passport-api.mihoyo.com/";
const pAbu: Readonly<string> = "https://passport-api.mihoyo.com/";
// PassportV4ApiBaseUrl => p4Abu
const p4Abu = "https://passport-api-v4.mihoyo.com/";
const p4Abu: Readonly<string> = "https://passport-api-v4.mihoyo.com/";
/**
* @description 获取登录ticket
@@ -22,22 +22,20 @@ async function createAuthTicketByGameBiz(
account: TGApp.Sqlite.Account.Game,
cookie: TGApp.App.Account.Cookie,
): Promise<TGApp.BBS.Response.Base | string> {
const url = `${pAbu}account/ma-cn-verifier/app/createAuthTicketByGameBiz`;
const params = {
const params: Record<string, string> = {
game_biz: account.gameBiz,
stoken: cookie.stoken,
uid: account.gameUid,
mid: cookie.mid,
};
const header = {
const headers: Record<string, string> = {
"x-rpc-client_type": "3",
"x-rpc-app_id": "ddxf5dufpuyo",
};
const resp = await TGHttp<TGApp.BBS.Response.getAuthTicketByGameBiz>(url, {
method: "POST",
headers: header,
query: params,
});
const resp = await TGHttp<TGApp.BBS.Response.getAuthTicketByGameBiz>(
`${pAbu}account/ma-cn-verifier/app/createAuthTicketByGameBiz`,
{ method: "POST", headers: headers, query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.ticket;
}
@@ -51,17 +49,11 @@ async function createAuthTicketByGameBiz(
async function getCookieAccountInfoBySToken(
cookie: TGApp.App.Account.Cookie,
): Promise<string | TGApp.BBS.Response.Base> {
const url = `${pAbu}account/auth/api/getCookieAccountInfoBySToken`;
const ck = { stoken: cookie.stoken, mid: cookie.mid };
const params = { stoken: cookie.stoken };
const header = getRequestHeader(ck, "GET", params, "common");
const resp = await TGHttp<TGApp.BBS.Response.getCookieTokenBySToken | TGApp.BBS.Response.Base>(
url,
{
method: "GET",
headers: header,
query: params,
},
`${pAbu}account/auth/api/getCookieAccountInfoBySToken`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.cookie_token;
@@ -76,39 +68,30 @@ async function getCookieAccountInfoBySToken(
async function getLTokenBySToken(
cookie: TGApp.App.Account.Cookie,
): Promise<string | TGApp.BBS.Response.Base> {
const url = `${pAbu}account/auth/api/getLTokenBySToken`;
const ck = { mid: cookie.mid, stoken: cookie.stoken };
const params = { stoken: cookie.stoken };
const header = getRequestHeader(ck, "GET", params, "common");
const resp = await TGHttp<TGApp.BBS.Response.getLTokenBySToken | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
query: params,
});
const resp = await TGHttp<TGApp.BBS.Response.getLTokenBySToken | TGApp.BBS.Response.Base>(
`${pAbu}account/auth/api/getLTokenBySToken`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.ltoken;
}
/**
* @description 验证 ltoken 有效性,返回 mid
* @since Beta v0.5.0
* @since Beta v0.6.5
* @param {TGApp.App.Account.Cookie} cookie - 账户 cookie
* @returns {Promise<string | TGApp.BBS.Response.Base>}
*/
async function verifyLToken(
cookie: TGApp.App.Account.Cookie,
): Promise<string | TGApp.BBS.Response.Base> {
const url = `${p4Abu}account/ma-cn-session/web/verifyLtoken`;
const ck = { ltoken: cookie.ltoken, ltuid: cookie.ltuid };
const data = { ltoken: cookie.ltoken };
const header = getRequestHeader(ck, "POST", data, "common");
const resp = await TGHttp<TGApp.BBS.Response.verifyUserInfoBySToken | TGApp.BBS.Response.Base>(
url,
{
method: "POST",
headers: header,
body: JSON.stringify(data),
},
`${p4Abu}account/ma-cn-session/web/verifyLtoken`,
{ method: "POST", headers: getRequestHeader(ck, "POST", data), body: JSON.stringify(data) },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.user_info.mid;
@@ -117,10 +100,7 @@ async function verifyLToken(
const PassportApi = {
authTicket: createAuthTicketByGameBiz,
cookieToken: getCookieAccountInfoBySToken,
lToken: {
get: getLTokenBySToken,
verify: verifyLToken,
},
lToken: { get: getLTokenBySToken, verify: verifyLToken },
};
export default PassportApi;

View File

@@ -8,7 +8,8 @@ import TGHttp from "../../utils/TGHttp.js";
import { getRequestHeader } from "../utils/getRequestHeader.js";
// TakumiRecordGenshinApiBaseUrl => trgAbu
const trgAbu = "https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/";
const trgAbu: Readonly<string> =
"https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/";
/**
* @description 获取角色详情
@@ -23,15 +24,12 @@ async function characterDetail(
user: TGApp.Sqlite.Account.Game,
avatarIds: string[],
): Promise<TGApp.Game.Avatar.AvatarDetail | TGApp.BBS.Response.Base> {
const url = `${trgAbu}character/detail`;
const ck = { account_id: cookie.account_id, cookie_token: cookie.cookie_token };
const data = { role_id: user.gameUid, server: user.region, character_ids: avatarIds };
const header = getRequestHeader(ck, "POST", data, "common");
const resp = await TGHttp<TGApp.Game.Avatar.DetailResponse | TGApp.BBS.Response.Base>(url, {
method: "POST",
body: JSON.stringify(data),
headers: header,
});
const resp = await TGHttp<TGApp.Game.Avatar.DetailResponse | TGApp.BBS.Response.Base>(
`${trgAbu}character/detail`,
{ method: "POST", body: JSON.stringify(data), headers: getRequestHeader(ck, "POST", data) },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
@@ -47,15 +45,12 @@ async function characterList(
cookie: TGApp.App.Account.Cookie,
user: TGApp.Sqlite.Account.Game,
): Promise<TGApp.Game.Avatar.Avatar[] | TGApp.BBS.Response.Base> {
const url = `${trgAbu}character/list`;
const ck = { account_id: cookie.account_id, cookie_token: cookie.cookie_token };
const data = { role_id: user.gameUid, server: user.region };
const header = getRequestHeader(ck, "POST", data, "common");
const resp = await TGHttp<TGApp.Game.Avatar.ListResponse | TGApp.BBS.Response.Base>(url, {
method: "POST",
body: JSON.stringify(data),
headers: header,
});
const resp = await TGHttp<TGApp.Game.Avatar.ListResponse | TGApp.BBS.Response.Base>(
`${trgAbu}character/list`,
{ method: "POST", body: JSON.stringify(data), headers: getRequestHeader(ck, "POST", data) },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.list;
}
@@ -73,15 +68,12 @@ async function index(
user: TGApp.Sqlite.Account.Game,
listType: number = 0,
): Promise<TGApp.Game.Record.FullData | TGApp.BBS.Response.Base> {
const url = `${trgAbu}index`;
const ck = { account_id: cookie.account_id, cookie_token: cookie.cookie_token };
const params = { avatar_list_type: listType, role_id: user.gameUid, server: user.region };
const header = getRequestHeader(ck, "GET", params, "common");
const resp = await TGHttp<TGApp.Game.Record.Response | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
query: params,
});
const resp = await TGHttp<TGApp.Game.Record.Response | TGApp.BBS.Response.Base>(
`${trgAbu}index`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
@@ -97,7 +89,6 @@ async function roleCombat(
cookie: TGApp.App.Account.Cookie,
user: TGApp.Sqlite.Account.Game,
): Promise<TGApp.Game.Combat.Combat[] | TGApp.BBS.Response.Base | false> {
const url = `${trgAbu}role_combat`;
const ck = {
account_id: cookie.account_id,
cookie_token: cookie.cookie_token,
@@ -105,12 +96,10 @@ async function roleCombat(
ltuid: cookie.ltuid,
};
const params = { role_id: user.gameUid, server: user.region, active: 1, need_detail: true };
const header = getRequestHeader(ck, "GET", params, "common");
const resp = await TGHttp<TGApp.Game.Combat.Response | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
query: params,
});
const resp = await TGHttp<TGApp.Game.Combat.Response | TGApp.BBS.Response.Base>(
`${trgAbu}role_combat`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
if (!resp.data.is_unlock) return false;
return resp.data.data;
@@ -129,7 +118,6 @@ async function spiralAbyss(
user: TGApp.Sqlite.Account.Game,
schedule: string,
): Promise<TGApp.Game.Abyss.FullData | TGApp.BBS.Response.Base> {
const url = `${trgAbu}spiralAbyss`;
const ck = {
account_id: cookie.account_id,
cookie_token: cookie.cookie_token,
@@ -137,22 +125,17 @@ async function spiralAbyss(
ltuid: cookie.ltuid,
};
const params = { role_id: user.gameUid, schedule_type: schedule, server: user.region };
const header = getRequestHeader(ck, "GET", params, "common");
const resp = await TGHttp<TGApp.Game.Abyss.Response | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
query: params,
});
const resp = await TGHttp<TGApp.Game.Abyss.Response | TGApp.BBS.Response.Base>(
`${trgAbu}spiralAbyss`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data;
}
const TakumiRecordGenshinApi = {
index: index,
character: {
list: characterList,
detail: characterDetail,
},
character: { list: characterList, detail: characterDetail },
roleCombat: roleCombat,
spiralAbyss: spiralAbyss,
};

View File

@@ -4,13 +4,12 @@
* @since Beta v0.6.3
*/
import TGHttp from "../../utils/TGHttp.js";
import TGConstant from "../constant/TGConstant.js";
import { getRequestHeader } from "../utils/getRequestHeader.js";
// TakumiAuthApiBaseUrl => taAbu
const taAbu = "https://api-takumi.mihoyo.com/auth/api/";
const taAbu: Readonly<string> = "https://api-takumi.mihoyo.com/auth/api/";
// TakumiBingApiBaseUrl => tbAbu
const tbAbu = "https://api-takumi.mihoyo.com/binding/api/";
const tbAbu: Readonly<string> = "https://api-takumi.mihoyo.com/binding/api/";
/**
* @description 根据stoken获取action_ticket
@@ -25,15 +24,12 @@ async function getActionTicketBySToken(
user: TGApp.Sqlite.Account.Game,
actionType: string,
): Promise<TGApp.BBS.Response.getActionTicketBySToken> {
const url = `${taAbu}getActionTicketBySToken`;
const ck = { stoken: cookie.stoken, mid: cookie.mid };
const params = { action_type: actionType, stoken: cookie.stoken, uid: user.gameUid };
const header = getRequestHeader(ck, "GET", params, "k2");
return await TGHttp<TGApp.BBS.Response.getActionTicketBySToken>(url, {
method: "GET",
headers: header,
query: params,
});
return await TGHttp<TGApp.BBS.Response.getActionTicketBySToken>(
`${taAbu}getActionTicketBySToken`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params, "K2"), query: params },
);
}
/**
@@ -47,7 +43,6 @@ async function genAuthKey(
cookie: TGApp.App.Account.Cookie,
account: TGApp.Sqlite.Account.Game,
): Promise<string | TGApp.BBS.Response.Base> {
const url = `${tbAbu}genAuthKey`;
const ck = { stoken: cookie.stoken, mid: cookie.mid };
const data = {
auth_appid: "webview_gacha",
@@ -55,12 +50,14 @@ async function genAuthKey(
game_uid: account.gameUid,
region: account.region,
};
const header = getRequestHeader(ck, "POST", JSON.stringify(data), "lk2", true);
const resp = await TGHttp<TGApp.Game.Gacha.AuthkeyResponse | TGApp.BBS.Response.Base>(url, {
const resp = await TGHttp<TGApp.Game.Gacha.AuthkeyResponse | TGApp.BBS.Response.Base>(
`${tbAbu}genAuthKey`,
{
method: "POST",
headers: header,
headers: getRequestHeader(ck, "POST", JSON.stringify(data), "LK2", true),
body: JSON.stringify(data),
});
},
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.authkey;
}
@@ -76,46 +73,35 @@ async function genAuthKey2(
cookie: Record<string, string>,
payload: Record<string, string>,
): Promise<TGApp.BBS.Response.Base> {
const url = `${tbAbu}genAuthKey`;
const header = getRequestHeader(cookie, "POST", JSON.stringify(payload), "lk2", true);
return await TGHttp<TGApp.BBS.Response.Base>(url, {
return await TGHttp<TGApp.BBS.Response.Base>(`${tbAbu}genAuthKey`, {
method: "POST",
headers: header,
headers: getRequestHeader(cookie, "POST", JSON.stringify(payload), "LK2", true),
body: JSON.stringify(payload),
});
}
/**
* @description 通过cookie获取游戏账号
* @since Beta v0.6.3
* @since Beta v0.6.5
* @param {TGApp.App.Account.Cookie} cookie cookie
* @returns {Promise<TGApp.BBS.Account.GameAccount[]|TGApp.BBS.Response.Base>}
*/
async function getUserGameRolesByCookie(
cookie: TGApp.App.Account.Cookie,
): Promise<TGApp.BBS.Account.GameAccount[] | TGApp.BBS.Response.Base> {
const url = `${tbAbu}getUserGameRolesByCookie`;
const ck = { account_id: cookie.account_id, cookie_token: cookie.cookie_token };
const params = { game_biz: TGConstant.GAME_BIZ };
const header = getRequestHeader(ck, "GET", params, "common");
const resp = await TGHttp<TGApp.BBS.Response.getGameAccounts | TGApp.BBS.Response.Base>(url, {
method: "GET",
headers: header,
query: params,
});
const params = { game_biz: "hk4e_cn" };
const resp = await TGHttp<TGApp.BBS.Response.getGameAccounts | TGApp.BBS.Response.Base>(
`${tbAbu}getUserGameRolesByCookie`,
{ method: "GET", headers: getRequestHeader(ck, "GET", params), query: params },
);
if (resp.retcode !== 0) return <TGApp.BBS.Response.Base>resp;
return resp.data.list;
}
const TakumiApi = {
auth: {
actionTicket: getActionTicketBySToken,
},
bind: {
authKey: genAuthKey,
authKey2: genAuthKey2,
gameRoles: getUserGameRolesByCookie,
},
auth: { actionTicket: getActionTicketBySToken },
bind: { authKey: genAuthKey, authKey2: genAuthKey2, gameRoles: getUserGameRolesByCookie },
};
export default TakumiApi;

View File

@@ -7,34 +7,35 @@
import Md5 from "js-md5";
import { getDeviceInfo, getRandomString } from "../../utils/toolFunc.js";
import TGConstant from "../constant/TGConstant.js";
import { transCookie, transParams } from "./tools.js";
type SaltType = "common" | "prod" | "lk2" | "k2";
import { BBS_VERSION } from "../constant/bbs.js";
/**
* @description 获取 salt
* @since Beta v0.6.3
* @version 2.59.1
* @param {SaltType} saltType salt 类型
* @returns {string} salt
* @description salt 类型
* @since Beta v0.6.5
* @enum {number}
*/
function getSalt(saltType: SaltType): string {
switch (saltType) {
case "common":
return TGConstant.Salt.X4;
case "prod":
return TGConstant.Salt.PROD;
case "lk2":
return TGConstant.Salt.LK2;
case "k2":
return TGConstant.Salt.K2;
default:
return TGConstant.Salt.X4;
}
const enum SaltType {
K2,
LK2,
X4,
X6,
PROD,
}
/**
* @description salt 值
* @version 2.78.1
* @since Beta v0.6.5
*/
const Salt: Readonly<Record<keyof typeof SaltType, string>> = {
K2: "GuODIETRPuJxpiUQoZairQxHtmzZKYFl",
LK2: "ACDpsiiEFSqqLiEpzXMuXNsLNqGkrIQc",
X4: "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs",
X6: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
PROD: "t0qEgfub6cvueAPgR5m9aQWWVciEer7v",
};
const UserAgent: Readonly<string> = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) miHoYoBBS/${BBS_VERSION}`;
/**
* @description 获取随机数
* @since Alpha v0.2.0
@@ -56,8 +57,13 @@ function getRandomNumber(min: number, max: number): number {
* @param {boolean} isSign 是否为签名
* @returns {string} ds
*/
function getDS(method: string, data: string, saltType: SaltType, isSign: boolean): string {
const salt = getSalt(saltType);
function getDS(
method: string,
data: string,
saltType: keyof typeof SaltType,
isSign: boolean,
): string {
const salt = Salt[saltType];
const time = Math.floor(Date.now() / 1000).toString();
let random = getRandomNumber(100000, 200000).toString();
if (isSign) random = getRandomString(6);
@@ -69,38 +75,64 @@ function getDS(method: string, data: string, saltType: SaltType, isSign: boolean
return `${time},${random},${md5Str}`;
}
/**
* @description ds 算法需要数据转换后的字符串是按照字典序排序的
* @since Beta v0.6.5
* @param { Record<string, string | number | boolean | Array<string>> | string} obj object
* @returns {string} query string
*/
function transParams(
obj: Record<string, string | number | boolean | Array<string>> | string,
): string {
if (typeof obj === "string") return obj;
let res = "";
const keys = Object.keys(obj).sort();
for (const key of keys) {
res += `${key}=${obj[key].toString()}&`;
}
return res.slice(0, -1);
}
/**
* @description 将 cookie 对象转换为字符串
* @since Alpha v0.1.5
* @param {Record<string, string>} cookie cookie
* @returns {string} 转换后的 cookie
*/
function transCookie(cookie: Record<string, string>): string {
let res = "";
for (const key of Object.keys(cookie).sort()) {
res += `${key}=${cookie[key]};`;
}
return res;
}
/**
* @description 获取请求头
* @since Beta v0.6.2
* @since Beta v0.6.5
* @param {Record<string, string>} cookie cookie
* @param {string} method 请求方法
* @param {Record<string, string|number|string[]|boolean>|string} data 请求数据
* @param {SaltType} saltType salt 类型
* @param {Record<string, string | number | boolean | Array<string>> | string} data 请求数据
* @param {keyof typeof SaltType} saltType salt 类型
* @param {boolean} isSign 是否为签名
* @returns {Record<string, string>} 请求头
*/
export function getRequestHeader(
cookie: Record<string, string>,
method: string,
data: Record<string, string | number | string[] | boolean> | string,
saltType: SaltType,
data: Record<string, string | number | boolean | Array<string>> | string,
saltType: keyof typeof SaltType = "X4",
isSign: boolean = false,
): Record<string, string> {
let ds;
if (typeof data === "string") {
ds = getDS(method, data, saltType, isSign);
} else {
ds = getDS(method, transParams(data), saltType, isSign);
}
return {
"user-agent": TGConstant.BBS.UA_PC,
"x-rpc-app_version": TGConstant.BBS.VERSION,
"user-agent": UserAgent,
"x-rpc-app_version": BBS_VERSION,
"x-rpc-client_type": "5",
"x-requested-with": "com.mihoyo.hyperion",
referer: "https://webstatic.mihoyo.com",
"x-rpc-device_id": getDeviceInfo("device_id"),
"x-rpc-device_fp": getDeviceInfo("device_fp"),
ds,
ds: getDS(method, transParams(data), saltType, isSign),
cookie: transCookie(cookie),
};
}
@@ -108,26 +140,31 @@ export function getRequestHeader(
/**
* @description 获取 DS
* @since Beta v0.3.9
* @param {SaltType} saltType salt 类型
* @param {keyof typeof SaltType} saltType salt 类型
* @param {number} dsType ds 类型
* @param {Record<string, string|number>|string} body
* @param {Record<string, string|number>|string} query
* @returns {string} DS
*/
export function getDS4JS(saltType: SaltType, dsType: 1, body: undefined, query: undefined): string;
export function getDS4JS(
saltType: SaltType,
saltType: keyof typeof SaltType,
dsType: 1,
body: undefined,
query: undefined,
): string;
export function getDS4JS(
saltType: keyof typeof SaltType,
dsType: 2,
body: Record<string, string | number> | string,
query: Record<string, string | number> | string,
): string;
export function getDS4JS(
saltType: SaltType,
saltType: keyof typeof SaltType,
dsType: 1 | 2,
body?: Record<string, string | number> | string,
query?: Record<string, string | number> | string,
): string {
const salt = getSalt(saltType);
const salt = Salt[saltType];
const time = Math.floor(Date.now() / 1000).toString();
let random = getRandomNumber(100000, 200000).toString();
let hashStr: string;

View File

@@ -26,35 +26,6 @@ export function decodeRegExp(data: string): string {
return res;
}
/**
* @description 将 cookie 对象转换为字符串
* @since Alpha v0.1.5
* @param {Record<string, string>} cookie cookie
* @returns {string} 转换后的 cookie
*/
export function transCookie(cookie: Record<string, string>): string {
let res = "";
for (const key of Object.keys(cookie).sort()) {
res += `${key}=${cookie[key]};`;
}
return res;
}
/**
* @description ds 算法需要数据转换后的字符串是按照字典序排序的
* @since Beta v0.6.3
* @param {Record<string, string|number>} obj object
* @returns {string} query string
*/
export function transParams(obj: Record<string, string | number | string[] | boolean>): string {
let res = "";
const keys = Object.keys(obj).sort();
for (const key of keys) {
res += `${key}=${obj[key].toString()}&`;
}
return res.slice(0, -1);
}
/**
* @description 根据 gid 获取游戏名称
* @param {number} gid