Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+109
View File
@@ -0,0 +1,109 @@
package dramaser
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"strings"
"time"
"91porn-server/app/appg"
"91porn-server/common/db"
"91porn-server/models/v/media_buy_record_mod"
"91porn-server/models/v/mediacontentmod"
"91porn-server/models/v/mediamod"
"91porn-server/models/v/usermod"
"91porn-server/models/v/walletmod"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var (
ErrDramaEntitlementRequired = errors.New("active drama entitlement is required")
ErrDownloadResourceInvalid = errors.New("short drama episode is unavailable")
)
const downloadTicketTTL = 6 * time.Hour
type DownloadAuthorizeRequest struct {
MediaID primitive.ObjectID `json:"mediaId" binding:"required"`
ContentID primitive.ObjectID `json:"contentId" binding:"required"`
}
type DownloadAuthorizeResponse struct {
MediaID primitive.ObjectID `json:"mediaId"`
ContentID primitive.ObjectID `json:"contentId"`
EpisodeNumber int `json:"episodeNumber"`
Name string `json:"name"`
Cover string `json:"cover"`
DownloadURL string `json:"downloadUrl"`
H265DownloadURL string `json:"h265DownloadUrl"`
MediaSize int64 `json:"mediaSize"`
ExpiresAt time.Time `json:"expiresAt"`
RemainingDownloadCount int64 `json:"remainingDownloadCount"`
Charged bool `json:"charged"`
}
func AuthorizeDownload(ctx context.Context, uid uint64, requestID string, req DownloadAuthorizeRequest, now time.Time) (DownloadAuthorizeResponse, error) {
requestID = strings.TrimSpace(requestID)
if len(requestID) < 1 || len(requestID) > 128 {
return DownloadAuthorizeResponse{}, errors.New("X-Request-ID长度必须为1到128")
}
media, err := mediamod.GetInfo(req.MediaID)
if err != nil || media.MediaType != mediamod.MediaTypeDrama || media.Status != 1 || media.IsDelete {
return DownloadAuthorizeResponse{}, ErrDownloadResourceInvalid
}
content, err := mediacontentmod.GetInfo(req.ContentID)
if err != nil || content.MediaID != req.MediaID || content.MediaType != mediamod.MediaTypeDrama || content.Status != 1 || !content.IsActive || content.IsDelete {
return DownloadAuthorizeResponse{}, ErrDownloadResourceInvalid
}
requestKey := downloadRequestKey(uid, requestID)
fingerprint := req.MediaID.Hex() + ":" + req.ContentID.Hex()
var wallet *walletmod.Wallet
var charged bool
for attempt := 0; attempt < 3; attempt++ {
err = appg.VideoDB.TransCtx(ctx, func(t *db.MongoTool) error {
user, userErr := usermod.FindUserByUIDTrans(t, uid)
if userErr != nil {
return userErr
}
bought := false
if user != nil && !user.HasLocked && !user.DramaExpire.After(now) {
bought, userErr = media_buy_record_mod.IsBuyWithTool(t, uid, req.MediaID, req.ContentID)
if userErr != nil {
return userErr
}
}
if !canAuthorizeDramaDownload(user, bought, now) {
return ErrDramaEntitlementRequired
}
wallet, charged, userErr = walletmod.AuthorizeDramaDownload(t, uid, requestKey, fingerprint, now)
return userErr
})
if !errors.Is(err, walletmod.ErrDownloadAuthorizationRace) {
break
}
}
if err != nil {
return DownloadAuthorizeResponse{}, err
}
name := strings.TrimSpace(content.Name)
if name == "" {
name = fmt.Sprintf("第%d集", content.EpisodeNumber)
}
return DownloadAuthorizeResponse{
MediaID: req.MediaID, ContentID: req.ContentID, EpisodeNumber: content.EpisodeNumber,
Name: name, Cover: content.Cover, DownloadURL: content.VideoUrl,
H265DownloadURL: appg.H265URLForApp(content.H265Url), MediaSize: content.MediaSize,
ExpiresAt: now.Add(downloadTicketTTL), RemainingDownloadCount: wallet.DownloadCount, Charged: charged,
}, nil
}
func downloadRequestKey(uid uint64, requestID string) string {
return fmt.Sprintf("%x", sha256.Sum256([]byte(fmt.Sprintf("%d:%s", uid, requestID))))
}
func canAuthorizeDramaDownload(user *usermod.User, bought bool, now time.Time) bool {
return user != nil && !user.HasLocked && (user.DramaExpire.After(now) || bought)
}