dylinkprov: link expire based provider:

- Depends on the "expire" query param in yt links
    - Usually is the best case for cache expire

Signed-off-by: HeshamTB <hishaminv@gmail.com>
This commit is contained in:
HeshamTB 2024-07-11 19:16:36 +03:00
parent 1fc423d08a
commit cc98100ab4
Signed by: Hesham
GPG Key ID: 74876157D199B09E
2 changed files with 147 additions and 6 deletions

View File

@ -10,8 +10,8 @@ import (
"time" "time"
"gitea.hbanafa.com/hesham/yttopodcast/bouncer" "gitea.hbanafa.com/hesham/yttopodcast/bouncer"
"gitea.hbanafa.com/hesham/yttopodcast/dylinkprovider"
"gitea.hbanafa.com/hesham/yttopodcast/feed" "gitea.hbanafa.com/hesham/yttopodcast/feed"
"gitea.hbanafa.com/hesham/yttopodcast/ytlinkprov"
) )
var ( var (
@ -36,11 +36,12 @@ func main() {
os.Exit(1) os.Exit(1)
} }
cache, err := ytlinkprov.NewCachedLinkProvider(time.Minute * time.Duration(*interval)) // cache, err := ytlinkprov.NewCachedLinkProvider(time.Minute * time.Duration(*interval))
if err != nil { // if err != nil {
l.Println(err.Error()) // l.Println(err.Error())
os.Exit(1) // os.Exit(1)
} // }
cache := dylinkprovider.NewDynCacheExpLinkProv(&l)
bouncer, err := bouncer.NewBouncerHTTPServer(context.Background(), *listenAddr, cache) bouncer, err := bouncer.NewBouncerHTTPServer(context.Background(), *listenAddr, cache)
if err != nil { if err != nil {

View File

@ -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
}