diff --git a/.gitignore b/.gitignore index 98667ef..bdcc74c 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ main_test.go old/ +bin/ .env ext-cfg.yaml diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..f4cdd62 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,19 @@ +run: + timeout: 5m + +linters: + enable: + - bodyclose + - gocritic + - unconvert + - ineffassign + - staticcheck + - prealloc + - nilerr + - gosimple + - asasalint + disable: + - errcheck + +issues: + exclude-use-default: false diff --git a/AUTHENTICATION.md b/AUTHENTICATION.md index d037669..1e4a7e6 100644 --- a/AUTHENTICATION.md +++ b/AUTHENTICATION.md @@ -1,9 +1,9 @@ # authentication -some extractors require cookies to access the content. you can export them from your browser in netscape format and place the file in `cookies` folder (e.g. `cookies/reddit.txt`). you can easily export cookies using _Get cookies.txt LOCALLY_ extension for your browser ([chrome](https://chrome.google.com/webstore/detail/get-cookies-txt-locally/nhdogjmejiglipccpnnnanhbledajbpd) - [firefox](https://addons.mozilla.org/en-US/firefox/addon/get-cookies-txt-locally/)). +some extractors require cookies to access the content. you can export them from your browser in netscape format and place the file in `cookies` folder (e.g. `cookies/reddit.txt`). you can easily export cookies using _Get cookies.txt LOCALLY_ extension for your browser ([chrome](https://chromewebstore.google.com/detail/cclelndahbckbenkjhflpdbgdldlbecc?utm_source=item-share-cb) - [firefox](https://addons.mozilla.org/en-US/firefox/addon/get-cookies-txt-locally/)). extractors that **need** authentication: - reddit - twitter > [!CAUTION] -> using cookies _may_ be leading to account bans. we are not responsible for any bans or issues that may arise from using cookies. if you are using cookies, please make sure to use them responsibly and at your own risk. \ No newline at end of file +> using cookies _may_ be leading to account bans. we are not responsible for any bans or issues that may arise from using cookies. if you are using cookies, please make sure to use them responsibly and at your own risk. diff --git a/bot/core/default.go b/bot/core/default.go index 9246d41..51a9b2f 100644 --- a/bot/core/default.go +++ b/bot/core/default.go @@ -114,7 +114,7 @@ func HandleDefaultStoredFormatDownload( storedMedias[0], isCaptionEnabled, ) - var medias []*models.DownloadedMedia + medias := make([]*models.DownloadedMedia, 0, len(storedMedias)) for _, media := range storedMedias { medias = append(medias, &models.DownloadedMedia{ FilePath: "", diff --git a/bot/core/util.go b/bot/core/util.go index eb88493..3d6b7bb 100644 --- a/bot/core/util.go +++ b/bot/core/util.go @@ -103,12 +103,12 @@ func StoreMedias( msgs []gotgbot.Message, medias []*models.DownloadedMedia, ) error { - var storedMedias []*models.Media - if len(medias) == 0 { return errors.New("no media to store") } + storedMedias := make([]*models.Media, 0, len(medias)) + for idx, msg := range msgs { fileID := GetMessageFileID(&msg) if len(fileID) == 0 { diff --git a/ext/instagram/main.go b/ext/instagram/main.go index aa08338..818d281 100644 --- a/ext/instagram/main.go +++ b/ext/instagram/main.go @@ -137,12 +137,12 @@ func GetEmbedMediaList( } func GetIGramMediaList(ctx *models.DownloadContext) ([]*models.Media, error) { - var mediaList []*models.Media postURL := ctx.MatchedContentURL details, err := GetFromIGram(ctx, postURL) if err != nil { return nil, fmt.Errorf("failed to get post: %w", err) } + mediaList := make([]*models.Media, 0, len(details.Items)) for _, item := range details.Items { media := ctx.Extractor.NewMedia( ctx.MatchedContentID, diff --git a/ext/instagram/util.go b/ext/instagram/util.go index 7cd9abd..5342bf6 100644 --- a/ext/instagram/util.go +++ b/ext/instagram/util.go @@ -1,6 +1,7 @@ package instagram import ( + "bytes" "crypto/rand" "crypto/sha256" "encoding/hex" @@ -137,14 +138,14 @@ func ParseGQLMedia( func ParseEmbedGQL( body []byte, ) (*Media, error) { - match := embedPattern.FindStringSubmatch(string(body)) + match := embedPattern.FindSubmatch(body) if len(match) < 2 { return nil, errors.New("failed to find JSON in response") } jsonData := match[1] var data map[string]any - if err := json5.Unmarshal([]byte(jsonData), &data); err != nil { + if err := json5.Unmarshal(jsonData, &data); err != nil { return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) } igCtx := util.TraverseJSON(data, "contextJSON") @@ -193,39 +194,27 @@ func BuildIGramPayload(contentURL string) (io.Reader, error) { if err != nil { return nil, fmt.Errorf("error marshalling payload: %w", err) } - reader := strings.NewReader(string(parsedPayload)) + reader := bytes.NewReader(parsedPayload) return reader, nil } func ParseIGramResponse(body []byte) (*IGramResponse, error) { - var rawResponse any + // try to unmarshal as a single IGramMedia and then as a slice + var media IGramMedia - if err := sonic.ConfigFastest.Unmarshal(body, &rawResponse); err != nil { - return nil, fmt.Errorf("failed to decode response1: %w", err) - } - - switch rawResponse.(type) { - case []any: - // array of IGramMedia - var media []*IGramMedia - if err := sonic.ConfigFastest.Unmarshal(body, &media); err != nil { - return nil, fmt.Errorf("failed to decode response2: %w", err) + if err := sonic.ConfigFastest.Unmarshal(body, &media); err != nil { + // try with slice + var mediaList []*IGramMedia + if err := sonic.ConfigFastest.Unmarshal(body, &mediaList); err != nil { + return nil, fmt.Errorf("failed to decode response: %w", err) } return &IGramResponse{ - Items: media, + Items: mediaList, }, nil - case map[string]any: - // single IGramMedia - var media IGramMedia - if err := sonic.ConfigFastest.Unmarshal(body, &media); err != nil { - return nil, fmt.Errorf("failed to decode response3: %w", err) } - return &IGramResponse{ - Items: []*IGramMedia{&media}, - }, nil - default: - return nil, fmt.Errorf("unexpected response type: %T", rawResponse) - } + return &IGramResponse{ + Items: []*IGramMedia{&media}, + }, nil } func GetCDNURL(contentURL string) (string, error) { diff --git a/ext/ninegag/util.go b/ext/ninegag/util.go index 755ccc0..08be524 100644 --- a/ext/ninegag/util.go +++ b/ext/ninegag/util.go @@ -36,7 +36,6 @@ func FindBestPhoto( func ParseVideoFormats( images map[string]*Media, ) ([]*models.MediaFormat, error) { - var formats []*models.MediaFormat var video *Media var thumbnailURL string @@ -63,6 +62,8 @@ func ParseVideoFormats( "av1Url": {"Av1URL", enums.MediaCodecAV1}, } + formats := make([]*models.MediaFormat, 0, len(codecMapping)) + for _, mapping := range codecMapping { url := getField(video, mapping.Field) if url == "" { diff --git a/ext/pinterest/util.go b/ext/pinterest/util.go index 9359186..90ec067 100644 --- a/ext/pinterest/util.go +++ b/ext/pinterest/util.go @@ -31,6 +31,7 @@ func ParseVideoObject(videoObj *Videos) ([]*models.MediaFormat, error) { if err != nil { return nil, fmt.Errorf("failed to extract hls formats: %w", err) } + formats = make([]*models.MediaFormat, 0, len(hlsFormats)) for _, hlsFormat := range hlsFormats { hlsFormat.Duration = video.Duration / 1000 hlsFormat.Thumbnail = []string{video.Thumbnail} diff --git a/ext/tiktok/main.go b/ext/tiktok/main.go index e269a52..996ada3 100644 --- a/ext/tiktok/main.go +++ b/ext/tiktok/main.go @@ -176,7 +176,7 @@ func GetVideoAPI( decoder := sonic.ConfigFastest.NewDecoder(resp.Body) err = decoder.Decode(&data) if err != nil { - return nil, fmt.Errorf("failed to unmarshal response: %w", err) + return nil, fmt.Errorf("failed to decode response: %w", err) } videoData, err := FindVideoData(data, awemeID) if err != nil { diff --git a/ext/twitter/main.go b/ext/twitter/main.go index 5b6d921..73e3c7e 100644 --- a/ext/twitter/main.go +++ b/ext/twitter/main.go @@ -45,12 +45,12 @@ var ShortExtractor = &models.Extractor{ if err != nil { return nil, fmt.Errorf("failed to read body: %w", err) } - matchedURL := Extractor.URLPattern.FindStringSubmatch(string(body)) + matchedURL := Extractor.URLPattern.FindSubmatch(body) if matchedURL == nil { return nil, errors.New("failed to find url in body") } return &models.ExtractorResponse{ - URL: matchedURL[0], + URL: string(matchedURL[0]), }, nil }, } @@ -91,11 +91,12 @@ func MediaListFromAPI(ctx *models.DownloadContext) ([]*models.Media, error) { caption := CleanCaption(tweetData.FullText) var mediaEntities []MediaEntity - if tweetData.ExtendedEntities != nil && len(tweetData.ExtendedEntities.Media) > 0 { + switch { + case tweetData.ExtendedEntities != nil && len(tweetData.ExtendedEntities.Media) > 0: mediaEntities = tweetData.ExtendedEntities.Media - } else if tweetData.Entities != nil && len(tweetData.Entities.Media) > 0 { + case tweetData.Entities != nil && len(tweetData.Entities.Media) > 0: mediaEntities = tweetData.Entities.Media - } else { + default: return nil, nil } @@ -173,13 +174,9 @@ func GetTweetAPI( if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("invalid response code: %s", resp.Status) } - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read body: %w", err) - } var apiResponse APIResponse - err = sonic.ConfigFastest.Unmarshal(body, &apiResponse) + err = sonic.ConfigFastest.NewDecoder(resp.Body).Decode(&apiResponse) if err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } @@ -190,11 +187,12 @@ func GetTweetAPI( } var tweet *Tweet - if result.Tweet != nil { + switch { + case result.Tweet != nil: tweet = result.Tweet - } else if result.Legacy != nil { + case result.Legacy != nil: tweet = result.Legacy - } else { + default: return nil, errors.New("failed to get tweet data") } return tweet, nil diff --git a/models/media.go b/models/media.go index 83a338d..32db32f 100644 --- a/models/media.go +++ b/models/media.go @@ -246,7 +246,7 @@ func (media *Media) GetSortedFormats() []*MediaFormat { } // combine the best video and audio into a final list - var finalSortedList []*MediaFormat + finalSortedList := make([]*MediaFormat, 0, len(groupedVideos)+len(groupedAudios)+len(media.Formats)) for _, best := range groupedVideos { finalSortedList = append(finalSortedList, best) } diff --git a/util/edgeproxy.go b/util/edgeproxy.go index 70ec9af..ca132dd 100644 --- a/util/edgeproxy.go +++ b/util/edgeproxy.go @@ -107,13 +107,10 @@ func copyHeaders(source, destination http.Header) { } func parseProxyResponse(proxyResp *http.Response, originalReq *http.Request) (*http.Response, error) { - body, err := io.ReadAll(proxyResp.Body) - if err != nil { - return nil, fmt.Errorf("error reading proxy response: %w", err) - } var response models.EdgeProxyResponse - if err := sonic.ConfigFastest.Unmarshal(body, &response); err != nil { + decoder := sonic.ConfigFastest.NewDecoder(proxyResp.Body) + if err := decoder.Decode(&response); err != nil { return nil, fmt.Errorf("error parsing proxy response: %w", err) } diff --git a/util/parser/m3u8.go b/util/parser/m3u8.go index 8cf5ff7..2db1c2e 100644 --- a/util/parser/m3u8.go +++ b/util/parser/m3u8.go @@ -246,33 +246,37 @@ func parseVariantType( } func getVideoCodec(codecs string) enums.MediaCodec { - if strings.Contains(codecs, "avc") || strings.Contains(codecs, "h264") { + switch { + case strings.Contains(codecs, "avc"), strings.Contains(codecs, "h264"): return enums.MediaCodecAVC - } else if strings.Contains(codecs, "hvc") || strings.Contains(codecs, "h265") { + case strings.Contains(codecs, "hvc"), strings.Contains(codecs, "h265"): return enums.MediaCodecHEVC - } else if strings.Contains(codecs, "av01") { + case strings.Contains(codecs, "av01"): return enums.MediaCodecAV1 - } else if strings.Contains(codecs, "vp9") { + case strings.Contains(codecs, "vp9"): return enums.MediaCodecVP9 - } else if strings.Contains(codecs, "vp8") { + case strings.Contains(codecs, "vp8"): return enums.MediaCodecVP8 + default: + return "" } - return "" } func getAudioCodec(codecs string) enums.MediaCodec { - if strings.Contains(codecs, "mp4a") { + switch { + case strings.Contains(codecs, "mp4a"): return enums.MediaCodecAAC - } else if strings.Contains(codecs, "opus") { + case strings.Contains(codecs, "opus"): return enums.MediaCodecOpus - } else if strings.Contains(codecs, "mp3") { + case strings.Contains(codecs, "mp3"): return enums.MediaCodecMP3 - } else if strings.Contains(codecs, "flac") { + case strings.Contains(codecs, "flac"): return enums.MediaCodecFLAC - } else if strings.Contains(codecs, "vorbis") { + case strings.Contains(codecs, "vorbis"): return enums.MediaCodecVorbis + default: + return "" } - return "" } func resolveURL(base *url.URL, uri string) string {