UPDATE: Support RobinSR
All checks were successful
Gitea Auto Deploy / Deploy-Container (push) Successful in 1m9s

This commit is contained in:
2026-04-12 15:53:51 +07:00
parent 16ba852029
commit 9003b06b0e
15 changed files with 153 additions and 392 deletions

View File

@@ -3,6 +3,7 @@
import { connectToPS, syncDataToPS } from "@/helper"
import useConnectStore from "@/stores/connectStore"
import useGlobalStore from "@/stores/globalStore"
import { PSConnectType } from "@/types"
import { useTranslations } from "next-intl"
import { useState } from "react"
@@ -21,11 +22,10 @@ export default function ConnectBar() {
setUsername,
setPassword
} = useConnectStore()
const { isConnectPS } = useGlobalStore()
const { isConnectPS, setIsConnectPS } = useGlobalStore()
return (
<div className="px-6 py-4">
{/* Select connection type */}
<div className="form-control grid grid-cols-1 w-full mb-6">
<label className="label">
<span className="label-text font-semibold text-purple-300">{transI18n("connectionType")}</span>
@@ -33,15 +33,18 @@ export default function ConnectBar() {
<select
className="select w-full select-bordered border-purple-500/30 focus:border-purple-500 bg-base-200 mt-1"
value={connectionType}
onChange={(e) => setConnectionType(e.target.value)}
onChange={(e) => {
setIsConnectPS(false)
setConnectionType(e.target.value)
}}
>
<option value="FireflyGo">FireflyGo</option>
<option value="Other">{transI18n("other")}</option>
<option value={PSConnectType.FireflyGo}>FireflyGo</option>
<option value={PSConnectType.RobinSR}>RobinSR</option>
<option value={PSConnectType.Other}>{transI18n("other")}</option>
</select>
</div>
{/* Show host/port if Other */}
{connectionType === "Other" && (
{connectionType === PSConnectType.Other && (
<div className="flex flex-col md:space-x-4 mb-6 gap-2">
<div className="form-control w-full mb-4 md:mb-0">
<label className="label">
@@ -105,7 +108,6 @@ export default function ConnectBar() {
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mt-6 mb-2">
{/* Status */}
<div className="flex items-center justify-center md:justify-start">
<span className="text-md mr-2">{transI18n("status")}:</span>
<span
@@ -114,6 +116,15 @@ export default function ConnectBar() {
>
{isConnectPS ? transI18n("connected") : transI18n("unconnected")}
</span>
{isConnectPS && (
<span
className={`badge ${isConnectPS ? "badge-success" : "badge-error"
} badge-lg`}
>
{isConnectPS ? transI18n("connected") : transI18n("unconnected")}
</span>
)}
</div>
{/* Buttons */}

View File

@@ -1,7 +1,7 @@
export const listCurrentLanguage = {
ja: "JP",
ko: "KR",
en: "US",
en: "EN",
vi: "VN",
zh: "CN"
};

View File

@@ -5,111 +5,101 @@ import useUserDataStore from "@/stores/userDataStore"
import { converterToFreeSRJson } from "./converterToFreeSRJson"
import { psResponseSchema } from "@/zod"
import useGlobalStore from "@/stores/globalStore"
import { ActionResult, ExtraData, ProxyPayload, ProxyResponse, PSConnectType, PSResponse } from "@/types"
export const connectToPS = async (): Promise<{ success: boolean, message: string }> => {
const {
connectionType,
privateType,
serverUrl,
username,
password
} = useConnectStore.getState()
const { setExtraData, setIsConnectPS } = useGlobalStore.getState()
let urlQuery = serverUrl
if (!urlQuery.startsWith("http://") && !urlQuery.startsWith("https://")) {
urlQuery = `http://${urlQuery}`
}
if (connectionType === "FireflyGo") {
urlQuery = "http://localhost:21000/sync"
} else if (connectionType === "Other" && privateType === "Server") {
const response = await SendDataThroughProxy({data: {username, password, serverUrl, data: null, method: "POST"}})
if (response instanceof Error) {
return { success: false, message: response.message }
} else if (response.error) {
return { success: false, message: response.error }
} else {
const parsed = psResponseSchema.safeParse(response.data)
if (!parsed.success) {
return { success: false, message: "Invalid response schema" }
}
return { success: true, message: "" }
}
}
const response = await SendDataToServer(username, password, urlQuery, null)
if (typeof response === "string") {
setIsConnectPS(false)
return { success: false, message: response }
} else if (response.status != 200) {
setIsConnectPS(false)
return { success: false, message: response.message }
} else {
setIsConnectPS(true)
setExtraData(response?.extra_data)
return { success: true, message: "" }
const getUrlQuery = (connectionType: PSConnectType | string, serverUrl: string): string => {
if (connectionType === PSConnectType.FireflyGo) return "http://localhost:21000/sync"
if (connectionType === PSConnectType.RobinSR) return "http://localhost:21000/srtools"
if (!serverUrl.startsWith("http://") && !serverUrl.startsWith("https://")) {
return `http://${serverUrl}`
}
return serverUrl
}
export const syncDataToPS = async (): Promise<{ success: boolean, message: string }> => {
const {
connectionType,
privateType,
serverUrl,
username,
password
} = useConnectStore.getState()
const {extraData, setIsConnectPS, setExtraData, isEnableChangePath, isEnableLua} = useGlobalStore.getState()
const {avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config} = useUserDataStore.getState()
const data = converterToFreeSRJson(avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config)
let urlQuery = serverUrl
if (!urlQuery.startsWith("http://") && !urlQuery.startsWith("https://")) {
urlQuery = `http://${urlQuery}`
}
if (connectionType === "FireflyGo") {
urlQuery = "http://localhost:21000/sync"
} else if (connectionType === "Other" && privateType === "Server") {
const response = await SendDataThroughProxy({data: {username, password, serverUrl, data, method: "POST"}})
if (response instanceof Error) {
return { success: false, message: response.message }
} else if (response.error) {
return { success: false, message: response.error }
} else {
const parsed = psResponseSchema.safeParse(response.data)
if (!parsed.success) {
return { success: false, message: "Invalid response schema" }
}
return { success: true, message: "" }
}
}
const newExtra = structuredClone(extraData)
if (newExtra && !isEnableChangePath) {
newExtra.multi_path = undefined
const handleProxyRequest = async (payload: ProxyPayload): Promise<ActionResult> => {
const response = await SendDataThroughProxy({
data: { ...payload, method: "POST" }
}) as ProxyResponse | Error
if (response instanceof Error) {
return { success: false, message: response.message }
}
if (newExtra && !isEnableLua) {
newExtra.lua = null
if (response.error) {
return { success: false, message: response.error }
}
const parsed = psResponseSchema.safeParse(response.data)
if (!parsed.success) {
return { success: false, message: "Invalid response schema" }
}
return { success: true, message: "" }
}
const response = await SendDataToServer(username, password, urlQuery, data, newExtra)
const handleDirectServerResponse = (
response: PSResponse | string,
setIsConnectPS: (val: boolean) => void,
onSuccess: (extraData?: ExtraData) => void
): ActionResult => {
if (typeof response === "string") {
setIsConnectPS(false)
return { success: false, message: response }
} else if (response.status != 200) {
}
if (response.status !== 200) {
setIsConnectPS(false)
return { success: false, message: response.message }
} else {
setIsConnectPS(true)
const newData = structuredClone(response?.extra_data)
}
setIsConnectPS(true)
onSuccess(response?.extra_data)
return { success: true, message: "" }
}
export const connectToPS = async (): Promise<ActionResult> => {
const { connectionType, privateType, serverUrl, username, password } = useConnectStore.getState()
const { setExtraData, setIsConnectPS } = useGlobalStore.getState()
if (connectionType === "Other" && privateType === "Server") {
return handleProxyRequest({ username, password, serverUrl, data: undefined })
}
const urlQuery = getUrlQuery(connectionType, serverUrl)
const response = await SendDataToServer(username, password, urlQuery, undefined)
return handleDirectServerResponse(response, setIsConnectPS, (extraData) => {
setExtraData(extraData)
})
}
export const syncDataToPS = async (): Promise<ActionResult> => {
const { connectionType, privateType, serverUrl, username, password } = useConnectStore.getState()
const { extraData, setIsConnectPS, setExtraData, isEnableChangePath, isEnableLua } = useGlobalStore.getState()
const { avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config } = useUserDataStore.getState()
const data = converterToFreeSRJson(avatars, battle_type, moc_config, pf_config, as_config, ce_config, peak_config)
if (connectionType === "Other" && privateType === "Server") {
return handleProxyRequest({ username, password, serverUrl, data })
}
const urlQuery = getUrlQuery(connectionType, serverUrl)
const payloadExtra: PSResponse['extra_data'] = structuredClone(extraData)
if (payloadExtra) {
if (!isEnableChangePath) payloadExtra.multi_path = undefined
if (!isEnableLua) payloadExtra.lua = null
}
const response = await SendDataToServer(username, password, urlQuery, data, payloadExtra)
return handleDirectServerResponse(response, setIsConnectPS, (responseExtraData) => {
const newData = structuredClone(responseExtraData)
if (newData) {
newData.lua = extraData?.lua || null
}
setExtraData(newData)
return { success: true, message: "" }
}
})
}

View File

@@ -1,6 +1,7 @@
import useConnectStore from "@/stores/connectStore";
import useDetailDataStore from "@/stores/detailDataStore";
import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, RelicJson } from "@/types";
import { ASConfigStore, AvatarJson, AvatarStore, BattleConfigJson, CEConfigStore, FreeSRJson, LightconeJson, MOCConfigStore, PEAKConfigStore, PFConfigStore, PSConnectType, RelicJson } from "@/types";
export function converterToFreeSRJson(
@@ -13,6 +14,7 @@ export function converterToFreeSRJson(
peak_config: PEAKConfigStore,
): FreeSRJson {
const { skillConfig } = useDetailDataStore.getState()
const { connectionType } = useConnectStore.getState()
const lightcones: LightconeJson[] = []
const relics: RelicJson[] = []
let battleJson: BattleConfigJson
@@ -48,7 +50,7 @@ export function converterToFreeSRJson(
}
} else if (battle_type === "CE") {
battleJson = {
battle_type: battle_type,
battle_type: connectionType === PSConnectType.FireflyGo ? battle_type : "DEFAULT",
blessings: ce_config.blessings,
custom_stats: [],
cycle_count: ce_config.cycle_count,
@@ -58,7 +60,7 @@ export function converterToFreeSRJson(
}
} else if (battle_type === "PEAK") {
battleJson = {
battle_type: battle_type,
battle_type: connectionType === PSConnectType.FireflyGo ? battle_type : "DEFAULT",
blessings: peak_config.blessings,
custom_stats: [],
cycle_count: peak_config.cycle_count,

View File

@@ -117,7 +117,7 @@ export async function SendDataToServer(
username: string,
password: string,
serverUrl: string,
data: FreeSRJson | null,
data?: FreeSRJson,
extraData?: ExtraData
): Promise<PSResponse | string> {
try {

View File

@@ -17,4 +17,5 @@ export * from "./modelConfig"
export * from "./metaData"
export * from "./monsterDetail"
export * from "./filter"
export * from "./metaData"
export * from "./metaData"
export * from "./psConnect"

32
src/types/psConnect.ts Normal file
View File

@@ -0,0 +1,32 @@
import { ExtraData } from "./extraData";
import { FreeSRJson } from "./srtools";
export enum PSConnectType {
FireflyGo = "FireflyGo",
RobinSR = "RobinSR",
Other = "Other",
}
export interface ProxyPayload {
username?: string;
password?: string;
serverUrl: string;
data?: FreeSRJson;
}
export interface ProxyResponse {
error?: string;
message?: string;
data?: unknown;
}
export interface PSResponse {
status: number;
message: string;
extra_data?: ExtraData
}
export interface ActionResult {
success: boolean;
message: string;
}

View File

@@ -1,5 +1,3 @@
import { ExtraData } from "./extraData";
export interface SubAffix {
sub_affix_id: number;
count: number;
@@ -81,9 +79,4 @@ export interface FreeSRJson {
loadout?: LoadoutJson[];
}
export interface PSResponse {
status: number;
message: string;
extra_data?: ExtraData
}