yikart/AiToEarn · error · BilibiliPlatformException

ChannelPlatformMediaProcessingFailed

ChannelPlatformMediaProcessingFailed

Error message

ResponseCode.ChannelPlatformMediaProcessingFailed

What it means

Bilibili's open-platform cover upload endpoint (POST /arcopen/fn/archive/cover/upload) completed at the HTTP level, but the response body contained no `data.url`. The service treats a cover upload without a returned URL as a platform-side media processing failure and throws ChannelPlatformMediaProcessingFailed, blaming the Bilibili platform (PlatformErrorCauseType.Platform) rather than the caller.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/bilibili/bilibili.service.ts:354

  async uploadCover(
    accessToken: string,
    coverUrl: string,
  ): Promise<string> {
    const coverBuffer = await this.mediaService.getBuffer({
      platform: AccountType.Bilibili,
      endpoint: 'downloadCover',
      url: coverUrl,
    })

    const formData = new FormData()
    formData.append('file', new Blob([new Uint8Array(coverBuffer)]), this.resolveFileName(coverUrl, 'cover.jpg'))

    const response = await this.platformHttp.post<BilibiliApiResponse<{ url: string }>>(`${this.openBaseUrl}/arcopen/fn/archive/cover/upload`, formData, {
      headers: this.buildSignedHeaders(accessToken, '', 'multipart/form-data'),
    })

    if (!response.data.data?.url) {
      throw new BilibiliPlatformException({
        code: ResponseCode.ChannelPlatformMediaProcessingFailed,
        category: PlatformErrorCategory.MediaProcessingFailed,
        context: { endpoint: 'POST /arcopen/fn/archive/cover/upload' },
        cause: { type: PlatformErrorCauseType.Platform },
      })
    }

    return response.data.data.url
  }

  async getArchiveDetail(
    accessToken: string,
    bvid: string,
  ): Promise<BilibiliArchiveDetail> {
    const data = await this.signedRequest<{
      resource_id: string
      title: string
      desc: string

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Log the full response body of the cover upload call to see Bilibili's in-body error code/message (a 200 status can still carry an error payload).
  2. Verify the cover file is a supported format (jpg/png) and within Bilibili's size limits before uploading.
  3. Check the access token is valid and has archive-write scope; refresh it if expired and retry the upload.
  4. Compare the actual response shape against this handler — if Bilibili moved the `url` field, update the extraction in bilibili.service.ts.

Example fix

// before
const response = await this.platformHttp.post<BilibiliApiResponse<{ url: string }>>(`${this.openBaseUrl}/arcopen/fn/archive/cover/upload`, formData, { headers: this.buildSignedHeaders(accessToken, '', 'multipart/form-data') })
if (!response.data.data?.url) {
  throw new BilibiliPlatformException({ code: ResponseCode.ChannelPlatformMediaProcessingFailed, context: { endpoint: 'POST /arcopen/fn/archive/cover/upload' }, cause: { type: PlatformErrorCauseType.Platform } })
}
// after
const response = await this.platformHttp.post<BilibiliApiResponse<{ url: string }>>(`${this.openBaseUrl}/arcopen/fn/archive/cover/upload`, formData, { headers: this.buildSignedHeaders(accessToken, '', 'multipart/form-data') })
this.logger.warn({ body: response.data }, 'bilibili cover upload raw response') // surface in-body error code
if (!response.data.data?.url) {
  throw new BilibiliPlatformException({ code: ResponseCode.ChannelPlatformMediaProcessingFailed, context: { endpoint: 'POST /arcopen/fn/archive/cover/upload', body: response.data }, cause: { type: PlatformErrorCauseType.Platform } })
}
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight checks before cover upload
function validateCoverForBilibili(file: { mimetype: string; size: number }): string | null {
  const allowed = ['image/jpeg', 'image/png']
  if (!allowed.includes(file.mimetype)) return `unsupported format ${file.mimetype}; use jpg/png`
  if (file.size > 5 * 1024 * 1024) return 'cover exceeds Bilibili size limit'
  return null
}

Type guard

function hasUploadedCoverUrl(res: BilibiliApiResponse<{ url: string }>): res is BilibiliApiResponse<{ url: string }> & { data: { data: { url: string } } } {
  return typeof res?.data?.url === 'string' && res.data.url.length > 0
}

Try / catch

try {
  const coverUrl = await channels.cover(channelId, coverFile)
} catch (e) {
  if (e.code === 'ChannelPlatformMediaProcessingFailed') {
    logger.warn('bilibili rejected cover upload; check image format/size and token, then retry')
    // fall back to default cover or re-queue upload
  } else throw e
}

Prevention

When it happens

Trigger: Calling cover/uploadCover for a bilibili channel when the signed multipart POST succeeds but the JSON body is missing `data.data.url` — e.g. Bilibili returned an error code inside a 200 response, rejected the image silently, or changed the response shape.

Common situations: Cover image in an unsupported format/size (not jpg/png, oversized), expired or insufficiently scoped access_token causing an in-body error, Bilibili open API degradation, or an API version change that renames/moves the `url` field.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/058dfe71645206a5. Report an issue: GitHub.