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 {
|
func URLFilter(msg *gotgbot.Message) bool {
|
||||||
return message.Text(msg) && !message.Command(msg) && containsURL(msg)
|
return message.Text(msg) &&
|
||||||
}
|
!message.Command(msg) &&
|
||||||
|
message.Entity("url")(msg)
|
||||||
func containsURL(msg *gotgbot.Message) bool {
|
|
||||||
return message.Entity("url")(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMessageURL(msg *gotgbot.Message) string {
|
func getMessageURL(msg *gotgbot.Message) string {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"govd/ext/ninegag"
|
"govd/ext/ninegag"
|
||||||
"govd/ext/pinterest"
|
"govd/ext/pinterest"
|
||||||
"govd/ext/reddit"
|
"govd/ext/reddit"
|
||||||
|
"govd/ext/redgifs"
|
||||||
"govd/ext/tiktok"
|
"govd/ext/tiktok"
|
||||||
"govd/ext/twitter"
|
"govd/ext/twitter"
|
||||||
"govd/models"
|
"govd/models"
|
||||||
|
@ -23,5 +24,6 @@ var List = []*models.Extractor{
|
||||||
reddit.Extractor,
|
reddit.Extractor,
|
||||||
reddit.ShortExtractor,
|
reddit.ShortExtractor,
|
||||||
ninegag.Extractor,
|
ninegag.Extractor,
|
||||||
|
redgifs.Extractor,
|
||||||
// todo: add every ext lol
|
// 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