9gag: new extractor
This commit is contained in:
parent
3b625c8b0a
commit
60365973d8
6 changed files with 286 additions and 1 deletions
132
ext/ninegag/main.go
Normal file
132
ext/ninegag/main.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue