diff --git a/cmd/yttopodcast/main.go b/cmd/yttopodcast/main.go index d716734..c601c01 100644 --- a/cmd/yttopodcast/main.go +++ b/cmd/yttopodcast/main.go @@ -10,8 +10,8 @@ import ( "time" "gitea.hbanafa.com/hesham/yttopodcast/bouncer" + "gitea.hbanafa.com/hesham/yttopodcast/dylinkprovider" "gitea.hbanafa.com/hesham/yttopodcast/feed" - "gitea.hbanafa.com/hesham/yttopodcast/ytlinkprov" ) var ( @@ -36,11 +36,12 @@ func main() { os.Exit(1) } - cache, err := ytlinkprov.NewCachedLinkProvider(time.Minute * time.Duration(*interval)) - if err != nil { - l.Println(err.Error()) - os.Exit(1) - } + // cache, err := ytlinkprov.NewCachedLinkProvider(time.Minute * time.Duration(*interval)) + // if err != nil { + // l.Println(err.Error()) + // os.Exit(1) + // } + cache := dylinkprovider.NewDynCacheExpLinkProv(&l) bouncer, err := bouncer.NewBouncerHTTPServer(context.Background(), *listenAddr, cache) if err != nil { diff --git a/dylinkprovider/dylinkprov.go b/dylinkprovider/dylinkprov.go new file mode 100644 index 0000000..af738c7 --- /dev/null +++ b/dylinkprovider/dylinkprov.go @@ -0,0 +1,140 @@ +package dylinkprovider + +import ( + "context" + "fmt" + "log" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + "gitea.hbanafa.com/hesham/yttopodcast/ytlinkprov" + "github.com/lrstanley/go-ytdlp" +) + +const Q_EXPIRE = "expire" + +type DynamicCacheExpLinkProvider struct { + cache map[string]url.URL + l *log.Logger + lock *sync.RWMutex +} + +var _ = (ytlinkprov.YtLinkProvider)((*DynamicCacheExpLinkProvider)(nil)) + +func NewDynCacheExpLinkProv(l *log.Logger) *DynamicCacheExpLinkProvider { + p := new(DynamicCacheExpLinkProvider) + p.l = l + p.lock = &sync.RWMutex{} + p.cache = make(map[string]url.URL) + ytdlp.MustInstall(context.Background(), &ytdlp.InstallOptions{}) + return p +} + +// GetLink implements ytlinkprov.YtLinkProvider. +func (d *DynamicCacheExpLinkProvider) GetLink(id string) (link string, err error) { + + + d.lock.RLock() + cl1, ok := d.cache[id] + d.lock.RUnlock() + if ok && !isExpired(cl1) && is200(cl1) { + d.l.Printf("[cache] hit on %s\n", id) + return cl1.String(), nil + } + + d.l.Printf("[cache] miss on %s\n", id) + newlink, err := getRemoteLink(id) + if err != nil { + return "", err + } + + newlinkurl, err := url.Parse(newlink) + if err != nil { + return "", err + } + + d.lock.Lock() + d.cache[id] = *newlinkurl + d.lock.Unlock() + + d.l.Printf("[cache] new entry for %s\n", id) + d.l.Printf("%d items in map\n", len(d.cache)) + return newlinkurl.String(), nil + +} + +func getRemoteLink(id string) (string, error) { + + var link string + + vidUrl := fmt.Sprintf("https://youtube.com/watch?v=%s", id) + ytCmd := ytdlp.New().ExtractAudio().GetURL() + ytRes, err := ytCmd.Run(context.Background(), vidUrl) + if err != nil { + return "", err + } + linkFirst := strings.Split(ytRes.Stdout, "\n")[0] + + /* Get the last link in a chain of 3XX codes*/ + resp, err := http.Get(linkFirst) + if err != nil { + return "", err + } + + if resp.StatusCode != http.StatusOK { + return linkFirst, nil + } + + link = resp.Request.URL.String() + return link, nil + +} + +func isExpired(link url.URL) bool { + + exp := link.Query().Get("expire") + if exp == "" { + return true + } + + tunixd, err := strconv.ParseInt(exp, 10, 64) + if err != nil { + return true + } + + tunix := time.Unix(tunixd, 0) + // If (tunix - now) is negative, the link is expired + delta := tunix.Sub(time.Now()) + if delta <= 0 { + return true + } + + // Still not expired but we check delta against duration + durd := 0.0 + + dur := link.Query().Get("dur") + if dur == "" { + return true + } + + durd, err = strconv.ParseFloat(exp, 64) + if err != nil { + return true + } + + if delta < time.Duration(durd) + time.Second * 2400 { + return true + } + + return false +} + +func is200(link url.URL) bool { + resp, _ := http.Get(link.String()) + return resp.StatusCode == 200 +} +