package aiautoser import ( "errors" "fmt" "strconv" "91porn-server/app/appg" "91porn-server/common" "91porn-server/common/db" "91porn-server/common/httputil" "91porn-server/common/log" "91porn-server/models/commod" "91porn-server/models/v/aiUnDressmod" "91porn-server/models/v/aichangefaceimgmod" "91porn-server/models/v/aichangefacemod" "91porn-server/models/v/backpackmod" "91porn-server/models/v/txnmod" "91porn-server/models/v/walletmod" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) const autoOperator = "AI_AUTO" type changeFaceRequest struct { AppID int `json:"app_id"` OrderNumApp string `json:"order_num_app"` Title string `json:"title"` SourceImageURL string `json:"source_image_url"` NotifyURL string `json:"notify_url"` } type changeFaceResponse struct { Code int `json:"code"` } // SubmitVideo automatically approves and submits an AI video face-swap order. // Submission failures are handled with the same refund policy as the existing // Web callback flow. func SubmitVideo(id primitive.ObjectID) error { order, err := aichangefacemod.FindByID(nil, id) if err != nil { return err } transitioned, err := aichangefacemod.TransitionStatus(nil, id, aichangefacemod.StatusGenning, aichangefacemod.StatusSubmit, bson.M{ "operator": autoOperator, "remark": "已自动提交", }) if err != nil || !transitioned { return err } notifyURL, notifyErr := appg.AICallbackURL("/api/web/admin/ai/changeface/callback") if notifyErr != nil || len(order.Pic) == 0 || order.Pic[0] == "" || appg.Conf.URL.AiServer == "" { return refundVideo(id, "自动提交参数不完整") } req := changeFaceRequest{ AppID: int(commod.KFK_APPID), OrderNumApp: id.Hex(), Title: order.ModTitle, SourceImageURL: order.Pic[0], NotifyURL: notifyURL, } if err := postChangeFace(req); err != nil { log.Error("AI视频换脸自动提交失败", log.Any("id", id.Hex()), log.E(err)) return refundVideo(id, err.Error()) } return nil } // SubmitImage automatically approves and submits an AI image face-swap order. func SubmitImage(id primitive.ObjectID) error { order, err := aichangefaceimgmod.FindByID(nil, id) if err != nil { return err } transitioned, err := aichangefaceimgmod.TransitionStatus(nil, id, aichangefaceimgmod.Processing, aichangefaceimgmod.StatusSubmit, bson.M{ "updateAct": autoOperator, "remark": "已自动提交", }) if err != nil || !transitioned { return err } notifyURL, notifyErr := appg.AICallbackURL("/api/web/admin/ai/change_face_img/callback") if notifyErr != nil || order.OriginPic == "" || appg.Conf.URL.AiServer == "" { return refundImage(id, "自动提交参数不完整") } req := changeFaceRequest{ AppID: int(commod.KFK_APPID), OrderNumApp: id.Hex(), Title: order.ModTitle, SourceImageURL: order.OriginPic, NotifyURL: notifyURL, } if err := postChangeFace(req); err != nil { log.Error("AI图片换脸自动提交失败", log.Any("id", id.Hex()), log.E(err)) return refundImage(id, err.Error()) } return nil } // SubmitUndress automatically approves and submits an AI undress order. func SubmitUndress(id primitive.ObjectID) error { order, err := aiUnDressmod.FindByID(nil, id) if err != nil { return err } transitioned, err := aiUnDressmod.TransitionStatus(nil, id, aiUnDressmod.Processing, aiUnDressmod.SubmitOrder, bson.M{ "updateAct": autoOperator, "remark": "已自动提交", }) if err != nil || !transitioned { return err } notifyURL, notifyErr := appg.AICallbackURL("/api/web/admin/ai/undress/callback") if notifyErr != nil || len(order.OriginPics) == 0 || appg.Conf.URL.AiUndressServer == "" { return refundUndress(id, "自动提交参数不完整") } req := aiUnDressmod.AiUndressOrderReq{ AppId: int(commod.KFK_APPID), FileUrl: order.OriginPics, UserId: strconv.FormatUint(order.UID, 10), AppOrderNum: id.Hex(), NotifyUrl: notifyURL, } var resp aiUnDressmod.AiUndressOrderResp statusCode, err := httputil.DefaultClientPostJsonWithResp( &resp, common.BindUrl(appg.Conf.URL.AiUndressServer, "/api/undress/prd/create_job"), nil, req, ) if err != nil || statusCode != 200 { if err == nil { err = fmt.Errorf("third-party HTTP status %d", statusCode) } log.Error("AI脱衣自动提交失败", log.Any("id", id.Hex()), log.E(err)) return refundUndress(id, err.Error()) } return nil } func postChangeFace(req changeFaceRequest) error { var resp changeFaceResponse statusCode, err := httputil.DefaultClientPostJsonWithResp( &resp, common.BindUrl(appg.Conf.URL.AiServer, "/api/changeface/prd/changeFaceByVideo"), nil, req, ) if err != nil { return err } if statusCode != 200 { return fmt.Errorf("third-party HTTP status %d", statusCode) } if resp.Code == 40000 { return errors.New("third-party template is unavailable") } return nil } func refundVideo(id primitive.ObjectID, remark string) error { return appg.VideoDB.Trans(func(tool *db.MongoTool) error { order, err := aichangefacemod.FindByID(tool, id) if err != nil { return err } transitioned, err := aichangefacemod.TransitionStatus(tool, id, aichangefacemod.StatusSubmit, aichangefacemod.StatusRefund, bson.M{ "operator": autoOperator, "remark": remark, }) if err != nil || !transitioned { return err } if len(order.Discount) > 0 { if _, err = backpackmod.UnuseManyGoods(tool, order.Discount); err != nil { return err } } if order.Coin == 0 && order.IncomeCoin == 0 { return nil } wallet, err := walletmod.ReturnAmountAndIncome(tool, -order.Coin, -order.IncomeCoin, order.Uid) if err != nil { return err } totalCoin := order.Coin + order.IncomeCoin return txnmod.InsertTransactionLog(tool, &txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: order.Uid, Amount: totalCoin, ActualAmount: float64(totalCoin), TranType: txnmod.AiChangefaceDebitGoldReturn.Key(), TranTypeInt: int64(txnmod.AiChangefaceDebitGoldReturn), Desc: fmt.Sprintf("AI换脸金币退返-%d", totalCoin), RealAmount: walletmod.GetRealAmount(wallet), }) }) } func refundImage(id primitive.ObjectID, remark string) error { return appg.VideoDB.Trans(func(tool *db.MongoTool) error { order, err := aichangefaceimgmod.FindByID(tool, id) if err != nil { return err } transitioned, err := aichangefaceimgmod.TransitionStatus(tool, id, aichangefaceimgmod.StatusSubmit, aichangefaceimgmod.REFUND, bson.M{ "updateAct": autoOperator, "remark": remark, }) if err != nil || !transitioned { return err } return refundImageBalance(tool, order) }) } func refundImageBalance(tool *db.MongoTool, order *aichangefaceimgmod.AiChangeFaceImg) error { logs := make([]txnmod.TransactionLog, 0, 3) if order.Count > 0 || order.IsFreeTimes { count := order.Count if count == 0 { count = 1 } wallet, err := walletmod.DebitAiFreeTimes(tool, -count, order.UID) if err != nil { return err } logs = append(logs, txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: order.UID, Amount: count, ActualAmount: float64(count), TranType: txnmod.AiChangeFaceImgDebitFreeTimesReturn.Key(), TranTypeInt: int64(txnmod.AiChangeFaceImgDebitFreeTimesReturn), Desc: fmt.Sprintf("AI图片换脸免费次数退返[%d次]", count), RealAmount: walletmod.GetRealAmount(wallet), }) } if order.DebitAmountCoin > 0 || order.DebitIncomeCoin > 0 { wallet, err := walletmod.ReturnAmountAndIncome(tool, -order.DebitAmountCoin, -order.DebitIncomeCoin, order.UID) if err != nil { return err } if order.DebitIncomeCoin > 0 { logs = append(logs, txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: order.UID, Amount: order.DebitIncomeCoin, ActualAmount: float64(order.DebitIncomeCoin), TranType: txnmod.AiChangeFaceImgReturnIncomeGold.Key(), TranTypeInt: int64(txnmod.AiChangeFaceImgReturnIncomeGold), Desc: fmt.Sprintf("AI图片换脸收益金币退返-%d", order.DebitIncomeCoin), RealAmount: walletmod.GetRealAmount(wallet), }) } if order.DebitAmountCoin > 0 { logs = append(logs, txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: order.UID, Amount: order.DebitAmountCoin, ActualAmount: float64(order.DebitAmountCoin), TranType: txnmod.AiChangeFaceImgReturnGold.Key(), TranTypeInt: int64(txnmod.AiChangeFaceImgReturnGold), Desc: fmt.Sprintf("AI图片换脸金币退返-%d", order.DebitAmountCoin), RealAmount: walletmod.GetRealAmount(wallet), }) } } if len(logs) == 0 { return nil } return txnmod.InsertManyTransactionLog(tool, logs) } func refundUndress(id primitive.ObjectID, remark string) error { return appg.VideoDB.Trans(func(tool *db.MongoTool) error { order, err := aiUnDressmod.FindByID(tool, id) if err != nil { return err } transitioned, err := aiUnDressmod.TransitionStatus(tool, id, aiUnDressmod.SubmitOrder, aiUnDressmod.REFUND, bson.M{ "updateAct": autoOperator, "remark": remark, }) if err != nil || !transitioned { return err } return refundUndressBalance(tool, order) }) } func refundUndressBalance(tool *db.MongoTool, order *aiUnDressmod.AiUnDress) error { logs := make([]txnmod.TransactionLog, 0, 3) if order.Count > 0 || order.IsFreeTimes { count := order.Count if count == 0 { count = 1 } wallet, err := walletmod.DebitAiFreeTimes(tool, -count, order.UID) if err != nil { return err } logs = append(logs, txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: order.UID, Amount: count, ActualAmount: float64(count), TranType: txnmod.AiUndressDebitFreeTimesReturn.Key(), TranTypeInt: int64(txnmod.AiUndressDebitFreeTimesReturn), Desc: fmt.Sprintf("AI脱衣免费次数退返[%d次]", count), RealAmount: walletmod.GetRealAmount(wallet), }) } debitAmount := order.DebitAmountCoin debitIncome := order.DebitIncomeCoin if !order.IsFreeTimes && debitAmount == 0 && debitIncome == 0 { debitAmount = order.Coin } if debitAmount > 0 || debitIncome > 0 { wallet, err := walletmod.ReturnAmountAndIncome(tool, -debitAmount, -debitIncome, order.UID) if err != nil { return err } if debitIncome > 0 { logs = append(logs, txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: order.UID, Amount: debitIncome, ActualAmount: float64(debitIncome), TranType: txnmod.AiUndressDebitIncomeGoldReturn.Key(), TranTypeInt: int64(txnmod.AiUndressDebitIncomeGoldReturn), Desc: fmt.Sprintf("AI脱衣收益金币退返-%d", debitIncome), RealAmount: walletmod.GetRealAmount(wallet), }) } if debitAmount > 0 { logs = append(logs, txnmod.TransactionLog{ TransNo: primitive.NewObjectID(), UID: order.UID, Amount: debitAmount, ActualAmount: float64(debitAmount), TranType: txnmod.AiUndressDebitGoldReturn.Key(), TranTypeInt: int64(txnmod.AiUndressDebitGoldReturn), Desc: fmt.Sprintf("AI脱衣金币退返-%d", debitAmount), RealAmount: walletmod.GetRealAmount(wallet), }) } } if len(logs) == 0 { return nil } return txnmod.InsertManyTransactionLog(tool, logs) }