From 47a5331174704c423985b7b440ca83208c4398d8 Mon Sep 17 00:00:00 2001 From: Anthony Ilinykh Date: Wed, 1 May 2024 14:17:58 +0300 Subject: [PATCH] Use `YtDlpApi` for instagram flow --- api/instagram.go | 48 ---------------------- api/instagram_api.go | 79 ------------------------------------ api/tiktok_media_factory.go | 2 +- api/youtube_media_factory.go | 4 +- api/ytdlp_api.go | 4 +- pullanusbot.go | 5 +-- usecases/instagram_flow.go | 60 +++++++++++---------------- 7 files changed, 31 insertions(+), 171 deletions(-) delete mode 100644 api/instagram.go delete mode 100644 api/instagram_api.go diff --git a/api/instagram.go b/api/instagram.go deleted file mode 100644 index 0b6d6b0..0000000 --- a/api/instagram.go +++ /dev/null @@ -1,48 +0,0 @@ -package api - -type IgReel struct { - Items []IgReelItem -} - -type IgUser struct { - Username string - FullName string `json:"full_name"` -} - -type IgReelItem struct { - Code string - User IgUser - Caption IgCaption - VideoDuration float64 `json:"video_duration"` - VideoVersions []IgReelVideo `json:"video_versions"` - ClipsMetadata IgReelClipsMetadata `json:"clips_metadata"` -} - -type IgReelVideo struct { - Width int - Height int - URL string -} - -type IgCaption struct { - Text string -} - -type IgReelClipsMetadata struct { - MusicInfo *IgReelMusicInfo `json:"music_info"` - OriginalSoundInfo *IgReelOriginalSoundInfo `json:"original_sound_info"` -} - -type IgReelMusicInfo struct { - MusicAssetInfo IgReelMusicAssetInfo `json:"music_asset_info"` -} - -type IgReelMusicAssetInfo struct { - DisplayArtist string `json:"display_artist"` - Title string - ProgressiveDownloadURL string `json:"progressive_download_url"` -} - -type IgReelOriginalSoundInfo struct { - ProgressiveDownloadURL string `json:"progressive_download_url"` -} diff --git a/api/instagram_api.go b/api/instagram_api.go deleted file mode 100644 index 27a242b..0000000 --- a/api/instagram_api.go +++ /dev/null @@ -1,79 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "regexp" - - "github.com/ailinykh/pullanusbot/v2/core" -) - -// CreateInstagramAPI -func CreateInstagramAPI(l core.ILogger, cookie string) InstAPI { - return &InstagramAPI{l, cookie} -} - -type InstAPI interface { - GetReel(string) (*IgReel, error) -} - -// Instagram API -type InstagramAPI struct { - l core.ILogger - cookie string -} - -func (api *InstagramAPI) GetReel(urlString string) (*IgReel, error) { - body, err := api.getContent(urlString, map[string]string{ - "sec-fetch-mode": "navigate", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36", - "cookie": api.cookie, - "accept": "text/html", - }) - if err != nil { - api.l.Error(err) - return nil, err - } - - // os.WriteFile("instagram-reel.html", body, 0644) - - r := regexp.MustCompile(`"xdt_api__v1__media__shortcode__web_info":(.*)\},"extensions"`) - match := r.FindSubmatch(body) - if len(match) < 2 { - return nil, fmt.Errorf("parse HTML failed: %s", urlString) - } - - // os.WriteFile("instagram-reel.json", match[1], 0644) - - var reel IgReel - err = json.Unmarshal(match[1], &reel) - if err != nil { - api.l.Error(err) - return nil, err - } - - return &reel, nil -} - -func (api *InstagramAPI) getContent(urlString string, headers map[string]string) ([]byte, error) { - req, err := http.NewRequest("GET", urlString, nil) - - for k, v := range headers { - req.Header.Set(k, v) - } - - if err != nil { - api.l.Error(err) - return nil, err - } - - resp, err := http.DefaultClient.Do(req) - if err != nil { - api.l.Error(err) - return nil, err - } - defer resp.Body.Close() - return ioutil.ReadAll(resp.Body) -} diff --git a/api/tiktok_media_factory.go b/api/tiktok_media_factory.go index 01b61c5..cdf3a47 100644 --- a/api/tiktok_media_factory.go +++ b/api/tiktok_media_factory.go @@ -16,7 +16,7 @@ type TikTokMediaFactory struct { } func (factory *TikTokMediaFactory) CreateMedia(url string) ([]*core.Media, error) { - item, err := factory.api.get(url) + item, err := factory.api.Get(url) if err != nil { factory.l.Error(err) return nil, err diff --git a/api/youtube_media_factory.go b/api/youtube_media_factory.go index fdb2104..79ec2c2 100644 --- a/api/youtube_media_factory.go +++ b/api/youtube_media_factory.go @@ -22,7 +22,7 @@ type YoutubeMediaFactory struct { // CreateMedia is a core.IMediaFactory interface implementation func (y *YoutubeMediaFactory) CreateMedia(url string) ([]*core.Media, error) { - resp, err := y.api.get(url) + resp, err := y.api.Get(url) if err != nil { y.l.Error(err) return nil, err @@ -73,7 +73,7 @@ func (y *YoutubeMediaFactory) getFormats(resp *YtDlpResponse) (*YtDlpFormat, *Yt // CreateVideo is a core.IVideoFactory interface implementation func (y *YoutubeMediaFactory) CreateVideo(id string) (*core.Video, error) { - resp, err := y.api.get(id) + resp, err := y.api.Get(id) if err != nil { y.l.Error(err) return nil, err diff --git a/api/ytdlp_api.go b/api/ytdlp_api.go index 83340c1..027a4e5 100644 --- a/api/ytdlp_api.go +++ b/api/ytdlp_api.go @@ -10,7 +10,7 @@ import ( ) type YoutubeApi interface { - get(string) (*YtDlpResponse, error) + Get(string) (*YtDlpResponse, error) } func CreateYtDlpApi(cookie string, l core.ILogger) YoutubeApi { @@ -22,7 +22,7 @@ type YtDlpApi struct { l core.ILogger } -func (api *YtDlpApi) get(url string) (*YtDlpResponse, error) { +func (api *YtDlpApi) Get(url string) (*YtDlpResponse, error) { args := []string{ "--quiet", "--no-warnings", diff --git a/pullanusbot.go b/pullanusbot.go index 5e72eca..8c49b26 100644 --- a/pullanusbot.go +++ b/pullanusbot.go @@ -100,9 +100,8 @@ func main() { iDoNotCare := usecases.CreateIDoNotCare() telebot.AddHandler(iDoNotCare) - instaAPI := api.CreateInstagramAPI(logger, os.Getenv("INSTAGRAM_COOKIE")) - downloadVideoFactory := helpers.CreateDownloadVideoFactory(logger, fileDownloader, converter) - instaFlow := usecases.CreateInstagramFlow(logger, instaAPI, downloadVideoFactory, localMediaSender, sendVideoStrategySplitDecorator) + instaAPI := api.CreateYtDlpApi(path.Join(getWorkingDir(), "cookies.txt"), logger) + instaFlow := usecases.CreateInstagramFlow(logger, instaAPI, localMediaSender) removeInstaSourceDecorator := usecases.CreateRemoveSourceDecorator(logger, instaFlow, core.SInstagramFlowRemoveSource, boolSettingProvider) telebot.AddHandler(removeInstaSourceDecorator) diff --git a/usecases/instagram_flow.go b/usecases/instagram_flow.go index 8822788..fd7f167 100644 --- a/usecases/instagram_flow.go +++ b/usecases/instagram_flow.go @@ -9,16 +9,14 @@ import ( "github.com/ailinykh/pullanusbot/v2/core" ) -func CreateInstagramFlow(l core.ILogger, api api.InstAPI, createVideo core.IVideoFactory, sendMedia core.ISendMediaStrategy, sendVideo core.ISendVideoStrategy) core.ITextHandler { - return &InstagramFlow{l, api, createVideo, sendMedia, sendVideo} +func CreateInstagramFlow(l core.ILogger, api api.YoutubeApi, sendMedia core.ISendMediaStrategy) core.ITextHandler { + return &InstagramFlow{l, api, sendMedia} } type InstagramFlow struct { - l core.ILogger - api api.InstAPI - createVideo core.IVideoFactory - sendMedia core.ISendMediaStrategy - sendVideo core.ISendVideoStrategy + l core.ILogger + api api.YoutubeApi + sendMedia core.ISendMediaStrategy } // HandleText is a core.ITextHandler protocol implementation @@ -56,23 +54,13 @@ func (flow *InstagramFlow) HandleText(message *core.Message, bot core.IBot) erro func (flow *InstagramFlow) handleReel(url string, message *core.Message, bot core.IBot) error { flow.l.Infof("processing %s", url) - reel, err := flow.api.GetReel(url) + resp, err := flow.api.Get(url) if err != nil { flow.l.Error(err) return err } - if len(reel.Items) < 1 { - return fmt.Errorf("insufficient reel items") - } - - item := reel.Items[0] - - caption := item.Caption.Text - if info := item.ClipsMetadata.MusicInfo; info != nil { - caption = fmt.Sprintf("\nšŸŽ¶ %s - %s\n\n%s", info.MusicAssetInfo.ProgressiveDownloadURL, info.MusicAssetInfo.DisplayArtist, info.MusicAssetInfo.Title, caption) - } - caption = fmt.Sprintf("šŸ“· %s (by %s)\n%s", url, item.User.FullName, message.Sender.DisplayName(), caption) + caption := fmt.Sprintf("šŸ“· %s (by %s)\n%s", url, resp.Uploader, message.Sender.DisplayName(), resp.Description) if len(caption) > 1024 { // strip by last space or line break if caption size limit exceeded index := strings.LastIndex(caption[:1024], " ") @@ -83,31 +71,31 @@ func (flow *InstagramFlow) handleReel(url string, message *core.Message, bot cor caption = caption[:index] } - if item.VideoDuration < 360 { // apparently 6 min file takes less than 50 MB - return flow.sendAsMedia(item, caption, message, bot) - } - - video, err := flow.createVideo.CreateVideo(item.VideoVersions[0].URL) + vf, err := flow.getPreferredVideoFormat(resp) if err != nil { flow.l.Error(err) return err } - defer video.Dispose() - return flow.sendVideo.SendVideo(video, caption, bot) + media := core.Media{ + Caption: caption, + ResourceURL: vf.Url, + URL: url, + } + return flow.sendMedia.SendMedia([]*core.Media{&media}, bot) } -func (flow *InstagramFlow) sendAsMedia(item api.IgReelItem, caption string, message *core.Message, bot core.IBot) error { - media := &core.Media{ - ResourceURL: item.VideoVersions[0].URL, - URL: "https://www.instagram.com/reel/" + item.Code + "/", - Title: item.User.FullName, - Caption: caption, +func (flow *InstagramFlow) getPreferredVideoFormat(resp *api.YtDlpResponse) (*api.YtDlpFormat, error) { + idx := -1 + for i, f := range resp.Formats { + if strings.HasPrefix(f.FormatId, "dash-") { + continue + } + idx = i } - err := flow.sendMedia.SendMedia([]*core.Media{media}, bot) - if err != nil { - flow.l.Error(err) + if idx < 0 { + return nil, fmt.Errorf("no appropriate format found") } - return err + return resp.Formats[idx], nil }