Skip to content

Commit

Permalink
Use Odysee video duration as read time
Browse files Browse the repository at this point in the history
This feature works by scraping the Odysee website.

To enable it, set the FETCH_ODYSEE_WATCH_TIME environment variable to
1.
  • Loading branch information
kmein committed Mar 21, 2023
1 parent 6eed037 commit 8a0a08c
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 0 deletions.
10 changes: 10 additions & 0 deletions config/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const (
defaultProxyMediaTypes = "image"
defaultProxyUrl = ""
defaultFetchYouTubeWatchTime = false
defaultFetchOdyseeWatchTime = false
defaultCreateAdmin = false
defaultAdminUsername = ""
defaultAdminPassword = ""
Expand Down Expand Up @@ -127,6 +128,7 @@ type Options struct {
proxyMediaTypes []string
proxyUrl string
fetchYouTubeWatchTime bool
fetchOdyseeWatchTime bool
oauth2UserCreationAllowed bool
oauth2ClientID string
oauth2ClientSecret string
Expand Down Expand Up @@ -196,6 +198,7 @@ func NewOptions() *Options {
proxyMediaTypes: []string{defaultProxyMediaTypes},
proxyUrl: defaultProxyUrl,
fetchYouTubeWatchTime: defaultFetchYouTubeWatchTime,
fetchOdyseeWatchTime: defaultFetchOdyseeWatchTime,
oauth2UserCreationAllowed: defaultOAuth2UserCreation,
oauth2ClientID: defaultOAuth2ClientID,
oauth2ClientSecret: defaultOAuth2ClientSecret,
Expand Down Expand Up @@ -429,6 +432,12 @@ func (o *Options) FetchYouTubeWatchTime() bool {
return o.fetchYouTubeWatchTime
}

// FetchOdyseeWatchTime returns true if the Odysee video duration
// should be fetched and used as a reading time.
func (o *Options) FetchOdyseeWatchTime() bool {
return o.fetchOdyseeWatchTime
}

// ProxyOption returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy.
func (o *Options) ProxyOption() string {
return o.proxyOption
Expand Down Expand Up @@ -574,6 +583,7 @@ func (o *Options) SortedOptions(redactSecret bool) []*Option {
"DISABLE_SCHEDULER_SERVICE": !o.schedulerService,
"DISABLE_HTTP_SERVICE": !o.httpService,
"FETCH_YOUTUBE_WATCH_TIME": o.fetchYouTubeWatchTime,
"FETCH_ODYSEE_WATCH_TIME": o.fetchOdyseeWatchTime,
"HTTPS": o.HTTPS,
"HTTP_CLIENT_MAX_BODY_SIZE": o.httpClientMaxBodySize,
"HTTP_CLIENT_PROXY": o.httpClientProxy,
Expand Down
36 changes: 36 additions & 0 deletions reader/processor/processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (

var (
youtubeRegex = regexp.MustCompile(`youtube\.com/watch\?v=(.*)`)
odyseeRegex = regexp.MustCompile(`odysee\.com/@(.*)/(.*)`)
iso8601Regex = regexp.MustCompile(`^P((?P<year>\d+)Y)?((?P<month>\d+)M)?((?P<week>\d+)W)?((?P<day>\d+)D)?(T((?P<hour>\d+)H)?((?P<minute>\d+)M)?((?P<second>\d+)S)?)?$`)
customReplaceRuleRegex = regexp.MustCompile(`rewrite\("(.*)"\|"(.*)"\)`)
)
Expand Down Expand Up @@ -220,6 +221,15 @@ func shouldFetchYouTubeWatchTime(entry *model.Entry) bool {
return urlMatchesYouTubePattern
}

func shouldFetchOdyseeWatchTime(entry *model.Entry) bool {
if !config.Opts.FetchOdyseeWatchTime() {
return false
}
matches := odyseeRegex.FindStringSubmatch(entry.URL)
urlMatchesOdyseePattern := len(matches) == 3
return urlMatchesOdyseePattern
}

func fetchYouTubeWatchTime(url string) (int, error) {
clt := client.NewClientWithConfig(url, config.Opts)
response, browserErr := browser.Exec(clt)
Expand All @@ -245,6 +255,32 @@ func fetchYouTubeWatchTime(url string) (int, error) {
return int(dur.Minutes()), nil
}

func fetchOdyseeWatchTime(url string) (int, error) {
clt := client.NewClientWithConfig(url, config.Opts)
response, browserErr := browser.Exec(clt)
if browserErr != nil {
return 0, browserErr
}

doc, docErr := goquery.NewDocumentFromReader(response.Body)
if docErr != nil {
return 0, docErr
}

durs, exists := doc.Find(`meta[property="og:video:duration"]`).First().Attr("content")
// durs contains video watch time in seconds
if !exists {
return 0, errors.New("duration has not found")
}

dur, err := strconv.ParseInt(durs, 10, 64)
if err != nil {
return 0, fmt.Errorf("unable to parse duration %s: %v", durs, err)
}

return int(dur / 60), nil
}

// parseISO8601 parses an ISO 8601 duration string.
func parseISO8601(from string) (time.Duration, error) {
var match []string
Expand Down

0 comments on commit 8a0a08c

Please sign in to comment.