| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
- // SPDX-License-Identifier: Apache-2.0
- package processor // import "miniflux.app/v2/internal/reader/processor"
- import (
- "encoding/json"
- "fmt"
- "log/slog"
- "regexp"
- "strings"
- "miniflux.app/v2/internal/config"
- "miniflux.app/v2/internal/model"
- "miniflux.app/v2/internal/proxyrotator"
- "miniflux.app/v2/internal/reader/fetcher"
- )
- var (
- bilibiliVideoIdRegex = regexp.MustCompile(`/video/(?:av(\d+)|BV([a-zA-Z0-9]+))`)
- )
- func shouldFetchBilibiliWatchTime(entry *model.Entry) bool {
- if !config.Opts.FetchBilibiliWatchTime() {
- return false
- }
- return strings.Contains(entry.URL, "bilibili.com/video/")
- }
- func extractBilibiliVideoID(websiteURL string) (string, string, error) {
- matches := bilibiliVideoIdRegex.FindStringSubmatch(websiteURL)
- if matches == nil {
- return "", "", fmt.Errorf("no video ID found in URL: %s", websiteURL)
- }
- if matches[1] != "" {
- return "aid", matches[1], nil
- }
- if matches[2] != "" {
- return "bvid", matches[2], nil
- }
- return "", "", fmt.Errorf("unexpected regex match result for URL: %s", websiteURL)
- }
- func fetchBilibiliWatchTime(websiteURL string) (int, error) {
- requestBuilder := fetcher.NewRequestBuilder()
- requestBuilder.WithTimeout(config.Opts.HTTPClientTimeout())
- requestBuilder.WithProxyRotator(proxyrotator.ProxyRotatorInstance)
- idType, videoID, extractErr := extractBilibiliVideoID(websiteURL)
- if extractErr != nil {
- return 0, extractErr
- }
- bilibiliApiURL := "https://api.bilibili.com/x/web-interface/view?" + idType + "=" + videoID
- responseHandler := fetcher.NewResponseHandler(requestBuilder.ExecuteRequest(bilibiliApiURL))
- defer responseHandler.Close()
- if localizedError := responseHandler.LocalizedError(); localizedError != nil {
- slog.Warn("Unable to fetch Bilibili API",
- slog.String("website_url", websiteURL),
- slog.String("api_url", bilibiliApiURL),
- slog.Any("error", localizedError.Error()))
- return 0, localizedError.Error()
- }
- var result map[string]any
- doc := json.NewDecoder(responseHandler.Body(config.Opts.HTTPClientMaxBodySize()))
- if docErr := doc.Decode(&result); docErr != nil {
- return 0, fmt.Errorf("failed to decode API response: %v", docErr)
- }
- if code, ok := result["code"].(float64); !ok || code != 0 {
- return 0, fmt.Errorf("API returned error code: %v", result["code"])
- }
- data, ok := result["data"].(map[string]any)
- if !ok {
- return 0, fmt.Errorf("data field not found or not an object")
- }
- duration, ok := data["duration"].(float64)
- if !ok {
- return 0, fmt.Errorf("duration not found or not a number")
- }
- intDuration := int(duration)
- durationMin := intDuration / 60
- if intDuration%60 != 0 {
- durationMin++
- }
- return durationMin, nil
- }
|