9gag: new extractor

This commit is contained in:
stefanodvx 2025-04-18 12:48:37 +02:00
parent 3b625c8b0a
commit 60365973d8
6 changed files with 286 additions and 1 deletions

132
ext/ninegag/main.go Normal file
View file

@ -0,0 +1,132 @@
package ninegag
import (
"fmt"
"net/http"
"regexp"
"govd/enums"
"govd/models"
"govd/util"
"github.com/bytedance/sonic"
)
const (
apiEndpoint = "https://9gag.com/v1/post"
postNotFound = "Post not found"
)
// 9gag gives 403 unless you use
// real browser TLS fingerprint
var httpSession = util.NewChromeClient()
var Extractor = &models.Extractor{
Name: "9GAG",
CodeName: "ninegag",
Type: enums.ExtractorTypeSingle,
Category: enums.ExtractorCategorySocial,
URLPattern: regexp.MustCompile(`https?://(?:www\.)?9gag\.com/gag/(?P<id>[^/?&#]+)`),
Host: []string{"9gag.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
contentID := ctx.MatchedContentID
contentURL := ctx.MatchedContentURL
postData, err := GetPostData(contentID)
if err != nil {
return nil, fmt.Errorf("failed to get post data: %w", err)
}
media := ctx.Extractor.NewMedia(contentID, contentURL)
media.SetCaption(postData.Title)
if postData.Nsfw == 1 {
media.NSFW = true
}
switch postData.Type {
case "Photo":
bestPhoto, err := FindBestPhoto(postData.Images)
if err != nil {
return nil, err
}
media.AddFormat(&models.MediaFormat{
FormatID: "photo",
Type: enums.MediaTypePhoto,
URL: []string{bestPhoto.URL},
Width: int64(bestPhoto.Width),
Height: int64(bestPhoto.Height),
})
case "Animated":
videoFormats, err := ParseVideoFormats(postData.Images)
if err != nil {
return nil, err
}
for _, format := range videoFormats {
media.AddFormat(format)
}
}
if len(media.Formats) > 0 {
mediaList = append(mediaList, media)
}
return mediaList, nil
}
func GetPostData(postID string) (*Post, error) {
url := apiEndpoint + "?id=" + postID
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("User-Agent", util.ChromeUA)
resp, err := httpSession.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to send request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("invalid status code: %d", resp.StatusCode)
}
var response Response
decoder := sonic.ConfigFastest.NewDecoder(resp.Body)
err = decoder.Decode(&response)
if err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
if response.Meta != nil && response.Meta.Status != "Success" {
return nil, fmt.Errorf("API error: %s", response.Meta.Status)
}
if response.Meta != nil && response.Meta.ErrorMessage == postNotFound {
return nil, util.ErrUnavailable
}
if response.Data == nil || response.Data.Post == nil {
return nil, fmt.Errorf("no post data found")
}
return response.Data.Post, nil
}