redgifs: new extractor
This commit is contained in:
parent
f8b1cb6d48
commit
ebba6a8835
5 changed files with 244 additions and 5 deletions
|
@ -55,11 +55,9 @@ func URLHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
|
|||
}
|
||||
|
||||
func URLFilter(msg *gotgbot.Message) bool {
|
||||
return message.Text(msg) && !message.Command(msg) && containsURL(msg)
|
||||
}
|
||||
|
||||
func containsURL(msg *gotgbot.Message) bool {
|
||||
return message.Entity("url")(msg)
|
||||
return message.Text(msg) &&
|
||||
!message.Command(msg) &&
|
||||
message.Entity("url")(msg)
|
||||
}
|
||||
|
||||
func getMessageURL(msg *gotgbot.Message) string {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"govd/ext/ninegag"
|
||||
"govd/ext/pinterest"
|
||||
"govd/ext/reddit"
|
||||
"govd/ext/redgifs"
|
||||
"govd/ext/tiktok"
|
||||
"govd/ext/twitter"
|
||||
"govd/models"
|
||||
|
@ -23,5 +24,6 @@ var List = []*models.Extractor{
|
|||
reddit.Extractor,
|
||||
reddit.ShortExtractor,
|
||||
ninegag.Extractor,
|
||||
redgifs.Extractor,
|
||||
// todo: add every ext lol
|
||||
}
|
||||
|
|
150
ext/redgifs/main.go
Normal file
150
ext/redgifs/main.go
Normal file
|
@ -0,0 +1,150 @@
|
|||
package redgifs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"govd/enums"
|
||||
"govd/models"
|
||||
"govd/util"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
const (
|
||||
baseAPI = "https://api.redgifs.com/v2/"
|
||||
tokenEndpoint = baseAPI + "auth/temporary"
|
||||
videoEndpoint = baseAPI + "gifs/"
|
||||
)
|
||||
|
||||
var (
|
||||
session = util.GetHTTPSession()
|
||||
|
||||
baseApiHeaders = map[string]string{
|
||||
"referer": "https://www.redgifs.com/",
|
||||
"origin": "https://www.redgifs.com",
|
||||
"content-type": "application/json",
|
||||
}
|
||||
)
|
||||
|
||||
var Extractor = &models.Extractor{
|
||||
Name: "RedGifs",
|
||||
CodeName: "redgifs",
|
||||
Type: enums.ExtractorTypeSingle,
|
||||
Category: enums.ExtractorCategorySocial,
|
||||
URLPattern: regexp.MustCompile(`https?://(?:(?:www\.)?redgifs\.com/(?:watch|ifr)/|thumbs2\.redgifs\.com/)(?P<id>[^-/?#\.]+)`),
|
||||
Host: []string{
|
||||
"redgifs.com",
|
||||
"thumbs2.redgifs.com",
|
||||
},
|
||||
|
||||
Run: func(ctx *models.DownloadContext) (*models.ExtractorResponse, error) {
|
||||
mediaList, err := MediaListFromAPI(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get media: %w", err)
|
||||
}
|
||||
return &models.ExtractorResponse{
|
||||
MediaList: mediaList,
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
func MediaListFromAPI(ctx *models.DownloadContext) ([]*models.Media, error) {
|
||||
var mediaList []*models.Media
|
||||
|
||||
response, err := GetVideo(ctx.MatchedContentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get from api: %w", err)
|
||||
}
|
||||
gif := response.Gif
|
||||
media := ctx.Extractor.NewMedia(
|
||||
ctx.MatchedContentID,
|
||||
ctx.MatchedContentURL,
|
||||
)
|
||||
|
||||
if gif.Description != "" {
|
||||
media.SetCaption(gif.Description)
|
||||
}
|
||||
media.NSFW = true // always nsfw
|
||||
|
||||
if gif.Urls.Sd != "" {
|
||||
media.AddFormat(&models.MediaFormat{
|
||||
FormatID: "sd",
|
||||
Type: enums.MediaTypeVideo,
|
||||
URL: []string{gif.Urls.Sd},
|
||||
VideoCodec: enums.MediaCodecAVC,
|
||||
Width: int64(gif.Width / 2),
|
||||
Height: int64(gif.Height / 2),
|
||||
})
|
||||
}
|
||||
|
||||
if gif.Urls.Hd != "" {
|
||||
media.AddFormat(&models.MediaFormat{
|
||||
FormatID: "hd",
|
||||
Type: enums.MediaTypeVideo,
|
||||
URL: []string{gif.Urls.Hd},
|
||||
VideoCodec: enums.MediaCodecAVC,
|
||||
Width: int64(gif.Width),
|
||||
Height: int64(gif.Height),
|
||||
})
|
||||
}
|
||||
|
||||
if gif.Urls.Poster != "" {
|
||||
thumbnails := []string{gif.Urls.Poster}
|
||||
if gif.Urls.Thumbnail != "" {
|
||||
thumbnails = append(thumbnails, gif.Urls.Thumbnail)
|
||||
}
|
||||
|
||||
for _, format := range media.Formats {
|
||||
format.Thumbnail = thumbnails
|
||||
format.Duration = int64(gif.Duration)
|
||||
}
|
||||
}
|
||||
|
||||
if gif.HasAudio {
|
||||
for _, format := range media.Formats {
|
||||
format.AudioCodec = enums.MediaCodecAAC
|
||||
}
|
||||
}
|
||||
|
||||
if len(media.Formats) > 0 {
|
||||
mediaList = append(mediaList, media)
|
||||
}
|
||||
|
||||
return mediaList, nil
|
||||
}
|
||||
|
||||
func GetVideo(videoID string) (*Response, error) {
|
||||
url := videoEndpoint + videoID + "?views=true"
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
token, err := GetAccessToken()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get access token: %w", err)
|
||||
}
|
||||
req.Header.Set("authorization", "Bearer "+token.AccessToken)
|
||||
req.Header.Set("user-agent", token.Agent)
|
||||
req.Header.Set("x-customheader", "https://www.redgifs.com/watch/"+videoID)
|
||||
for k, v := range baseApiHeaders {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
res, err := session.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("failed to get video: %s", res.Status)
|
||||
}
|
||||
var response Response
|
||||
err = sonic.ConfigFastest.NewDecoder(res.Body).Decode(&response)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
if response.Gif == nil {
|
||||
return nil, fmt.Errorf("failed to get video: %s", res.Status)
|
||||
}
|
||||
return &response, nil
|
||||
}
|
44
ext/redgifs/models.go
Normal file
44
ext/redgifs/models.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package redgifs
|
||||
|
||||
type Response struct {
|
||||
Gif *Gif `json:"gif"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
AccessToken string `json:"token"`
|
||||
Agent string `json:"agent"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
}
|
||||
|
||||
type Urls struct {
|
||||
Silent string `json:"silent"`
|
||||
Sd string `json:"sd"`
|
||||
Hd string `json:"hd"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
HTML string `json:"html"`
|
||||
Poster string `json:"poster"`
|
||||
}
|
||||
|
||||
type Gif struct {
|
||||
AvgColor string `json:"avgColor"`
|
||||
CreateDate int `json:"createDate"`
|
||||
Description string `json:"description"`
|
||||
Duration float64 `json:"duration"`
|
||||
HasAudio bool `json:"hasAudio"`
|
||||
Height int `json:"height"`
|
||||
HideHome bool `json:"hideHome"`
|
||||
HideTrending bool `json:"hideTrending"`
|
||||
Hls bool `json:"hls"`
|
||||
ID string `json:"id"`
|
||||
Likes int `json:"likes"`
|
||||
Niches []string `json:"niches"`
|
||||
Published bool `json:"published"`
|
||||
Type int `json:"type"`
|
||||
Sexuality []string `json:"sexuality"`
|
||||
Tags []string `json:"tags"`
|
||||
Urls Urls `json:"urls"`
|
||||
UserName string `json:"userName"`
|
||||
Verified bool `json:"verified"`
|
||||
Views int `json:"views"`
|
||||
Width int `json:"width"`
|
||||
}
|
45
ext/redgifs/util.go
Normal file
45
ext/redgifs/util.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package redgifs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"govd/util"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
var accessToken *Token
|
||||
|
||||
func GetAccessToken() (*Token, error) {
|
||||
if accessToken == nil || time.Now().Unix() >= accessToken.ExpiresIn {
|
||||
if err := RefreshAccessToken(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return accessToken, nil
|
||||
}
|
||||
|
||||
func RefreshAccessToken() error {
|
||||
req, err := http.NewRequest(http.MethodGet, tokenEndpoint, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
req.Header.Set("User-Agent", util.ChromeUA)
|
||||
res, err := session.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("failed to get access token: %s", res.Status)
|
||||
}
|
||||
var token Token
|
||||
err = sonic.ConfigFastest.NewDecoder(res.Body).Decode(&token)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode response: %w", err)
|
||||
}
|
||||
token.ExpiresIn = time.Now().Add(23 * time.Hour).Unix()
|
||||
accessToken = &token
|
||||
return nil
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue