fix(115): resolve direct link via app/chrome/downurl + bind CDN link to client UA

115 deprecated the plain web /files/download endpoint (ordinary cookies no
longer get file_url), breaking 302 playback with 'no file_url'. Switch
Resolve() to the current proapi.115.com/app/chrome/downurl endpoint, which
uses 115's m115 (RSA+XOR) request/response encryption (vendored from the
MIT-licensed SheltonZhu/115driver).

115 CDN links are bound to the User-Agent used to request them, so resolve
with the playback client's own UA (plumbed from the play handler) — the host
can then issue a pure 302 the client fetches directly, preserving true offload.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
soldosluka857
2026-05-30 12:06:27 +00:00
committed by Shuke
parent 74271081f4
commit 3fa6932c99
5 changed files with 355 additions and 26 deletions
+34 -2
View File
@@ -221,14 +221,46 @@ func (s *StorageConfigService) CloudList(ctx context.Context, typ, dirID string)
}
// CloudResolve resolves a cloud file reference to a direct link.
func (s *StorageConfigService) CloudResolve(ctx context.Context, typ, fileRef string) (*cloud.DirectLink, error) {
p, err := s.CloudProvider(ctx, typ)
//
// clientUA is the User-Agent of the playback client that will follow the 302
// redirect. 115/夸克 CDN links are bound to the UA used to request them, so we
// resolve with the client's own UA — that way the pure 302 the host issues
// points at a link the client can fetch directly (true offload). When clientUA
// is empty the provider's default UA is used.
func (s *StorageConfigService) CloudResolve(ctx context.Context, typ, fileRef, clientUA string) (*cloud.DirectLink, error) {
p, err := s.cloudProviderWithUA(ctx, typ, clientUA)
if err != nil {
return nil, err
}
return p.Resolve(ctx, fileRef)
}
// cloudProviderWithUA builds a provider, overriding the request UA when a
// non-empty clientUA is supplied.
func (s *StorageConfigService) cloudProviderWithUA(ctx context.Context, typ, clientUA string) (cloud.Provider, error) {
if !cloud.IsCloudType(typ) {
return nil, fmt.Errorf("not a cloud provider: %q", typ)
}
view, err := s.Get(ctx, typ)
if err != nil {
return nil, err
}
if view == nil {
return nil, fmt.Errorf("%s storage not configured", typ)
}
cfg := view.Config
if strings.TrimSpace(clientUA) != "" {
// Copy so we never mutate the cached view config.
cp := make(map[string]any, len(cfg)+1)
for k, v := range cfg {
cp[k] = v
}
cp["ua"] = clientUA
cfg = cp
}
return cloud.New(typ, cfg, s.client)
}
// cloudLibraryName maps a provider type to a friendly Chinese library name.
func cloudLibraryName(typ string) string {
switch typ {