From b2a9b224780d969103ce7eebf541c49ec4253428 Mon Sep 17 00:00:00 2001 From: Andy <88590076+AAndyProgram@users.noreply.github.com> Date: Fri, 28 Apr 2023 10:13:46 +0300 Subject: [PATCH] 2023.4.28.0 Plugins IPluginContentProvider: added DownloadSingleObject function; added tokens to GetMedia and Download functions; removed GetSpecialData function Add IDownloadableMedia interface Removed 'Channel' option from all functions and enums ISiteSettings: added GetSingleMediaInstance function ExchangeOptions: removed 'IsChannel' UserMediaTypes: added Audio and AudioPre enums IUserMedia, PluginUserMedia: changed ContentType and DownloadState from integers to their enums SCrawler Add YouTube standalone downloader Add gallery-dl & yt-dlp support Remove 'UserInfo' requirement from 'ProfilesSaved' Update 'SiteSettingsBase' to use domains and Netscape cookies UserDataBase: remove channels; remove old 'Merge' const; standardize SavedPosts file naming; move 'ValidateMD5' function from Twitter to UserDataBase to use it in other UserData classes; add 'DownloadSingleObject' environment for single posts; add validating file extension for m3u8 during download; add reindex of video file during download Rewritten DomainsContainer Create a universal settings form and PSettingsArttribute Gfycat, Imgur: turn these classes into IUserData to download a single object All plugins: update 'GetInstance' function for saved posts; update domains where implemented; remove 'OptionForm' where it exists; update options where they exist; update unix date providers; reconfigure channels where they exist LPSG: fix attachments; update converters and regex Add sites: ThisVid, Mastodon, Pinterest, YouTube, YouTube music Reddit: standardize container parsing for all data types; new channel environment; fix 'ReparseMissing' function; redirect data downloading to the base download function, saved crossposts support Twitter: fixed gif path bug; fixed downloading saved posts PornHub: hide unnecessary errors; photo galleries bug RedGifs: add 'UserAgent' option Added icons to download progress Rename some objects Completely redesigned standalone downloader form and rewritten its environment WebClient2: update to use tokens Labels: update label form (save labels to file only when OK button is clicked); change removing labels.txt from recycle bin to permanent; disable storing label 'NoParsedUser' UserCreatorForm: remove the 'Channel' checkbox and related functions; ability to extract the user's URL from the buffer and apply parameters if found Remove temporary 'EncryptCookies' module MainFrame: added simplified way to create new users (Ctrl+Insert to create a new user with default parameters from clipboard URL); removed SCrawler command line argument "-v" (remove the ability to run SCrawler as video downloader) PropertyValueHost: update for option forms compatibility SettingsHost: removed 'GetSpecialData' fork; added 'GetSingleMediaInstance' fork UserDataHost: update functions with tokens; update events; add 'DownloadSingleObject' function Settings: add the ability to get environment from 4 destinations; add the ability to set the program environment manually; add CMDEncoding; add cache; remove the old function 'RemoveUnusedPlugins'; add 'STDownloader' properties; add YT compatibility; add new notification options; add deleting user settings file when 'SettingsCLS.Dispose()' if where are no users in SCrawler UserFinder: remove old 'Merge' const; remove channel option UserInfo: remove channel option --- .gitattributes | 3 + .github/FUNDING.yml | 4 +- .github/ISSUE_TEMPLATE/bug_report.md | 24 +- .gitignore | 6 +- CONTRIBUTING.md | 19 +- Changelog.md | 44 + FAQ.md | 21 +- HowToSupport.md | 2 +- ProgramScreenshots/AppYouTube.png | Bin 0 -> 33011 bytes ProgramScreenshots/AppYouTubeMusic.png | Bin 0 -> 25948 bytes ProgramScreenshots/AppYouTubePlaylist.png | Bin 0 -> 48489 bytes .../AppYouTubePlaylistParser.png | Bin 0 -> 45469 bytes ProgramScreenshots/AppYouTubeSettings.png | Bin 0 -> 25400 bytes ProgramScreenshots/AppYouTubeVideo.png | Bin 0 -> 57412 bytes ProgramScreenshots/CreateUserClear.png | Bin 17279 -> 16604 bytes ProgramScreenshots/SavedPosts.png | Bin 13932 -> 30747 bytes ProgramScreenshots/SettingsGlobalBasis.png | Bin 23465 -> 23768 bytes ProgramScreenshots/SettingsGlobalBehavior.png | Bin 13665 -> 14028 bytes ProgramScreenshots/SettingsGlobalChannels.png | Bin 12445 -> 12620 bytes ProgramScreenshots/SettingsGlobalDefaults.png | Bin 11544 -> 11858 bytes .../SettingsGlobalDownloader.png | Bin 0 -> 15581 bytes .../SettingsGlobalDownloading.png | Bin 17640 -> 18025 bytes .../SettingsGlobalEnvironment.png | Bin 0 -> 20208 bytes ProgramScreenshots/SettingsGlobalFeed.png | Bin 14349 -> 14616 bytes .../SettingsGlobalNotifications.png | Bin 10576 -> 12496 bytes ProgramScreenshots/SettingsSiteMastodon.png | Bin 0 -> 22314 bytes .../SettingsSiteMastodonAdditional.png | Bin 0 -> 13213 bytes ProgramScreenshots/SettingsSitePinterest.png | Bin 0 -> 15253 bytes ProgramScreenshots/SettingsSiteRedGifs.png | Bin 15826 -> 19080 bytes ProgramScreenshots/SettingsSiteThisVid.png | Bin 0 -> 14716 bytes ProgramScreenshots/SettingsSiteTwitter.png | Bin 20578 -> 19424 bytes ProgramScreenshots/SettingsSiteYouTube.png | Bin 0 -> 16255 bytes ProgramsComparison.md | 64 +- README.md | 53 +- .../Interfaces/IDownloadableMedia.vb | 34 + .../Interfaces/IPluginContentProvider.vb | 9 +- .../Interfaces/ISiteSettings.vb | 6 +- .../My Project/AssemblyInfo.vb | 4 +- .../Objects/ExchangeOptions.vb | 6 +- .../Objects/PluginUserMedia.vb | 17 +- .../SCrawler.PluginProvider.vbproj | 1 + SCrawler.YouTube/.editorconfig | 3 + SCrawler.YouTube/App.config | 6 + .../Attributes/GridVisibleAttribute.vb | 30 + SCrawler.YouTube/Base/Structures.vb | 81 + .../Base/TableControlsProcessor.vb | 38 + SCrawler.YouTube/Base/YouTubeFunctions.vb | 165 ++ SCrawler.YouTube/Base/YouTubeSettings.vb | 337 ++++ .../Content/Icons/YouTubeIcon_32.ico | Bin 0 -> 4286 bytes .../Content/Icons/YouTubeMusicIcon_32.ico | Bin 0 -> 4286 bytes .../Content/Pictures/ArrowDownPic_Blue_24.png | Bin 0 -> 1789 bytes .../Content/Pictures/AudioMusic_32.png | Bin 0 -> 1322 bytes .../Content/Pictures/ClockPic_16.png | Bin 0 -> 457 bytes .../Content/Pictures/HeartPic_32.png | Bin 0 -> 525 bytes .../Content/Pictures/ImagePic_32.png | Bin 0 -> 569 bytes .../Content/Pictures/InfoPic_32.png | Bin 0 -> 368 bytes .../Content/Pictures/LinkPic_32.png | Bin 0 -> 3531 bytes .../Content/Pictures/RulerPic_32.png | Bin 0 -> 3038 bytes .../Content/Pictures/SettingsPic_16.bmp | Bin 0 -> 824 bytes .../Content/Pictures/VideoCamera_32.png | Bin 0 -> 402 bytes .../Content/Pictures/YouTubeMusicPic_96.png | Bin 0 -> 1903 bytes .../Content/Pictures/YouTubePic_96.png | Bin 0 -> 2576 bytes .../Controls/MusicPlaylistsForm.Designer.vb | 469 ++++++ .../Controls/MusicPlaylistsForm.resx | 243 +++ .../Controls/MusicPlaylistsForm.vb | 262 +++ .../Controls/ParsingProgressForm.Designer.vb | 94 ++ .../Controls/ParsingProgressForm.resx | 3 - .../Controls/ParsingProgressForm.vb | 48 + .../Controls/PlayListParserForm.Designer.vb | 171 ++ .../Controls/PlayListParserForm.resx | 147 ++ .../Controls/PlayListParserForm.vb | 60 + .../Controls/PlaylistArrayForm.Designer.vb | 96 +- .../Controls/PlaylistArrayForm.resx | 3 + .../Controls/PlaylistArrayForm.vb | 54 + .../Controls/VideoOption.Designer.vb | 132 ++ SCrawler.YouTube/Controls/VideoOption.resx | 123 ++ SCrawler.YouTube/Controls/VideoOption.vb | 87 + .../Controls/VideoOptionsForm.Designer.vb | 749 +++++++++ .../Controls/VideoOptionsForm.resx | 271 +++ SCrawler.YouTube/Controls/VideoOptionsForm.vb | 474 ++++++ SCrawler.YouTube/Declarations.vb | 112 ++ .../Downloader/IDownloaderSettings.vb | 20 + .../Downloader/MediaItem.Designer.vb | 257 +++ SCrawler.YouTube/Downloader/MediaItem.resx | 241 +++ SCrawler.YouTube/Downloader/MediaItem.vb | 468 ++++++ SCrawler.YouTube/Downloader/Notificator.vb | 32 + .../Downloader/STDownloaderDeclarations.vb | 20 + .../Downloader/VideoListForm.Designer.vb | 306 ++++ .../Downloader/VideoListForm.resx | 390 +++++ SCrawler.YouTube/Downloader/VideoListForm.vb | 499 ++++++ SCrawler.YouTube/MainModShared.vb | 39 + .../My Project/Application.Designer.vb | 13 + SCrawler.YouTube/My Project/Application.myapp | 11 + SCrawler.YouTube/My Project/AssemblyInfo.vb | 37 + .../My Project/Resources.Designer.vb | 163 ++ SCrawler.YouTube/My Project/Resources.resx | 151 ++ .../My Project/Settings.Designer.vb | 73 + SCrawler.YouTube/My Project/Settings.settings | 7 + SCrawler.YouTube/Objects/Channel.vb | 98 ++ .../Objects/IYouTubeMediaContainer.vb | 88 + SCrawler.YouTube/Objects/PlayList.vb | 43 + SCrawler.YouTube/Objects/Track.vb | 61 + SCrawler.YouTube/Objects/Video.vb | 29 + .../Objects/YouTubeMediaContainerBase.vb | 1457 +++++++++++++++++ SCrawler.YouTube/SCrawler.YouTube.vbproj | 318 ++++ SCrawler.YouTube/SiteYouTube.Designer.vb | 107 ++ SCrawler.YouTube/SiteYouTube.resx | 133 ++ SCrawler.YouTubeDownloader/.editorconfig | 3 + SCrawler.YouTubeDownloader/App.config | 6 + .../Content/Icons/ArrowDownIcon_Orange_24.ico | Bin 0 -> 29926 bytes .../Content/Icons/RainbowIcon_48.ico | Bin 0 -> 9662 bytes .../MainFrame.Designer.vb | 64 + SCrawler.YouTubeDownloader/MainFrame.resx | 232 +++ SCrawler.YouTubeDownloader/MainFrame.vb | 87 + .../My Project/Application.Designer.vb | 38 + .../My Project/Application.myapp | 10 + .../My Project/AssemblyInfo.vb | 37 + .../My Project/Resources.Designer.vb | 83 + .../My Project/Resources.resx | 127 ++ .../My Project/Settings.Designer.vb | 73 + .../My Project/Settings.settings | 7 + .../My Project/app.manifest | 79 + .../SCrawler.YouTubeDownloader.vbproj | 198 +++ SCrawler.sln | 29 +- SCrawler/API/Base/Declarations.vb | 4 + SCrawler/API/Base/DownDetector.vb | 2 +- SCrawler/API/Base/GDLBatch.vb | 86 + SCrawler/API/Base/M3U8Base.vb | 34 +- SCrawler/API/Base/ProfileSaved.vb | 13 +- SCrawler/API/Base/SiteSettingsBase.vb | 107 +- SCrawler/API/Base/Structures.vb | 24 +- SCrawler/API/Base/UserDataBase.vb | 430 ++++- SCrawler/API/BaseObjects/DomainEnvir.vb | 86 - SCrawler/API/BaseObjects/DomainsContainer.vb | 108 ++ .../InternalSettingsForm.Designer.vb | 89 + .../BaseObjects/InternalSettingsForm.resx} | 11 +- .../API/BaseObjects/InternalSettingsForm.vb | 256 +++ SCrawler/API/Gfycat/Envir.vb | 78 +- SCrawler/API/Imgur/Envir.vb | 51 +- SCrawler/API/Instagram/Declarations.vb | 7 +- .../API/Instagram/EditorExchangeOptions.vb | 16 +- .../API/Instagram/OptionsForm.Designer.vb | 135 -- SCrawler/API/Instagram/OptionsForm.vb | 40 - SCrawler/API/Instagram/SiteSettings.vb | 46 +- SCrawler/API/Instagram/UserData.vb | 54 +- SCrawler/API/LPSG/Declarations.vb | 45 +- SCrawler/API/LPSG/SiteSettings.vb | 2 +- SCrawler/API/LPSG/UserData.vb | 22 +- SCrawler/API/Mastodon/Credentials.vb | 48 + SCrawler/API/Mastodon/Declarations.vb | 14 + SCrawler/API/Mastodon/MastodonDomains.vb | 65 + .../API/Mastodon/SettingsForm.Designer.vb | 165 ++ SCrawler/API/Mastodon/SettingsForm.resx | 292 ++++ SCrawler/API/Mastodon/SettingsForm.vb | 154 ++ SCrawler/API/Mastodon/SiteSettings.vb | 214 +++ SCrawler/API/Mastodon/UserData.vb | 287 ++++ SCrawler/API/PathPlugin/SiteSettings.vb | 6 +- SCrawler/API/Pinterest/Declarations.vb | 21 + SCrawler/API/Pinterest/SiteSettings.vb | 101 ++ SCrawler/API/Pinterest/UserData.vb | 330 ++++ SCrawler/API/PornHub/Declarations.vb | 2 + SCrawler/API/PornHub/M3U8.vb | 9 +- SCrawler/API/PornHub/OptionsForm.vb | 34 - SCrawler/API/PornHub/SiteSettings.vb | 48 +- SCrawler/API/PornHub/UserData.vb | 93 +- SCrawler/API/PornHub/UserExchangeOptions.vb | 6 + SCrawler/API/Reddit/Channel.vb | 9 +- SCrawler/API/Reddit/ChannelsCollection.vb | 2 +- SCrawler/API/Reddit/Declarations.vb | 6 +- SCrawler/API/Reddit/M3U8.vb | 70 +- SCrawler/API/Reddit/RedditViewSettingsForm.vb | 2 +- SCrawler/API/Reddit/SiteSettings.vb | 48 +- SCrawler/API/Reddit/UserData.vb | 946 +++++------ SCrawler/API/Redgifs/Declarations.vb | 1 - SCrawler/API/Redgifs/SiteSettings.vb | 70 +- SCrawler/API/Redgifs/UserData.vb | 30 +- SCrawler/API/ThisVid/Declarations.vb | 22 + SCrawler/API/ThisVid/SiteSettings.vb | 69 + SCrawler/API/ThisVid/UserData.vb | 332 ++++ SCrawler/API/ThisVid/UserExchangeOptions.vb | 32 + SCrawler/API/TikTok/Declarations.vb | 12 +- SCrawler/API/TikTok/SiteSettings.vb | 7 +- SCrawler/API/TikTok/UserData.vb | 25 +- SCrawler/API/Twitter/Declarations.vb | 1 - SCrawler/API/Twitter/EditorExchangeOptions.vb | 22 +- SCrawler/API/Twitter/OptionsForm.Designer.vb | 185 --- SCrawler/API/Twitter/OptionsForm.vb | 81 - SCrawler/API/Twitter/SiteSettings.vb | 170 +- SCrawler/API/Twitter/UserData.vb | 327 ++-- SCrawler/API/UserDataBind.vb | 11 +- SCrawler/API/XVIDEOS/Declarations.vb | 4 +- SCrawler/API/XVIDEOS/M3U8.vb | 9 +- SCrawler/API/XVIDEOS/SiteSettings.vb | 84 +- SCrawler/API/XVIDEOS/UserData.vb | 133 +- SCrawler/API/Xhamster/Declarations.vb | 1 - SCrawler/API/Xhamster/M3U8.vb | 9 +- SCrawler/API/Xhamster/SiteSettings.vb | 110 +- SCrawler/API/Xhamster/UserData.vb | 94 +- SCrawler/API/YouTube/SiteSettings.vb | 116 ++ SCrawler/API/YouTube/UserData.vb | 245 +++ SCrawler/API/YouTube/UserExchangeOptions.vb | 33 + SCrawler/API/YouTube/YTSettings_Internal.vb | 38 + SCrawler/Channels/ChannelViewForm.vb | 10 +- .../Icons/SiteIcons/MastodonIcon_48.ico | Bin 0 -> 15086 bytes .../Icons/SiteIcons/PinterestIcon_32.ico | Bin 0 -> 4286 bytes .../Icons/SiteIcons/ThisVidIcon_16.ico | Bin 0 -> 1150 bytes .../Pictures/SitePictures/MastodonPic_48.png | Bin 0 -> 1680 bytes .../Pictures/SitePictures/PinterestPic_48.png | Bin 0 -> 1711 bytes .../Pictures/SitePictures/ThisVidPic_16.png | Bin 0 -> 684 bytes .../Download/ActiveDownloadingProgress.vb | 2 +- .../Download/Automation/AutoDownloader.vb | 17 +- .../Automation/AutoDownloaderEditorForm.vb | 21 +- .../Automation/AutoDownloaderPauseButtons.vb | 4 +- SCrawler/Download/Automation/Scheduler.vb | 4 +- .../Automation/SchedulerEditorForm.vb | 2 +- SCrawler/Download/DownloadProgress.vb | 49 +- SCrawler/Download/DownloadSavedPostsForm.vb | 2 +- SCrawler/Download/DownloadedInfoForm.vb | 2 +- .../Feed/DownloadFeedForm.Designer.vb | 1 + SCrawler/Download/Feed/DownloadFeedForm.vb | 36 +- SCrawler/Download/Feed/FeedMedia.vb | 6 +- SCrawler/Download/Feed/FeedVideo.vb | 10 +- SCrawler/Download/Groups/DownloadGroup.vb | 4 +- .../Groups/DownloadGroupCollection.vb | 2 +- SCrawler/Download/Groups/GroupDefaults.vb | 2 +- SCrawler/Download/Groups/GroupEditorForm.vb | 14 +- SCrawler/Download/MissingPostsForm.vb | 22 +- .../Download/STDownloader/Declarations.vb | 20 + .../DownloaderUrlForm.Designer.vb | 139 ++ .../STDownloader/DownloaderUrlForm.resx} | 39 +- .../STDownloader/DownloaderUrlForm.vb | 50 + .../DownloaderUrlsArrForm.Designer.vb | 136 ++ .../STDownloader/DownloaderUrlsArrForm.resx | 149 ++ .../STDownloader/DownloaderUrlsArrForm.vb | 57 + .../VideoDownloaderForm.Designer.vb | 35 + .../STDownloader/VideoDownloaderForm.resx | 120 ++ .../STDownloader/VideoDownloaderForm.vb | 198 +++ SCrawler/Download/TDownloader.vb | 26 +- .../Download/VideosDownloaderForm.Designer.vb | 179 -- SCrawler/Download/VideosDownloaderForm.vb | 171 -- SCrawler/Download/WebClient2.vb | 66 +- .../Editors/GlobalSettingsForm.Designer.vb | 432 ++++- SCrawler/Editors/GlobalSettingsForm.resx | 199 +++ SCrawler/Editors/GlobalSettingsForm.vb | 72 + SCrawler/Editors/LabelsForm.vb | 15 +- SCrawler/Editors/SiteEditorForm.vb | 37 +- SCrawler/Editors/SiteSelectionForm.vb | 2 +- SCrawler/Editors/UserCreatorForm.Designer.vb | 37 +- SCrawler/Editors/UserCreatorForm.vb | 60 +- SCrawler/EncryptCookies.vb | 21 - SCrawler/LabelsKeeper.vb | 20 +- SCrawler/ListImagesLoader.vb | 6 +- SCrawler/MainFrame.Designer.vb | 7 +- SCrawler/MainFrame.vb | 195 +-- SCrawler/MainFrameObjects.vb | 9 +- SCrawler/MainMod.vb | 158 +- SCrawler/My Project/AssemblyInfo.vb | 4 +- .../Attributes/Attributes.vb | 44 + .../Hosts/DownloadableMediaHost.vb | 150 ++ .../PluginsEnvironment/Hosts/PluginHost.vb | 8 +- .../Hosts/PropertyValueHost.vb | 130 +- .../PluginsEnvironment/Hosts/SettingsHost.vb | 36 +- .../PluginsEnvironment/Hosts/UserDataHost.vb | 22 +- SCrawler/SCrawler.vbproj | 130 +- SCrawler/SettingsCLS.vb | 249 ++- SCrawler/SiteResources.Designer.vb | 60 + SCrawler/SiteResources.resx | 18 + SCrawler/UserFinder.vb | 14 +- SCrawler/UserInfo.vb | 20 +- SCrawler/UserSearchForm.vb | 2 +- 270 files changed, 18120 insertions(+), 3332 deletions(-) create mode 100644 ProgramScreenshots/AppYouTube.png create mode 100644 ProgramScreenshots/AppYouTubeMusic.png create mode 100644 ProgramScreenshots/AppYouTubePlaylist.png create mode 100644 ProgramScreenshots/AppYouTubePlaylistParser.png create mode 100644 ProgramScreenshots/AppYouTubeSettings.png create mode 100644 ProgramScreenshots/AppYouTubeVideo.png create mode 100644 ProgramScreenshots/SettingsGlobalDownloader.png create mode 100644 ProgramScreenshots/SettingsGlobalEnvironment.png create mode 100644 ProgramScreenshots/SettingsSiteMastodon.png create mode 100644 ProgramScreenshots/SettingsSiteMastodonAdditional.png create mode 100644 ProgramScreenshots/SettingsSitePinterest.png create mode 100644 ProgramScreenshots/SettingsSiteThisVid.png create mode 100644 ProgramScreenshots/SettingsSiteYouTube.png create mode 100644 SCrawler.PluginProvider/Interfaces/IDownloadableMedia.vb create mode 100644 SCrawler.YouTube/.editorconfig create mode 100644 SCrawler.YouTube/App.config create mode 100644 SCrawler.YouTube/Attributes/GridVisibleAttribute.vb create mode 100644 SCrawler.YouTube/Base/Structures.vb create mode 100644 SCrawler.YouTube/Base/TableControlsProcessor.vb create mode 100644 SCrawler.YouTube/Base/YouTubeFunctions.vb create mode 100644 SCrawler.YouTube/Base/YouTubeSettings.vb create mode 100644 SCrawler.YouTube/Content/Icons/YouTubeIcon_32.ico create mode 100644 SCrawler.YouTube/Content/Icons/YouTubeMusicIcon_32.ico create mode 100644 SCrawler.YouTube/Content/Pictures/ArrowDownPic_Blue_24.png create mode 100644 SCrawler.YouTube/Content/Pictures/AudioMusic_32.png create mode 100644 SCrawler.YouTube/Content/Pictures/ClockPic_16.png create mode 100644 SCrawler.YouTube/Content/Pictures/HeartPic_32.png create mode 100644 SCrawler.YouTube/Content/Pictures/ImagePic_32.png create mode 100644 SCrawler.YouTube/Content/Pictures/InfoPic_32.png create mode 100644 SCrawler.YouTube/Content/Pictures/LinkPic_32.png create mode 100644 SCrawler.YouTube/Content/Pictures/RulerPic_32.png create mode 100644 SCrawler.YouTube/Content/Pictures/SettingsPic_16.bmp create mode 100644 SCrawler.YouTube/Content/Pictures/VideoCamera_32.png create mode 100644 SCrawler.YouTube/Content/Pictures/YouTubeMusicPic_96.png create mode 100644 SCrawler.YouTube/Content/Pictures/YouTubePic_96.png create mode 100644 SCrawler.YouTube/Controls/MusicPlaylistsForm.Designer.vb create mode 100644 SCrawler.YouTube/Controls/MusicPlaylistsForm.resx create mode 100644 SCrawler.YouTube/Controls/MusicPlaylistsForm.vb create mode 100644 SCrawler.YouTube/Controls/ParsingProgressForm.Designer.vb rename SCrawler/API/PornHub/OptionsForm.resx => SCrawler.YouTube/Controls/ParsingProgressForm.resx (96%) create mode 100644 SCrawler.YouTube/Controls/ParsingProgressForm.vb create mode 100644 SCrawler.YouTube/Controls/PlayListParserForm.Designer.vb create mode 100644 SCrawler.YouTube/Controls/PlayListParserForm.resx create mode 100644 SCrawler.YouTube/Controls/PlayListParserForm.vb rename SCrawler/API/PornHub/OptionsForm.Designer.vb => SCrawler.YouTube/Controls/PlaylistArrayForm.Designer.vb (55%) rename SCrawler/API/Instagram/OptionsForm.resx => SCrawler.YouTube/Controls/PlaylistArrayForm.resx (97%) create mode 100644 SCrawler.YouTube/Controls/PlaylistArrayForm.vb create mode 100644 SCrawler.YouTube/Controls/VideoOption.Designer.vb create mode 100644 SCrawler.YouTube/Controls/VideoOption.resx create mode 100644 SCrawler.YouTube/Controls/VideoOption.vb create mode 100644 SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb create mode 100644 SCrawler.YouTube/Controls/VideoOptionsForm.resx create mode 100644 SCrawler.YouTube/Controls/VideoOptionsForm.vb create mode 100644 SCrawler.YouTube/Declarations.vb create mode 100644 SCrawler.YouTube/Downloader/IDownloaderSettings.vb create mode 100644 SCrawler.YouTube/Downloader/MediaItem.Designer.vb create mode 100644 SCrawler.YouTube/Downloader/MediaItem.resx create mode 100644 SCrawler.YouTube/Downloader/MediaItem.vb create mode 100644 SCrawler.YouTube/Downloader/Notificator.vb create mode 100644 SCrawler.YouTube/Downloader/STDownloaderDeclarations.vb create mode 100644 SCrawler.YouTube/Downloader/VideoListForm.Designer.vb create mode 100644 SCrawler.YouTube/Downloader/VideoListForm.resx create mode 100644 SCrawler.YouTube/Downloader/VideoListForm.vb create mode 100644 SCrawler.YouTube/MainModShared.vb create mode 100644 SCrawler.YouTube/My Project/Application.Designer.vb create mode 100644 SCrawler.YouTube/My Project/Application.myapp create mode 100644 SCrawler.YouTube/My Project/AssemblyInfo.vb create mode 100644 SCrawler.YouTube/My Project/Resources.Designer.vb create mode 100644 SCrawler.YouTube/My Project/Resources.resx create mode 100644 SCrawler.YouTube/My Project/Settings.Designer.vb create mode 100644 SCrawler.YouTube/My Project/Settings.settings create mode 100644 SCrawler.YouTube/Objects/Channel.vb create mode 100644 SCrawler.YouTube/Objects/IYouTubeMediaContainer.vb create mode 100644 SCrawler.YouTube/Objects/PlayList.vb create mode 100644 SCrawler.YouTube/Objects/Track.vb create mode 100644 SCrawler.YouTube/Objects/Video.vb create mode 100644 SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb create mode 100644 SCrawler.YouTube/SCrawler.YouTube.vbproj create mode 100644 SCrawler.YouTube/SiteYouTube.Designer.vb create mode 100644 SCrawler.YouTube/SiteYouTube.resx create mode 100644 SCrawler.YouTubeDownloader/.editorconfig create mode 100644 SCrawler.YouTubeDownloader/App.config create mode 100644 SCrawler.YouTubeDownloader/Content/Icons/ArrowDownIcon_Orange_24.ico create mode 100644 SCrawler.YouTubeDownloader/Content/Icons/RainbowIcon_48.ico create mode 100644 SCrawler.YouTubeDownloader/MainFrame.Designer.vb create mode 100644 SCrawler.YouTubeDownloader/MainFrame.resx create mode 100644 SCrawler.YouTubeDownloader/MainFrame.vb create mode 100644 SCrawler.YouTubeDownloader/My Project/Application.Designer.vb create mode 100644 SCrawler.YouTubeDownloader/My Project/Application.myapp create mode 100644 SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb create mode 100644 SCrawler.YouTubeDownloader/My Project/Resources.Designer.vb create mode 100644 SCrawler.YouTubeDownloader/My Project/Resources.resx create mode 100644 SCrawler.YouTubeDownloader/My Project/Settings.Designer.vb create mode 100644 SCrawler.YouTubeDownloader/My Project/Settings.settings create mode 100644 SCrawler.YouTubeDownloader/My Project/app.manifest create mode 100644 SCrawler.YouTubeDownloader/SCrawler.YouTubeDownloader.vbproj create mode 100644 SCrawler/API/Base/GDLBatch.vb delete mode 100644 SCrawler/API/BaseObjects/DomainEnvir.vb create mode 100644 SCrawler/API/BaseObjects/DomainsContainer.vb create mode 100644 SCrawler/API/BaseObjects/InternalSettingsForm.Designer.vb rename SCrawler/{Download/VideosDownloaderForm.resx => API/BaseObjects/InternalSettingsForm.resx} (88%) create mode 100644 SCrawler/API/BaseObjects/InternalSettingsForm.vb delete mode 100644 SCrawler/API/Instagram/OptionsForm.Designer.vb delete mode 100644 SCrawler/API/Instagram/OptionsForm.vb create mode 100644 SCrawler/API/Mastodon/Credentials.vb create mode 100644 SCrawler/API/Mastodon/Declarations.vb create mode 100644 SCrawler/API/Mastodon/MastodonDomains.vb create mode 100644 SCrawler/API/Mastodon/SettingsForm.Designer.vb create mode 100644 SCrawler/API/Mastodon/SettingsForm.resx create mode 100644 SCrawler/API/Mastodon/SettingsForm.vb create mode 100644 SCrawler/API/Mastodon/SiteSettings.vb create mode 100644 SCrawler/API/Mastodon/UserData.vb create mode 100644 SCrawler/API/Pinterest/Declarations.vb create mode 100644 SCrawler/API/Pinterest/SiteSettings.vb create mode 100644 SCrawler/API/Pinterest/UserData.vb delete mode 100644 SCrawler/API/PornHub/OptionsForm.vb create mode 100644 SCrawler/API/ThisVid/Declarations.vb create mode 100644 SCrawler/API/ThisVid/SiteSettings.vb create mode 100644 SCrawler/API/ThisVid/UserData.vb create mode 100644 SCrawler/API/ThisVid/UserExchangeOptions.vb delete mode 100644 SCrawler/API/Twitter/OptionsForm.Designer.vb delete mode 100644 SCrawler/API/Twitter/OptionsForm.vb create mode 100644 SCrawler/API/YouTube/SiteSettings.vb create mode 100644 SCrawler/API/YouTube/UserData.vb create mode 100644 SCrawler/API/YouTube/UserExchangeOptions.vb create mode 100644 SCrawler/API/YouTube/YTSettings_Internal.vb create mode 100644 SCrawler/Content/Icons/SiteIcons/MastodonIcon_48.ico create mode 100644 SCrawler/Content/Icons/SiteIcons/PinterestIcon_32.ico create mode 100644 SCrawler/Content/Icons/SiteIcons/ThisVidIcon_16.ico create mode 100644 SCrawler/Content/Pictures/SitePictures/MastodonPic_48.png create mode 100644 SCrawler/Content/Pictures/SitePictures/PinterestPic_48.png create mode 100644 SCrawler/Content/Pictures/SitePictures/ThisVidPic_16.png create mode 100644 SCrawler/Download/STDownloader/Declarations.vb create mode 100644 SCrawler/Download/STDownloader/DownloaderUrlForm.Designer.vb rename SCrawler/{API/Twitter/OptionsForm.resx => Download/STDownloader/DownloaderUrlForm.resx} (89%) create mode 100644 SCrawler/Download/STDownloader/DownloaderUrlForm.vb create mode 100644 SCrawler/Download/STDownloader/DownloaderUrlsArrForm.Designer.vb create mode 100644 SCrawler/Download/STDownloader/DownloaderUrlsArrForm.resx create mode 100644 SCrawler/Download/STDownloader/DownloaderUrlsArrForm.vb create mode 100644 SCrawler/Download/STDownloader/VideoDownloaderForm.Designer.vb create mode 100644 SCrawler/Download/STDownloader/VideoDownloaderForm.resx create mode 100644 SCrawler/Download/STDownloader/VideoDownloaderForm.vb delete mode 100644 SCrawler/Download/VideosDownloaderForm.Designer.vb delete mode 100644 SCrawler/Download/VideosDownloaderForm.vb delete mode 100644 SCrawler/EncryptCookies.vb create mode 100644 SCrawler/PluginsEnvironment/Attributes/Attributes.vb create mode 100644 SCrawler/PluginsEnvironment/Hosts/DownloadableMediaHost.vb diff --git a/.gitattributes b/.gitattributes index dfe0770..7a84664 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ # Auto detect text files and perform LF normalization * text=auto + +# Declare files that will always have CRLF line endings on checkout. +*.md text eol=crlf \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 4229ef1..170ada8 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -3,11 +3,11 @@ github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username -ko_fi: andyprogram +ko_fi: #andyprogram tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: ['https://blockchair.com/bitcoin/address/BC1Q0NH839FT5TA44DD7L7RLR97XDQAG9V8D6N7XET'] +custom: ['https://github.com/AAndyProgram/SCrawler/blob/main/HowToSupport.md'] diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d8a0a35..02a349c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -13,11 +13,16 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. **Profile URL**: -2. Do something -3. See error +2. **Post URL**: +3. Do something +4. See error -**Log data** -If the program log contains any data. +
+Log data +
+If the program log contains any data, replace this line with the log data. If the program log does not contain any data, write here about.
+
+
**Expected behavior** A clear and concise description of what you expected to happen. @@ -26,10 +31,13 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Release information (please complete the following information):** - - OS [e.g. Windows 10, Windows 11] - - Architecture [e.g. x86, x64] - - Version [e.g. 2.0.0.0] - - NET.Framework version + - OS: [e.g. Windows 10, Windows 11] + - Architecture: [e.g. x86, x64] + - Version: [e.g. 2023.3.5.0] + - NET.Framework version: + - ffmpeg version (command `ffmpeg -version`): + - yt-dlp version (command `yt-dlp --version`): + - gallery-dl version (command `gallery-dl --version`): **Additional context** Add any other context about the problem here. diff --git a/.gitignore b/.gitignore index 480c100..2798902 100644 --- a/.gitignore +++ b/.gitignore @@ -9,8 +9,9 @@ *.user *.userosscache *.sln.docstates -ffmpeg.exe +.obsidian/ ToDo.txt +ToDo.md # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs @@ -33,10 +34,7 @@ bld/ [Oo]bj/ [Ll]og/ [Ll]ogs/ -ffmpeg/ -cURL/ Info/ -Hidden/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 762fe83..0d2fbc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,20 +8,21 @@ I welcome requests! Follow these steps to contribute: 1. If you have a code change suggestion, you can post a replacement code block. I also accept pull requests. # How to build from source -1. Delete the ```PersonalUtilities``` project from the solution. -1. Delete the ```PersonalUtilities.Notifications``` project from the solution. -1. Delete the ```cURL``` folder from the solution. -1. Delete the ```ffmpeg.exe``` from the solution. +1. Delete the `PersonalUtilities` project from the solution. +1. Delete the `PersonalUtilities.Notifications` project from the solution. 1. The following libraries must be added to project references with the '**Copy to output folder**' option: - - ```PersonalUtilities.dll``` - - ```PersonalUtilities.Notifications.dll``` - - ```Microsoft.Toolkit.Uwp.Notifications.dll``` - - ```System.ValueTuple.dll``` -1. Import ```PersonalUtilities.Functions``` for the whole project. + - `PersonalUtilities.dll` + - `PersonalUtilities.Notifications.dll` + - `Microsoft.Toolkit.Uwp.Notifications.dll` + - `System.ValueTuple.dll` +1. Import `PersonalUtilities.Functions` for the whole project. **Always use the correct libraries. You must download libraries from the same release date as the code commit date.** # How to request a new site + +**I'm currently not accepting requests to develop new sites.** + 1. Check [issues](https://github.com/AAndyProgram/SCrawler/issues) (open and [closed](https://github.com/AAndyProgram/SCrawler/issues?q=is%3Aissue+is%3Aclosed)) and [discussions](https://github.com/AAndyProgram/SCrawler/discussions) to find your issue. Perhaps I have already answered your request. 1. If you don't find anything, create a new issue with your request. I usually reply as soon as possible (within the next few hours). diff --git a/Changelog.md b/Changelog.md index 7692a25..1de7195 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,47 @@ +# 2023.4.28.0 + +*2023-04-28* + +- Added + - **YouTube** + - **YouTube Music** + - **Mastodon** + - **Pinterest** + - **ThisVid** + - **YouTube downloader (standalone app)** + - Redesigned standalone downloader and update environment + - Added icons to download progress + - Added icons to saved posts downloader + - **Cookies**: new ways to add cookies. You can now export cookies using the browser extension and then import them into SCrawler! + - User creation: ability to extract the user's URL from the buffer and apply parameters if found + - User creation: simplified way to create new users (`Ctrl+Insert` to create a new user with default parameters from clipboard URL) + - Ability to customize the placement of ffmpeg (and other) files + - Ability to customize the command line encoding + - New notification options for standalone downloader + - Reddit: now it can download saved crossposts + - RedGifs: added `UserAgent` option + - Other improvements +- Removed + - User creation: remove the 'Channel' checkbox because it confuses people + - Removed an ability to open SCrawler with `-v` argument + - All ways to create users except URL. You can only properly create a user using the user's URL. +- Plugins + - Added `IDownloadableMedia` interface + - Removed `Channel` option from all functions and enums + - ISiteSettings: added `GetSingleMediaInstance` function + - IPluginContentProvider: added `DownloadSingleObject` function + - IPluginContentProvider: added tokens to `GetMedia` and `Download` functions + - IPluginContentProvider: removed `GetSpecialData` function + - UserMediaTypes: added `Audio` and `AudioPre` enums +- Fixed + - LPSG: attachments not downloading (Issue #114) + - Twitter: saved posts not downloading (Issue #119) + - XVIDEOS: saved posts not downloading + - Deleting labels file + - PornHub: hide unnecessary errors (Issue #116) + - PornHub: photo galleries bug (Issue #115) + - Minor bugs + # 2023.3.5.0 *2023-03-05* diff --git a/FAQ.md b/FAQ.md index f165970..028e401 100644 --- a/FAQ.md +++ b/FAQ.md @@ -14,11 +14,7 @@ Any other questions I will keep in this file. A: https://github.com/AAndyProgram/SCrawler/wiki/Settings#how-to-set-up-cookies ----- - -#### Q: **I can't copy cookies.** - -A: Use the mouse. Don't use ```Ctrl+A```! + ---- @@ -30,21 +26,22 @@ A: This is a GUI program. #### Q: **Will CLI be added in the future?** -A: I do not think so. +A: NO. ---- #### Q: **I want to add "...." site. How to request.** -A: How to request a new site you can read [here](CONTRIBUTING.md#how-to-request-a-new-site) + +**I'm currently not accepting requests to develop new sites.** ---- -#### Q: **Twitter/Instagram download failed.** +#### Q: **Site download failed.** -A: Check your credentials. Both of these sites require cookies. Check your [Twitter tokens](https://github.com/AAndyProgram/SCrawler/wiki/Settings#how-to-find-twitter-tokens) and [Instagram settings](https://github.com/AAndyProgram/SCrawler/wiki/Settings#instagram). If all settings are set, but nothing works, go to [create a new issue](https://github.com/AAndyProgram/SCrawler/issues). Don't forget to attach the LOG. +A: Check your credentials and **[SITES REQUIREMENTS](https://github.com/AAndyProgram/SCrawler/wiki/Settings#sites-requirements)**. If all settings are set, but nothing works, go to [create a new issue](https://github.com/AAndyProgram/SCrawler/issues). Don't forget to attach the LOG. -**[SITES REQUIREMENTS](https://github.com/AAndyProgram/SCrawler/wiki/Settings#sites-requirements)** +**ATTENTION! Issues without URLs will be closed without a response!** ---- @@ -80,7 +77,7 @@ A: The program stored posts IDs in users' folders. For the first time, the progr #### Q: **How to redownload all data** -A: Double-click on the user you want to redownload. In the opened window open folder setting. Delete the files ending with ```_Data.xml``` and ```_Posts.txt```. Download this user again. +A: Double-click on the user you want to redownload. In the opened window open folder setting. Delete the files ending with ```_Data.xml``` and ```_Posts.txt```. Restart SCrawler. Download this user again. ---- @@ -116,4 +113,4 @@ A: I can only [suggest](#q-you-lost-me-your-program-is-too-complicated) you find #### Q: **Can you add a step-by-step guide or video on how to use the program?** -A: **NO**! I will not do it. If you want, you can create a video tutorial and send it to me. Then I add it. All options and what each option does described on the wiki. The wiki also contains a description of all settings and how-to configure them. For complex settings, there is a steep-by-steep guide. Read the [main](README.md) information and [GUIDE](https://github.com/AAndyProgram/SCrawler/wiki/) and you won't have any problems. I have developed a program with an intuitive interface. There is a Settings button, download buttons, a context menu that drops down when a user is clicked, and other controls. Anyone can use it. \ No newline at end of file +A: **NO! NEVER!** The guide fully covers all the functionality of SCrawler! If you don't respect my work, I don't waste my time. If you want, you can create a video tutorial and send it to me. Then I add it. All options and what each option does described on the wiki. The wiki also contains a description of all settings and how-to configure them. For complex settings, there is a steep-by-steep guide. Read the [main](README.md) information and [GUIDE](https://github.com/AAndyProgram/SCrawler/wiki/) and you won't have any problems. I have developed a program with an intuitive interface. There is a Settings button, download buttons, a context menu that drops down when a user is clicked, and other controls. Anyone can use it. \ No newline at end of file diff --git a/HowToSupport.md b/HowToSupport.md index 89ace8e..8cffa11 100644 --- a/HowToSupport.md +++ b/HowToSupport.md @@ -2,7 +2,6 @@ Your support is very valuable to me. Any support is greatly appreciated. Your su You can support the program by: - **Bitcoin**: BC1Q0NH839FT5TA44DD7L7RLR97XDQAG9V8D6N7XET - - :heavy_dollar_sign: make a donation on this site: https://ko-fi.com/andyprogram - :repeat: make a post about my program on your profile (Reddit, Twitter, Instagram and any other social networks) - :speech_balloon: tell your friends about the program - :heart: like the program on this site: https://alternativeto.net/software/scrawler/about/ @@ -10,3 +9,4 @@ You can support the program by: - suggest my program as an alternative ([on this site](https://alternativeto.net/software/scrawler/about/)) to any program you have used before I would be very grateful for any support! :blush: + \ No newline at end of file diff --git a/ProgramScreenshots/AppYouTube.png b/ProgramScreenshots/AppYouTube.png new file mode 100644 index 0000000000000000000000000000000000000000..e8313ae62d63f8123d3f78f0e15bb0d3270a3b37 GIT binary patch literal 33011 zcma&N1yCGO6E#XgkO0A9aRLN)cL;&SCAho0%c4ne&jxpQx5eGv-Q6KLEcVFv{r0L} zz51(mYiDNf?49ZEd%Dl*ozU;{5}%L=kYHe7K1oT6DZ#+JcY=X=H;)Mac7z8|uln}! z&RI#~8%+5a(c#<92XhfQ5g3@NXyhkDxVQa}4w9PAFfgdy|K9KV?F&s{V19&1iHWFq z=$)=2=wR({!#h}8A46uBn(oBGt#`QSKx{`?a#2$n@?T+uICpDa;m9gtD_-ZCbr3hJ zggI1O>(}wu)~_rd5q?=5KSRAALDGMky`0@OqZ2+zi+Lpb8{!4My%hCSfaNJb(SO^9 zc;X_4VB`NDx6s)6&xRp30WR-i*Y{QW_>Og5x|#$=*1C z#?Q%^4W*j}rF$T+awqJ0(+jCjBRK z&KPZ-LL*h*0l~MD&3ANRVv0zEB{WXS!!rAU2@E;F;Swgj3QvK5)&n(S=jY;>rTbAh z1)jRb^3Vn{Ijy(dMcGJo4}d!^wtoZ$KDmH1)@W(@^RdfvoO*c(3evK%|7^trl6J4K zXXbJKPMCGRP*$H%Jo0Q z$~Z;WE7}1cW+!YzE?@0i!t}q5bQD{rQ5MxxzWmfjIWYW9Aiz zBqi7S>7RJTxTVx#^{_uF+Yit|h8XtforeWJdw%P9(@4`c&BnqX7c#Dl( z{HXst)Y2un!!$2zxc9)F!{ks=(j)a|xyLgg@T{W!N1gRgtJ%vDWdkslA`XEfc{Orc z6T<;6nQP|+Ei-6rSXXTVbXc3&cJ`~>dPgW}y!|kW5m+-|8qM6ym@M%*uj+htlvvm> z59%@owHztXF6O8g_05nv&Xs=|mYn}sW=Sx-Z|8N(2D*d&=gR#2f6-E}lz2lsBJgUq zR!Dw#+6>oH=Xpvnk^kxMoObn<0K-O^s;bJaSbcy6>9eQrwRC~O4llF2rpW#kQ-+sr z@y!Hyzf8aJc>carUu2nFZkYDBGivxlUe2#Z3F<5DcJH~?4j-zq{gZ7Qg5jNb^R%j~ zRky}d)3=yTR3{8w*-id8x7rM)JhTX;Ww*BVFH+qW-A!c(v>+eo)M7K@W9-+wB-}>! zCutGj;xSjr$3(m|A)k`($4xHzgL`nt4)MRXb5qI>8Z~C$|{e-;4Nx{bJY#Cyd+~-SsKaBc*UF)B* zU$U^{SE}Efyr7TBkXNCoo6AuY^l&5GbJV0-nWv|kJNv3ZKV>?_VVOQ?v?kfzRBg=q zA4p;8l>vtl{R`c(F&S#Q7I*XX?O@bUzjZMi#J?Vc`BsN3_xB6k;l&|mHY?7o!iHn6+U2r0<- z-H?eom@cvjc;0`vPN&W;e5v(dC5a*m)$fbyHNUvHmyONU_j^{gZsVqXEm>Aw!M<%& zO#A!SA(lq*eV%k&*V$^b>Aqp&)=sc~2D!z#&y3DRAl>MkPvC=kEosRE5GvvF6KFSg zQUR{IQZPAv5&lAzF20gUF@R0UBzTI3A}0{C)=AGw!-Z92<`YRK8uht$oM6$BgF-Om zi_udTAZ)7-oHGa*Y$b+yZ6b}zU}^KdQU?A*lkbG2709N)*dl$b~6;F zkSS-WSi+*x;{6B5H30#6sZM4oDGwq*aK__pFRiuptnUQTvE0z>ksA(oppRXwQoWt7 zYUuO`bNdrHKl4?4lD-0QceE7j-|a$=C*RXQbF~u3>Kl&%l20uzBrg*lIyjU{mdXI0 z#*_QfjmKqw1T-Wjr?xh)D#7QXBO0B#vvzPJeREHq-4jlU+H(LL@|iJ K4FX|`k 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 bUwMi$Z~ro7(#3dd3Y(#>sZP1p<%oX+v?vAE literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/AppYouTubePlaylist.png b/ProgramScreenshots/AppYouTubePlaylist.png new file mode 100644 index 0000000000000000000000000000000000000000..8dea79510194c0dd01c17d6645528f87d7fb470f GIT binary patch literal 48489 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 HY3Tn0zfd`N literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/AppYouTubePlaylistParser.png b/ProgramScreenshots/AppYouTubePlaylistParser.png new file mode 100644 index 0000000000000000000000000000000000000000..25e419cdcb825a720c1fcfc23df22dfada46b216 GIT binary patch literal 45469 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 z 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
  • [\r\n\s]*?
    [\r\n\s]*?\<[^\>]*?alt=""([^""]*)""" albumName = StringTrim(RegexReplace(r, albumRegex)) If albumName.IsEmptyString Then albumName = block.AlbumID - Using j As EContainer = JsonDocument.Parse("{" & block.Data & "}", jErr) - If Not j Is Nothing Then - If If(j("urls")?.Count, 0) > 0 Then - _TempMediaList.ListAddList(j("urls").Select(Function(jj) _ - New UserMedia(jj.ItemF({0}).XmlIfNothingValue, UTypes.Picture) With { - .SpecialFolder = $"Albums\{albumName}\"}), LNC) - End If + j = JsonDocument.Parse("{" & block.Data & "}", jErr) + If Not j Is Nothing Then + If If(j("urls")?.Count, 0) > 0 Then + _TempMediaList.ListAddList(j("urls").Select(Function(jj) _ + New UserMedia(jj.ItemF({0}).XmlIfNothingValue, UTypes.Picture) With { + .SpecialFolder = $"Albums\{albumName}\", + .File = CreatePhotoFile(.URL, .File)}), LNC) End If - End Using + j.Dispose() + End If Next l.Clear() End If @@ -444,7 +456,9 @@ Namespace API.PornHub If Not r.IsEmptyString Then url = RegexReplace(r, Regex_Photo_PornHub_SinglePhoto) If Not url.IsEmptyString Then _ - _TempMediaList.ListAddValue(New UserMedia(url, UTypes.Picture) With {.SpecialFolder = $"Albums\{AlbumName}\"}, LNC) + _TempMediaList.ListAddValue(New UserMedia(url, UTypes.Picture) With { + .SpecialFolder = $"Albums\{AlbumName}\", + .File = CreatePhotoFile(url, .File)}, LNC) End If Catch End Try @@ -468,7 +482,7 @@ Namespace API.PornHub If r.Contains(HtmlPageNotFoundPhoto) Then Return False Dim urls As List(Of String) = RegexReplace(r, Regex_Photo_PornHub_AlbumPhotoArr) If urls.ListExists Then - Dim NewUrl$ + Dim NewUrl$, pFile$ Dim m As UserMedia Dim l2 As List(Of UserMedia) = urls.Select(Function(__url) New UserMedia(__url, UTypes.Picture) With { .Post = __url.Split("/").LastOrDefault}).ToList @@ -487,7 +501,8 @@ Namespace API.PornHub NewUrl = RegexReplace(r, Regex_Photo_PornHub_SinglePhoto) If Not NewUrl.IsEmptyString Then m.URL = NewUrl - m.File = NewUrl + pFile = RegexReplace(NewUrl, Regex_Photo_File) + If Not pFile.IsEmptyString Then m.File = pFile Else m.File = NewUrl _TempPostsList.ListAddValue(m.Post.ID, LNC) Else Throw New Exception @@ -511,13 +526,17 @@ Namespace API.PornHub #End Region #End Region #Region "ReparseVideo" - Protected Overrides Sub ReparseVideo(ByVal Token As CancellationToken) + Protected Overloads Overrides Sub ReparseVideo(ByVal Token As CancellationToken) + ReparseVideo(Token, False) + End Sub + Protected Overloads Sub ReparseVideo(ByVal Token As CancellationToken, ByVal CreateFileName As Boolean, + Optional ByRef Data As IYouTubeMediaContainer = Nothing) Const ERR_NEW_URL$ = "ERR_NEW_URL" Dim URL$ = String.Empty Try If _TempMediaList.Count > 0 AndAlso _TempMediaList.Exists(Function(tm) tm.Type = UTypes.VideoPre) Then Dim m As UserMedia - Dim r$, NewUrl$ + Dim r$, NewUrl$, tmpName$ For i% = _TempMediaList.Count - 1 To 0 Step -1 If _TempMediaList(i).Type = UTypes.VideoPre Then m = _TempMediaList(i) @@ -532,6 +551,14 @@ Namespace API.PornHub Else m.URL = NewUrl m.Type = UTypes.m3u8 + If CreateFileName Then + tmpName = RegexReplace(r, RegexVideoPageTitle) + If Not tmpName.IsEmptyString Then + If Not Data Is Nothing Then Data.Title = tmpName + m.File.Name = TitleHtmlConverter(tmpName) + m.File.Extension = "mp4" + End If + End If _TempMediaList(i) = m End If Else @@ -565,7 +592,7 @@ Namespace API.PornHub m = _ContentList(i) If m.State = UserMedia.States.Missing AndAlso Not m.URL_BASE.IsEmptyString Then ThrowAny(Token) - r = Responser.Curl(m.URL_BASE, eCurl) + r = Responser.Curl(m.URL_BASE,, eCurl) If Not r.IsEmptyString Then Dim NewUrl$ = CreateVideoURL(r) If Not NewUrl.IsEmptyString Then @@ -591,12 +618,12 @@ Namespace API.PornHub Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) DownloadContentDefault(Token) End Sub - Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile) As SFile - Return M3U8.Download(URL, Responser, DestinationFile) + Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile + Return M3U8.Download(URL, Responser, DestinationFile, Token, If(UseInternalM3U8Function_UseProgress, Progress, Nothing)) End Function #End Region #Region "CreateVideoURL" - Private Shared Function CreateVideoURL(ByVal r As String) As String + Private Function CreateVideoURL(ByVal r As String) As String Try Dim OutStr$ = String.Empty If Not r.IsEmptyString Then @@ -619,26 +646,18 @@ Namespace API.PornHub End If Return OutStr Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, "[API.PornHub.UserData.CreateVideoURL]", String.Empty) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.PornHub.UserData.CreateVideoURL]", String.Empty) End Try End Function #End Region -#Region "Standalone downloader" - Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal Responser As Responser, ByVal Destination As SFile) As UserMedia - Try - Dim r$ = Responser.Curl(URL) - If Not r.IsEmptyString Then - Dim NewUrl$ = CreateVideoURL(r) - If Not NewUrl.IsEmptyString Then - Dim f As SFile = M3U8.Download(NewUrl, Responser, Destination) - If Not f.IsEmptyString Then Return New UserMedia With {.State = UserMedia.States.Downloaded} - End If - End If - Return Nothing - Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog + EDP.ReturnValue, ex, $"PornHub standalone download error: [{URL}]", New UserMedia) - End Try - End Function +#Region "DownloadSingleObject" + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + _TempMediaList.Add(New UserMedia(Data.URL, UTypes.VideoPre)) + ReparseVideo(Token, True, Data) + End Sub + Protected Overrides Sub DownloadSingleObject_PostProcessing(ByVal Data As IYouTubeMediaContainer, Optional ByVal ResetTitle As Boolean = True) + MyBase.DownloadSingleObject_PostProcessing(Data, False) + End Sub #End Region #Region "Exceptions" Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, diff --git a/SCrawler/API/PornHub/UserExchangeOptions.vb b/SCrawler/API/PornHub/UserExchangeOptions.vb index 9ccc4b4..8906308 100644 --- a/SCrawler/API/PornHub/UserExchangeOptions.vb +++ b/SCrawler/API/PornHub/UserExchangeOptions.vb @@ -6,18 +6,24 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY +Imports SCrawler.Plugin.Attributes Namespace API.PornHub Friend Class UserExchangeOptions + Friend Property DownloadGifs As Boolean + Friend Property DownloadPhotoOnlyFromModelHub As Boolean + Private ReadOnly Property MySettings As SiteSettings Friend Sub New(ByVal u As UserData) DownloadGifs = u.DownloadGifs DownloadPhotoOnlyFromModelHub = u.DownloadPhotoOnlyFromModelHub + MySettings = u.HOST.Source End Sub Friend Sub New(ByVal s As SiteSettings) Dim v As CheckState = CInt(s.DownloadGifs.Value) DownloadGifs = Not v = CheckState.Unchecked DownloadPhotoOnlyFromModelHub = s.DownloadPhotoOnlyFromModelHub.Value + MySettings = s End Sub End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Reddit/Channel.vb b/SCrawler/API/Reddit/Channel.vb index fcc9d25..37cf9e7 100644 --- a/SCrawler/API/Reddit/Channel.vb +++ b/SCrawler/API/Reddit/Channel.vb @@ -258,7 +258,8 @@ Namespace API.Reddit .Progress = p, .SaveToCache = True, .SkipExistsUsers = SkipExists, - .ChannelInfo = Me + .ChannelInfo = Me, + .IsChannel = True } With d .SetEnvironment(HOST, CUser, False) @@ -306,7 +307,7 @@ Namespace API.Reddit Friend Function GetEnumerator() As IEnumerator(Of UserPost) Implements IEnumerable(Of UserPost).GetEnumerator Return New MyEnumerator(Of UserPost)(Me) End Function - Friend Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator Return GetEnumerator() End Function #End Region @@ -373,7 +374,7 @@ Namespace API.Reddit Dim l As New List(Of String) If Posts.Count > 0 Or PostsLatest.Count > 0 Then l.ListAddList((From p In PostsAll Where Not p.ID.IsEmptyString Select p.ID), LNC) l.ListAddList(PostsNames, LNC) - If l.Count > 0 Then TextSaver.SaveTextToFile(l.ListToString("|"), FilePosts, True,, EDP.SendInLog) + If l.Count > 0 Then TextSaver.SaveTextToFile(l.ListToString("|"), FilePosts, True,, EDP.SendToLog) End If Using x As New XmlFile With {.AllowSameNames = True, .Name = "Channel"} x.Add(Name_Name, Name) @@ -418,7 +419,7 @@ Namespace API.Reddit CountOfAddedUsers.Clear() CountOfLoadedPostsPerSession.Clear() ChannelExistentUserNames.Clear() - CachePath.Delete(SFO.Path, SFODelete.None, EDP.SendInLog) + CachePath.Delete(SFO.Path, SFODelete.None, EDP.SendToLog) End If disposedValue = True End If diff --git a/SCrawler/API/Reddit/ChannelsCollection.vb b/SCrawler/API/Reddit/ChannelsCollection.vb index 799c496..ddaa9f7 100644 --- a/SCrawler/API/Reddit/ChannelsCollection.vb +++ b/SCrawler/API/Reddit/ChannelsCollection.vb @@ -55,7 +55,7 @@ Namespace API.Reddit Return Nothing End If Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog + EDP.ReturnValue, ex, "[API.Reddit.ChannelsCollection.GetUserFiles]") + Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, "[API.Reddit.ChannelsCollection.GetUserFiles]") End Try End Function Friend Sub UpdateUsersStats() diff --git a/SCrawler/API/Reddit/Declarations.vb b/SCrawler/API/Reddit/Declarations.vb index bd114d7..be26dae 100644 --- a/SCrawler/API/Reddit/Declarations.vb +++ b/SCrawler/API/Reddit/Declarations.vb @@ -15,10 +15,12 @@ Namespace API.Reddit Friend ReadOnly JsonNodesJson() As NodeParams = {New NodeParams("posts", True, True, True, True, 3)} Friend ReadOnly ChannelJsonNodes() As NodeParams = {New NodeParams("data", True, True, True, True, 1), New NodeParams("children", True, True, True)} + Friend ReadOnly SingleJsonNodes() As NodeParams = {New NodeParams("data", True, True, True, True, 2), + New NodeParams("children", True, True, True), + New NodeParams("data", True, True, True, True, 1)} Friend ReadOnly UrlBasePattern As RParams = RParams.DM("(?<=/)([^/]+?\.[\w]{3,4})(?=(\?|\Z))", 0) Friend ReadOnly VideoRegEx As RParams = RParams.DM("http.{0,1}://[^" & Chr(34) & "]+?mp4", 0) Private ReadOnly EUR_PROVIDER As New ANumbers(ANumbers.Cultures.EUR) - Friend ReadOnly DateProvider As New CustomProvider(Function(v, d, p, n, e) ADateTime.ParseUnicodeJS(v, n, e)) - Friend ReadOnly DateProviderChannel As New CustomProvider(Function(v, d, p, n, e) ADateTime.ParseUnicode(AConvert(Of Integer)(v, EUR_PROVIDER, v), n, e)) + Friend ReadOnly UnixDate32ProviderReddit As New CustomProvider(Function(v, d, p, n, e) ADateTime.ParseUnix32(AConvert(Of Integer)(v, EUR_PROVIDER, v), n, e)) End Module End Namespace \ No newline at end of file diff --git a/SCrawler/API/Reddit/M3U8.vb b/SCrawler/API/Reddit/M3U8.vb index 7578d65..4581c8f 100644 --- a/SCrawler/API/Reddit/M3U8.vb +++ b/SCrawler/API/Reddit/M3U8.vb @@ -7,8 +7,11 @@ ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY Imports System.Net +Imports System.Threading Imports SCrawler.API.Reddit.M3U8_Declarations +Imports PersonalUtilities.Tools Imports PersonalUtilities.Tools.Web +Imports PersonalUtilities.Forms.Toolbars Imports PersonalUtilities.Functions.RegularExpressions Namespace API.Reddit Namespace M3U8_Declarations @@ -19,7 +22,7 @@ Namespace API.Reddit ''' Audio, Video Friend ReadOnly PlayListRegEx_2 As RParams = RParams.DM("(?<=#EXT-X-BYTERANGE.+?[\r\n]{1,2})(.+)(?=[\r\n]{0,2})", 0, RegexReturn.List) Friend ReadOnly PlayListAudioRegEx As RParams = RParams.DM("(HLS_AUDIO_(\d+)[^""]+)", 0, RegexReturn.List) - Friend ReadOnly DPED As New ErrorsDescriber(EDP.SendInLog + EDP.ReturnValue) + Friend ReadOnly DPED As New ErrorsDescriber(EDP.SendToLog + EDP.ReturnValue) End Module End Namespace Friend NotInheritable Class M3U8 : Implements IDisposable @@ -52,9 +55,12 @@ Namespace API.Reddit Private OutFile As SFile Private VideoFile As SFile Private AudioFile As SFile - Private CachePath As SFile + Private ReadOnly Cache As CacheKeeper + Private ReadOnly CacheFiles As CacheKeeper + Private ReadOnly Property Progress As MyProgress + Private ReadOnly ProgressExists As Boolean #End Region - Private Sub New(ByVal URL As String, ByVal OutFile As SFile) + Private Sub New(ByVal URL As String, ByVal OutFile As SFile, ByVal Progress As MyProgress) PlayListURL = URL BaseURL = RegexReplace(URL, BaseUrlPattern) Video = New List(Of String) @@ -62,7 +68,10 @@ Namespace API.Reddit Me.OutFile = OutFile Me.OutFile.Name = "PlayListFile" Me.OutFile.Extension = "mp4" - CachePath = $"{OutFile.PathWithSeparator}_Cache\{SFile.GetDirectories($"{OutFile.PathWithSeparator}_Cache\",,, EDP.ReturnValue).ListIfNothing.Count + 1}\" + Me.Progress = Progress + ProgressExists = Not Me.Progress Is Nothing + Cache = New CacheKeeper($"{OutFile.PathWithSeparator}_{Base.M3U8Base.TempCacheFolderName}\") + CacheFiles = Cache.NewInstance End Sub #Region "Internal functions" #Region "GetPlaylistUrls" @@ -78,7 +87,7 @@ Namespace API.Reddit If Not r.IsEmptyString Then Dim l As New List(Of Resolution) If Type = Types.Video Then - l = RegexFields(Of Resolution)(r, {PlayListRegEx_1}, {6, 4}) + l = RegexFields(Of Resolution)(r, {PlayListRegEx_1}, {6, 4}, EDP.ReturnValue) Else Try l = RegexFields(Of Resolution)(r, {PlayListAudioRegEx}, {1, 2}) @@ -112,41 +121,44 @@ Namespace API.Reddit End Function #End Region #Region "ConcatData" - Private Overloads Sub ConcatData() - ConcatData(Video, Types.Video, VideoFile) - ConcatData(Audio, Types.Audio, AudioFile) + Private Overloads Sub ConcatData(ByVal Token As CancellationToken) + ConcatData(Video, Types.Video, VideoFile, Token) + ConcatData(Audio, Types.Audio, AudioFile, Token) MergeFiles() End Sub - Private Overloads Sub ConcatData(ByVal Urls As List(Of String), ByVal Type As Types, ByRef TFile As SFile) + Private Overloads Sub ConcatData(ByVal Urls As List(Of String), ByVal Type As Types, ByRef TFile As SFile, ByVal Token As CancellationToken) Try + Token.ThrowIfCancellationRequested() If Urls.ListExists Then - Dim ConcatFile As SFile = OutFile + Dim tmpCache As CacheKeeper = CacheFiles.NewInstance + Dim ConcatFile As SFile = CacheFiles If Type = Types.Audio Then - ConcatFile.Name &= "_AUDIO" + ConcatFile.Name &= "AUDIO" ConcatFile.Extension = "aac" Else - If Audio.Count > 0 Then ConcatFile.Name &= "_VIDEO" + If Audio.Count > 0 Then ConcatFile.Name &= "VIDEO" ConcatFile.Extension = "mp4" End If - If CachePath.Exists(SFO.Path) Then - Dim p As New SFileNumbers(ConcatFile.Name,,, New ANumbers With {.Format = ANumbers.Formats.General}) - ConcatFile = SFile.Indexed_IndexFile(ConcatFile,, p, EDP.ThrowException) + If tmpCache.Validate Then Dim i% - Dim eFiles As New List(Of SFile) - Dim dFile As SFile = CachePath + Dim dFile As SFile = tmpCache.RootDirectory + If ProgressExists Then Progress.Maximum += Urls.Count dFile.Extension = New SFile(Urls(0)).Extension If dFile.Extension.IsEmptyString Then dFile.Extension = "ts" Using w As New WebClient For i = 0 To Urls.Count - 1 + If ProgressExists Then Progress.Perform() + Token.ThrowIfCancellationRequested() dFile.Name = $"ConPart_{i}" w.DownloadFile(Urls(i), dFile) - eFiles.Add(dFile) + tmpCache.AddFile(dFile, True) Next End Using - TFile = FFMPEG.ConcatenateFiles(eFiles, Settings.FfmpegFile, ConcatFile, p, DPED) - eFiles.Clear() + TFile = FFMPEG.ConcatenateFiles(tmpCache, Settings.FfmpegFile.File, ConcatFile, Settings.CMDEncoding,, DPED) End If End If + Catch oex As OperationCanceledException When Token.IsCancellationRequested + Throw oex Catch ex As Exception ErrorsDescriber.Execute(DPED, ex, $"[M3U8.Save({Type})]") End Try @@ -154,25 +166,27 @@ Namespace API.Reddit #End Region Private Sub MergeFiles() Try + Dim p As SFileNumbers = SFileNumbers.Default(OutFile.Name) + Dim f As SFile = SFile.IndexReindex(OutFile,,, p, EDP.ReturnValue) If Not VideoFile.IsEmptyString And Not AudioFile.IsEmptyString Then - Dim p As New SFileNumbers(OutFile.Name,, RParams.DMS("PlayListFile_(\d*)", 1), New ANumbers With {.Format = ANumbers.Formats.General}) - OutFile = FFMPEG.MergeFiles({VideoFile, AudioFile}, Settings.FfmpegFile, OutFile, p, DPED) + OutFile = FFMPEG.MergeFiles({VideoFile, AudioFile}, Settings.FfmpegFile.File, f, Settings.CMDEncoding, p, DPED) Else - OutFile = VideoFile + If f.IsEmptyString Then f = OutFile + If Not SFile.Move(VideoFile, f) Then OutFile = VideoFile End If Catch ex As Exception ErrorsDescriber.Execute(DPED, ex, $"[M3U8.MergeFiles]") End Try End Sub - Friend Function Download() As SFile + Friend Function Download(ByVal Token As CancellationToken) As SFile GetPlaylistUrls() - ConcatData() + ConcatData(Token) Return OutFile End Function #End Region #Region "Statics" - Friend Shared Function Download(ByVal URL As String, ByVal f As SFile) As SFile - Using m As New M3U8(URL, f) : Return m.Download() : End Using + Friend Shared Function Download(ByVal URL As String, ByVal f As SFile, ByVal Token As CancellationToken, ByVal Progress As MyProgress) As SFile + Using m As New M3U8(URL, f, Progress) : Return m.Download(Token) : End Using End Function #End Region #Region "IDisposable Support" @@ -182,7 +196,7 @@ Namespace API.Reddit If disposing Then Video.Clear() Audio.Clear() - CachePath.Delete(SFO.Path, SFODelete.None, DPED) + Cache.Dispose() End If disposedValue = True End If diff --git a/SCrawler/API/Reddit/RedditViewSettingsForm.vb b/SCrawler/API/Reddit/RedditViewSettingsForm.vb index e65fe0e..7ed1653 100644 --- a/SCrawler/API/Reddit/RedditViewSettingsForm.vb +++ b/SCrawler/API/Reddit/RedditViewSettingsForm.vb @@ -18,7 +18,7 @@ Namespace API.Reddit MyOptions = opt MyDefs = New DefaultFormOptions(Me, Settings.Design) End Sub - Private Sub ChannelSettingsForm_Load(sender As Object, e As EventArgs) Handles Me.Load + Private Sub RedditViewSettingsForm_Load(sender As Object, e As EventArgs) Handles Me.Load Try Dim n$ = String.Empty If TypeOf MyOptions Is Channel Then diff --git a/SCrawler/API/Reddit/SiteSettings.vb b/SCrawler/API/Reddit/SiteSettings.vb index df6aaf2..8aa0bba 100644 --- a/SCrawler/API/Reddit/SiteSettings.vb +++ b/SCrawler/API/Reddit/SiteSettings.vb @@ -38,31 +38,28 @@ Namespace API.Reddit End With SavedPostsUserName = New PropertyValue(String.Empty, GetType(String)) UseM3U8 = New PropertyValue(True) - UrlPatternUser = "https://www.reddit.com/user/{0}/" - UrlPatternChannel = "https://www.reddit.com/r/{0}/" + UrlPatternUser = "https://www.reddit.com/{0}/{1}/" ImageVideoContains = "reddit.com" UserRegex = RParams.DM("[htps:/]{7,8}.*?reddit.com/([user]{1,4})/([^/]+)", 0, RegexReturn.ListByMatch, EDP.ReturnValue) End Sub Friend Overrides Function GetInstance(ByVal What As Download) As IPluginContentProvider - Select Case What - Case Download.Main : Return New UserData - Case Download.Channel : Return New UserData With {.SaveToCache = False, .SkipExistsUsers = False, .AutoGetLimits = True} - Case Download.SavedPosts - Dim u As New UserData With {.IsSavedPosts = True} - DirectCast(u, UserDataBase).User = New UserInfo With { - .Name = CStr(AConvert(Of String)(SavedPostsUserName.Value, String.Empty)), - .IsChannel = True - } - Return u - End Select - Return Nothing + Return New UserData End Function + Friend Const ChannelOption As String = "r" Friend Overrides Function IsMyUser(ByVal UserURL As String) As ExchangeOptions Dim l As List(Of String) = RegexReplace(UserURL, UserRegex) - If l.ListExists(3) Then Return New ExchangeOptions(Site, l(2), l(1) = "r") Else Return Nothing + If l.ListExists(3) Then + Dim n$ = l(2) + If Not l(1).IsEmptyString AndAlso l(1) = ChannelOption Then n &= $"@{ChannelOption}" + Return New ExchangeOptions(Site, n) + Else + Return Nothing + End If End Function Friend Overrides Function Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean Try + Dim trueValue As Boolean = Not What = Download.SavedPosts OrElse (Responser.CookiesExists And ACheck(SavedPostsUserName.Value)) + If Not trueValue Then Return False Dim dl As List(Of DownDetector.Data) = DownDetector.GetData("reddit") If dl.ListExists Then dl = dl.Take(4).ToList @@ -76,7 +73,7 @@ Namespace API.Reddit dl.ListToString(vbCr) & vbCr & vbCr & "Do you want to continue parsing Reddit data?", "There are outage reports on Reddit"}, vbYesNo) = vbYes Then UpdateRedGifsToken() - Return True + Return trueValue Else Return False End If @@ -84,28 +81,29 @@ Namespace API.Reddit End If End If UpdateRedGifsToken() - Return True + Return trueValue Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog + EDP.ReturnValue, ex, "[API.Reddit.SiteSettings.Available]", True) + Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, "[API.Reddit.SiteSettings.Available]", True) End Try End Function Private Sub UpdateRedGifsToken() DirectCast(Settings(RedGifs.RedGifsSiteKey).Source, RedGifs.SiteSettings).UpdateTokenIfRequired() End Sub - Friend Overrides Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable - Dim spf$ = String.Empty - Dim f As SFile = GetSpecialDataFile(Path, AskForPath, spf) - f = $"{f.PathWithSeparator}OptionalPath\" - Return UserData.GetVideoInfo(URL, Responser, f, spf) - End Function Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) If Options Is Nothing OrElse Not TypeOf Options Is RedditViewExchange Then Options = New RedditViewExchange If OpenForm Then Using f As New RedditViewSettingsForm(Options) : f.ShowDialog() : End Using End If End Sub + Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String + With DirectCast(User, UserData) : Return String.Format(UrlPatternUser, IIf(.IsChannel, ChannelOption, "user"), .TrueName) : End With + End Function Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String - Return $"https://www.reddit.com/comments/{Media.Post.ID.Split("_").LastOrDefault}/" + If Not Media.Post.ID.IsEmptyString Then + Return $"https://www.reddit.com/comments/{Media.Post.ID.Split("_").LastOrDefault}/" + Else + Return String.Empty + End If End Function End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Reddit/UserData.vb b/SCrawler/API/Reddit/UserData.vb index 71917d2..87cbd73 100644 --- a/SCrawler/API/Reddit/UserData.vb +++ b/SCrawler/API/Reddit/UserData.vb @@ -10,10 +10,10 @@ Imports System.Net Imports System.Threading Imports SCrawler.API.Base Imports SCrawler.API.Reddit.RedditViewExchange +Imports SCrawler.API.YouTube.Objects Imports SCrawler.Plugin.Hosts Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions -Imports PersonalUtilities.Tools.ImageRenderer Imports PersonalUtilities.Tools.Web.Clients Imports PersonalUtilities.Tools.Web.Documents.JSON Imports UStates = SCrawler.API.Base.UserMedia.States @@ -22,14 +22,20 @@ Imports CView = SCrawler.API.Reddit.IRedditView.View Imports CPeriod = SCrawler.API.Reddit.IRedditView.Period Namespace API.Reddit Friend Class UserData : Inherits UserDataBase : Implements IChannelData, IRedditView +#Region "XML names" + Private Const Name_TrueName As String = "TrueName" +#End Region +#Region "Declarations" + Private Const CannelsLabelName As String = "Channels" + Friend Const CannelsLabelName_ChannelsForm As String = "RChannels" Private ReadOnly Property MySiteSettings As SiteSettings Get Return DirectCast(HOST.Source, SiteSettings) End Get End Property - Private Shared ReadOnly Property DateTrueProvider(ByVal IsChannel As Boolean) As IFormatProvider + Private ReadOnly Property DateTrueProvider(ByVal IsChannel As Boolean) As IFormatProvider Get - Return If(IsChannel, DateProviderChannel, DateProvider) + Return If(IsChannel, UnixDate32ProviderReddit, UnixDate64Provider) End Get End Property Private ReadOnly Property UseM3U8 As Boolean @@ -37,6 +43,9 @@ Namespace API.Reddit Return Settings.UseM3U8 And CBool(DirectCast(HOST.Source, SiteSettings).UseM3U8.Value) End Get End Property + Friend Property IsChannel As Boolean = False + Friend Property TrueName As String = String.Empty +#End Region #Region "Channels Support" #Region "IChannelLimits Support" Friend Property DownloadLimitCount As Integer? Implements IChannelLimits.DownloadLimitCount @@ -60,7 +69,7 @@ Namespace API.Reddit #End Region Friend Property ChannelInfo As Channel Private ReadOnly ChannelPostsNames As List(Of String) - Friend Property SkipExistsUsers As Boolean = True Implements IChannelData.SkipExistsUsers + Friend Property SkipExistsUsers As Boolean = False Implements IChannelData.SkipExistsUsers Private ReadOnly _ExistsUsersNames As List(Of String) Friend Property SaveToCache As Boolean = False Implements IChannelData.SaveToCache Friend Function GetNewChannelPosts() As IEnumerable(Of UserPost) @@ -109,17 +118,49 @@ Namespace API.Reddit ChannelPostsNames = New List(Of String) _ExistsUsersNames = New List(Of String) _CrossPosts = New List(Of String) + UseMD5Comparison = True + StartMD5Checked = True + RemoveExistingDuplicates = False + UseInternalDownloadFileFunction = True + UseInternalM3U8Function = True End Sub #End Region #Region "Load and Update user info" + Private Sub UpdateNames() + If TrueName.IsEmptyString Then + Dim n$() = Name.Split("@") + If n.ListExists Then + If n.Length = 2 Then + TrueName = n(0) + IsChannel = True + ElseIf IsChannel Then + TrueName = Name + Else + TrueName = n(0) + End If + End If + If Not IsSavedPosts Then + Dim l$ = IIf(IsChannel, CannelsLabelName, UserLabelName) + Settings.Labels.Add(l) + Labels.ListAddValue(l, LNC) + Labels.Sort() + End If + End If + End Sub Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean) With Container If Loading Then ViewMode = .Value(Name_ViewMode).FromXML(Of Integer)(CInt(CView.New)) ViewPeriod = .Value(Name_ViewPeriod).FromXML(Of Integer)(CInt(CPeriod.All)) + IsChannel = .Value(Name_IsChannel).FromXML(Of Boolean)(False) + TrueName = .Value(Name_TrueName) + UpdateNames() Else + UpdateNames() .Add(Name_ViewMode, CInt(ViewMode)) .Add(Name_ViewPeriod, CInt(ViewPeriod)) + .Add(Name_IsChannel, IsChannel.BoolToInteger) + .Add(Name_TrueName, TrueName) End If End With End Sub @@ -133,7 +174,13 @@ Namespace API.Reddit #Region "Download Overrides" Friend Overrides Sub DownloadData(ByVal Token As CancellationToken) _CrossPosts.Clear() + If CreatedByChannel And Settings.FromChannelDownloadTopUse And Settings.FromChannelDownloadTop > 0 Then _ + DownloadTopCount = Settings.FromChannelDownloadTop.Value + If IsChannel Or IsSavedPosts Then UseMD5Comparison = False + If IsSavedPosts Then TrueName = MySiteSettings.SavedPostsUserName.Value + UpdateNames() If Not IsSavedPosts AndAlso (IsChannel AndAlso Not ChannelInfo Is Nothing) Then + UseMD5Comparison = False EnvirDownloadSet() If Not Responser Is Nothing Then Responser.Dispose() Responser = New Responser @@ -152,7 +199,6 @@ Namespace API.Reddit Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) _TotalPostsDownloaded = 0 If IsSavedPosts Then - 'TODO: Reddit saved posts: remove Unicode converter? Responser.DecodersError = EDP.ReturnValue DownloadDataChannel(String.Empty, Token) ElseIf IsChannel Then @@ -183,257 +229,195 @@ Namespace API.Reddit Private ReadOnly _CrossPosts As List(Of String) Private Const SiteGfycatKey As String = "gfycat" Private Const SiteRedGifsKey As String = "redgifs" + Private Const Node_CrosspostRootId As String = "crosspostRootId" + Private Const Node_CrosspostParentId As String = "crosspostParentId" + Private Const Node_CrosspostParent As String = "crosspost_parent" Private Sub DownloadDataUser(ByVal POST As String, ByVal Token As CancellationToken) - Const CPRI$ = "crosspostRootId" - Const CPPI$ = "crosspostParentId" + Dim eObj% = 0 + Dim round% = 0 Dim URL$ = String.Empty - Try - Dim PostID$ = String.Empty, PostTmp$ = String.Empty - Dim PostDate$ - Dim n As EContainer, nn As EContainer, s As EContainer - Dim NewPostDetected As Boolean = False - Dim ExistsDetected As Boolean = False - Dim _ItemsBefore% - Dim added As Boolean - Dim __ItemType$ - Dim tmpType As UTypes - Dim IsCrossPost As Predicate(Of EContainer) = Function(e) Not (e.Value(CPRI).IsEmptyString And e.Value(CPPI).IsEmptyString) - Dim CheckNode As Predicate(Of EContainer) = Function(e) Not ParseUserMediaOnly OrElse e("author").XmlIfNothingValue("/").ToLower.Equals(Name.ToLower) - Dim UPicType As Func(Of String, UTypes) = Function(input) IIf(input = "image", UTypes.Picture, UTypes.GIF) - Dim _PostID As Func(Of String) = Function() IIf(PostTmp.IsEmptyString, PostID, PostTmp) + Dim _completed As Boolean = False + Do + round += 1 + Try + Dim PostID$ = String.Empty, PostTmp$ = String.Empty + Dim PostDate$ + Dim n As EContainer, nn As EContainer + Dim NewPostDetected As Boolean = False + Dim ExistsDetected As Boolean = False + Dim IsCrossPost As Predicate(Of EContainer) = Function(e) Not e.Value(Node_CrosspostRootId).IsEmptyString Or Not e.Value(Node_CrosspostParentId).IsEmptyString Or Not e.Value(Node_CrosspostParent).IsEmptyString + Dim CheckNode As Predicate(Of EContainer) = Function(e) Not ParseUserMediaOnly OrElse If(e("author")?.Value, "/").ToLower.Equals(TrueName.StringToLower) + Dim UPicType As Func(Of String, UTypes) = Function(input) IIf(input = "image", UTypes.Picture, UTypes.GIF) + Dim _PostID As Func(Of String) = Function() PostTmp.IfNullOrEmpty(PostID) - URL = $"https://gateway.reddit.com/desktopapi/v1/user/{Name}/posts?rtj=only&allow_quarantined=true&allow_over18=1&include=identity&after={POST}&dist=25&sort={View}&t={Period}&layout=classic" - ThrowAny(Token) - Dim r$ = Responser.GetResponse(URL,, EDP.ThrowException) - If Not r.IsEmptyString Then - Using w As EContainer = JsonDocument.Parse(r).XmlIfNothing - If w.Count > 0 Then - 'TODELETE: moved to 'GetUserInfo' 2023.2.5.0 - 'If UserDescriptionNeedToUpdate() Then UserDescriptionUpdate(w.ItemF({"subredditAboutInfo", 0, "publicDescription"}).XmlIfNothingValue) - n = w.GetNode(JsonNodesJson) - If Not n Is Nothing AndAlso n.Count > 0 Then - For Each nn In n - ThrowAny(Token) - If nn.Count > 0 Then - If CheckNode(nn) Then + URL = $"https://gateway.reddit.com/desktopapi/v1/user/{TrueName}/posts?rtj=only&allow_quarantined=true&allow_over18=1&include=identity&after={POST}&dist=25&sort={View}&t={Period}&layout=classic" + ThrowAny(Token) + Dim r$ = Responser.GetResponse(URL) + If Not r.IsEmptyString Then + Using w As EContainer = JsonDocument.Parse(r).XmlIfNothing + If w.Count > 0 Then + n = w.GetNode(JsonNodesJson) + If Not n Is Nothing AndAlso n.Count > 0 Then + For Each nn In n + ThrowAny(Token) + If nn.Count > 0 Then + If CheckNode(nn) Then - 'Obtain post ID - PostTmp = nn.Name - If PostTmp.IsEmptyString Then PostTmp = nn.Value("id") - If PostTmp.IsEmptyString Then Continue For - 'Check for CrossPost - If IsCrossPost(nn) Then - _CrossPosts.ListAddList({nn.Value(CPRI), nn.Value(CPPI)}, LNC) - Continue For - Else - If Not _CrossPosts.Contains(PostTmp) Then PostID = PostTmp : PostTmp = String.Empty - End If - - 'Download decision - If Not _TempPostsList.Contains(_PostID()) Then - NewPostDetected = True - _TempPostsList.Add(_PostID()) - Else - If Not _CrossPosts.Contains(_PostID()) Then ExistsDetected = True - Continue For - End If - If nn.Contains("created") Then PostDate = nn("created").Value Else PostDate = String.Empty - Select Case CheckDatesLimit(PostDate, DateTrueProvider(IsChannel)) - Case DateResult.Skip : Continue For - Case DateResult.Exit : Exit Sub - End Select - - _ItemsBefore = _TempMediaList.Count - added = True - s = nn.ItemF({"source", "url"}) - If s.XmlIfNothingValue("/").StringContains({$"{SiteRedGifsKey}.com", $"{SiteGfycatKey}.com"}) Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.VideoPre, s.Value, _PostID(), PostDate,, IsChannel), LNC) - ElseIf Not CreateImgurMedia(s.XmlIfNothingValue, _PostID(), PostDate,, IsChannel) Then - s = nn.ItemF({"media"}).XmlIfNothing - __ItemType = s("type").XmlIfNothingValue - Select Case __ItemType - Case "gallery" : If Not DownloadGallery(s, _PostID(), PostDate) Then added = False - Case "image", "gifvideo" - If s.Contains("content") Then - _TempMediaList.ListAddValue(MediaFromData(UPicType(__ItemType), s.Value("content"), - _PostID(), PostDate,, IsChannel), LNC) - Else - added = False - End If - Case "video" - If UseM3U8 AndAlso s("hlsUrl").XmlIfNothingValue("/").ToLower.Contains("m3u8") Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.m3u8, s.Value("hlsUrl"), - _PostID(), PostDate,, IsChannel), LNC) - ElseIf Not UseM3U8 AndAlso s("fallback_url").XmlIfNothingValue("/").ToLower.Contains("mp4") Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, s.Value("fallback_url"), - _PostID(), PostDate,, IsChannel), LNC) - Else - added = False - End If - Case Else : added = False - End Select - End If - If Not added Then - s = nn.ItemF({"source", "url"}).XmlIfNothing - If Not s.IsEmptyString AndAlso TryFile(s.Value) Then - With s.Value.ToLower - Select Case True - Case .Contains(SiteRedGifsKey), .Contains(SiteGfycatKey) : tmpType = UTypes.VideoPre - Case .Contains("m3u8") : If Settings.UseM3U8 Then tmpType = UTypes.m3u8 - Case .Contains(".gif") And TryFile(s.Value) : tmpType = UTypes.GIF - Case TryFile(s.Value) : tmpType = UTypes.Picture - Case Else : tmpType = UTypes.Undefined - End Select - End With - If Not tmpType = UTypes.Undefined Then - _TempMediaList.ListAddValue(MediaFromData(tmpType, s.Value, _PostID(), PostDate,, IsChannel), LNC) - End If + 'Obtain post ID + PostTmp = nn.Name + If PostTmp.IsEmptyString Then PostTmp = nn.Value("id") + If PostTmp.IsEmptyString Then Continue For + 'Check for CrossPost + If IsCrossPost(nn) Then + _CrossPosts.ListAddList({nn.Value(Node_CrosspostRootId), + nn.Value(Node_CrosspostParentId), + nn.Value(Node_CrosspostParent)}, LNC) + Continue For + Else + If Not _CrossPosts.Contains(PostTmp) Then PostID = PostTmp : PostTmp = String.Empty End If + + 'Download decision + If Not _TempPostsList.Contains(_PostID()) Then + NewPostDetected = True + _TempPostsList.Add(_PostID()) + Else + If Not _CrossPosts.Contains(_PostID()) Then ExistsDetected = True + Continue For + End If + If nn.Contains("created") Then PostDate = nn("created").Value Else PostDate = String.Empty + Select Case CheckDatesLimit(PostDate, DateTrueProvider(IsChannel)) + Case DateResult.Skip : Continue For + Case DateResult.Exit : Exit Sub + End Select + + ParseContainer(nn, _PostID(), PostDate) End If End If - End If - Next + Next + End If End If - End If - End Using - If POST.IsEmptyString And ExistsDetected Then Exit Sub - If Not PostID.IsEmptyString And NewPostDetected Then DownloadDataUser(PostID, Token) - End If - Catch ex As Exception - ProcessException(ex, Token, $"data downloading error [{URL}]") - End Try + End Using + If POST.IsEmptyString And ExistsDetected Then Exit Sub + If Not PostID.IsEmptyString And NewPostDetected Then DownloadDataUser(PostID, Token) + End If + _completed = True + Catch ex As Exception + If ProcessException(ex, Token, $"data downloading error [{URL}]",, eObj) = HttpStatusCode.InternalServerError Then + If round = 2 Then eObj = HttpStatusCode.InternalServerError + Else + _completed = True + End If + End Try + Loop While Not _completed End Sub Private Sub DownloadDataChannel(ByVal POST As String, ByVal Token As CancellationToken) + Dim eObj% = 0 + Dim round% = 0 Dim URL$ = String.Empty - Try - Dim PostID$ = String.Empty - Dim PostDate$, _UserID$, tmpUrl$ - Dim n As EContainer, nn As EContainer, s As EContainer, ss As EContainer - Dim NewPostDetected As Boolean = False - Dim ExistsDetected As Boolean = False - Dim eCount As Predicate(Of EContainer) = Function(e) e.Count > 0 - Dim lDate As Date? + Dim _completed As Boolean = False + Do + round += 1 + Try + Dim PostID$ = String.Empty + Dim PostDate$, _UserID$ + Dim n As EContainer, nn As EContainer, s As EContainer + Dim NewPostDetected As Boolean = False + Dim ExistsDetected As Boolean = False + Dim eCount As Predicate(Of EContainer) = Function(e) e.Count > 0 + Dim lDate As Date? - If IsSavedPosts Then - URL = $"https://www.reddit.com/user/{Name}/saved.json?after={POST}" - Else - URL = $"https://reddit.com/r/{Name}/{View}.json?allow_quarantined=true&allow_over18=1&include=identity&after={POST}&dist=25&sort={View}&t={Period}&layout=classic" - End If + If IsSavedPosts Then + URL = $"https://www.reddit.com/user/{TrueName}/saved.json?after={POST}" + Else + URL = $"https://reddit.com/r/{TrueName}/{View}.json?allow_quarantined=true&allow_over18=1&include=identity&after={POST}&dist=25&sort={View}&t={Period}&layout=classic" + End If - ThrowAny(Token) - Dim r$ = Responser.GetResponse(URL,, EDP.ThrowException) - If Not r.IsEmptyString Then - Using w As EContainer = JsonDocument.Parse(r).XmlIfNothing - If w.Count > 0 Then - n = w.GetNode(ChannelJsonNodes) - If Not n Is Nothing AndAlso n.Count > 0 Then - For Each nn In n - ThrowAny(Token) - s = nn.ItemF({eCount}) - If Not s Is Nothing AndAlso s.Count > 0 Then - PostID = s.Value("name") - If PostID.IsEmptyString AndAlso s.Contains("id") Then PostID = s("id").Value + ThrowAny(Token) + Dim r$ = Responser.GetResponse(URL) + If Not r.IsEmptyString Then + Using w As EContainer = JsonDocument.Parse(r).XmlIfNothing + If w.Count > 0 Then + n = w.GetNode(ChannelJsonNodes) + If Not n Is Nothing AndAlso n.Count > 0 Then + For Each nn In n + ThrowAny(Token) + s = nn.ItemF({eCount}) + If If(s?.Count, 0) > 0 Then + PostID = s.Value("name") + If PostID.IsEmptyString AndAlso s.Contains("id") Then PostID = s("id").Value - If ChannelPostsNames.Contains(PostID) Then - If ViewMode = CView.New Then ExistsDetected = True Else NewPostDetected = True 'bypass - Continue For - End If - If DownloadLimitCount.HasValue AndAlso _TotalPostsDownloaded >= DownloadLimitCount.Value Then Exit Sub - If Not DownloadLimitPost.IsEmptyString AndAlso DownloadLimitPost = PostID Then Exit Sub - If ViewMode = CView.New AndAlso DownloadLimitDate.HasValue AndAlso _TempMediaList.Count > 0 Then - With (From __u In _TempMediaList Where __u.Post.Date.HasValue Select __u.Post.Date.Value) - If .Count > 0 Then lDate = .Min Else lDate = Nothing - End With - If lDate.HasValue AndAlso lDate.Value <= DownloadLimitDate.Value Then Exit Sub - End If - - If IsSavedPosts Then - If Not _TempPostsList.Contains(PostID) Then - NewPostDetected = True - _TempPostsList.Add(PostID) - Else - ExistsDetected = True + If ChannelPostsNames.Contains(PostID) Then + If ViewMode = CView.New Then ExistsDetected = True Else NewPostDetected = True 'bypass Continue For End If - Else - NewPostDetected = True - End If + If DownloadLimitCount.HasValue AndAlso _TotalPostsDownloaded >= DownloadLimitCount.Value Then Exit Sub + If Not DownloadLimitPost.IsEmptyString AndAlso DownloadLimitPost = PostID Then Exit Sub + If ViewMode = CView.New AndAlso DownloadLimitDate.HasValue AndAlso _TempMediaList.Count > 0 Then + With (From __u In _TempMediaList Where __u.Post.Date.HasValue Select __u.Post.Date.Value) + If .Count > 0 Then lDate = .Min Else lDate = Nothing + End With + If lDate.HasValue AndAlso lDate.Value <= DownloadLimitDate.Value Then Exit Sub + End If - If s.Contains("created") Then PostDate = s("created").Value Else PostDate = String.Empty - _UserID = s.Value("author") - - If Not IsSavedPosts AndAlso SkipExistsUsers AndAlso _ExistsUsersNames.Count > 0 AndAlso - Not _UserID.IsEmptyString AndAlso _ExistsUsersNames.Contains(_UserID) Then - If Not IsSavedPosts AndAlso Not ChannelInfo Is Nothing Then _ - ChannelInfo.ChannelExistentUserNames.ListAddValue(_UserID, LNC) - Continue For - End If - - tmpUrl = s.Value("url") - If Not tmpUrl.IsEmptyString AndAlso tmpUrl.StringContains({"redgifs.com", "gfycat.com"}) Then - If SaveToCache Then - tmpUrl = s.Value({"media", "oembed"}, "thumbnail_url") - If Not tmpUrl.IsEmptyString Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, _UserID, IsChannel), LNC) - _TotalPostsDownloaded += 1 + If IsSavedPosts Then + If Not _TempPostsList.Contains(PostID) Then + NewPostDetected = True + _TempPostsList.Add(PostID) + Else + ExistsDetected = True + Continue For End If Else - _TempMediaList.ListAddValue(MediaFromData(UTypes.VideoPre, tmpUrl, PostID, PostDate, _UserID, IsChannel), LNC) - _TotalPostsDownloaded += 1 + NewPostDetected = True End If - ElseIf Not s.Value({"media", "reddit_video"}, "fallback_url").IsEmptyString Then - tmpUrl = s.Value({"media", "reddit_video"}, "fallback_url") - If SaveToCache Then - tmpUrl = GetVideoRedditPreview(s) - If Not tmpUrl.IsEmptyString Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, _UserID, IsChannel, False), LNC) - _TotalPostsDownloaded += 1 - End If - ElseIf UseM3U8 AndAlso Not s.Value({"media", "reddit_video"}, "hls_url").IsEmptyString Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.m3u8, s.Value({"media", "reddit_video"}, "hls_url"), - PostID, PostDate, _UserID, IsChannel), LNC) - Else - _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, tmpUrl, PostID, PostDate, _UserID, IsChannel), LNC) - _TotalPostsDownloaded += 1 - End If - ElseIf CreateImgurMedia(tmpUrl, PostID, PostDate, _UserID, IsChannel) Then - _TotalPostsDownloaded += 1 - ElseIf s.Item("media_metadata").XmlIfNothing.Count > 0 Then - DownloadGallery(s, PostID, PostDate, _UserID, SaveToCache) - _TotalPostsDownloaded += 1 - ElseIf s.Contains("preview") Then - ss = s.ItemF({"preview", "images", eCount, "source", "url"}).XmlIfNothing - If Not ss.Value.IsEmptyString Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, ss.Value, PostID, PostDate, _UserID, IsChannel), LNC) - _TotalPostsDownloaded += 1 + + If s.Contains("created") Then PostDate = s("created").Value Else PostDate = String.Empty + _UserID = s.Value("author") + + If Not IsSavedPosts AndAlso SkipExistsUsers AndAlso _ExistsUsersNames.Count > 0 AndAlso + Not _UserID.IsEmptyString AndAlso _ExistsUsersNames.Contains(_UserID) Then + If Not IsSavedPosts AndAlso Not ChannelInfo Is Nothing Then _ + ChannelInfo.ChannelExistentUserNames.ListAddValue(_UserID, LNC) + Continue For End If + + ParseContainer(s, PostID, PostDate, _UserID) End If - End If - Next + Next + End If End If - End If - End Using - If POST.IsEmptyString And ExistsDetected Then Exit Sub - If Not PostID.IsEmptyString And NewPostDetected Then DownloadDataChannel(PostID, Token) - End If - Catch ex As Exception - ProcessException(ex, Token, $"channel data downloading error [{URL}]") - End Try + End Using + If POST.IsEmptyString And ExistsDetected Then Exit Sub + If Not PostID.IsEmptyString And NewPostDetected Then DownloadDataChannel(PostID, Token) + End If + _completed = True + Catch ex As Exception + If ProcessException(ex, Token, $"channel data downloading error [{URL}]",, eObj) = HttpStatusCode.InternalServerError Then + If round = 2 Then eObj = HttpStatusCode.InternalServerError + Else + _completed = True + End If + End Try + Loop While Not _completed End Sub +#End Region +#Region "GetUserInfo" Private Sub GetUserInfo() Try If Not IsSavedPosts And ChannelInfo Is Nothing Then - Dim r$ = Responser.GetResponse($"https://reddit.com/{IIf(IsChannel, "r", "user")}/{Name}/about.json",, EDP.ReturnValue) + Dim r$ = Responser.GetResponse($"https://reddit.com/{IIf(IsChannel, "r", "user")}/{TrueName}/about.json",, EDP.ReturnValue) If Not r.IsEmptyString Then Using j As EContainer = JsonDocument.Parse(r) If Not j Is Nothing AndAlso j.Contains({"data", "subreddit"}) Then + If ID.IsEmptyString Then ID = j.Value({"data"}, "id") With j({"data", "subreddit"}) UserSiteNameUpdate(.Value("title")) UserDescriptionUpdate(.Value("public_description")) Dim dir As SFile = MyFile.CutPath Dim __getFile As Action(Of String) = Sub(ByVal img As String) If Not img.IsEmptyString Then - Dim f As SFile = UrlToFile(img) + Dim f As SFile = CreateFileFromUrl(img) If Not f.Name.IsEmptyString Then If f.Extension.IsEmptyString Then f.Extension = "jpg" f.Path = dir.Path @@ -452,29 +436,149 @@ Namespace API.Reddit End Try End Sub #End Region +#Region "ParseContainer" + Private Function ParseContainer(ByVal e As EContainer, ByVal PostID As String, ByVal PostDate As String, Optional ByVal UserID As String = Nothing, + Optional ByVal AllowReparse As Boolean = True) As Boolean + If Not e Is Nothing Then + Dim UPicType As Func(Of String, UTypes) = Function(input) IIf(input = "image", UTypes.Picture, UTypes.GIF) + Dim eCount As Predicate(Of EContainer) = Function(item) item.Count > 0 + Dim added As Boolean = True + Dim tmpUrl$ = e.Value("url").IfNullOrEmpty(e.Value({"source"}, "url")) + If Not tmpUrl.IsEmptyString AndAlso tmpUrl.StringContains({$"{SiteRedGifsKey}.com", $"{SiteGfycatKey}.com"}) Then + If SaveToCache Then + tmpUrl = e.Value({"media", "oembed"}, "thumbnail_url") + If Not tmpUrl.IsEmptyString Then + _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + Else + added = False + End If + Else + _TempMediaList.ListAddValue(MediaFromData(UTypes.VideoPre, tmpUrl, PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + End If + ElseIf CreateImgurMedia(tmpUrl, PostID, PostDate, UserID, IsChannel) Then + _TotalPostsDownloaded += 1 + ElseIf DownloadGallery(e, PostID, PostDate, UserID, SaveToCache) Then + _TotalPostsDownloaded += 1 + ElseIf Not If(e({"media"}, "type")?.Value, String.Empty).IsEmptyString Then + With e("media") + Dim t$ = .Item("type").Value + Select Case t + Case "gallery" : If DownloadGallery(.Self, PostID, PostDate) Then _TotalPostsDownloaded += 1 Else added = False + Case "image", "gifvideo" + If .Contains("content") Then + _TempMediaList.ListAddValue(MediaFromData(UPicType(t), .Value("content"), PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + Else + added = False + End If + Case "video" + If UseM3U8 AndAlso .Item("hlsUrl").XmlIfNothingValue("/").ToLower.Contains("m3u8") Then + _TempMediaList.ListAddValue(MediaFromData(UTypes.m3u8, .Value("hlsUrl"), PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + ElseIf Not UseM3U8 AndAlso .Item("fallback_url").XmlIfNothingValue("/").ToLower.Contains("mp4") Then + _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, .Value("fallback_url"), PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + Else + added = False + End If + Case Else : added = False + End Select + End With + ElseIf Not If(e({"media", "reddit_video"}, "fallback_url")?.Value, String.Empty).IsEmptyString Then + tmpUrl = e({"media", "reddit_video"}, "fallback_url").Value + If SaveToCache Then + tmpUrl = GetVideoRedditPreview(e) + If Not tmpUrl.IsEmptyString Then + _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, UserID, False), LNC) + _TotalPostsDownloaded += 1 + Else + added = False + End If + ElseIf UseM3U8 AndAlso Not If(e({"media", "reddit_video"}, "hls_url")?.Value, String.Empty).IsEmptyString Then + _TempMediaList.ListAddValue(MediaFromData(UTypes.m3u8, e.Value({"media", "reddit_video"}, "hls_url"), PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + Else + _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, tmpUrl, PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + End If + Else + added = False + End If + If Not added Then + If AllowReparse Then + If If(e.ItemF({"crosspost_parent_list", 0})?.Count, 0) > 0 Then + added = ParseContainer(e.ItemF({"crosspost_parent_list", 0}), PostID, PostDate, UserID, True) + Else + Dim tPostId$ = e.Value(Node_CrosspostParent).IfNullOrEmpty(e.Value(Node_CrosspostParentId)).IfNullOrEmpty(e.Value(Node_CrosspostRootId)) + Dim r$ = Responser.GetResponse($"https://www.reddit.com/comments/{tPostId.Split("_").LastOrDefault}/.json",, EDP.ReturnValue) + If Not r.IsEmptyString Then + Using j As EContainer = JsonDocument.Parse(r, EDP.ReturnValue) + If j.ListExists Then + With j.ItemF({0, "data", "children", 0, "data"}) + If .ListExists Then added = ParseContainer(.Self, PostID, PostDate, UserID, False) + End With + End If + End Using + End If + End If + End If + If Not added Then + Dim node As EContainer = e({"source", "url"}) + Dim tmpType As UTypes = UTypes.Undefined + If Not If(node?.Value, String.Empty).IsEmptyString Then + With node.Value.ToLower + Select Case True + Case .Contains(SiteRedGifsKey), .Contains(SiteGfycatKey) : If Not SaveToCache Then tmpType = UTypes.VideoPre + Case .Contains("m3u8") : If Settings.UseM3U8 And Not SaveToCache Then tmpType = UTypes.m3u8 + Case .Contains(".gif") And TryFile(node.Value) : tmpType = UTypes.GIF + Case TryFile(node.Value) : tmpType = UTypes.Picture + Case Else : tmpType = UTypes.Undefined + End Select + End With + If Not tmpType = UTypes.Undefined Then + _TempMediaList.ListAddValue(MediaFromData(tmpType, node.Value, PostID, PostDate, UserID), LNC) + added = True + End If + End If + If Not added And e.Contains("preview") Then + tmpUrl = If(e.ItemF({"preview", "images", eCount, "source", "url"})?.Value, String.Empty) + If Not tmpUrl.IsEmptyString Then + _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, UserID), LNC) + _TotalPostsDownloaded += 1 + added = True + End If + End If + End If + End If + Return added + Else + Return False + End If + End Function +#End Region #Region "Download Base Functions" Private Function CreateImgurMedia(ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, Optional ByVal _UserID As String = "", Optional ByVal IsChannel As Boolean = False) As Boolean If Not _URL.IsEmptyString AndAlso _URL.Contains("imgur") Then If _URL.StringContains({".jpg", ".png", ".jpeg"}) Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL, PostID, PostDate, _UserID, IsChannel), LNC) + _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL, PostID, PostDate, _UserID), LNC) ElseIf _URL.Contains(".gifv") Then If SaveToCache Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL.Replace(".gifv", ".gif"), - PostID, PostDate, _UserID, IsChannel), LNC) + _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL.Replace(".gifv", ".gif"), PostID, PostDate, _UserID), LNC) Else - _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL.Replace(".gifv", ".mp4"), - PostID, PostDate, _UserID, IsChannel), LNC) + _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL.Replace(".gifv", ".mp4"), PostID, PostDate, _UserID), LNC) End If ElseIf _URL.Contains(".mp4") Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL, PostID, PostDate, _UserID, IsChannel), LNC) + _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL, PostID, PostDate, _UserID), LNC) ElseIf _URL.Contains(".gif") Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.GIF, _URL, PostID, PostDate, _UserID, IsChannel), LNC) + _TempMediaList.ListAddValue(MediaFromData(UTypes.GIF, _URL, PostID, PostDate, _UserID), LNC) Else Dim obj As IEnumerable(Of UserMedia) = Imgur.Envir.GetVideoInfo(_URL, EDP.ReturnValue) If Not obj.ListExists Then If Not TryFile(_URL) Then _URL &= ".jpg" - _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL, PostID, PostDate, _UserID, IsChannel), LNC) + _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL, PostID, PostDate, _UserID), LNC) Else Dim ut As UTypes Dim m As UserMedia @@ -489,7 +593,7 @@ Namespace API.Reddit Case "gif" : ut = UTypes.GIF Case Else : ut = UTypes.Picture : .File.Extension = "jpg" End Select - m = MediaFromData(ut, _URL, PostID, PostDate, _UserID, IsChannel) + m = MediaFromData(ut, _URL, PostID, PostDate, _UserID) m.URL = .URL m.File = .File.File _TempMediaList.ListAddValue(m, LNC) @@ -504,17 +608,22 @@ Namespace API.Reddit Return False End If End Function - Private Function DownloadGallery(ByVal w As EContainer, ByVal PostID As String, ByVal PostDate As String, + Private Function DownloadGallery(ByVal e As EContainer, ByVal PostID As String, ByVal PostDate As String, Optional ByVal _UserID As String = Nothing, Optional ByVal FirstOnly As Boolean = False) As Boolean Try Dim added As Boolean = False - Dim cn$ = IIf(IsChannel, "media_metadata", "mediaMetadata") - If Not w Is Nothing AndAlso w(cn).XmlIfNothing.Count > 0 Then + Dim node As EContainer = Nothing + If e.Contains("media_metadata") Then + node = e("media_metadata") + ElseIf e.Contains("mediaMetadata") Then + node = e("mediaMetadata") + End If + If If(node?.Count, 0) > 0 Then Dim t As EContainer - For Each n As EContainer In w(cn) + For Each n As EContainer In node t = n.ItemF({"s", "u"}) If Not t Is Nothing AndAlso Not t.Value.IsEmptyString Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, t.Value, PostID, PostDate, _UserID, IsChannel), LNC) + _TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, t.Value, PostID, PostDate, _UserID), LNC) added = True If FirstOnly Then Exit For End If @@ -558,6 +667,8 @@ Namespace API.Reddit Return String.Empty End Try End Function +#End Region +#Region "ReparseVideo" Protected Overrides Sub ReparseVideo(ByVal Token As CancellationToken) Dim RedGifsResponser As Responser = Nothing Try @@ -619,6 +730,8 @@ Namespace API.Reddit If Not RedGifsResponser Is Nothing Then RedGifsResponser.Dispose() End Try End Sub +#End Region +#Region "ReparseMissing" Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken) Dim rList As New List(Of Integer) Dim RedGifsResponser As Responser = Nothing @@ -628,21 +741,33 @@ Namespace API.Reddit Dim RedGifsHost As SettingsHost = Settings(RedGifs.RedGifsSiteKey) RedGifsResponser = RedGifsHost.Responser.Copy Dim m As UserMedia, m2 As UserMedia + Dim r$ + Dim j As EContainer + Dim lastCount%, li% For i% = 0 To _ContentList.Count - 1 m = _ContentList(i) If m.State = UStates.Missing AndAlso Not m.Post.ID.IsEmptyString Then ThrowAny(Token) - If Not m.URL.IsEmptyString AndAlso m.URL.Contains(SiteRedGifsKey) Then - m2 = RedGifs.UserData.GetDataFromUrlId(m.URL, False, RedGifsResponser, RedGifsHost) - If m2.State = RedGifs.UserData.DataGone Then - rList.Add(i) - ElseIf Not m2.Type = UTypes.Undefined And Not m2.State = UStates.Missing Then - m.Type = m2.Type - m.File = m2.File - m.URL_BASE = m.URL - m.URL = m2.URL - rList.Add(i) - _TempMediaList.ListAddValue(m, LNC) + r = Responser.GetResponse($"https://www.reddit.com/comments/{m.Post.ID.Split("_").LastOrDefault}/.json",, EDP.ReturnValue) + If Not r.IsEmptyString Then + j = JsonDocument.Parse(r, EDP.ReturnValue) + If Not j Is Nothing Then + If j.Count > 0 Then + lastCount = _TempMediaList.Count + With j.GetNode(SingleJsonNodes) + If .ListExists AndAlso ParseContainer(.Self, m.Post.ID, String.Empty) Then + If lastCount <> _TempMediaList.Count Then + For li = IIf(lastCount < 0, 0, lastCount) To _TempMediaList.Count - 1 + m2 = _TempMediaList(i) + m2.Post.Date = m.Post.Date + _TempMediaList(i) = m2 + Next + End If + rList.Add(i) + End If + End With + End If + j.Dispose() End If End If End If @@ -658,82 +783,27 @@ Namespace API.Reddit End If End Try End Sub - Private Sub ParsePost(ByVal URL As String) - Try - If Not URL.IsEmptyString Then - Dim __id$ = RegexReplace(URL, RParams.DMS("comments/([^/]+)", 1, EDP.ReturnValue)) - If Not __id.IsEmptyString Then - URL = $"https://www.reddit.com/comments/{__id.Split("_").LastOrDefault}/.json" - Dim r$ = Responser.GetResponse(URL,, EDP.ReturnValue) - If Not r.IsEmptyString Then - Using j As EContainer = JsonDocument.Parse(r).XmlIfNothing - With j.ItemF({0, "data", "children", 0, "data"}) - If .ListExists Then - If .Contains({"media"}, "reddit_video") Then - With .Item({"media"}, "reddit_video") - If UseM3U8 AndAlso .Item("hls_url").XmlIfNothingValue("/").ToLower.Contains("m3u8") Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.m3u8, .Value("hls_url"), __id, String.Empty), LNC) - ElseIf Not UseM3U8 AndAlso .Item("fallback_url").XmlIfNothingValue("/").ToLower.Contains("mp4") Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.Video, .Value("fallback_url"), __id, String.Empty), LNC) - End If - End With - ElseIf Not .Value("url").IsEmptyString Then - If .Value("url").StringContains({$"{SiteRedGifsKey}.com", $"{SiteGfycatKey}.com"}) Then - _TempMediaList.ListAddValue(MediaFromData(UTypes.VideoPre, .Value("url"), __id, String.Empty), LNC) - Else - CreateImgurMedia(.Value("url"), __id, String.Empty) - End If - End If - End If - End With - End Using - End If - End If - End If - Catch ex As Exception - ErrorsDescriber.Execute(EDP.SendInLog, ex, $"API.Reddit.ParsePost({URL})") - End Try +#End Region +#Region "DownloadSingleObject" + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim __id$ = RegexReplace(Data.URL, RParams.DMS("comments/([^/]+)", 1, EDP.ReturnValue)) + If Not __id.IsEmptyString Then + User.File = Data.File + User.File.Name = String.Empty + User.File.Extension = String.Empty + _ContentList.Add(New UserMedia With {.State = UStates.Missing, .Post = __id}) + ReparseMissing(Token) + ReparseVideo(Token) + End If End Sub - Private Class AbsProgress : Inherits PersonalUtilities.Forms.Toolbars.MyProgress - Public Overrides Sub Perform(Optional ByVal Value As Double = 1) - End Sub - End Class - Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal resp As Responser, ByVal f As SFile, ByVal SpecialFolder As String) As IEnumerable(Of UserMedia) - Try - If Not URL.IsEmptyString Then - Using r As New UserData - r.SetEnvironment(Settings(RedditSiteKey), Nothing, False, False) - r.Responser = New Responser - r.Responser.Copy(resp) - r.ParsePost(URL) - If r._TempMediaList.Count > 0 Then - r.ReparseVideo(Nothing) - If r._TempMediaList.Count > 0 Then - r._ContentNew.AddRange(r._TempMediaList) - r.Progress = New AbsProgress - r.User.File.Path = f.Path - r.SeparateVideoFolder = False - r.DownloadContent(Nothing) - If r._ContentNew.Exists(Function(c) c.State = UStates.Downloaded) Then _ - Return {New UserMedia With {.State = UStates.Downloaded, .SpecialFolder = SpecialFolder}} - End If - End If - End Using - End If - Return Nothing - Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, $"[API.Reddit.UserData.GetVideoInfo({URL})]") - End Try - End Function #End Region #Region "Structure creator" - Protected Shared Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, - Optional ByVal _UserID As String = "", Optional ByVal IsChannel As Boolean = False, - Optional ByVal ReplacePreview As Boolean = True) As UserMedia + Private Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, + Optional ByVal _UserID As String = "", Optional ByVal ReplacePreview As Boolean = True) As UserMedia If _URL.IsEmptyString And t = UTypes.Picture Then Return Nothing _URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern)) Dim m As New UserMedia(_URL, t) With {.Post = New UserPost With {.ID = PostID, .UserID = _UserID}} - If t = UTypes.Picture Or t = UTypes.GIF Then m.File = UrlToFile(m.URL) Else m.File = Nothing + If t = UTypes.Picture Or t = UTypes.GIF Then m.File = CreateFileFromUrl(m.URL) Else m.File = Nothing If ReplacePreview And m.URL.Contains("preview") Then m.URL = $"https://i.redd.it/{m.File.File}" If Not PostDate.IsEmptyString Then m.Post.Date = AConvert(Of Date)(PostDate, DateTrueProvider(IsChannel), Nothing) Else m.Post.Date = Nothing Return m @@ -741,203 +811,67 @@ Namespace API.Reddit Private Function TryFile(ByVal URL As String) As Boolean Try If Not URL.IsEmptyString AndAlso URL.StringContains({".jpg", ".png", ".jpeg"}) Then - Dim f As SFile = CStr(RegexReplace(URL, FilesPattern)) - Return Not f.File.IsEmptyString + Return Not CreateFileFromUrl(URL).IsEmptyString + Else + Return False End If - Return False Catch ex As Exception Return False End Try End Function - Private Shared Function UrlToFile(ByVal URL As String) As SFile - Return CStr(RegexReplace(URL, FilesPattern)) + Protected Overrides Function CreateFileFromUrl(ByVal URL As String) As SFile + Return New SFile(CStr(RegexReplace(URL, FilesPattern))) End Function #End Region +#Region "DownloadContent" + Private _RedGifsResponser As Responser = Nothing Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) - Dim RedGifsResponser As Responser = Nothing - Try - Const _RFN$ = "RedditVideo" - Const RFN$ = _RFN & "{0}" - Dim i% - Dim dCount% = 0, dTotal% = 0 - ThrowAny(Token) - If _ContentNew.Count > 0 Then - _ContentNew.RemoveAll(Function(c) c.URL.IsEmptyString) - If _ContentNew.Count > 0 Then - RedGifsResponser = Settings(RedGifs.RedGifsSiteKey).Responser.Copy - MyFile.Exists(SFO.Path) - Dim MissingErrorsAdd As Boolean = Settings.AddMissingErrorsToLog - Dim IsImgurStuff As Boolean - Dim MyDir$ - If Not IsSavedPosts AndAlso (IsChannel And SaveToCache And Not ChannelInfo Is Nothing) Then - MyDir = ChannelInfo.CachePath.PathNoSeparator - Else - MyDir = MyFile.CutPath.PathNoSeparator - End If - Dim StartRFN% = 0 - If _ContentNew.Exists(Function(c) c.Type = UTypes.Video And c.URL.Contains("redd.it")) Then - StartRFN = SFile.Indexed_GetMaxIndex($"{MyDir}\{IIf(SeparateVideoFolderF, "Video\", String.Empty)}{_RFN}.mp4",, New SFileNumbers(_RFN, String.Empty), EDP.ReturnValue) - End If - Dim HashList As New List(Of String) - If _ContentList.Count > 0 Then HashList.ListAddList((From h In _ContentList Where Not h.MD5.IsEmptyString Select h.MD5), LNC) - Dim f As SFile - Dim v As UserMedia - Dim cached As Boolean = IsChannel And SaveToCache - Dim vsf As Boolean = SeparateVideoFolderF - Dim UseMD5 As Boolean = Not IsChannel Or (Not cached And Settings.ChannelsRegularCheckMD5) - Dim bDP As New ErrorsDescriber(EDP.None) - Dim RGRERROR As New ErrorsDescriber(EDP.ThrowException) - Dim ImgurUrls As New List(Of String) - Dim TryBytes As Func(Of String, Imaging.ImageFormat, String) = - Function(ByVal __URL As String, ByVal ImgFormat As Imaging.ImageFormat) As String - Try - Return ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__URL, bDP), ImgFormat)) - Catch hash_ex As Exception - Return String.Empty - End Try - End Function - Dim MD5BS As Func(Of String, UTypes, - SFile, Boolean, String) = Function(ByVal __URL As String, ByVal __MT As UTypes, - ByVal __File As SFile, ByVal __IsBase As Boolean) As String - Try - ImgurUrls.Clear() - Dim ImgFormat As Imaging.ImageFormat - If __MT = UTypes.GIF Then - ImgFormat = Imaging.ImageFormat.Gif - ElseIf __IsBase Then - ImgFormat = GetImageFormat(CStr(RegexReplace(__URL, UrlBasePattern))) - Else - ImgFormat = GetImageFormat(__File) - End If - - Dim tmpBytes$ = TryBytes(__URL, ImgFormat) - If tmpBytes.IsEmptyString And Not __MT = UTypes.GIF Then - ImgFormat = Imaging.ImageFormat.Png - tmpBytes = TryBytes(__URL, ImgFormat) - If Not tmpBytes.IsEmptyString Then Return tmpBytes - Else - Return tmpBytes - End If - - If tmpBytes.IsEmptyString And Not __MT = UTypes.GIF And __URL.Contains("imgur.com") Then - For c% = 0 To 1 - If c = 0 Then - ImgurUrls.ListAddList(Imgur.Envir.GetGallery(__URL)) - Else - ImgurUrls.ListAddValue(Imgur.Envir.GetImage(__URL)) - End If - If ImgurUrls.Count > 0 Then Exit For - Next - End If - Return tmpBytes - Catch hash_ex As Exception - Return String.Empty - End Try - End Function - Dim m$ - Using w As New WebClient - If vsf Then CSFileP($"{MyDir}\Video\").Exists(SFO.Path) - Progress.Maximum += _ContentNew.Count - For i = 0 To _ContentNew.Count - 1 - ThrowAny(Token) - v = _ContentNew(i) - v.State = UStates.Tried - If v.File.IsEmptyString Then - f = UrlToFile(v.URL) - Else - f = v.File - End If - f.Separator = "\" - m = String.Empty - If (v.Type = UTypes.Picture Or v.Type = UTypes.GIF) And UseMD5 Then - m = MD5BS(v.URL, v.Type, f, False) - If ImgurUrls.Count = 0 AndAlso m.IsEmptyString AndAlso Not v.URL_BASE.IsEmptyString AndAlso Not v.URL_BASE = v.URL Then - m = MD5BS(v.URL_BASE, v.Type, f, True) - If Not m.IsEmptyString Then v.URL = v.URL_BASE - End If - End If - - If (Not m.IsEmptyString AndAlso Not HashList.Contains(m)) Or Not (v.Type = UTypes.Picture Or - v.Type = UTypes.GIF) Or Not UseMD5 Or ImgurUrls.Count > 0 Then - IsImgurStuff = ImgurUrls.Count > 0 - Do - If Not cached And Not m.IsEmptyString Then HashList.Add(m) - v.MD5 = m - If ImgurUrls.Count > 0 Then - If ImgurUrls(0).IsEmptyString Then ImgurUrls.RemoveAt(0) : Continue Do - f = UrlToFile(ImgurUrls(0)) - If f.Extension.IsEmptyString Then f.Extension = "gif" - If f.Name.IsEmptyString Then - f.Path = MyDir - f.Name = $"ImgurImg_{v.File.Name}" - f = SFile.Indexed_IndexFile(f,,, EDP.ReturnValue) - End If - End If - If f.Extension = "webp" And Settings.DownloadNativeImageFormat Then f.Extension = "jpg" - f.Path = MyDir - Try - If (v.Type = UTypes.Video Or v.Type = UTypes.m3u8 Or (ImgurUrls.Count > 0 AndAlso f.Extension = "mp4")) And - vsf Then f.Path = $"{f.PathWithSeparator}Video" - If v.Type = UTypes.Video AndAlso v.URL.Contains("redd.it") Then - StartRFN += 1 - f.Name = String.Format(RFN, StartRFN) - End If - If v.Type = UTypes.m3u8 Then - f = M3U8.Download(v.URL, f) - ElseIf ImgurUrls.Count > 0 Then - w.DownloadFile(ImgurUrls(0), f.ToString) - ElseIf v.URL.Contains(SiteRedGifsKey) Then - RedGifsResponser.DownloadFile(v.URL, f, RGRERROR) - Else - w.DownloadFile(v.URL, f.ToString) - End If - If Not v.Type = UTypes.m3u8 Or Not f.IsEmptyString Then - Select Case v.Type - Case UTypes.Picture, UTypes.GIF : DownloadedPictures(False) += 1 - Case UTypes.Video, UTypes.m3u8 : DownloadedVideos(False) += 1 - End Select - If Not IsChannel Or Not SaveToCache Then - v.File = ChangeFileNameByProvider(f, v) - Else - v.File = f - End If - v.Post.CachedFile = f - v.State = UStates.Downloaded - dCount += 1 - End If - Catch wex As Exception - If Not IsChannel Then - If Not IsImgurStuff And MissingErrorsAdd Then ErrorDownloading(f, v.URL) - v.Attempts += 1 - v.State = UStates.Missing - End If - End Try - If ImgurUrls.Count > 0 Then ImgurUrls.RemoveAt(0) - Loop While ImgurUrls.Count > 0 - Else - v.State = UStates.Skipped - End If - _ContentNew(i) = v - If (CreatedByChannel And Settings.FromChannelDownloadTopUse And dCount >= Settings.FromChannelDownloadTop) Or - (DownloadTopCount.HasValue AndAlso dCount >= DownloadTopCount.Value) Then - Progress.Perform(_ContentNew.Count - dTotal) - Exit Sub - Else - dTotal += 1 - Progress.Perform() - End If - Next - End Using - End If - End If - Catch iex As IndexOutOfRangeException When Disposed - Catch oex As OperationCanceledException When Token.IsCancellationRequested - Catch dex As ObjectDisposedException When Disposed - Catch ex As Exception - LogError(ex, "content downloading error") - HasError = True - End Try + If _ContentNew.Count > 0 Then + Try + If Not _RedGifsResponser Is Nothing Then _RedGifsResponser.Dispose() + _RedGifsResponser = Settings(RedGifs.RedGifsSiteKey).Responser.Copy + DownloadContentDefault(Token) + Finally + If Not _RedGifsResponser Is Nothing Then _RedGifsResponser.Dispose() : _RedGifsResponser = Nothing + End Try + End If End Sub + Protected Overrides Function DownloadContentDefault_GetRootDir() As String + If Not IsSavedPosts AndAlso (IsChannel And SaveToCache And Not ChannelInfo Is Nothing) Then + Return ChannelInfo.CachePath.PathNoSeparator + Else + Return MyBase.DownloadContentDefault_GetRootDir() + End If + End Function + Protected Overrides Sub DownloadContentDefault_PostProcessing(ByRef m As UserMedia, ByVal File As SFile, ByVal Token As CancellationToken) + m.Post.CachedFile = File + MyBase.DownloadContentDefault_PostProcessing(m, File, Token) + End Sub + Protected Overrides Function DownloadContentDefault_ProcessDownloadException() As Boolean + Return Not IsChannel Or Not SaveToCache + End Function + Protected Overrides Function DownloadFile(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile + If _RedGifsResponser.DownloadFile(URL, DestinationFile, EDP.ThrowException) Then + Return DestinationFile + Else + Return Nothing + End If + End Function + Protected Overrides Function ValidateDownloadFile(ByVal URL As String, ByVal Media As UserMedia, ByRef Interrupt As Boolean) As Boolean + Return URL.Contains(SiteRedGifsKey) + End Function + Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile + Return M3U8.Download(URL, DestinationFile, Token, IIf(IsSingleObjectDownload, Progress, Nothing)) + End Function + Protected Overrides Function ChangeFileNameByProvider(ByVal f As SFile, ByVal m As UserMedia) As SFile + If Not IsChannel Or Not SaveToCache Then + Return MyBase.ChangeFileNameByProvider(f, m) + Else + Return f + End If + End Function +#End Region +#Region "Exception" Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, Optional ByVal EObj As Object = Nothing) As Integer With Responser @@ -949,6 +883,9 @@ Namespace API.Reddit MyMainLOG = $"[{CInt(Responser.StatusCode)}] Reddit is currently unavailable ({ToString()})" ElseIf .StatusCode = HttpStatusCode.GatewayTimeout Then Return 1 + ElseIf .StatusCode = HttpStatusCode.InternalServerError Then + If Not IsNothing(EObj) AndAlso IsNumeric(EObj) AndAlso CInt(EObj) = HttpStatusCode.InternalServerError Then Return 1 + Return HttpStatusCode.InternalServerError Else If Not FromPE Then LogError(ex, Message) : HasError = True Return 0 @@ -956,9 +893,12 @@ Namespace API.Reddit End With Return 1 End Function +#End Region +#Region "IDisposable Support" Protected Overrides Sub Dispose(ByVal disposing As Boolean) If Not disposedValue And disposing Then ChannelPostsNames.Clear() : _ExistsUsersNames.Clear() : _CrossPosts.Clear() MyBase.Dispose(disposing) End Sub +#End Region End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Redgifs/Declarations.vb b/SCrawler/API/Redgifs/Declarations.vb index 3fd01e6..401c42c 100644 --- a/SCrawler/API/Redgifs/Declarations.vb +++ b/SCrawler/API/Redgifs/Declarations.vb @@ -11,7 +11,6 @@ Namespace API.RedGifs Friend Module Declarations Friend Const RedGifsSiteKey As String = "AndyProgram_RedGifs" Friend Const RedGifsSite As String = "RedGifs" - Friend ReadOnly DateProvider As New CustomProvider(Function(v, d, p, n, e) ADateTime.ParseUnicode(v, n, e)) Friend ReadOnly WatchIDRegex As RParams = RParams.DMS(".+?watch/([^\?&""/]+)", 1, EDP.ReturnValue) Friend ReadOnly ThumbsIDRegex As RParams = RParams.DMS("([^/\?&""]+?)(-\w+?|)\.(mp4|jpg)", 1, EDP.ReturnValue, CType(Function(Input$) Input.StringToLower.StringTrim, Func(Of String, String))) diff --git a/SCrawler/API/Redgifs/SiteSettings.vb b/SCrawler/API/Redgifs/SiteSettings.vb index dfdb5b5..e664b28 100644 --- a/SCrawler/API/Redgifs/SiteSettings.vb +++ b/SCrawler/API/Redgifs/SiteSettings.vb @@ -14,8 +14,6 @@ Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Clients Imports PersonalUtilities.Tools.Web.Documents.JSON -Imports UTypes = SCrawler.API.Base.UserMedia.Types -Imports UStates = SCrawler.API.Base.UserMedia.States Namespace API.RedGifs Friend Class SiteSettings : Inherits SiteSettingsBase @@ -32,18 +30,17 @@ Namespace API.RedGifs End Property Friend ReadOnly Property Token As PropertyValue + + Private ReadOnly Property UserAgent As PropertyValue Friend ReadOnly Property TokenLastDateUpdated As PropertyValue Private Const TokenName As String = "authorization" #Region "TokenUpdateInterval" Friend ReadOnly Property TokenUpdateInterval As PropertyValue - Private Class TokenIntervalProvider : Implements IFieldsCheckerProvider - Private Property ErrorMessage As String Implements IFieldsCheckerProvider.ErrorMessage - Private Property Name As String Implements IFieldsCheckerProvider.Name - Private Property TypeError As Boolean Implements IFieldsCheckerProvider.TypeError - Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, - Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert + Private Class TokenIntervalProvider : Inherits FieldsCheckerProviderBase + Public Overrides Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, + Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object TypeError = False ErrorMessage = String.Empty If Not ACheck(Of Integer)(Value) Then @@ -52,12 +49,10 @@ Namespace API.RedGifs Return Value Else ErrorMessage = $"The value of [{Name}] field must be greater than or equal to 1" + HasError = True End If Return Nothing End Function - Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat - Throw New NotImplementedException("[GetFormat] is not available in the context of [TokenIntervalProvider]") - End Function End Class Private ReadOnly Property TokenUpdateIntervalProvider As IFormatProvider @@ -68,12 +63,14 @@ Namespace API.RedGifs MyBase.New(RedGifsSite, "redgifs.com") Dim t$ = String.Empty With Responser - Dim b As Boolean = Not .Mode = Responser.Modes.WebClient .Mode = Responser.Modes.WebClient + If Not .UserAgentExists Then .UserAgent = ParserUserAgent + .ClientWebUseCookies = False + .ClientWebUseHeaders = True t = .Headers.Value(TokenName) - If b Then .SaveSettings() End With - Token = New PropertyValue(t, GetType(String), Sub(v) UpdateResponse(v)) + Token = New PropertyValue(t, GetType(String), Sub(v) UpdateResponse(NameOf(Token), v)) + UserAgent = New PropertyValue(Responser.UserAgent, GetType(String), Sub(v) UpdateResponse(NameOf(UserAgent), v)) TokenLastDateUpdated = New PropertyValue(Now.AddYears(-1), GetType(Date)) TokenUpdateInterval = New PropertyValue(60 * 12, GetType(Integer)) TokenUpdateIntervalProvider = New TokenIntervalProvider @@ -83,8 +80,11 @@ Namespace API.RedGifs End Sub #End Region #Region "Response updater" - Private Sub UpdateResponse(ByVal Value As String) - Responser.Headers.Add(TokenName, Value) + Private Sub UpdateResponse(ByVal Name As String, ByVal Value As String) + Select Case Name + Case NameOf(Token) : Responser.Headers.Add(TokenName, Value) + Case NameOf(UserAgent) : Responser.UserAgent = Value + End Select Responser.SaveSettings() End Sub #End Region @@ -101,16 +101,18 @@ Namespace API.RedGifs Friend Function UpdateToken() As Boolean Try Dim r$ - Dim NewToken$ = String.Empty + Dim NewToken$ = String.Empty, NewAgent$ = String.Empty Using resp As New Responser : r = resp.GetResponse("https://api.redgifs.com/v2/auth/temporary",, EDP.ThrowException) : End Using If Not r.IsEmptyString Then Dim j As EContainer = JsonDocument.Parse(r) If Not j Is Nothing Then NewToken = j.Value("token") + NewAgent = j.Value("agent") j.Dispose() End If End If If Not NewToken.IsEmptyString Then + If Not NewAgent.IsEmptyString Then UserAgent.Value = NewAgent Token.Value = $"Bearer {NewToken}" TokenLastDateUpdated.Value = Now Return True @@ -118,7 +120,7 @@ Namespace API.RedGifs Return False End If Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, "[API.RedGifs.SiteSettings.UpdateToken]", False) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.RedGifs.SiteSettings.UpdateToken]", False) End Try End Function #End Region @@ -129,8 +131,10 @@ Namespace API.RedGifs MyBase.BeginEdit() End Sub Friend Overrides Sub Update() - Dim NewToken$ = AConvert(Of String)(Token.Value, AModes.Var, String.Empty) - If Not _LastTokenValue = NewToken Then TokenLastDateUpdated.Value = Now + If _SiteEditorFormOpened Then + Dim NewToken$ = AConvert(Of String)(Token.Value, AModes.Var, String.Empty) + If Not _LastTokenValue = NewToken Then TokenLastDateUpdated.Value = Now + End If MyBase.Update() End Sub Friend Overrides Sub EndEdit() @@ -141,32 +145,6 @@ Namespace API.RedGifs Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider Return New UserData End Function - Friend Overrides Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable - If BaseAuthExists() Then - Using resp As Responser = Responser.Copy - Dim m As UserMedia = UserData.GetDataFromUrlId(URL, False, resp, Settings(RedGifsSiteKey)) - If Not m.State = UStates.Missing And Not m.State = UserData.DataGone And (m.Type = UTypes.Picture Or m.Type = UTypes.Video) Then - Try - Dim spf$ = String.Empty - Dim f As SFile = GetSpecialDataFile(Path, AskForPath, spf) - If f.IsEmptyString Then - f = m.File.File - Else - f.Name = m.File.Name - f.Extension = m.File.Extension - End If - resp.DownloadFile(m.URL, f, EDP.ThrowException) - m.State = UStates.Downloaded - m.SpecialFolder = spf - Return {m} - Catch ex As Exception - ErrorsDescriber.Execute(EDP.SendInLog, ex, $"Redgifs standalone download error: [{URL}]") - End Try - End If - End Using - End If - Return Nothing - End Function Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String Return $"https://www.redgifs.com/watch/{Media.Post.ID}" End Function diff --git a/SCrawler/API/Redgifs/UserData.vb b/SCrawler/API/Redgifs/UserData.vb index 64e46f3..db4c4e9 100644 --- a/SCrawler/API/Redgifs/UserData.vb +++ b/SCrawler/API/Redgifs/UserData.vb @@ -9,6 +9,7 @@ Imports System.Net Imports System.Threading Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Clients @@ -42,7 +43,7 @@ Namespace API.RedGifs Try Dim _page As Func(Of String) = Function() If(Page = 1, String.Empty, $"&page={Page}") URL = $"https://api.redgifs.com/v2/users/{Name}/search?order=recent{_page.Invoke}" - Dim r$ = Responser.GetResponse(URL,, EDP.ThrowException) + Dim r$ = Responser.GetResponse(URL) Dim postDate$, postID$ Dim pTotal% = 0 If Not r.IsEmptyString Then @@ -51,7 +52,7 @@ Namespace API.RedGifs pTotal = j.Value("pages").FromXML(Of Integer)(0) For Each g As EContainer In j("gifs") postDate = g.Value("createDate") - Select Case CheckDatesLimit(postDate, DateProvider) + Select Case CheckDatesLimit(postDate, UnixDate32Provider) Case DateResult.Skip : Continue For Case DateResult.Exit : Exit Sub End Select @@ -106,13 +107,13 @@ Namespace API.RedGifs Dim u As UserMedia Dim j As EContainer For i% = 0 To _ContentList.Count - 1 - If _ContentList(i).State = UserMedia.States.Missing Then + If _ContentList(i).State = UStates.Missing Then ThrowAny(Token) u = _ContentList(i) If Not u.Post.ID.IsEmptyString Then url = String.Format(PostDataUrl, u.Post.ID.ToLower) Try - r = Responser.GetResponse(url,, EDP.ThrowException) + r = Responser.GetResponse(url) If Not r.IsEmptyString Then j = JsonDocument.Parse(r) If Not j Is Nothing Then @@ -207,20 +208,29 @@ Namespace API.RedGifs MyMainLOG = String.Format(_errText, URL) Return m Else - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, String.Format(_errText, URL), m) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, String.Format(_errText, URL), m) End If End If End Try End Function #End Region +#Region "Single data downloader" + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim m As UserMedia = GetDataFromUrlId(Data.URL, False, Responser, HOST) + If Not m.State = UStates.Missing And Not m.State = DataGone And (m.Type = UTypes.Picture Or m.Type = UTypes.Video) Then + m.URL_BASE = MySettings.GetUserPostUrl(Me, m) + _TempMediaList.Add(m) + End If + End Sub +#End Region #Region "Create media" - Private Shared Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, - ByVal PostDateStr As String, ByVal PostDateDate As Date?, ByVal State As UStates) As UserMedia + Private Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, + ByVal PostDateStr As String, ByVal PostDateDate As Date?, ByVal State As UStates) As UserMedia _URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern)) Dim m As New UserMedia(_URL, t) With {.Post = New UserPost With {.ID = PostID}} If Not m.URL.IsEmptyString Then m.File = CStr(RegexReplace(m.URL, FilesPattern)) If Not PostDateStr.IsEmptyString Then - m.Post.Date = AConvert(Of Date)(PostDateStr, DateProvider, Nothing) + m.Post.Date = AConvert(Of Date)(PostDateStr, UnixDate32Provider, Nothing) ElseIf PostDateDate.HasValue Then m.Post.Date = PostDateDate Else @@ -233,8 +243,8 @@ Namespace API.RedGifs #Region "Exception" Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, Optional ByVal EObj As Object = Nothing) As Integer - Dim s As WebExceptionStatus = Responser.Client.Status - Dim sc As HttpStatusCode = Responser.Client.StatusCode + Dim s As WebExceptionStatus = Responser.Status + Dim sc As HttpStatusCode = Responser.StatusCode If sc = HttpStatusCode.NotFound Or s = DataGone Then UserExists = False ElseIf sc = HttpStatusCode.Unauthorized Then diff --git a/SCrawler/API/ThisVid/Declarations.vb b/SCrawler/API/ThisVid/Declarations.vb new file mode 100644 index 0000000..a95bc94 --- /dev/null +++ b/SCrawler/API/ThisVid/Declarations.vb @@ -0,0 +1,22 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Functions.RegularExpressions +Namespace API.ThisVid + Friend Module Declarations + Friend Const ThisVidSiteKey As String = "AndyProgram_ThisVid" + Friend ReadOnly RegExNextPage As RParams = RParams.DMS("class=.pagination-next...a class=.selective..href=""([^""]+)""", 1) + Friend ReadOnly RegExVideoList As RParams = RParams.DMS("\[\r\n\s]*[\r\n\s]*\ + Friend Class SiteSettings : Inherits SiteSettingsBase +#Region "Declarations" + Friend Overrides ReadOnly Property Icon As Icon + Get + Return My.Resources.SiteResources.ThisVidIcon_16 + End Get + End Property + Friend Overrides ReadOnly Property Image As Image + Get + Return My.Resources.SiteResources.ThisVidPic_16 + End Get + End Property + + Friend ReadOnly Property DownloadPublic As PropertyValue + + Friend ReadOnly Property DownloadPrivate As PropertyValue + + Friend ReadOnly Property DifferentFolders As PropertyValue +#End Region +#Region "Initializer" + Friend Sub New() + MyBase.New("ThisVid", "thisvid.com") + DownloadPublic = New PropertyValue(True) + DownloadPrivate = New PropertyValue(True) + DifferentFolders = New PropertyValue(True) + CheckNetscapeCookiesOnEndInit = True + UseNetscapeCookies = True + UserRegex = RParams.DMS("thisvid.com/members/(\d+)", 1) + UrlPatternUser = "https://thisvid.com/members/{0}/" + ImageVideoContains = "https://thisvid.com/videos/" + End Sub +#End Region +#Region "GetInstance, GetSpecialData" + Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider + Return New UserData + End Function +#End Region +#Region "Downloading" + Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean + Return Settings.YtdlpFile.Exists And (What = ISiteSettings.Download.SingleObject Or Responser.CookiesExists) + End Function +#End Region +#Region "UserOptions" + Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) + If Options Is Nothing OrElse Not TypeOf Options Is UserExchangeOptions Then Options = New UserExchangeOptions(Me) + If OpenForm Then + Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using + End If + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/ThisVid/UserData.vb b/SCrawler/API/ThisVid/UserData.vb new file mode 100644 index 0000000..b7f8aa0 --- /dev/null +++ b/SCrawler/API/ThisVid/UserData.vb @@ -0,0 +1,332 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Threading +Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.RegularExpressions +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Tools.Web.Documents.JSON +Namespace API.ThisVid + Friend Class UserData : Inherits UserDataBase +#Region "XML names" + Private Const Name_DownloadPublic As String = "DownloadPublic" + Private Const Name_DownloadPrivate As String = "DownloadPrivate" + Private Const Name_DifferentFolders As String = "DifferentFolders" +#End Region +#Region "Structures" + Private Structure Album : Implements IRegExCreator + Friend URL As String + Friend Title As String + Private Function CreateFromArray(ByVal ParamsArray() As String) As Object Implements IRegExCreator.CreateFromArray + If ParamsArray.ListExists(2) Then + URL = ParamsArray(0) + Title = TitleHtmlConverter(ParamsArray(1)) + End If + Return Me + End Function + End Structure +#End Region +#Region "Declarations" + Friend Property DownloadPublic As Boolean = True + Friend Property DownloadPrivate As Boolean = True + Friend Property DifferentFolders As Boolean = True +#End Region +#Region "Loaders" + Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean) + With Container + If Loading Then + DownloadPublic = .Value(Name_DownloadPublic).FromXML(Of Boolean)(True) + DownloadPrivate = .Value(Name_DownloadPrivate).FromXML(Of Boolean)(True) + DifferentFolders = .Value(Name_DifferentFolders).FromXML(Of Boolean)(True) + Else + .Add(Name_DownloadPublic, DownloadPublic.BoolToInteger) + .Add(Name_DownloadPrivate, DownloadPrivate.BoolToInteger) + .Add(Name_DifferentFolders, DifferentFolders.BoolToInteger) + End If + End With + End Sub + Friend Overrides Function ExchangeOptionsGet() As Object + Return New UserExchangeOptions(Me) + End Function + Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object) + If Not Obj Is Nothing AndAlso TypeOf Obj Is UserExchangeOptions Then + With DirectCast(Obj, UserExchangeOptions) + DownloadPublic = .DownloadPublic + DownloadPrivate = .DownloadPrivate + DifferentFolders = .DifferentFolders + End With + End If + End Sub +#End Region +#Region "Initializer" + Friend Sub New() + UseClientTokens = True + End Sub +#End Region +#Region "Validation" + Private Function IsValid() As Boolean + Const ProfileDataPattern$ = "{0}[\r\n\s\W]*:[\r\n\s\W]*\[\r\n\s\W]*([^\<]*)[\r\n\s\W]*\[\r\n\s\W]*([^\<]*)[\r\n\s\W]*\<" + Try + If Not IsSavedPosts Then + Dim r$ = Responser.GetResponse($"https://thisvid.com/members/{ID}/") + If Not r.IsEmptyString Then + Dim rr As New RParams("", Nothing, 1, EDP.ReturnValue) + Dim __getValue As Func(Of String, Boolean, String) = Function(ByVal member As String, ByVal appendMember As Boolean) As String + rr.Pattern = String.Format(ProfileDataPattern, member) + Dim v$ = CStr(RegexReplace(r, rr)).StringTrim + If Not v.IsEmptyString And appendMember Then v = $"{member}: {v}" + Return v + End Function + UserSiteNameUpdate(__getValue("Name", False)) + If Not UserSiteName.IsEmptyString And FriendlyName.IsEmptyString Then FriendlyName = UserSiteName : _ForceSaveUserData = True + Dim descr$ = String.Empty + descr.StringAppendLine(__getValue("Birth date", True)) + descr.StringAppendLine(__getValue("Country", True)) + descr.StringAppendLine(__getValue("City", True)) + descr.StringAppendLine(__getValue("Gender", True)) + descr.StringAppendLine(__getValue("Orientation", True)) + descr.StringAppendLine(__getValue("Relationship status", True)) + descr.StringAppendLine(__getValue("Favourite category", True)) + descr.StringAppendLine(__getValue("My interests", True)) + rr.Pattern = DescriptionPattern + descr.StringAppendLine(CStr(RegexReplace(r, rr)).StringTrim) + UserDescriptionUpdate(descr) + Else + Return False + End If + End If + Return True + Catch ex As Exception + UserExists = False + Return False + End Try + End Function +#End Region +#Region "Download functions" + Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) + If ID.IsEmptyString Then ID = Name + If IsValid() Then + If IsSavedPosts Then + DownloadData(1, True, Token) + DownloadData_Images(Token) + Else + If DownloadVideos Then + If DownloadPublic Then DownloadData(1, True, Token) + If DownloadPrivate Then DownloadData(1, False, Token) + End If + If DownloadImages Then DownloadData_Images(Token) + End If + End If + End Sub + Private Overloads Sub DownloadData(ByVal Page As Integer, ByVal IsPublic As Boolean, ByVal Token As CancellationToken) + Dim URL$ = String.Empty + Try + Dim p$ = IIf(Page = 1, String.Empty, $"{Page}/") + If IsSavedPosts Then + URL = $"https://thisvid.com/my_favourite_videos/{p}" + Else + URL = $"https://thisvid.com/members/{ID}/{IIf(IsPublic, "public", "private")}_videos/{p}" + End If + ThrowAny(Token) + Dim r$ = Responser.GetResponse(URL) + Dim cBefore% = _TempMediaList.Count + If Not r.IsEmptyString Then + Dim __SpecialFolder$ = IIf(DifferentFolders, IIf(IsPublic, "Public", "Private"), String.Empty) + Dim l As List(Of String) = RegexReplace(r, If(IsSavedPosts, RegExVideoListSavedPosts, RegExVideoList)) + If l.ListExists Then + For Each u$ In l + If Not u.IsEmptyString Then + If Not _TempPostsList.Contains(u) Then + _TempPostsList.Add(u) + _TempMediaList.Add(New UserMedia(u) With {.Type = UserMedia.Types.VideoPre, .SpecialFolder = __SpecialFolder}) + Else + Exit Sub + End If + End If + Next + End If + End If + If Not cBefore = _TempMediaList.Count Then DownloadData(Page + 1, IsPublic, Token) + Catch ex As Exception + ProcessException(ex, Token, $"videos downloading error [{URL}]") + End Try + End Sub + Private Sub DownloadData_Images(ByVal Token As CancellationToken) + Dim __baseUrl$ = If(IsSavedPosts, "https://thisvid.com/my_favourite_albums/", $"https://thisvid.com/members/{ID}/albums/") + Dim URL$ = String.Empty + Try + Dim r$ + Dim i% = 0 + Dim __continue As Boolean = False + Dim rAlbums As RParams = If(IsSavedPosts, RegExAlbumsListSaved, RegExAlbumsList) + Do + i += 1 + __continue = False + URL = __baseUrl + If i > 1 Then URL &= $"{i}/" + r = Responser.GetResponse(URL) + If Not r.IsEmptyString() Then + Dim albums As List(Of Album) = RegexFields(Of Album)(r, {rAlbums}, {1, 2}, EDP.ReturnValue) + Dim images As List(Of String) + Dim albumId$, img$, imgUrl$, imgId$ + Dim u As UserMedia + Dim rErr As New ErrorsDescriber(EDP.ReturnValue) + __continue = True + If albums.ListExists Then + If albums.Count < 20 Then __continue = False + For Each a As Album In albums + If Not a.URL.IsEmptyString Then + ThrowAny(Token) + r = Responser.GetResponse(a.URL,, rErr) + If Not r.IsEmptyString Then + albumId = RegexReplace(r, RegExAlbumID) + If a.Title.IsEmptyString Then a.Title = albumId + images = RegexReplace(r, RegExAlbumImagesList) + If images.ListExists Then + For Each img In images + ThrowAny(Token) + r = Responser.GetResponse(img,, rErr) + If Not r.IsEmptyString Then + imgUrl = RegexReplace(r, RegExAlbumImageUrl) + If Not imgUrl.IsEmptyString Then + u = New UserMedia(imgUrl) With { + .SpecialFolder = a.Title, + .Type = UserMedia.Types.Picture, + .URL_BASE = img + } + If Not u.File.File.IsEmptyString Then + imgId = $"{albumId}_{u.File.Name}" + If u.File.Extension.IsEmptyString Then u.File.Extension = "jpg" + u.Post = imgId + If Not _TempPostsList.Contains(imgId) Then + _TempPostsList.Add(imgId) + _TempMediaList.Add(u) + Else + Exit For + End If + End If + End If + End If + Next + images.Clear() + End If + End If + End If + Next + Else + Exit Do + End If + End If + Loop While __continue + Catch ex As Exception + ProcessException(ex, Token, $"images downloading error [{URL}]") + End Try + End Sub +#End Region +#Region "ReparseVideo" + Protected Overrides Sub ReparseVideo(ByVal Token As CancellationToken) + Try + If _TempMediaList.Count > 0 Then + Dim u As UserMedia + Dim dirCmd$ = String.Empty + Dim f As SFile = Settings.YtdlpFile.File + Dim n$ + Dim cookieFile As SFile = DirectCast(HOST.Source, SiteSettings).CookiesNetscapeFile + Dim command$ + Dim e As EContainer + For i% = _TempMediaList.Count - 1 To 0 Step -1 + u = _TempMediaList(i) + If u.Type = UserMedia.Types.VideoPre Then + ThrowAny(Token) + command = $"""{f}"" --verbose --dump-json " + If cookieFile.Exists Then command &= $"--no-cookies-from-browser --cookies ""{cookieFile}"" " + command &= u.URL + e = GetJson(command) + If Not e Is Nothing Then + u.URL = e.Value("url") + u.Post = New UserPost(e.Value("id"), ADateTime.ParseUnix32(e.Value("epoch"))) + If u.Post.Date.HasValue Then + Select Case CheckDatesLimit(u.Post.Date.Value, Nothing) + Case DateResult.Skip : _TempPostsList.ListAddValue(u.Post.ID, LNC) : _TempMediaList.RemoveAt(i) : Continue For + Case DateResult.Exit : Exit Sub + End Select + End If + n = TitleHtmlConverter(e.Value("title")) + If Not n.IsEmptyString Then n = n.Replace("ThisVid.com", String.Empty).StringTrim.StringTrimEnd("-").StringTrim + If n.IsEmptyString Then n = u.Post.ID + If n.IsEmptyString Then n = "VideoFile" + u.File = $"{n}.mp4" + If u.URL.IsEmptyString OrElse (Not u.Post.ID.IsEmptyString AndAlso _TempPostsList.Contains(u.Post.ID)) Then + _TempMediaList.RemoveAt(i) + Else + u.Type = UserMedia.Types.Video + _TempPostsList.Add(u.Post.ID) + _TempMediaList(i) = u + End If + e.Dispose() + End If + End If + Next + End If + Catch ex As Exception + ProcessException(ex, Token, "video reparsing error") + End Try + End Sub +#End Region +#Region "GetJson" + Private Function GetJson(ByVal Command As String) As EContainer + Try + Using b As New BatchExecutor(True) + b.Execute(Command, EDP.ReturnValue) + If b.OutputData.Count > 0 Then + Dim e As EContainer + For Each d$ In b.OutputData + If Not d.IsEmptyString AndAlso d.StartsWith("{") Then + e = JsonDocument.Parse(d, EDP.ReturnValue) + If Not e Is Nothing Then Return e + End If + Next + End If + End Using + Return Nothing + Catch ex As Exception + HasError = True + LogError(ex, $"GetJson({Command})") + Return Nothing + End Try + End Function +#End Region +#Region "DownloadContent" + Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) + Dim s As Boolean? = SeparateVideoFolder + If DifferentFolders Then SeparateVideoFolder = False Else SeparateVideoFolder = Nothing + DownloadContentDefault(Token) + SeparateVideoFolder = s + End Sub +#End Region +#Region "Standalone downloader" + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + _TempMediaList.Add(New UserMedia(Data.URL) With {.Type = UserMedia.Types.VideoPre}) + ReparseVideo(Token) + End Sub +#End Region +#Region "DownloadingException" + Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, + Optional ByVal EObj As Object = Nothing) As Integer + If Responser.StatusCode = Net.HttpStatusCode.NotFound Then + Return 1 + Else + Return 0 + End If + End Function +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/ThisVid/UserExchangeOptions.vb b/SCrawler/API/ThisVid/UserExchangeOptions.vb new file mode 100644 index 0000000..3b91793 --- /dev/null +++ b/SCrawler/API/ThisVid/UserExchangeOptions.vb @@ -0,0 +1,32 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports SCrawler.Plugin.Attributes +Namespace API.ThisVid + Friend Class UserExchangeOptions + + Friend Property DownloadPublic As Boolean = True + + Friend Property DownloadPrivate As Boolean = True + + Friend Property DifferentFolders As Boolean = True + Private ReadOnly Property MySettings As SiteSettings + Friend Sub New(ByVal s As SiteSettings) + DownloadPublic = s.DownloadPublic.Value + DownloadPrivate = s.DownloadPrivate.Value + DifferentFolders = s.DifferentFolders.Value + MySettings = s + End Sub + Friend Sub New(ByVal u As UserData) + DownloadPublic = u.DownloadPublic + DownloadPrivate = u.DownloadPrivate + DifferentFolders = u.DifferentFolders + MySettings = u.HOST.Source + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/TikTok/Declarations.vb b/SCrawler/API/TikTok/Declarations.vb index e749d95..2b030da 100644 --- a/SCrawler/API/TikTok/Declarations.vb +++ b/SCrawler/API/TikTok/Declarations.vb @@ -10,11 +10,7 @@ Imports PersonalUtilities.Functions.RegularExpressions Namespace API.TikTok Friend Module Declarations Friend ReadOnly RegexEnvir As New RegexParseEnvir - Friend ReadOnly CheckDateProvider As New CustomProvider(Function(v, d, p, n, e) - With DirectCast(v, Date?) - If .HasValue Then Return .Value Else Return Nothing - End With - End Function) + Friend ReadOnly CheckDateProvider As New CustomProvider(Function(v) IIf(CType(v, Date?).HasValue, CObj(CType(v, Date?).Value), Nothing)) Friend Class RegexParseEnvir Private ReadOnly UrlIdRegex As RParams = RParams.DMS("http[s]?://[w\.]{0,4}tiktok.com/[^/]+?/video/(\d+)", 1, EDP.ReturnValue) Private ReadOnly RegexItemsArrPre As RParams = RParams.DMS("ItemList"":\{""user-post"":\{""list"":\[([^\[]+)\]", 1) @@ -33,7 +29,7 @@ Namespace API.TikTok End If Return Nothing Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, "[API.TikTok.RegexParseEnvir.GetIDList]") + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.TikTok.RegexParseEnvir.GetIDList]") End Try End Function Friend Function GetVideoData(ByVal r As String, ByVal ID As String, ByRef URL As String, ByRef [Date] As Date?) As Boolean @@ -46,12 +42,12 @@ Namespace API.TikTok Dim u$ = RegexReplace(r, VideoPattern) If Not u.IsEmptyString Then URL = SymbolsConverter.Unicode.Decode(u, EDP.ReturnValue) Dim d$ = RegexReplace(r, DatePattern) - If Not d.IsEmptyString Then [Date] = ADateTime.ParseUnicode(d) + If Not d.IsEmptyString Then [Date] = ADateTime.ParseUnix32(d) Return Not URL.IsEmptyString End If Return False Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, "[API.TikTok.RegexParseEnvir.GetVideoData]", False) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.TikTok.RegexParseEnvir.GetVideoData]", False) End Try End Function Friend Function ExtractPostID(ByVal URL As String) As String diff --git a/SCrawler/API/TikTok/SiteSettings.vb b/SCrawler/API/TikTok/SiteSettings.vb index fd3574e..bf9df98 100644 --- a/SCrawler/API/TikTok/SiteSettings.vb +++ b/SCrawler/API/TikTok/SiteSettings.vb @@ -32,11 +32,12 @@ Namespace API.TikTok Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider Return New UserData End Function - Friend Overrides Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable - Return UserData.GetVideoInfo(URL, Responser) - End Function Friend Overrides Function BaseAuthExists() As Boolean Return Responser.CookiesExists End Function + Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean + 'TODO: TikTok disabled + Return False + End Function End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/TikTok/UserData.vb b/SCrawler/API/TikTok/UserData.vb index fbf1b4d..9ff3590 100644 --- a/SCrawler/API/TikTok/UserData.vb +++ b/SCrawler/API/TikTok/UserData.vb @@ -26,7 +26,7 @@ Namespace API.TikTok Dim PostURL$ = String.Empty Dim r$ URL = $"https://www.tiktok.com/@{Name}" - r = Responser.GetResponse(URL,, EDP.ThrowException) + r = Responser.GetResponse(URL) PostIDs = RegexEnvir.GetIDList(r) If PostIDs.ListExists Then For Each __id$ In PostIDs @@ -52,28 +52,7 @@ Namespace API.TikTok Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) DownloadContentDefault(Token) End Sub - Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal Responser As Responser, Optional ByVal e As ErrorsDescriber = Nothing) As IEnumerable(Of UserMedia) - Try - If Not URL.IsEmptyString Then - Dim PostId$ = String.Empty - Dim PostDate As Date? = Nothing - Dim PostURL$ = String.Empty - Dim r$ - PostId = RegexEnvir.ExtractPostID(URL) - If Not PostId.IsEmptyString Then - Using resp As Responser = Responser.Copy() : r = resp.GetResponse(URL,, EDP.ThrowException) : End Using - If Not r.IsEmptyString Then - If RegexEnvir.GetVideoData(r, PostId, PostURL, PostDate) Then Return {MediaFromData(PostURL, PostId, PostDate)} - End If - End If - End If - Return Nothing - Catch ex As Exception - If Not e.Exists Then e = New ErrorsDescriber(EDP.ShowMainMsg + EDP.SendInLog) - Return ErrorsDescriber.Execute(e, ex, $"TikTok standalone downloader: fetch media error ({URL})") - End Try - End Function - Private Shared Function MediaFromData(ByVal _URL As String, ByVal PostID As String, ByVal PostDate As Date?) As UserMedia + Private Function MediaFromData(ByVal _URL As String, ByVal PostID As String, ByVal PostDate As Date?) As UserMedia _URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern)) Dim m As New UserMedia(_URL, UserMedia.Types.Video) With {.Post = New UserPost With {.ID = PostID}} If Not m.URL.IsEmptyString Then m.File = $"{PostID}.mp4" diff --git a/SCrawler/API/Twitter/Declarations.vb b/SCrawler/API/Twitter/Declarations.vb index 1073ac4..cee4a04 100644 --- a/SCrawler/API/Twitter/Declarations.vb +++ b/SCrawler/API/Twitter/Declarations.vb @@ -16,7 +16,6 @@ Namespace API.Twitter Friend ReadOnly DateProvider As ADateTime = GetDateProvider() Friend ReadOnly VideoNode As NodeParams() = {New NodeParams("video_info", True, True, True, True, 10)} Friend ReadOnly VideoSizeRegEx As RParams = RParams.DMS("\d+x(\d+)", 1, EDP.ReturnValue) - Friend ReadOnly UserIdRegEx As RParams = RParams.DMS("user_id.:.(\d+)", 1, EDP.ReturnValue) Private Function GetDateProvider() As ADateTime Dim n As DateTimeFormatInfo = CultureInfo.GetCultureInfo("en-us").DateTimeFormat.Clone n.FullDateTimePattern = "ddd MMM dd HH:mm:ss +ffff yyyy" diff --git a/SCrawler/API/Twitter/EditorExchangeOptions.vb b/SCrawler/API/Twitter/EditorExchangeOptions.vb index 346a41f..6ef40c0 100644 --- a/SCrawler/API/Twitter/EditorExchangeOptions.vb +++ b/SCrawler/API/Twitter/EditorExchangeOptions.vb @@ -6,20 +6,37 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY +Imports SCrawler.Plugin.Attributes Namespace API.Twitter Friend Class EditorExchangeOptions + Private Const DefaultOffset As Integer = 100 + Friend Property SiteKey As String = TwitterSiteKey + Friend Property GifsDownload As Boolean + Friend Property GifsSpecialFolder As String + Friend Property GifsPrefix As String + Friend Property UseMD5Comparison As Boolean = False + Friend Property RemoveExistingDuplicates As Boolean = False - Friend Sub New() - End Sub + Private ReadOnly Property MySettings As Object Friend Sub New(ByVal s As SiteSettings) GifsDownload = s.GifsDownload.Value GifsSpecialFolder = s.GifsSpecialFolder.Value GifsPrefix = s.GifsPrefix.Value UseMD5Comparison = s.UseMD5Comparison.Value + MySettings = s + End Sub + Friend Sub New(ByVal s As Mastodon.SiteSettings) + GifsDownload = s.GifsDownload.Value + GifsSpecialFolder = s.GifsSpecialFolder.Value + GifsPrefix = s.GifsPrefix.Value + UseMD5Comparison = s.UseMD5Comparison.Value + MySettings = s End Sub Friend Sub New(ByVal u As UserData) GifsDownload = u.GifsDownload @@ -27,6 +44,7 @@ Namespace API.Twitter GifsPrefix = u.GifsPrefix UseMD5Comparison = u.UseMD5Comparison RemoveExistingDuplicates = u.RemoveExistingDuplicates + MySettings = u.HOST.Source End Sub End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Twitter/OptionsForm.Designer.vb b/SCrawler/API/Twitter/OptionsForm.Designer.vb deleted file mode 100644 index 1b2dd82..0000000 --- a/SCrawler/API/Twitter/OptionsForm.Designer.vb +++ /dev/null @@ -1,185 +0,0 @@ -' Copyright (C) 2023 Andy https://github.com/AAndyProgram -' This program is free software: you can redistribute it and/or modify -' it under the terms of the GNU General Public License as published by -' the Free Software Foundation, either version 3 of the License, or -' (at your option) any later version. -' -' This program is distributed in the hope that it will be useful, -' but WITHOUT ANY WARRANTY -Namespace API.Twitter - - Partial Friend Class OptionsForm : Inherits System.Windows.Forms.Form - - Protected Overrides Sub Dispose(ByVal disposing As Boolean) - Try - If disposing AndAlso components IsNot Nothing Then - components.Dispose() - End If - Finally - MyBase.Dispose(disposing) - End Try - End Sub - Private components As System.ComponentModel.IContainer - - Private Sub InitializeComponent() - Me.components = New System.ComponentModel.Container() - Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer - Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel - Dim ActionButton3 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() - Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(OptionsForm)) - Dim ActionButton4 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() - Dim TT_MAIN As System.Windows.Forms.ToolTip - Me.CH_DOWN_GIFS = New System.Windows.Forms.CheckBox() - Me.TXT_GIF_FOLDER = New PersonalUtilities.Forms.Controls.TextBoxExtended() - Me.TXT_GIF_PREFIX = New PersonalUtilities.Forms.Controls.TextBoxExtended() - Me.CH_USE_MD5 = New System.Windows.Forms.CheckBox() - Me.CH_REMOVE_EXISTING_DUP = New System.Windows.Forms.CheckBox() - CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() - TP_MAIN = New System.Windows.Forms.TableLayoutPanel() - TT_MAIN = New System.Windows.Forms.ToolTip(Me.components) - CONTAINER_MAIN.ContentPanel.SuspendLayout() - CONTAINER_MAIN.SuspendLayout() - TP_MAIN.SuspendLayout() - CType(Me.TXT_GIF_FOLDER, System.ComponentModel.ISupportInitialize).BeginInit() - CType(Me.TXT_GIF_PREFIX, System.ComponentModel.ISupportInitialize).BeginInit() - Me.SuspendLayout() - ' - 'CONTAINER_MAIN - ' - ' - 'CONTAINER_MAIN.ContentPanel - ' - CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) - CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(304, 161) - CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill - CONTAINER_MAIN.LeftToolStripPanelVisible = False - CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) - CONTAINER_MAIN.Name = "CONTAINER_MAIN" - CONTAINER_MAIN.RightToolStripPanelVisible = False - CONTAINER_MAIN.Size = New System.Drawing.Size(304, 161) - CONTAINER_MAIN.TabIndex = 0 - CONTAINER_MAIN.TopToolStripPanelVisible = False - ' - 'TP_MAIN - ' - TP_MAIN.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] - TP_MAIN.ColumnCount = 1 - TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_MAIN.Controls.Add(Me.CH_DOWN_GIFS, 0, 0) - TP_MAIN.Controls.Add(Me.TXT_GIF_FOLDER, 0, 1) - TP_MAIN.Controls.Add(Me.TXT_GIF_PREFIX, 0, 2) - TP_MAIN.Controls.Add(Me.CH_USE_MD5, 0, 3) - TP_MAIN.Controls.Add(Me.CH_REMOVE_EXISTING_DUP, 0, 4) - TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill - TP_MAIN.Location = New System.Drawing.Point(0, 0) - TP_MAIN.Name = "TP_MAIN" - TP_MAIN.RowCount = 6 - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_MAIN.Size = New System.Drawing.Size(304, 161) - TP_MAIN.TabIndex = 0 - ' - 'CH_DOWN_GIFS - ' - Me.CH_DOWN_GIFS.AutoSize = True - Me.CH_DOWN_GIFS.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_DOWN_GIFS.Location = New System.Drawing.Point(4, 4) - Me.CH_DOWN_GIFS.Name = "CH_DOWN_GIFS" - Me.CH_DOWN_GIFS.Padding = New System.Windows.Forms.Padding(100, 0, 0, 0) - Me.CH_DOWN_GIFS.Size = New System.Drawing.Size(296, 19) - Me.CH_DOWN_GIFS.TabIndex = 0 - Me.CH_DOWN_GIFS.Text = "Download GIFs" - Me.CH_DOWN_GIFS.UseVisualStyleBackColor = True - ' - 'TXT_GIF_FOLDER - ' - ActionButton3.BackgroundImage = CType(resources.GetObject("ActionButton3.BackgroundImage"), System.Drawing.Image) - ActionButton3.Name = "Clear" - ActionButton3.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear - Me.TXT_GIF_FOLDER.Buttons.Add(ActionButton3) - Me.TXT_GIF_FOLDER.CaptionText = "GIFs special folder" - Me.TXT_GIF_FOLDER.CaptionToolTipText = "Put the GIFs in a special folder" - Me.TXT_GIF_FOLDER.Dock = System.Windows.Forms.DockStyle.Fill - Me.TXT_GIF_FOLDER.Location = New System.Drawing.Point(4, 30) - Me.TXT_GIF_FOLDER.Name = "TXT_GIF_FOLDER" - Me.TXT_GIF_FOLDER.Size = New System.Drawing.Size(296, 22) - Me.TXT_GIF_FOLDER.TabIndex = 1 - ' - 'TXT_GIF_PREFIX - ' - ActionButton4.BackgroundImage = CType(resources.GetObject("ActionButton4.BackgroundImage"), System.Drawing.Image) - ActionButton4.Name = "Clear" - ActionButton4.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear - Me.TXT_GIF_PREFIX.Buttons.Add(ActionButton4) - Me.TXT_GIF_PREFIX.CaptionText = "GIF prefix" - Me.TXT_GIF_PREFIX.CaptionToolTipText = "This prefix will be added to the beginning of the filename" - Me.TXT_GIF_PREFIX.Dock = System.Windows.Forms.DockStyle.Fill - Me.TXT_GIF_PREFIX.Location = New System.Drawing.Point(4, 59) - Me.TXT_GIF_PREFIX.Name = "TXT_GIF_PREFIX" - Me.TXT_GIF_PREFIX.Size = New System.Drawing.Size(296, 22) - Me.TXT_GIF_PREFIX.TabIndex = 2 - ' - 'CH_USE_MD5 - ' - Me.CH_USE_MD5.AutoSize = True - Me.CH_USE_MD5.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_USE_MD5.Location = New System.Drawing.Point(4, 88) - Me.CH_USE_MD5.Name = "CH_USE_MD5" - Me.CH_USE_MD5.Padding = New System.Windows.Forms.Padding(100, 0, 0, 0) - Me.CH_USE_MD5.Size = New System.Drawing.Size(296, 19) - Me.CH_USE_MD5.TabIndex = 3 - Me.CH_USE_MD5.Text = "Use MD5 comparison" - TT_MAIN.SetToolTip(Me.CH_USE_MD5, "Each image will be checked for existence using MD5") - Me.CH_USE_MD5.UseVisualStyleBackColor = True - ' - 'CH_REMOVE_EXISTING_DUP - ' - Me.CH_REMOVE_EXISTING_DUP.AutoSize = True - Me.CH_REMOVE_EXISTING_DUP.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_REMOVE_EXISTING_DUP.Location = New System.Drawing.Point(4, 114) - Me.CH_REMOVE_EXISTING_DUP.Name = "CH_REMOVE_EXISTING_DUP" - Me.CH_REMOVE_EXISTING_DUP.Padding = New System.Windows.Forms.Padding(100, 0, 0, 0) - Me.CH_REMOVE_EXISTING_DUP.Size = New System.Drawing.Size(296, 19) - Me.CH_REMOVE_EXISTING_DUP.TabIndex = 4 - Me.CH_REMOVE_EXISTING_DUP.Text = "Remove existing duplicates" - TT_MAIN.SetToolTip(Me.CH_REMOVE_EXISTING_DUP, "Existing files will be checked for duplicates and duplicates removed." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Works only" & - " on the first activation 'Use MD5 comparison'.") - Me.CH_REMOVE_EXISTING_DUP.UseVisualStyleBackColor = True - ' - 'OptionsForm - ' - Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) - Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font - Me.ClientSize = New System.Drawing.Size(304, 161) - Me.Controls.Add(CONTAINER_MAIN) - Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle - Me.Icon = Global.SCrawler.My.Resources.SiteResources.TwitterIcon_32 - Me.MaximizeBox = False - Me.MaximumSize = New System.Drawing.Size(320, 200) - Me.MinimizeBox = False - Me.MinimumSize = New System.Drawing.Size(320, 200) - Me.Name = "OptionsForm" - Me.ShowInTaskbar = False - Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide - Me.Text = "Options" - CONTAINER_MAIN.ContentPanel.ResumeLayout(False) - CONTAINER_MAIN.ResumeLayout(False) - CONTAINER_MAIN.PerformLayout() - TP_MAIN.ResumeLayout(False) - TP_MAIN.PerformLayout() - CType(Me.TXT_GIF_FOLDER, System.ComponentModel.ISupportInitialize).EndInit() - CType(Me.TXT_GIF_PREFIX, System.ComponentModel.ISupportInitialize).EndInit() - Me.ResumeLayout(False) - - End Sub - Private WithEvents CH_DOWN_GIFS As CheckBox - Private WithEvents TXT_GIF_FOLDER As PersonalUtilities.Forms.Controls.TextBoxExtended - Private WithEvents TXT_GIF_PREFIX As PersonalUtilities.Forms.Controls.TextBoxExtended - Private WithEvents CH_USE_MD5 As CheckBox - Private WithEvents CH_REMOVE_EXISTING_DUP As CheckBox - End Class -End Namespace \ No newline at end of file diff --git a/SCrawler/API/Twitter/OptionsForm.vb b/SCrawler/API/Twitter/OptionsForm.vb deleted file mode 100644 index 8cac46e..0000000 --- a/SCrawler/API/Twitter/OptionsForm.vb +++ /dev/null @@ -1,81 +0,0 @@ -' Copyright (C) 2023 Andy https://github.com/AAndyProgram -' This program is free software: you can redistribute it and/or modify -' it under the terms of the GNU General Public License as published by -' the Free Software Foundation, either version 3 of the License, or -' (at your option) any later version. -' -' This program is distributed in the hope that it will be useful, -' but WITHOUT ANY WARRANTY -Imports SCrawler.Plugin.Attributes -Imports PersonalUtilities.Forms -Imports PersonalUtilities.Forms.Controls -Namespace API.Twitter - Friend Class OptionsForm - Private WithEvents MyDefs As DefaultFormOptions - Private ReadOnly Property MyExchangeOptions As EditorExchangeOptions - Private ReadOnly MyGifTextProvider As SiteSettings.GifStringProvider - Friend Sub New(ByRef ExchangeOptions As EditorExchangeOptions) - InitializeComponent() - MyExchangeOptions = ExchangeOptions - MyGifTextProvider = New SiteSettings.GifStringProvider - MyDefs = New DefaultFormOptions(Me, Settings.Design) - End Sub - Private Sub OptionsForm_Load(sender As Object, e As EventArgs) Handles Me.Load - With MyDefs - .MyViewInitialize(True) - .AddOkCancelToolbar() - With MyExchangeOptions - CH_DOWN_GIFS.Checked = .GifsDownload - TXT_GIF_FOLDER.Text = .GifsSpecialFolder - TXT_GIF_FOLDER.Tag = NameOf(SiteSettings.GifsSpecialFolder) - TXT_GIF_PREFIX.Text = .GifsPrefix - TXT_GIF_PREFIX.Tag = NameOf(SiteSettings.GifsPrefix) - CH_USE_MD5.Checked = .UseMD5Comparison - CH_REMOVE_EXISTING_DUP.Checked = .RemoveExistingDuplicates - - Try - Dim p As PropertyOption - With Settings(TwitterSiteKey) - p = .PropList.Find(Function(pp) pp.Name = TXT_GIF_FOLDER.Tag).Options - If Not p Is Nothing Then - TXT_GIF_FOLDER.CaptionText = p.ControlText - TXT_GIF_FOLDER.CaptionToolTipText = p.ControlToolTip - TXT_GIF_FOLDER.CaptionToolTipEnabled = Not TXT_GIF_FOLDER.CaptionToolTipText.IsEmptyString - End If - - p = .PropList.Find(Function(pp) pp.Name = TXT_GIF_PREFIX.Tag).Options - If Not p Is Nothing Then - TXT_GIF_PREFIX.CaptionText = p.ControlText - TXT_GIF_PREFIX.CaptionToolTipText = p.ControlToolTip - TXT_GIF_PREFIX.CaptionToolTipEnabled = Not TXT_GIF_PREFIX.CaptionToolTipText.IsEmptyString - End If - End With - Catch - End Try - End With - .EndLoaderOperations() - End With - End Sub - Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick - With MyExchangeOptions - .GifsDownload = CH_DOWN_GIFS.Checked - .GifsSpecialFolder = TXT_GIF_FOLDER.Text - .GifsPrefix = TXT_GIF_PREFIX.Text - .UseMD5Comparison = CH_USE_MD5.Checked - .RemoveExistingDuplicates = CH_REMOVE_EXISTING_DUP.Checked - End With - MyDefs.CloseForm() - End Sub - Private Sub TXT_ActionOnTextChanged(ByVal Sender As TextBoxExtended, ByVal e As EventArgs) Handles TXT_GIF_FOLDER.ActionOnTextChanged, - TXT_GIF_PREFIX.ActionOnTextChanged - If Not MyDefs.Initializing Then - With Sender - MyGifTextProvider.PropertyName = .Tag - Dim s% = .SelectionStart - Dim t$ = AConvert(Of String)(.Text, String.Empty, MyGifTextProvider) - If Not .Text = t Then .Text = t : .Select(s, 0) - End With - End If - End Sub - End Class -End Namespace \ No newline at end of file diff --git a/SCrawler/API/Twitter/SiteSettings.vb b/SCrawler/API/Twitter/SiteSettings.vb index c5d2043..358f89b 100644 --- a/SCrawler/API/Twitter/SiteSettings.vb +++ b/SCrawler/API/Twitter/SiteSettings.vb @@ -11,12 +11,25 @@ Imports SCrawler.Plugin Imports SCrawler.Plugin.Attributes Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Clients -Imports PersonalUtilities.Tools.Web.Cookies Namespace API.Twitter Friend Class SiteSettings : Inherits SiteSettingsBase +#Region "Token names" Friend Const Header_Authorization As String = "authorization" Friend Const Header_Token As String = "x-csrf-token" +#End Region +#Region "Properties constants" + Friend Const GifsSpecialFolder_Text As String = "GIFs special folder" + Friend Const GifsSpecialFolder_ToolTip As String = "Put the GIFs in a special folder" & vbCr & + "This is a folder name, not an absolute path." & vbCr & + "This folder(s) will be created relative to the user's root folder." & vbCr & + "Examples:" & vbCr & "SomeFolderName" & vbCr & "SomeFolderName\SomeFolderName2" + Friend Const GifsPrefix_Text As String = "GIF prefix" + Friend Const GifsPrefix_ToolTip As String = "This prefix will be added to the beginning of the filename" + Friend Const GifsDownload_Text As String = "Download GIFs" + Friend Const UseMD5Comparison_Text As String = "Use MD5 comparison" + Friend Const UseMD5Comparison_ToolTip As String = "Each image will be checked for existence using MD5" +#End Region #Region "Declarations" Friend Overrides ReadOnly Property Icon As Icon Get @@ -34,19 +47,13 @@ Namespace API.Twitter Private ReadOnly Property Auth As PropertyValue Private ReadOnly Property Token As PropertyValue - - Friend ReadOnly Property SavedPostsUserName As PropertyValue #End Region #Region "Other properties" - + Friend ReadOnly Property GifsDownload As PropertyValue - + Friend ReadOnly Property GifsSpecialFolder As PropertyValue - + Friend ReadOnly Property GifsPrefix As PropertyValue Private ReadOnly Property GifStringChecker As IFormatProvider @@ -60,69 +67,18 @@ Namespace API.Twitter v = v.StringRemoveWinForbiddenSymbols Else v = v.StringReplaceSymbols(GetWinForbiddenSymbols.ToList.ListWithRemove("\").ToArray, String.Empty, EDP.ReturnValue) - v = v.StringTrim("\") End If End If Return v End Function Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat - Throw New NotImplementedException("[GetFormat] is not available in the context of [TimersChecker]") + Throw New NotImplementedException("[GetFormat] is not available in the context of [GifStringProvider]") End Function End Class - + Friend ReadOnly Property UseMD5Comparison As PropertyValue #End Region Friend Overrides ReadOnly Property Responser As Responser -#End Region - Friend Sub New() - MyBase.New(TwitterSite) - Responser = New Responser($"{SettingsFolderName}\Responser_{Site}.xml") - - Dim a$ = String.Empty - Dim t$ = String.Empty - - With Responser - If .File.Exists Then - Dim b As Boolean = .CookiesDomain.IsEmptyString - If EncryptCookies.CookiesEncrypted Then .CookiesEncryptKey = SettingsCLS.CookieEncryptKey - .LoadSettings() - a = .Headers.Value(Header_Authorization) - t = .Headers.Value(Header_Token) - .CookiesDomain = "twitter.com" - If b Then .SaveSettings() - Else - .ContentType = "application/json" - .Accept = "*/*" - .CookiesDomain = "twitter.com" - .CookiesEncryptKey = SettingsCLS.CookieEncryptKey - .Decoders.Add(SymbolsConverter.Converters.Unicode) - .Headers.Add("sec-ch-ua", " Not;A Brand"";v=""99"", ""Google Chrome"";v=""91"", ""Chromium"";v=""91""") - .Headers.Add("sec-ch-ua-mobile", "?0") - .Headers.Add("sec-fetch-dest", "empty") - .Headers.Add("sec-fetch-mode", "cors") - .Headers.Add("sec-fetch-site", "same-origin") - .Headers.Add(Header_Token, String.Empty) - .Headers.Add("x-twitter-active-user", "yes") - .Headers.Add("x-twitter-auth-type", "OAuth2Session") - .Headers.Add(Header_Authorization, String.Empty) - .SaveSettings() - End If - End With - - Auth = New PropertyValue(a, GetType(String), Sub(v) ChangeResponserFields(NameOf(Auth), v)) - Token = New PropertyValue(t, GetType(String), Sub(v) ChangeResponserFields(NameOf(Token), v)) - SavedPostsUserName = New PropertyValue(String.Empty, GetType(String)) - - GifsDownload = New PropertyValue(True) - GifsSpecialFolder = New PropertyValue(String.Empty, GetType(String)) - GifsPrefix = New PropertyValue("GIF_") - GifStringChecker = New GifStringProvider - UseMD5Comparison = New PropertyValue(False) - - UserRegex = RParams.DMS("[htps:/]{7,8}.*?twitter.com/([^/]+)", 1) - UrlPatternUser = "https://twitter.com/{0}" - ImageVideoContains = "twitter" - End Sub Private Sub ChangeResponserFields(ByVal PropName As String, ByVal Value As Object) If Not PropName.IsEmptyString Then Dim f$ = String.Empty @@ -137,15 +93,59 @@ Namespace API.Twitter End If End If End Sub +#End Region + Friend Sub New() + MyBase.New(TwitterSite) + Responser = New Responser($"{SettingsFolderName}\Responser_{Site}.xml") With {.DeclaredError = EDP.ThrowException} + + Dim a$ = String.Empty + Dim t$ = String.Empty + + With Responser + If .File.Exists Then + .CookiesDomain = "twitter.com" + .CookiesEncryptKey = SettingsCLS.CookieEncryptKey + .LoadSettings() + a = .Headers.Value(Header_Authorization) + t = .Headers.Value(Header_Token) + Else + .ContentType = "application/json" + .Accept = "*/*" + .CookiesDomain = "twitter.com" + .CookiesEncryptKey = SettingsCLS.CookieEncryptKey + .Decoders.Add(SymbolsConverter.Converters.Unicode) + .Headers.Add("sec-ch-ua", """Chromium"";v=""112"", ""Google Chrome"";v=""112"", ""Not:A-Brand"";v=""99""") + .Headers.Add("sec-ch-ua-mobile", "?0") + .Headers.Add("sec-fetch-dest", "empty") + .Headers.Add("sec-fetch-mode", "cors") + .Headers.Add("sec-fetch-site", "same-origin") + .Headers.Add(Header_Token, String.Empty) + .Headers.Add("x-twitter-active-user", "yes") + .Headers.Add("x-twitter-auth-type", "OAuth2Session") + .Headers.Add(Header_Authorization, String.Empty) + .SaveSettings() + End If + .Cookies.ChangedAllowInternalDrop = False + .Cookies.Changed = False + End With + + Auth = New PropertyValue(a, GetType(String), Sub(v) ChangeResponserFields(NameOf(Auth), v)) + Token = New PropertyValue(t, GetType(String), Sub(v) ChangeResponserFields(NameOf(Token), v)) + + GifsDownload = New PropertyValue(True) + GifsSpecialFolder = New PropertyValue(String.Empty, GetType(String)) + GifsPrefix = New PropertyValue("GIF_") + GifStringChecker = New GifStringProvider + UseMD5Comparison = New PropertyValue(False) + + UserRegex = RParams.DMS("[htps:/]{7,8}.*?twitter.com/([^/]+)", 1) + UrlPatternUser = "https://twitter.com/{0}" + ImageVideoContains = "twitter" + CheckNetscapeCookiesOnEndInit = True + UseNetscapeCookies = True + End Sub Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider - If What = ISiteSettings.Download.SavedPosts Then - Return New UserData With {.IsSavedPosts = True, .User = New UserInfo With {.Name = CStr(AConvert(Of String)(SavedPostsUserName.Value, String.Empty))}} - Else - Return New UserData - End If - End Function - Friend Overrides Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable - Return UserData.GetVideoInfo(URL, Responser) + Return New UserData End Function Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String Return $"https://twitter.com/{User.Name}/status/{Media.Post.ID}" @@ -153,11 +153,31 @@ Namespace API.Twitter Friend Overrides Function BaseAuthExists() As Boolean Return Responser.CookiesExists And ACheck(Token.Value) And ACheck(Auth.Value) End Function - Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) - If Options Is Nothing OrElse Not TypeOf Options Is EditorExchangeOptions Then Options = New EditorExchangeOptions(Me) - If OpenForm Then - Using f As New OptionsForm(Options) : f.ShowDialog() : End Using + Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean + If MyBase.Available(What, Silent) Then + If What = ISiteSettings.Download.SavedPosts Then + Return Settings.GalleryDLFile.Exists + Else + Return True + End If + Else + Return False End If + End Function + Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) + If Options Is Nothing OrElse (Not TypeOf Options Is EditorExchangeOptions OrElse + Not DirectCast(Options, EditorExchangeOptions).SiteKey = TwitterSiteKey) Then _ + Options = New EditorExchangeOptions(Me) + If OpenForm Then + Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using + End If + End Sub + Friend Overrides Sub Update() + If _SiteEditorFormOpened Then + Dim tf$ = GifsSpecialFolder.Value + If Not tf.IsEmptyString Then tf = tf.StringTrim("\") : GifsSpecialFolder.Value = tf + End If + MyBase.Update() End Sub End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Twitter/UserData.vb b/SCrawler/API/Twitter/UserData.vb index 855bf6c..069d567 100644 --- a/SCrawler/API/Twitter/UserData.vb +++ b/SCrawler/API/Twitter/UserData.vb @@ -7,39 +7,32 @@ ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY Imports System.Net -Imports System.Drawing Imports System.Threading Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Clients Imports PersonalUtilities.Tools.Web.Documents.JSON -Imports PersonalUtilities.Tools.ImageRenderer Imports UStates = SCrawler.API.Base.UserMedia.States Imports UTypes = SCrawler.API.Base.UserMedia.Types Namespace API.Twitter Friend Class UserData : Inherits UserDataBase - Private Const SinglePostUrl As String = "https://api.twitter.com/1.1/statuses/show.json?id={0}&tweet_mode=extended" + Protected SinglePostUrl As String = "https://api.twitter.com/1.1/statuses/show.json?id={0}&tweet_mode=extended" #Region "XML names" Private Const Name_GifsDownload As String = "GifsDownload" Private Const Name_GifsSpecialFolder As String = "GifsSpecialFolder" Private Const Name_GifsPrefix As String = "GifsPrefix" - Private Const Name_UseMD5Comparison As String = "UseMD5Comparison" - Private Const Name_RemoveExistingDuplicates As String = "RemoveExistingDuplicates" - Private Const Name_StartMD5Checked As String = "StartMD5Checked" #End Region #Region "Declarations" - Friend Property GifsDownload As Boolean - Friend Property GifsSpecialFolder As String - Friend Property GifsPrefix As String + Friend Property GifsDownload As Boolean = True + Friend Property GifsSpecialFolder As String = String.Empty + Friend Property GifsPrefix As String = String.Empty Private ReadOnly _DataNames As List(Of String) - Friend Property UseMD5Comparison As Boolean = False - Private StartMD5Checked As Boolean = False - Friend Property RemoveExistingDuplicates As Boolean = False #End Region #Region "Exchange options" Friend Overrides Function ExchangeOptionsGet() As Object - Return New EditorExchangeOptions(Me) + Return New EditorExchangeOptions(Me) With {.SiteKey = HOST.Key} End Function Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object) If Not Obj Is Nothing AndAlso TypeOf Obj Is EditorExchangeOptions Then @@ -83,45 +76,35 @@ Namespace API.Twitter Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) If IsSavedPosts Then If _ContentList.Count > 0 Then _DataNames.ListAddList(_ContentList.Select(Function(c) c.Post.ID), LAP.ClearBeforeAdd, LAP.NotContainsOnly) - DownloadData(String.Empty, Token) + DownloadData_SavedPosts(Token) Else If _ContentList.Count > 0 Then _DataNames.ListAddList(_ContentList.Select(Function(c) c.File.File), LAP.ClearBeforeAdd, LAP.NotContainsOnly) DownloadData(String.Empty, Token) - If UseMD5Comparison Then ValidateMD5(Token) End If End Sub Private Overloads Sub DownloadData(ByVal POST As String, ByVal Token As CancellationToken) Dim URL$ = String.Empty Try - Dim NextCursor$ = String.Empty - Dim __NextCursor As Predicate(Of EContainer) = Function(e) e.Value({"content", "operation", "cursor"}, "cursorType") = "Bottom" Dim PostID$ = String.Empty Dim PostDate$ - Dim nn As EContainer, s As EContainer + Dim nn As EContainer Dim NewPostDetected As Boolean = False Dim ExistsDetected As Boolean = False Dim UID As Func(Of EContainer, String) = Function(e) e.XmlIfNothing.Item({"user", "id"}).XmlIfNothingValue - If IsSavedPosts Then - If Name.IsEmptyString Then Throw New ArgumentNullException With {.HelpLink = 1} - URL = $"https://api.twitter.com/2/timeline/bookmark.json?screen_name={Name}&count=200" & - "&tweet_mode=extended&include_entities=true&include_user_entities=true&include_ext_media_availability=true" - If Not POST.IsEmptyString Then URL &= $"&cursor={SymbolsConverter.ASCII.EncodeSymbolsOnly(POST)}" - Else - URL = $"https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={Name}&count=200&exclude_replies=false&include_rts=1&tweet_mode=extended" - If Not POST.IsEmptyString Then URL &= $"&max_id={POST}" - End If + URL = $"https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={Name}&count=200&exclude_replies=false&include_rts=1&tweet_mode=extended" + If Not POST.IsEmptyString Then URL &= $"&max_id={POST}" ThrowAny(Token) - Dim r$ = Responser.GetResponse(URL,, EDP.ThrowException) + Dim r$ = Responser.GetResponse(URL) If Not r.IsEmptyString Then Using w As EContainer = JsonDocument.Parse(r) If w.ListExists Then - If Not IsSavedPosts And POST.IsEmptyString And Not w.ItemF({0, "user"}) Is Nothing Then + If POST.IsEmptyString And Not w.ItemF({0, "user"}) Is Nothing Then With w.ItemF({0, "user"}) - If .Value("screen_name").StringToLower = Name Then + If .Value("screen_name").StringToLower = Name.ToLower Then UserSiteNameUpdate(.Value("name")) UserDescriptionUpdate(.Value("description")) Dim __getImage As Action(Of String) = Sub(ByVal img As String) @@ -145,15 +128,10 @@ Namespace API.Twitter For Each nn In If(IsSavedPosts, w({"globalObjects", "tweets"}).XmlIfNothing, w) ThrowAny(Token) If nn.Count > 0 Then - If IsSavedPosts Then - PostID = nn.Value - If PostID.IsEmptyString Then PostID = nn.Value("id_str") - Else - PostID = nn.Value("id") - If ID.IsEmptyString Then - ID = UID(nn) - If Not ID.IsEmptyString Then UpdateUserInformation() - End If + PostID = nn.Value("id") + If ID.IsEmptyString Then + ID = UID(nn) + If Not ID.IsEmptyString Then UpdateUserInformation() End If 'Date Pattern: @@ -172,32 +150,58 @@ Namespace API.Twitter Continue For End If - If IsSavedPosts OrElse Not ParseUserMediaOnly OrElse - ( - Not nn.Contains("retweeted_status") OrElse - (Not ID.IsEmptyString AndAlso UID(nn("retweeted_status")) = ID) - ) Then ObtainMedia(nn, PostID, PostDate) + If Not ParseUserMediaOnly OrElse + (Not nn.Contains("retweeted_status") OrElse (Not ID.IsEmptyString AndAlso UID(nn("retweeted_status")) = ID)) Then _ + ObtainMedia(nn, PostID, PostDate) End If Next - - If IsSavedPosts Then - s = w.ItemF({"timeline", "instructions", 0, "addEntries", "entries"}).XmlIfNothing - If s.Count > 0 Then NextCursor = If(s.ItemF({__NextCursor})?.Value({"content", "operation", "cursor"}, "value"), String.Empty) - End If End If End Using - If IsSavedPosts Then - If Not NextCursor.IsEmptyString And Not NextCursor = POST Then DownloadData(NextCursor, Token) - Else - If POST.IsEmptyString And ExistsDetected Then Exit Sub - If Not PostID.IsEmptyString And NewPostDetected Then DownloadData(PostID, Token) + If POST.IsEmptyString And ExistsDetected Then Exit Sub + If Not PostID.IsEmptyString And NewPostDetected Then DownloadData(PostID, Token) + End If + Catch ex As Exception + ProcessException(ex, Token, $"data downloading error [{URL}]") + End Try + End Sub + Private Sub DownloadData_SavedPosts(ByVal Token As CancellationToken) + Try + Dim urls As List(Of String) = GetBookmarksUrlsFromGalleryDL() + If urls.ListExists Then + Dim postIds As New List(Of String) + Dim r$ + Dim j As EContainer, jj As EContainer + Dim jErr As New ErrorsDescriber(EDP.ReturnValue) + Dim rPattern As RParams = RParams.DM("(?<=tweet-)(\d+)\Z", 0, EDP.ReturnValue) + For Each url$ In urls + r = Responser.GetResponse(url) + If Not r.IsEmptyString Then + j = JsonDocument.Parse(r, jErr) + If Not j Is Nothing Then + jj = j.ItemF({"data", "bookmark_timeline_v2", "timeline", "instructions", 0, "entries"}) + If If(jj?.Count, 0) > 0 Then postIds.ListAddList(jj.Select(Function(jj2) CStr(RegexReplace(jj2.Value("entryId"), rPattern))), LNC) + j.Dispose() + End If + End If + Next + If postIds.Count > 0 Then postIds.RemoveAll(Function(pid) pid.IsEmptyString OrElse (_TempPostsList.Contains(pid) Or _DataNames.Contains(pid))) + If postIds.Count > 0 Then + For Each __id$ In postIds + _TempPostsList.Add(__id) + r = Responser.GetResponse(String.Format(SinglePostUrl, __id),, EDP.ReturnValue) + If Not r.IsEmptyString Then + j = JsonDocument.Parse(r, jErr) + If Not j Is Nothing Then + If j.Count > 0 Then ObtainMedia(j, __id, j.Value("created_at")) + j.Dispose() + End If + End If + Next End If End If - Catch ane As ArgumentNullException When ane.HelpLink = 1 - MyMainLOG = "Username not set for saved Twitter posts" Catch ex As Exception - ProcessException(ex, Token, $"data downloading error{IIf(IsSavedPosts, " (Saved Posts)", String.Empty)} [{URL}]") + ProcessException(ex, Token, "data downloading error (Saved Posts)") End Try End Sub #End Region @@ -252,18 +256,24 @@ Namespace API.Twitter If .ListExists Then For Each n As EContainer In .Self If n.Value("type") = "animated_gif" Then - With n({"video_info", "variants"}).XmlIfNothing.ItemF({gifUrl}).XmlIfNothing - url = .Value("url") - ff = UrlFile(url) - If Not ff.IsEmptyString Then - If GifsDownload And Not _DataNames.Contains(ff) Then - m = MediaFromData(url, PostID, PostDate,, State, UTypes.Video) - f = m.File - If Not f.IsEmptyString And Not GifsPrefix.IsEmptyString Then f.Name = $"{GifsPrefix}{f.Name}" : m.File = f - If Not GifsSpecialFolder.IsEmptyString Then m.SpecialFolder = $"{GifsSpecialFolder}*" - _TempMediaList.ListAddValue(m, LNC) - End If - Return True + With n({"video_info", "variants"}) + If .ListExists Then + With .ItemF({gifUrl}) + If .ListExists Then + url = .Value("url") + ff = UrlFile(url) + If Not ff.IsEmptyString Then + If GifsDownload And Not _DataNames.Contains(ff) Then + m = MediaFromData(url, PostID, PostDate,, State, UTypes.Video) + f = m.File + If Not f.IsEmptyString And Not GifsPrefix.IsEmptyString Then f.Name = $"{GifsPrefix}{f.Name}" : m.File = f + If Not GifsSpecialFolder.IsEmptyString Then m.SpecialFolder = $"{GifsSpecialFolder}*" + _TempMediaList.ListAddValue(m, LNC) + End If + Return True + End If + End If + End With End If End With End If @@ -276,7 +286,7 @@ Namespace API.Twitter Return False End Try End Function - Private Shared Function GetVideoNodeURL(ByVal w As EContainer) As String + Private Function GetVideoNodeURL(ByVal w As EContainer) As String Dim v As EContainer = w.GetNode(VideoNode) If v.ListExists Then Dim l As New List(Of Sizes) @@ -298,6 +308,18 @@ Namespace API.Twitter Return String.Empty End Function #End Region +#Region "Gallery-DL Support" + Private Function GetBookmarksUrlsFromGalleryDL() As List(Of String) + Dim command$ = $"gallery-dl --verbose --simulate --cookies ""{DirectCast(HOST.Source, SiteSettings).CookiesNetscapeFile}"" https://twitter.com/i/bookmarks" + Try + Using batch As New GDL.GDLBatch With {.TempPostsList = _TempPostsList} : Return GDL.GetUrlsFromGalleryDl(batch, command) : End Using + Catch ex As Exception + HasError = True + LogError(ex, $"GetJson({command})") + Return Nothing + End Try + End Function +#End Region #Region "ReparseMissing" Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken) Dim rList As New List(Of Integer) @@ -337,156 +359,19 @@ Namespace API.Twitter End Try End Sub #End Region -#Region "MD5 support" - Private Const VALIDATE_MD5_ERROR As String = "VALIDATE_MD5_ERROR" - Private Sub ValidateMD5(ByVal Token As CancellationToken) - Try - Dim missingMD5 As Predicate(Of UserMedia) = Function(d) (d.Type = UTypes.GIF Or d.Type = UTypes.Picture) And d.MD5.IsEmptyString - If UseMD5Comparison And _TempMediaList.Exists(missingMD5) Then - Dim i% - Dim data As UserMedia = Nothing - Dim hashList As New Dictionary(Of String, SFile) - Dim f As SFile - Dim ErrMD5 As New ErrorsDescriber(EDP.ReturnValue) - Dim __getMD5 As Func(Of UserMedia, Boolean, String) = - Function(ByVal __data As UserMedia, ByVal IsUrl As Boolean) As String - Try - Dim ImgFormat As Imaging.ImageFormat = Nothing - Dim hash$ = String.Empty - Dim __isGif As Boolean = False - If __data.Type = UTypes.GIF Then - ImgFormat = Imaging.ImageFormat.Gif - __isGif = True - ElseIf Not __data.File.IsEmptyString Then - ImgFormat = GetImageFormat(__data.File) - End If - If ImgFormat Is Nothing Then ImgFormat = Imaging.ImageFormat.Jpeg - If IsUrl Then - hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL_BASE.IfNullOrEmpty(__data.URL), ErrMD5), ImgFormat, ErrMD5)) - Else - hash = ByteArrayToString(GetMD5(SFile.GetBytes(__data.File, ErrMD5), ImgFormat, ErrMD5)) - End If - If hash.IsEmptyString And Not __isGif Then - If ImgFormat Is Imaging.ImageFormat.Jpeg Then ImgFormat = Imaging.ImageFormat.Png Else ImgFormat = Imaging.ImageFormat.Jpeg - If IsUrl Then - hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL_BASE.IfNullOrEmpty(__data.URL), ErrMD5), ImgFormat, ErrMD5)) - Else - hash = ByteArrayToString(GetMD5(SFile.GetBytes(__data.File, ErrMD5), ImgFormat, ErrMD5)) - End If - End If - Return hash - Catch - Return String.Empty - End Try - End Function - If Not StartMD5Checked Then - StartMD5Checked = True - If _ContentList.Exists(missingMD5) Then - Dim existingFiles As List(Of SFile) = SFile.GetFiles(MyFileSettings.CutPath, "*.jpg|*.jpeg|*.png|*.gif",, EDP.ReturnValue).ListIfNothing - Dim eIndx% - Dim eFinder As Predicate(Of SFile) = Function(ff) ff.File = data.File.File - If RemoveExistingDuplicates Then - RemoveExistingDuplicates = False - _ForceSaveUserInfo = True - If existingFiles.Count > 0 Then - Dim h$ - For i = existingFiles.Count - 1 To 0 Step -1 - h = __getMD5(New UserMedia With {.File = existingFiles(i)}, False) - If Not h.IsEmptyString Then - If hashList.ContainsKey(h) Then - MyMainLOG = $"{ToStringForLog()}: Removed image [{existingFiles(i).File}] (duplicate of [{hashList(h).File}])" - existingFiles(i).Delete(SFO.File, SFODelete.DeleteToRecycleBin, ErrMD5) - existingFiles.RemoveAt(i) - Else - hashList.Add(h, existingFiles(i)) - End If - End If - Next - End If - End If - For i = 0 To _ContentList.Count - 1 - data = _ContentList(i) - If (data.Type = UTypes.GIF Or data.Type = UTypes.Picture) Then - If data.MD5.IsEmptyString Then - ThrowAny(Token) - eIndx = existingFiles.FindIndex(eFinder) - If eIndx >= 0 Then - data.MD5 = __getMD5(New UserMedia With {.File = existingFiles(eIndx)}, False) - If Not data.MD5.IsEmptyString Then _ContentList(i) = data : _ForceSaveUserData = True - End If - End If - existingFiles.RemoveAll(eFinder) - End If - Next - If existingFiles.Count > 0 Then - For i = 0 To existingFiles.Count - 1 - f = existingFiles(i) - data = New UserMedia(f.File) With { - .State = UStates.Downloaded, - .Type = IIf(f.Extension = "gif", UTypes.GIF, UTypes.Picture), - .File = f - } - ThrowAny(Token) - data.MD5 = __getMD5(data, False) - If Not data.MD5.IsEmptyString Then _ContentList.Add(data) : _ForceSaveUserData = True - Next - existingFiles.Clear() - End If - End If - End If - - If _ContentList.Count > 0 Then - With _ContentList.Select(Function(d) d.MD5) - If .ListExists Then .ToList.ForEach(Sub(md5value) _ - If Not md5value.IsEmptyString AndAlso Not hashList.ContainsKey(md5value) Then hashList.Add(md5value, New SFile)) - End With - End If - - For i = _TempMediaList.Count - 1 To 0 Step -1 - data = _TempMediaList(i) - If missingMD5(data) Then - ThrowAny(Token) - data.MD5 = __getMD5(data, True) - If Not data.MD5.IsEmptyString Then - If hashList.ContainsKey(data.MD5) Then - _TempMediaList.RemoveAt(i) - Else - hashList.Add(data.MD5, New SFile) - _TempMediaList(i) = data - End If - End If - End If - Next +#Region "DownloadSingleObject" + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim PostID$ = RegexReplace(Data.URL, RParams.DM("(?<=/)\d+", 0)) + If Not PostID.IsEmptyString Then + Dim r$ = Responser.GetResponse(String.Format(SinglePostUrl, PostID),, EDP.ReturnValue) + If Not r.IsEmptyString Then + Using j As EContainer = JsonDocument.Parse(r) + If j.ListExists Then ObtainMedia(j, j.Value("id"), j.Value("created_at")) + End Using End If - Catch ex As Exception - ProcessException(ex, Token, "ValidateMD5",, VALIDATE_MD5_ERROR) - End Try + End If End Sub #End Region -#Region "Get video static" - Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal resp As Responser) As IEnumerable(Of UserMedia) - Try - If URL.Contains("twitter") Then - Dim PostID$ = RegexReplace(URL, RParams.DM("(?<=/)\d+", 0)) - If Not PostID.IsEmptyString Then - Dim r$ - Using rc As Responser = resp.Copy() : r = rc.GetResponse(String.Format(SinglePostUrl, PostID),, EDP.ReturnValue) : End Using - If Not r.IsEmptyString Then - Using j As EContainer = JsonDocument.Parse(r) - If j.ListExists Then - Dim u$ = GetVideoNodeURL(j) - If Not u.IsEmptyString Then Return {MediaFromData(u, PostID, String.Empty,,, UTypes.Video)} - End If - End Using - End If - End If - End If - Return Nothing - Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.ShowMainMsg + EDP.SendInLog, ex, $"Twitter standalone downloader: fetch media error ({URL})") - End Try - End Function -#End Region #Region "Picture options" Private Function GetPictureOption(ByVal w As EContainer) As String Const P4K As String = "4096x4096" @@ -541,10 +426,10 @@ Namespace API.Twitter End Function #End Region #Region "Create media" - Private Shared Function MediaFromData(ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, - Optional ByVal _PictureOption As String = Nothing, - Optional ByVal State As UStates = UStates.Unknown, - Optional ByVal Type As UTypes = UTypes.Undefined) As UserMedia + Private Function MediaFromData(ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, + Optional ByVal _PictureOption As String = Nothing, + Optional ByVal State As UStates = UStates.Unknown, + Optional ByVal Type As UTypes = UTypes.Undefined) As UserMedia _URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern)) Dim m As New UserMedia(_URL) With {.PictureOption = _PictureOption, .Post = New UserPost With {.ID = PostID}, .Type = Type} If Not m.URL.IsEmptyString Then m.File = CStr(RegexReplace(m.URL, FilesPattern)) diff --git a/SCrawler/API/UserDataBind.vb b/SCrawler/API/UserDataBind.vb index adcb318..86d3b2a 100644 --- a/SCrawler/API/UserDataBind.vb +++ b/SCrawler/API/UserDataBind.vb @@ -49,13 +49,10 @@ Namespace API _CollectionName = NewName If Count > 0 Then Collections.ForEach(Sub(c) c.CollectionName = NewName) End Sub - Friend Overrides Property Name As String + Friend Overrides ReadOnly Property Name As String Get Return CollectionName End Get - Set(ByVal NewCollectionName As String) - CollectionName = NewCollectionName - End Set End Property Friend Overrides Property FriendlyName As String Get @@ -367,7 +364,7 @@ Namespace API #End Region #Region "Open site, folder" Friend Overrides Sub OpenSite(Optional ByVal e As ErrorsDescriber = Nothing) - If Not e.Exists Then e = New ErrorsDescriber(EDP.SendInLog) + If Not e.Exists Then e = New ErrorsDescriber(EDP.SendToLog) If Count > 0 Then Collections.ForEach(Sub(c) c.OpenSite(e)) End Sub Private ReadOnly RealUser As Predicate(Of IUserData) = Function(u) u.UserModel = UsageModel.Default And Not u.HOST.Key = PathPlugin.PluginKey @@ -575,7 +572,7 @@ Namespace API MainFrameObj.ImageHandler(Me, False) Collections.ListClearDispose Dispose(False) - If Not f.IsEmptyString Then f.Delete(SFO.Path, SFODelete.EmptyOnly + Settings.DeleteMode, EDP.SendInLog) + If Not f.IsEmptyString Then f.Delete(SFO.Path, SFODelete.EmptyOnly + Settings.DeleteMode, EDP.SendToLog) Return 2 End If Case 1 @@ -592,7 +589,7 @@ Namespace API If Collections.All(Function(c) c.CollectionName.IsEmptyString) Then Settings.Users.Remove(Me) Collections.Clear() - If Not f.IsEmptyString Then f.Delete(SFO.Path, SFODelete.Default + Settings.DeleteMode, EDP.SendInLog) + If Not f.IsEmptyString Then f.Delete(SFO.Path, SFODelete.Default + Settings.DeleteMode, EDP.SendToLog) Downloader.UserRemove(Me) MainFrameObj.ImageHandler(Me, False) Dispose(False) diff --git a/SCrawler/API/XVIDEOS/Declarations.vb b/SCrawler/API/XVIDEOS/Declarations.vb index 4043f31..f174492 100644 --- a/SCrawler/API/XVIDEOS/Declarations.vb +++ b/SCrawler/API/XVIDEOS/Declarations.vb @@ -16,7 +16,7 @@ Namespace API.XVIDEOS Friend ReadOnly Regex_VideoID As RParams = RParams.DMS(".*?www.xvideos.com/(video\d+).*", 1) Friend ReadOnly Regex_M3U8_Reparse As RParams = RParams.DM("NAME=""(\d+).*?""[\r\n]*?(.+)(?=(|[\r\n]+?))", 0, RegexReturn.List) Friend ReadOnly Regex_M3U8_Appender As RParams = RParams.DM("(.+)(?=/.+?\.m3u8.*?)", 0) - Friend ReadOnly Regex_SavedVideosPlaylist As RParams = RParams.DM("
    - Friend Class SiteSettings : Inherits SiteSettingsBase : Implements IDomainContainer + Friend Class SiteSettings : Inherits SiteSettingsBase #Region "Declarations" - Friend Overrides ReadOnly Property Icon As Icon Implements IDomainContainer.Icon + Friend Overrides ReadOnly Property Icon As Icon Get Return My.Resources.SiteResources.XvideosIcon_48 End Get @@ -26,21 +25,10 @@ Namespace API.XVIDEOS Return My.Resources.SiteResources.XvideosPic_32 End Get End Property -#Region "Domains" - Private ReadOnly Property IDomainContainer_Site As String Implements IDomainContainer.Site - Get - Return Site - End Get - End Property - Private ReadOnly Property SiteDomains As PropertyValue Implements IDomainContainer.DomainsSettingProp - Friend ReadOnly Property Domains As List(Of String) Implements IDomainContainer.Domains - Private ReadOnly Property DomainsTemp As List(Of String) Implements IDomainContainer.DomainsTemp - Private Property DomainsChanged As Boolean = False Implements IDomainContainer.DomainsChanged - Private ReadOnly Property DomainsDefault As String = "xvideos.com|xnxx.com" Implements IDomainContainer.DomainsDefault -#End Region + Private ReadOnly Property SiteDomains As PropertyValue + Friend ReadOnly Property Domains As DomainsContainer Friend Property DownloadUHD As PropertyValue - Private Property Initialized As Boolean = False Implements IDomainContainer.Initialized 0 Then - If Domains.Exists(Function(d) URL.Contains(d)) Then Return New ExchangeOptions With {.UserName = URL, .Exists = True} - End If - Return Nothing - End Function - Friend Overrides Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable - If Not URL.IsEmptyString And Settings.UseM3U8 Then - Dim spf$ = String.Empty - Dim f As SFile = GetSpecialDataFile(Path, AskForPath, spf) - f.Name = "video" - f.Extension = "mp4" - Using resp As Responser = Responser.Copy - Using user As New UserData With {.HOST = Settings(XvideosSiteKey)} - DirectCast(user, UserDataBase).User.File = f - Dim p As UserMedia = user.Download(URL, resp, DownloadUHD.Value, String.Empty) - If p.State = UserMedia.States.Downloaded Then p.SpecialFolder = spf : Return {p} - End Using - End Using + If Domains.Domains.Exists(Function(d) URL.Contains(d)) Then Return New ExchangeOptions(Site, URL) End If Return Nothing End Function diff --git a/SCrawler/API/XVIDEOS/UserData.vb b/SCrawler/API/XVIDEOS/UserData.vb index 931cacf..05b04c7 100644 --- a/SCrawler/API/XVIDEOS/UserData.vb +++ b/SCrawler/API/XVIDEOS/UserData.vb @@ -8,11 +8,11 @@ ' but WITHOUT ANY WARRANTY Imports System.Threading Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Clients Imports PersonalUtilities.Tools.Web.Documents.JSON -Imports UStates = SCrawler.API.Base.UserMedia.States Imports UTypes = SCrawler.API.Base.UserMedia.Types Namespace API.XVIDEOS Friend Class UserData : Inherits UserDataBase @@ -24,8 +24,8 @@ Namespace API.XVIDEOS If ParamsArray.ListExists(3) Then ID = ParamsArray(0) URL = ParamsArray(1) - If Not URL.IsEmptyString Then URL = $"https://www.xvideos.com/{URL.StringTrimStart("/")}" - Title = ParamsArray(2) + If Not URL.IsEmptyString Then URL = $"https://www.xvideos.com/{HtmlConverter(URL).StringTrimStart("/")}" + Title = TitleHtmlConverter(ParamsArray(2)) End If Return Me End Function @@ -43,6 +43,7 @@ Namespace API.XVIDEOS Friend Sub New() SeparateVideoFolder = False UseInternalM3U8Function = True + UseClientTokens = True End Sub Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) If Not Settings.UseM3U8 Then MyMainLOG = $"{ToStringForLog()}: File [ffmpeg.exe] not found" : Exit Sub @@ -55,6 +56,7 @@ Namespace API.XVIDEOS End Sub Private Sub DownloadUserVideo(ByVal Token As CancellationToken) Dim URL$ = String.Empty + Dim isQuickies As Boolean = False Try Dim NextPage%, d% Dim limit% = If(DownloadTopCount, -1) @@ -77,39 +79,43 @@ Namespace API.XVIDEOS URL = $"https://www.xvideos.com/{user}/videos/new/{If(NextPage = 0, String.Empty, NextPage)}" Else 'Quickies URL = $"https://www.xvideos.com/quickies-api/profilevideos/all/none/N/{ID}/{NextPage}" + isQuickies = True End If + If Not j Is Nothing Then j.Dispose() r = Responser.GetResponse(URL,, EDP.ReturnValue) If Not r.IsEmptyString Then If Not EnvirSet Then UserExists = True : UserSuspended = False : EnvirSet = True - j = JsonDocument.Parse(r).XmlIfNothing - With j - If .Contains("videos") Then - With .Item("videos") - If .Count > 0 Then - NextPage += 1 - For Each jj In .Self - p = New UserMedia With { - .Post = jj.Value("id"), - .URL = $"https://www.xvideos.com/{jj.Value(n).StringTrimStart("/")}" - } - If Not p.Post.ID.IsEmptyString And Not jj.Value(n).IsEmptyString Then - If Not _TempPostsList.Contains(p.Post.ID) Then - _TempPostsList.Add(p.Post.ID) - _TempMediaList.Add(p) - d += 1 - If limit > 0 And d = limit Then Exit Do - Else - Exit Do + j = JsonDocument.Parse(r) + If Not j Is Nothing Then + With j + If .Contains("videos") Then + With .Item("videos") + If .Count > 0 Then + NextPage += 1 + For Each jj In .Self + p = New UserMedia With { + .Post = jj.Value("id"), + .URL = $"https://www.xvideos.com/{jj.Value(n).StringTrimStart("/")}" + } + If Not p.Post.ID.IsEmptyString And Not jj.Value(n).IsEmptyString Then + If Not _TempPostsList.Contains(p.Post.ID) Then + _TempPostsList.Add(p.Post.ID) + _TempMediaList.Add(p) + d += 1 + If limit > 0 And d = limit Then Exit Do + Else + Exit Do + End If End If - End If - Next - Continue Do - End If - End With - End If - End With + Next + Continue Do + End If + End With + End If + .Dispose() + End With + End If End If - If Not j Is Nothing Then j.Dispose() Exit Do Loop While NextPage < 100 Next @@ -119,18 +125,12 @@ Namespace API.XVIDEOS If _TempMediaList.Count > 0 Then For i% = 0 To _TempMediaList.Count - 1 ThrowAny(Token) - _TempMediaList(i) = GetVideoData(_TempMediaList(i), Responser, MySettings.DownloadUHD.Value) + _TempMediaList(i) = GetVideoData(_TempMediaList(i)) Next _TempMediaList.RemoveAll(Function(m) m.URL.IsEmptyString) End If - Catch oex As OperationCanceledException - Catch dex As ObjectDisposedException Catch ex As Exception - If Responser.StatusCode = Net.HttpStatusCode.NotFound Then - UserExists = False - Else - ProcessException(ex, Token, $"data downloading error [{URL}]") - End If + ProcessException(ex, Token, $"data downloading error [{URL}]",, isQuickies) Finally If _TempMediaList.ListExists Then _TempMediaList.RemoveAll(Function(m) m.URL.IsEmptyString) End Try @@ -152,8 +152,16 @@ Namespace API.XVIDEOS URL = $"{MySettings.SavedVideosPlaylist.Value}{If(NextPage = 0, String.Empty, $"/{NextPage}")}" r = Responser.GetResponse(URL,, EDP.ReturnValue) If Responser.HasError Then - If Responser.StatusCode = Net.HttpStatusCode.NotFound And NextPage > 0 Then Exit Do - Throw New Exception(Responser.ErrorText, Responser.ErrorException) + If Responser.StatusCode = Net.HttpStatusCode.NotFound Then + If NextPage = 0 Then + MyMainLOG = $"XVIDEOS saved video playlist {URL} not found." + Exit Sub + Else + Exit Do + End If + Else + Throw New Exception(Responser.ErrorText, Responser.ErrorException) + End If End If NextPage += 1 If Not r.IsEmptyString Then @@ -174,7 +182,7 @@ Namespace API.XVIDEOS If _TempMediaList.Count > 0 Then For i% = 0 To _TempMediaList.Count - 1 ThrowAny(Token) - _TempMediaList(i) = GetVideoData(_TempMediaList(i), Responser, MySettings.DownloadUHD.Value) + _TempMediaList(i) = GetVideoData(_TempMediaList(i)) Next _TempMediaList.RemoveAll(Function(m) m.URL.IsEmptyString) End If @@ -182,19 +190,19 @@ Namespace API.XVIDEOS ProcessException(ex, Token, $"data downloading error [{URL}]") End Try End Sub - Private Function GetVideoData(ByVal Media As UserMedia, ByVal resp As Responser, ByVal DownloadUHD As Boolean) As UserMedia + Private Function GetVideoData(ByVal Media As UserMedia) As UserMedia Try If Not Media.URL.IsEmptyString Then - Dim r$ = resp.GetResponse(Media.URL) + Dim r$ = Responser.GetResponse(Media.URL) If Not r.IsEmptyString Then Dim NewUrl$ = RegexReplace(r, Regex_M3U8) If Not NewUrl.IsEmptyString Then Dim appender$ = RegexReplace(NewUrl, Regex_M3U8_Appender) Dim t$ = If(Media.PictureOption.IsEmptyString, RegexReplace(r, Regex_VideoTitle), Media.PictureOption) - r = resp.GetResponse(NewUrl) + r = Responser.GetResponse(NewUrl) If Not r.IsEmptyString Then Dim ls As List(Of Sizes) = RegexFields(Of Sizes)(r, {Regex_M3U8_Reparse}, {1, 2}) - If ls.ListExists And Not DownloadUHD Then ls.RemoveAll(Function(v) Not v.Value.ValueBetween(1, 1080)) + If ls.ListExists And Not MySettings.DownloadUHD.Value Then ls.RemoveAll(Function(v) Not v.Value.ValueBetween(1, 1080)) If ls.ListExists Then ls.Sort() NewUrl = $"{appender}/{ls(0).Data.StringTrimStart("/")}" @@ -228,31 +236,28 @@ Namespace API.XVIDEOS Return Nothing End Try End Function - Friend Function Download(ByVal URL As String, ByVal resp As Responser, ByVal DownloadUHD As Boolean, ByVal ID As String) - Dim m As UserMedia = GetVideoData(New UserMedia(URL, UTypes.VideoPre) With {.Post = ID}, resp, DownloadUHD) - If Not m.URL.IsEmptyString Then - Dim f As SFile = m.File - f.Path = MyFile.PathNoSeparator - m.State = UStates.Tried - Try - f = M3U8.Download(m.URL, m.PictureOption, f) - m.File = f - m.State = UStates.Downloaded - Catch ex As Exception - m.State = UStates.Missing - End Try - End If - Return m - End Function Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) DownloadContentDefault(Token) End Sub - Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile) As SFile - Return M3U8.Download(Media.URL, Media.PictureOption, DestinationFile) + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim m As UserMedia = GetVideoData(New UserMedia(Data.URL, UTypes.VideoPre)) + If Not m.URL.IsEmptyString Then _TempMediaList.Add(m) + End Sub + Protected Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile + Return M3U8.Download(Media.URL, Media.PictureOption, DestinationFile, Token, If(UseInternalM3U8Function_UseProgress, Progress, Nothing)) End Function Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, Optional ByVal EObj As Object = Nothing) As Integer - Return 0 + Dim isQuickies As Boolean = False + If Not IsNothing(EObj) AndAlso TypeOf EObj Is Boolean Then isQuickies = CBool(EObj) + If Responser.StatusCode = Net.HttpStatusCode.NotFound Then + UserExists = False + Return 1 + ElseIf isQuickies And Responser.StatusCode = Net.HttpStatusCode.InternalServerError Then + Return 1 + Else + Return 0 + End If End Function End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Xhamster/Declarations.vb b/SCrawler/API/Xhamster/Declarations.vb index 0d07a8c..22ae673 100644 --- a/SCrawler/API/Xhamster/Declarations.vb +++ b/SCrawler/API/Xhamster/Declarations.vb @@ -13,7 +13,6 @@ Namespace API.Xhamster Friend Const XhamsterSiteKey As String = "AndyProgram_XHamster" Friend ReadOnly HtmlScript As RParams = RParams.DMS("\
  • o8o{G13OIl;kq)OJe8g&Kkp&G87SdBS1SF+}BMAAW zVU#R+MV54!-O53x`F%ZAj_Y>o{qiO_VnWT18{X(eKneTpGR=j~I1a$0P8iuDKrh@V z5Xl->C%`2znVixR85^PnE9MkO&jNee(-wDi#>_EXlU5TEdu~4FSTb_=Qo? z65ThwDi(u~OTdXpQZic$1zq(;oRow&ANRsG0zAv^X|&XtM#0ovJP!7FQG+B(3i~*X z&1#NX@qPV0dmTeIMac(F8SlCjt?e^|o6epMpcBB@2Xa28QO+|~CDBq^9II!0l z;r_5&Y(-!*AyX%)QCs`vL6s6Hj_K_d=Fe}Af| zs8fDPW%hBZ;k(TqZiV{j+ih~OB^4y>hP#0HqRL>rc%nM%5k6j81SMLyTN ziL3gv-z8myJuP}iT#lV`H@~2=#C*z~2**K|L*=i<>z7ZnKmiflrmzUyPzPSy@P#5R zW@n%y+`+V!^%;@lw8igf6wP-f=$}U4o*8@+JGdMn0UNyDfn5S@iKS?qi6rA?)x8>E|d?vPn8fs z0J2y~8uXY(;ot;~j-9$_hXB-MZ-4@l@HQJSu>xBG!711A;Cr99imZ%`XhrmLUok5z zc-gy&G^UhqEYEZOqJuQU3?`c@@*{lpup9L2YACC)!WdlYy!Eu>Sdv~;rfh&7!2s&f zA&tN>)Wk4bP1c}g%M}(;>+gr8LfZ-g(2~6as&IX*_mNe#5e-0-ovfd&N2u({Gom`J zH@{xS)1mr>O&}z&ExUvTn^^*`#1wMW4bAttCeLjJr}|^CLoxFfgqr5+$>Q`fuwRrx zJZ&0efeCC2ya0#qo{XoYevT0l<0n&)A#PzH5jzaV0ODPqN6+6+IC0`tDSU@%2 z^%Q&zZ*o-fy0;ng*627w>V)5mVd90Vw1kL0qnoSm&5E9 zZ#Nf9Syb&{rO9BLLwfD7(-b?fo)S?HP{C1Z zSHF`ek1HEvk8TSIp;Iu->P3qZxuQ(&w{T%*SN|43h|+L8^zPZ1O-SzFp3DmdsG^?%@XtNs80 literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/AppYouTubeSettings.png b/ProgramScreenshots/AppYouTubeSettings.png new file mode 100644 index 0000000000000000000000000000000000000000..51446397e2ee823ddafb28779e2089906209e55e GIT binary patch literal 25400 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 literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/CreateUserClear.png b/ProgramScreenshots/CreateUserClear.png index c1de5965f1a26417466698d53c9fa5092e005c4c..9bac0b302189fee3a92359d589d89ab5f7f59d2f 100644 GIT binary patch literal 16604 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 E0nV}(y8r+H literal 17279 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≠ZZ^HL`_c* z^Hf-W@J60M0Ka5e9h4nOc!W5t$2zkKVfHqB?H1E=tVQ}0;%u8iYTQtPc7$8>>5^l3 zMPK3%AI^_adI9F1Ob2pKbNk7(!e}{r-z}_yu*f@Gk?V-U>o&K=u|)ap8wY6x#Dx^8 z&GiiK5yX7w9o3LpOA`yvGR%yA+qJp&b~5|Dz@sbgqulfB?IY;!H!-ODdZt+z-q=o{ z4WTvPQ5a;}Ve89YIa4NNBcNb@$l2&W&*0TYGQt#!9Ugr~CLWCYmvkNe*b8U&a5X~L z3MG#{|GCiO_o%4~7A#8uw%aiMFndL6S@58H%mKGGpC&+T`9(QqBiM=L?DQYZ?f9jp zz@NY*WrE!QoW%WAzfc^@EajOBd_fG97*D>j{4tm1RW#kWVJ)B*(6%3wetiGuwAARi z&3>!Mai+OD3e9csoo>^KSuJ*J`aN6B)B6hn1^1?kMd|0n#0LefoFc+TG>Mv;%C2=| zzpBeR#0~R0U3KN5yX=T;S+5VLHYWoPBP-_VjvuVa9X=Pp?nq9ek!}&Ga=KCB8Mc>M z^1QXm4245GISW!{xaB>&>$jRbtF>y%UuKOxt)5KvdIoVBucA58OkNJsEv#JRv~6qt zfZOB4QH$f@o-3m+PyxDdSs2bJ?0B;>puDnOssuWOb0Ww5UO1pK9_P^ z#86-QyD{EOKzp=}A;ZzIM7#zp=`#7piK)g!B75``v^;Y2*F~%`3qJL0@yo>&9ySh< zK7&2SmcJC7>p!2{{P+@_oC=XI_OOD^mZ9$f)ujZxy&@YcPOQTPne&`MY&^*h-I`GI z@F$PYLgUhtcMee7IX_qPcA(Py?A9`n;KZam=>MlR{M^|Ii_n zU$gmzTL0J_9plmMG-H10So7W^28oe#{Ds8 zd#u#MDXF7?Dq~9Y+9i;$i!w}WA1_g zDK+lJHS^(QxO>}z%U9^1ey>Uz2^+)k`Q16I;(pT~Zs=<9PIJh3v{bJ384YP@lXt&L z#r(HGr@6YdceZndl|9kJrj|D>FaT1|$*o5<3@7DKrOpmtI@T-Z3PQo`k;5kvV%0La- zwOzl)(x<>^n&cvYZG9UT#Cj8RILEu{aKnG&{JA32GF5Vg~hw~Fp_WRYaj0(7c?cu^4 zc_6;cawG(humQ1bs>K+K>cY}#@Sl^1g5*sN-9HVtA=DZJ@3Uyr4sAddG$Yj3pIaR* zz@dcKd>r+aPPoFO1d;Qkg*HRlYwfcNN99cw&!F9+=_IddzU45*+MaZUKjB94@0di% z_s diff --git a/ProgramScreenshots/SavedPosts.png b/ProgramScreenshots/SavedPosts.png index f958f627d653de3198567c3ab5f2e10dc64983fa..a86a4885c0099d6dbd158e70de0c30a604a678ca 100644 GIT binary patch literal 30747 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 G?*9QKhl|Dl literal 13932 zcmeHu2T)U6+ipB47QlvrfD{D*>Ae?~CS3(75(pTINRuulV4;Z8TS8Z9(uF8Jp-LwR z2mv99(tGG3gmO1{&iTLpod16J&b{-`+?jj!Oa`*{-fOMB_TKOFKF{+e?2euW-6`f% zAP|U7^S0_e5a`Gh@ToXK4YXL@+)M-h9C5#=aSK$8xjYMeIcj%P_a+Eb8bz~Xbqx4^ z^2u#ecM#}w)4}IRtIG##5J>8Xrs_?@rxuH&4vvPCdowHQP>H+9aT>y-+EjS+QC;5; zz0&FyGX-%cC_&Ghtz!+KTzq#fUd-pxk_%~5i(ni|UM8Hqy$}-QC}hU#{hjU=QaZKo zo|c2Xy|!70%~?#l&zgLXi|3$6k)(qhVi+2138KsS=k*@8dhWyVnZsvo;mf7N{|!3I=QK~P_qTjP3CjV zX!GZ2%`z)TGnCVT?r4!!u`MDFPxlG?yUOoe(;u0YcCbug2Q}-YTc*f^HtCBoDd8gY zfuIxaH6f+;)tJ7gr*_V zgivc@#IC~Zmo-?;8zSZSvr}$0p{7+g5Bg4ODr{-G$R^lBYpAl&?ChA*;m@2s1vlZ^!jRjQE^;YH*Y!M8 zd%0)KpVr4BvgBE$qEzfU^VFap=3%)VRK2N|Fu84v1I0ya&4mWDMOAXK`V7c0eMGU# zIew#nZm$A|hkdCr9I?56$=?K0&$N}%kae|7%1pS#7n(MWpsfARi!rKX|sJ_s%})% zqcJzv-UO2~!uNVwYxK*ZS{q0snMTJ_O+Vpfi`ukvQ*NhV;xjJawnLal_VSZ%My0JX zL#B5^SVyt2h1#itvaX$Sg#cd#&s2G%q51rcsR{?ug%m%q<^J5`wd!GfyomF`fS~`DXz-E5Za{I#CP<1J3{3!A6^zQ28EkEg3q%o%m5xBujN&}2D z8(*L2fH@9N;-Drx!bo!ATGt+I4}?9QTTf^iG$)29EPCQr49XTuptk-!(Dl!wi23pL z>z}@6xWCw_ycRU<=G)$}_6E6Tv58_VtJUyiU+b)387?H^zDWT~C=4v&I~QtV7Z%yQ z=D%A26&-ZA-I2rcApVq0qXg1lWku_V8H!l{6um z;?B*So^rN|AdwM=gJ-+y## z&<{d&jI&EhUS{*nCkmlv@77|iOH07@lD=W&65}SDCtiPQ|J=KM{U<9Hwe)9gf1Fl9 z5brpHo63Ji-aoSS-m^YCA4XO|$R$|m5bP4zz-VuF~S!Cnxt9F4R?VZbZ( z;Ri+XHem;}aKXQBiWLB%JHCI#qenm_xIwlK`q#HQRJR0ZwEtHqE5B`EKo5HRn~zzv zH=;>NUEoxM>x;(7uv*^)n_#f)0#&7pTJfDcEoQg~)s4w3 zT_Hu|=WT%qdhO)e*RRpYp6(QBm`9p3jlB9r`nK{2mTaHz5}G^faUPzY4E_N>M$mR+!#y~tHYg5CfS-+y1z3l zDcbDytkxy)3hdAo5~`qK0*Dj(~P3$qs0h8SVft5g|*Lc8oRE)AoS&F)l+QE~jl z>x*u?{rcC)4aeP__WbPwmSkl;X72cA+-miVh>XmE%0HdW7sj+kvc}E!=h^`Q`dJt- zP}(tvCr!65tCI$xwIz2SaRgHK{OtY4>1M@|Kc1gaj=w@rNLPE4Wi!BMzrSHTD}EFG zurW>IryhDA%|-Ch*pqe7DKx3PbNaH(o%Kqs%-SWm5xIY~PFD1EpHusUXO-{d17;uv znK-YB*r+Z|e}B5AV*Flc7nzb8z9FR8;@4gE^~PM+*2koCBuom=OYUkF?#j$JjGZ5S zM)$ru#1@vov%<=$W$kS{G%Wn@CJLBv_-!u4U@(~Xt@oKtBFZq4BYqlUwq1eXeNI-W z8t}xCUvYJ9D=Z|J)f6h0N9V}_ zuw{TaA`cy$<}-`Gc+j7iy_FaI$3j)~ojgkpbJo@6I6>uofqe_4x}MTn_+^p1c<6$}-p0;atVz|q?}`~`jb(b zlw-v+^!H!Samva+c#wr{cAZ#hoowzD)*e;$87X%fLs7PM%u=|}yG9RkbmO>@gd9_( z_2(q}3Ir4fJH={jj`lZYW@Z-qEw#IajwP++APtXcYb=!bBu6u7@XZRF>Y3);4|{Rf zD46;lF~oVK`hGy*t|!^SJk61S#*xA-gy=Rj>3cJ|$M}^gI+1LOd^j9#b}=}zVEsZZ zK8BU{iO^x6x0@FF9?dE-UlBH+M&(>K)Zy4rFP@sR9j*(k^YHN4D)&iKf<5qB7`hUA zhJ91b^rE=4i;GK354^;`^;#F`j*r&)sfR$c%)^>?L?@h10mm|^qW(fnP* z=~TS@0POH`C8C*-!)yhbX=BAcUp7J5f%vuF4!f^2}0!Ey|m^vL7k!syOL zhDAR5xt1TIrj0CM_R-UC3r~n!W_~o8(~%xToN{mbJoQ6#_+NK@HK~>k%+}LwZPn9_ zBC^QXuzHt>+L`Jjj_{v5ypC`6ImLoJI$64oIENnee0N^|h-7K<3igCq2Ahh0+)#CMZWu*zc4ds_@gHncz+Ud zxvbcjncX(4eAaTJiu;|cT0cDeZMfVuQ$jz5J1iq0wzyb`AXSn+*_llVX3X*=TFlPQ zt~l}Af(Hl$VQJ^l<=yaYF!nf!F3#bwL!LenNad58vF%wB`*;BwZ=v|P&jU);dKI%u z)j(*yMKz4Jk4xR%g3j7MiblR+)b7MWI+HfN2o9T{K5G(;A?=ekofFkz?pQyf=EEjx zS??wA0XlBmNnO-+yNO}a38x&TMqYgEOtF94lq05g?%@0X+V0w?x^g{ z8x@EQg?b$Ph$BiT=_*be-a)>mZT(}dCwo6LYNBm!0G;Gc=tzhKm)(dfhM&{dTOzcwPV|br6>#wTbZAZO5Rvt1*MU6oGbYnL*VkLS9Ie5Bt$Zc>BZ=%;G9UG*pavSDn*R>6{c~f@SAGcuF()m?xLR(QDXiioLFq%ej8_50A6AEaU{)~GCmKmbR*sY$Tb97EP(qL z-N{Nn^;2Id=FJ(UnK!G^y>{zL8}X@IOErao(_jF%6dkM`fW(7*ta&rR> zjF>H#fb*VNzPUOv`v_iXmgACNdzAR= zip;z_r2`|v!`r61Q^WyS%D-RmdlyyUKq1Lb{#CTfn+V~KR*0|Wn5Spi~3E2bPI7(k|%kdE)3HmC#N z^{;RHxAhN+16HV)1!Xvv;=(2BnNL6I)Pm>9h_@9_8LYDr z@&adPfTip(so^K=+{-ij$yz|lK_4d2wWQ%TwKfa63*)lW(0;R?EEB1)SDE^hY!jBk z>oQWVTk8$Uuy=@St{`Z2ezNbg-W-u|NGy~RUu1AEtkHirp5F-<)>ctbQGbRUuC&Tr zO8{oeeclw25MpH4;QLx`AsGq8scL)CmO^6~rx$R8W+9JG0*@ z*x z9s3=GPav)44|ntk23K8pF@7|dl|J}mY3(nPBy<>VXGAjB*V&H)SY9jmZ`GGX`|$gk z_wTxe-CQpUR2tNRxYDBxs63X1-8e;&8Js4s;gZJTMC887>|Hru68~9{6c&xsM+Ch z%fs)N+{c(5B%*MpuFC~%j!fFBX_(GW@?LQy;_)?DVb$kLuQV`2KA(R?uUznrsi)h zQN3LNz`7_}M1DTM#kOs#3X{6=pcJWJEFzy<)*UYI=?GkJMZ2gyN*IyfNH377?gNL(A;MX>3 zS{5Sf=t~CzX#rFCa}cCja&j^Vbg^yZMQu~9O=n{4iAeejhsoILDS7TwR?w<-b~{T; zfQ8=#GPdcSj3~o@DDpoP`5%h>4@EvmP5zuge}Rv3;{(HP zR|TS{e01!Zm%6;)WBJ#I9HWlJUk0Yy5MeIj%hZGGZ*O2iVm>H}{{WX5&y)_cQSr() z(#ETp(4yvXPv>-olN23pqbQMSb8I8U*i0xCiT{!3NcvXkk}A;ZOKRV|bff)X)X|}1 znbSVS4IVCE^TQk-;aKWhiWMp4HPSv+B{2_(bz}$s)UQw!#oCYd?F8WN9M?uLx!*4o z^5Ai%*q|Fhy9Sb`Agp}`3QY~Rw_|@-<6sgaIG&+BsTi*?T~Lzo=V8i zIwYZNjgG%dGX}9DXXxl;DWkPM{TkTW@{dqM(dUjMm3NbKb>o`#l{Jlx;Vvo0J*$&_ zyNxt`)!rrR>+4(P7sZR`h(tJxwDVSgI;?p9B_*U?@=)ucw?_zrD>>S&?8INdjEZ76A%O`>^b`Etqnh+f z6BMHMYMS>%hPYkF>tLG6`sY+?Q3Vnm^8xk=%H6vu^y3`3L>&uwfpKv*Ad1}(0;pIC z%l!2@OMiJG&$iUMo^}&rAG8*mN<){02|ts<61tpAYcPFI1wuxu7jOtsBaHqDBWHJql1g zdzy-`*K~fTcMvbXuBaZ>Ep5nm={5@JqfpfBK&H_#c-6rDw1p#yV|+Z-0pQ2EzdY zQ~NNO7O@qHL0p077uZbv47Hg^_p{zYQzq*B^*j5#2?@9wZ+QxJl&~#$7mX6Pg%y{n znI85R$6)Jy5|d7IGT{_;-+=2*rAIO^q?TmCgr+z%Y0d^toHJE+Nq3ZN4FG_k)2kYF0Jixo@l4YMie=c(& zC`}XJ<2XBjwN|dt&q8So0(pX)=dBXU9qiZdcF7_yeB+WLT@%SdT05i zN2=_&yag~QUKefI@FE;!hor!9e}HCBr{@}6ojgW$CR}5|?%jUQ!@(-x=` zpAzwsdB!dyfiPH9GnrDSuC8vqHr0JV%?I)ae(wTYtPE#R;uNO zSJ!%0iINTz6XHE>N9ZRNTlB^uJ<7si3H;f(wUbtWz?-o-@cnx-pqShCr2nCqYv~iG z*76|5ynqA$XwScD{i_P}t`u?#lO&#P)q8dLvjco4!h&z5TRIjYHxRZe1trGmYax?# z=Luqr^2L4nLMh^Qdg?()*^%B^e?a&>w17yQvTNk^k`GF;$jodtU){?ZY=L0C+MTjy z-S`Tp5pH}7o#{OOKKaXBze7&9mM5+bsd=OglE5dcLmS@97jL) z{>j(1R0@!mifXK0-KN-CDICuwoBNMAHk@b5i_9iTSO~msaG-s2aRinzGud}1L*ks` zyp@cu)S6fzQh9{%EY$M8?F zavCU<_?zpsSNUN-P2(# zw(T>9AL|8*J3#OY;Z}yOTwV1}0JEEnKi8xlcK=IOZ;iev09_+jyKcxHdx&@x9-%Bf z@=!9*U{o^CM4r;h#ZaJG5>*q0%57-6I$uoVyjDc2+Z5b?0eWj0#Kg-J@s57r0-e*X zifdfX?=L&($WX40aVM6xeA|;ekKM>?T~acpx=Vu8ctMmk5Q-&mwtn21`Yeuv(KwTw zP}1p3+m1j9&{}b)7r1<5$^F+<;{^lA&Eg|URwK1Q@dMlmMY;UzB%27r(cCiJF3&0Z zq4^wa1kO^*t7FvN+^oV6>(0l*O9SA%fyoWUkrtP|QzxC5hr-H_4a)X<7RzIA3rx|u zNBCU6L|+e-MEviThy0Z=|0|;Y2S5I=s!RT%*8jNA|G3ZpxX=H%&;MV$&%7^Hq2PxR zVfEERL)E`n_Oc_5`SBl-s)*-hj{uuFqM1H#ni-#ukf@FVl-OprkLLk0`=u+oAXS8$ z%fi6Mwli-di0Q*40BRFB35&NOw4Z+r`3!sFgq*6#I=f~=2-ev(bT#s3acztob+K>a)Xj`Et z)9p)~KgurOI-BR-ljjCY#pje{BRA{wJc;wv#i3NZtN^q|w3KypSs1+Yz*1f?m0=Vj zlU{AVU>!;)KV9(fNs!gE>pThhWDteJY9DImBG-X&xH0w%->y4OOCbH^Mp$%=ea+`` zM7^o!i_fS_EtIZ}Dt=)_h|Z6HG#F?Y|Hb2Py6mh5e|4uaF`@0BZ_(4N$xWqS`9^2m z&}OT!i9ePu=d_qYm)jDhf7bsnU5FF962bjpcD_nTJcU)h`|^2}9=V88zwcG*`u4}E zk|#@vGCcK5be4ILvmQHNQ|j)O9J9Rl?{caCS%vf8v#x)C5NC(*oX(5*HBxfj)3l zT$oy$?$O{g{$P0RSGCqz+or)^MsaMA&!b0e!_$DbcHm0~vu3_NX+6>s(Wl^)%oVV$ zSzTT2KT_+PC=6d>w1^%=`DN$kwpJ7&T<-@4){R!WHj%{V+|AiXrFDLHo^Ln#|7<*_ z$sO0X@pe_Zg&8Z;rR4r%FDJEZepEuAH*%Rh2GGwt- zTGet`4RVzR8Da!ABp7C#n|LdH8uoL6CEWepa|JP**B-*=eA94&nCCsp@VzLdSk3}t ziY*2P_QZLJS@E%A3)#@4={iUq69s`Wi(ALQI_1S)L z;QrUE0s~=QNLE^1Ih{$F6UAeBC8FzK`>0Q-m5}u$)8+b5_KxTM z0u{g@H=t(87iXe>_xSjTYo5x~4ot7b7tS`0ngXnRPh0pG|a%yOXaOcMJIR@RzjRqn}KZ_qHA70?M6UzI>@?DcMM`Cj095%V19VxaCygukJ>61u}_LBpa4_NZWk>jn|{i@_K6scnw=iPZ*Yo9Z!Z5yZr zD3BEr0BliL+zG!D(fPHHm;4TSy|)e`RGF+Qq_p)`xI;xfqnGl{z-_FCShknu4it`& z3S)p$tppflQ#O!sv6w~$cblmQ9q5~C5n16@B8lgXcmI6FQ)6C;0FYUPbGtc zgMd&V?zic9AW&#?SsD5IN@wWrx~Z)k;Z#|h?#vElV}5gB#A8yg)4gG6FE+1Hby>d` z=7NrC{+6c~U%}*;;eRi=+wsAEV%3mqLV(+Fs;s-%)zt3cNWE!(Aa3b!m5J1_wCnsLJ}{e`Aw-@87U zb(JnD0V0AB1T8>EM|Yq|jE6-0S=eU8{2=WZpi+Hwz#P`kH1HPOqRR7Dsc^#a)z$4) z?|d%SD|aC*QB;NDA=_DDGk57%5;9=i8c}QsOG``Zo3~o;`<}8Oww#^g#lQ1tbuS^76J7=QD< zs8DH(s-<~Xp+o#r-30O+VgKt?9QCmV z;>jDDXKmjKFkiF1l^pqwz5yYT+#2>Y;#)ISuNU@M(}I#$dX$5lJP|ArsV76fXX>VY$J3857Vl*Bv->mdr;j4hcQ@c+1T+fCM)UR?0MzOc zrQh-%O23M{A%pxPC6O-IuiNd2{Bj=~+8qF!H>eY6X1R8bR_0mkfmR?Z+Lem()wiSQ zIr%|ZnDO-s-%;wRIk_cGkp}4>_!T9ADmhk0u_o#}=Yh0^h3*|jOY-e=CP%q+A541x z`(#ndY{WxoE>67S$Sc6-?`W2dNRtaLLXIu>4?nN;%?J&a#(HURYzh%2r)ZxKOFWCc z0*qSet(J?;E}r$><7$@;cKU6uM#b;_a?Vl*CV)OER6c+YvpsEwH<*Lg@rJW^hBF}) zf|w8>V4;%^!C+UEPg_j+IFf}>cI_k0x40%Tp-96<&i%ZBS(CZz*FT_BaYI>^0w=7@$ E54FnyY5)KL diff --git a/ProgramScreenshots/SettingsGlobalBasis.png b/ProgramScreenshots/SettingsGlobalBasis.png index 9cd66bcd728de8490717808fe0f55258357c02b9..5f191d5db9d0eb0a6455845faa917482b8870870 100644 GIT binary patch literal 23768 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 P6AVDk*;rJb^}6$KA;B() literal 23465 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 Z{%ftji2nX%uf&x**q*hivbuQdKLCShq&ffq diff --git a/ProgramScreenshots/SettingsGlobalBehavior.png b/ProgramScreenshots/SettingsGlobalBehavior.png index 54b607945c043126efe75a4a79ffe4557d4c7a30..4b8be2f9bb2153367a420cc28544eb082f021cae 100644 GIT binary patch literal 14028 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

    9SW_X|j)a`K+u4tpz~ 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 literal 13665 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 diff --git a/ProgramScreenshots/SettingsGlobalChannels.png b/ProgramScreenshots/SettingsGlobalChannels.png index 2199127469740c167c3ef95b41fb3533b68fc1a4..cba4b6e0b289ca04fd3a99773fca0c285daaa74d 100644 GIT binary patch literal 12620 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 diff --git a/ProgramScreenshots/SettingsGlobalDefaults.png b/ProgramScreenshots/SettingsGlobalDefaults.png index ff0b745ae60bebd089f246fad1802b7984d79137..9e8ca07d0a72477dd8c1d19aa31bb91465081729 100644 GIT binary patch literal 11858 zcmeHtXIxX;_HAsS0-~Z)C3a9LB2o?lIf!)WDnv?Dq(lhS&`DI9h``i!vZ!55RE z(pN>mBD<6_Z^mHvi+qWGM7Gx;E4l^-b~|KgaOO2;iI$L!F4~YQqd2pXL$G3Mx~Xsa zNge}%Qtt9#LLf{S2&BIscrwxFM?t?vWbXz2c3OHD=+PfTHb)bC0$b@Vos=iWbT!aZ z@?%S_xi4|Sp8j9x6iE{@bNMTGXy-@|ed3t?+K&4HDG1A|+0s-)so_VpRhX0n=W+>Y zH)#Gu+Cf7!ixom;@M@l5tAwc82CSW=Mi90wYBq)me6!or-PAkD=qu@K!^|+LjODUn z#4eC;H0F8#ML|xtC+%#kfN4;3ZVPFGId_G%rP3nn7orjTy$2h~oYlnr4Wd;VS&YdQRN^-6f#>Y;8KHjaTCxH28vjTMKb<(BL$g$ySsH(KahVV|1>e5SQ zIKF{tNhODdQm1&A1c+qz=JHsP`yqM%mTKxg5B09!KIDPX%8Lmb2X)(RSGb8mWGGn2kqo&JLGdbG7Sf z)|%@hEDKRLog2^D`VLHl*+6#UsBF?UZoj@&ct~83#R#(#A|z?2Iu+39xj3y7>4#dj zq`pkkG~bI6Z@y@{-7>sk=vZdZY`((oP;?GYcaDfN+52wB74%J?c;bb!P<&2!sKp?+ zdPmfLttT!l?qq{X!z4l_LI}M?wM6##kBL&Z{Kh5Z8c~NgU{CBu@P4{4qACqwHK){r zsL*p-2S6$E0=j7e27SOnB1@_QUFaoF1TejD84=sx)b-%borpE{4s5R$KAr(KdCajDm!? z=N8a&IehU8l1EFoZdB)3YpVG~@PS4OXiuv~e>)~Ck!X(`Wl#}IJ5Oq;4zPnu+|R-- zmG;{+!+~*}Y7J_uFicnuMbvBIldsd?eI@?TgXFp+m%p*wja5@FU z5+oVF7pR#A^e;swH%_;C_EqDV)U1b_nd1%Pwn0vGFWkCHO812JYnr<~NCxdIw2LDim9U!9CE2&$THAOO6 zK|9V>+f3$3@CISdj=DwIx_=~5^XT^XaeVlq7vYz_Hks)z6aJfy)^HAA4fIp~W?)+# zRI4D^uJ>x~IuEGc*yhTZoAo^)klDi9HvXUJrTnvYn^ezkS+uyOCkGNH?#{X4-zk7T zX}^+0Z|;<9y0EoLqJZtUhnW#X7~`F1j}1jAlhJXx*`SvbUA8ofZm8}Kakl?0EP;By zX5~B%2h9+rI@1^p$W$W_NAf#scc%j&-#F+q7sUtg0#VF>N#G6t)v0=_DQtu0IT{2x zPT@om1efX|+`Q>00wGLnTrew!Y{&<66pZ8Z@UvRS=xe+2Ko=$7lkez!hY#^X6%sZ&+^jiyX*W zUH9zB1&d}fW|PAQU@Y2ljh^P2^qQsjo?Di%qb4k}gAJY>)d4NQY->mXUoR`jK9&)@ z<@I2qcjxBjR(YO=RQfWn^P67NtqHwwJ@uOX29?|IzO=ZnP8fPe34mNG=K%67Tikmd z^pqa}(^xh`<1%%hw>L6{W|&uk{@YJrL~R!vE~Hobot@WkzMEKk zA-%%OaAm?+28oUoNs*u{IFzR#!Fd_m+ruGS!h(jOT@5d6 zw_!soeTQjtqA0v##8JRJ2s&b0d*rT$_P{%L#Bzc{wE4X(qvO1!|fTl6VA5=%k9 z$O=8r^rEnqe2sW8?mviFP8#bsL?5ruVx-e$%`Kaup1b$%XU(9sWxe?QzFNzzhn`t* zu+N%f_pPCR3{O>#hPWf}=FZWd6;f-fc#Cly@$9 zX%bUU^%Yt$+56ryUO5uEsP6245j&2WaU1r8YYcanORTqS=i zV!m%lw~b0oDuW+#A6+5Ue{^gdc4#b|8geQcBR}^|+J^e=ePxgaovPaT(dOoZxZ|MI z3v46X$1^|4&1Yu2Qe;g(BygZy zUj;TIH!ur%eaM&WUH>2e&nhW#%axXIKTF*Bj9XD@oK$IA4X}kZ&guqDqpEN!(r6or z;ZF3^*;wO65xE911BYl2?y<(LC&W&;ymElH+-Ps;Gi~Z^vra2yp7i(OKlF5L>?bIPZQqY$n7)#?AQnx*%#xy#UH+e^b zzcY_*Ulu%BS^qSZgXc-&Hyrww6fdYT->LN`}Nkm^KAwYuio)R zn)|tn{&jU*-+Vb!OmhUDFz)VurkIqHPa5`ZL~y%bYszzC7nGjsRM@!&P5a*Ap6HA* z4&hH8kE`mgLahYPSoeq$s3Iz3r84Z$GZcqw+f=2zUUuVPFR6pJKnFi$RE22Vt=1X) zyE>wiz2h*ytiWAy1HOX{gh+q#_;?|aJ+`=PvCY0ixPSc!8sYScqUsE)kqfZ9l zMYj5$8S*qWc)6nqEYI7d@54h2a`!jElCslt^eAqj=1nBjwNY0*IuWlC2b0qLX5NQh z8w;=r5?v#_yBABX;FxP5!Trj@b3T0)A07gsBh%m3u)2=` znM~=j{jj@x8B2*kZ$Q%LM9GSSV%`yzrc;;lZZl6Rbxeian!{Nl29sH0$(*Z3^m6yu zI|XWekJ`ON#vKm7*cG_5oui&PI29$sGcoSljg?(!n^lYpscfvH=yx)$qNmZr3ponV zMlE}QK!WTv5{aiv5fsw zDozqVE*Y!vs#5M-n;76i)-KR#75ij%T5>VLlSr<8d<8qe23i@T(?QB?$0C+AO46F# zlkh3ml@aY_TQksykPdKLtwYl{>z+#<sFx2EBx+-G7P;lD@8x2yOp#Uy_#L<*_6WcBYi)KR@8kH1r?CxqxS zGHC4&EKYMsNa_}AzY6-CU>|HJQ8`K9o?KJ-g+4Ok?q1I)!ExF=GXawY_$7H`6eqv3 zX(F1SGpy1{Ckj-y!Poh0eC%*>#!bn%)o@g44@A(N=JOjp=v~NTxKG4S;Nb4-UPR@$ z5r3J=dT_{DxKPjrnwkEa58dhg+y@Ng8e%$zIQ^x1B(4wkYLG5PYYj2}AVr7I+LGV_ z^tHmq)Q}!&4+THBdvVlw&TEYs2EEH#P7NiSbbZEa=cUkEPRKBTjSp=fw&$JN?%x%U zM!BEZ+I|5@EO649VnvhREn?xp8KM&vi_4W}=o6jjZR!tN&?%Me+*RB9RELSX5ZNpy zcj#e3FV|fxp}<$1tArDW^%T;Sy2SG8dTY@PW!m*MdvA`~lE$OBplYxqq}-ju45%9K zB}fl28&(T?-N&3LU0jaf-65;( z!Q-Z#)^~enV`0&bXtP_vxp+outYc%ySkLFRM{XSoMd*~O7%ijv+7NxBioN_Q_elGr zlgYylC)+=W#m#u-6HUQW`I@_KW21!@rLTN%gUI$)rE#~~5%kqFPhXLs-#A6Bs}sHT zk8X{PmC$i+e$pyFdUH)NZE)R*lBM%jXx-~;v79p#vwaniorr1KkX{-2u>$6K?!4Un zpV}PFi#hp-EZFhRnpp1DmDj2OZEr1?R6LDG~E4w&yZJ8lD}QT=a#DHw~y)BgM#FkGZF%YL`N4|#P;pR<70)V%KNjAw!?dB zimT8dYe?jI>7mneRRbn`OtRm^e0G(P$z%~Fvcv`bTG<=v+bpu@aN#rxO(Q$_bC{G>QiAqAR({q?q=6qPYF!<@P)k z{f#}K;m(fd77g!D>@nxpW%Qo_R&ZVVI1l%0(IO#AYW9Kui(Ma%=uUpoM8!z06$T$R zrIEL5O2%$-{UxVj1byQ^)q0pG@*TeNoOQ)Z*b23whK2p{{m)^;F`6 z>@lZfha!6Gy*A#L!tew6kV=zad#t!Yvrd1HXWF^oK}UEHoNP;~q*&UF7H%39>$Wna|M6pv6M&T&rVh_O-Wt(xx=oO3~WGX0Ss=PUat)wV~)|YAc&J zDy5OXK6bl4N=n`Fui1JdVv{6eR~O9p)wRED}MnRo)~)GIBZyQ67#k&Xg?;WcIy5~OsbTe6JVzf{`11W3RV9%T*V3+au;<+MqK(9@WZ^IkxTV1x&`1m zaq?FWJ6=#3JVXR*iW9FM zb-~JHxnQN~e%ex{@~z5woLlmIZAOuf8Fhl7`^1q}t)IZ@xgya@bhMre*V?+bH^tn= zFe`4@>RsW8``63O#-eTygA@jjJ-8%`CFOTW-Kv5P6^wxw^PpEiBi+6pW|#IDXjCd@ zQs)>&%N9hqrfH`#kW%j+%kNE6%Z0v25siT}i(XjG9=vLk5RrtNjWMgV4H%Dn98IXY^=0WWvCnwEWR$@FEVu~P!DA2$C_54H!elz$TDYm*-v zybDOdJM%>Ye4#jG=SZP1=xGbBY#%0+S1`gM_LmD1s-E{zW`^4B+2EhdHdk^=rsS&h z0aH*JT_P#_cI5j3lk2kMeiI>``!KencdZ?Pko3(!2pV?(b}Sha+E39=1Ovl_&meL7O#s)j1&p(=~}?k;XNhkV8&8()j0M|`@}qmB&X=Ga%C1eKM& z*@vll|KOr<{7>OC)V^Wk|Dw0k?ARW?V7t9Vldf1RyS5I53Z5+T_2uFI^r0`qSo>KT zP7_uU$3hOfnKx`UHdHz59sng(_h%nV{&RUr2Mvc{jRH%gR3wl?$F9KITT?MnK`Gwv za%Fm}ucv5O*hvyH1Wc7GT8C9#g-6W(245R?5f|d zsz@uzU5;tKQH=^d>vLSq5LuRQ-_D7k9e%@;xQ@J+30L7s4n$vYAB!aoPwPmhxe)9p z`t?X_I_#8&)cXB8xgi<+&|Kf~E&-4cX;Ule?zF%ZLM3Gn$j0fz8cDqw= z(-#Y@FOrPo#W?A|?CjT?TSWbEf1*veHv!P%wAxgKNuy?o4Md(aA7PJAu%QB zs?hurjGhho#;^gccc*Lm-SD_l3R`se&>N*J#)-%E;o?H=SDWrCtnq;KUI1a^Zy?K) zBl3!dkp)z7GyB^9#xwfsJS+6JADNh|r=D=WuyfpxxIJ`9*?(ijH4tmb4?zZuT9uOP zb_`2V^FEKWmJV?jS@G2T?pnmTrSmvNKd_YcchxJqL7jga?XmC9MdSjRfoNlndc+F= zW@cXy#I3P@_h3p)rN;Ct&rp=~yNv>y6B^P5vHg=jt&N(wcyvGgh zx(J0}V_4OQyV6$Z^*Wf{y=%F9L1}*z@&X-psF(DI`Jyn|OGP6g=9TFi22f2-#oZ85 zx35O>aZ0qj1*yq8|DC=c*bOH}YLdJKx+-qS?G7{#bqFNmI;{nN?dWdy*w(kOMXM1N ztnDLaLOW&N7|YxmJpK5Xav_pIuTmdy=htIwU-PJud?WnMEr)v>Dt|yeM$?8Q-Avs| zt5xJ!^U8qk!>>btSeCc|BrlzYct9`@+&v0N?(nmJ?+g9A0=q$fO1K_;CMsGK(9`Gt zd2gHH^ZrbYprj;sK~R|xka@{g{uFH>dY#@9InYC;gnk;Q-aXVhn`iuiI8rX$yzl)buq$5nTiIs9kj&qaBD6>RKjdu;NA zbR@BkY|KRLMU}}pjX7IR^PNrhCP%P*%v$Vzr#wLl*qYq=t2TV)4hnx zwYo6&H~o+xsEv3pL(*m9dEM#ghTg7`=`l%z4a`Ey^oA13n8LYbBMc{0qd$0AYjxDQQbi0(~tbF{hR5>;v8r3^o; zt$j~L6Fbqae_Zz^GA%HxRt+{5;1*1 zIdguBe#ZJZEU1xo)WW^KcA@R*3DcgI(dh7vTsTG9W_0)ml29i@+@HJ>pfPogdYIx| zYGOnF5YS$XfC(>nyx!iG{or4@w#@OwLPFqW)6CjVbw{#)1$YN(yO}p9#Kd-~)y|B? zAU-7f(2PuF83P-ZaF`HcDC8FIPDG^6m?8a@z16e4`Lj4u`m+Ld;m&TihxOe}Kn}MO zqQop8dgYKrA_B}H;WO=M11d{-13W-dtyY0ot>dvXg^Ve2-vJ@7kmgc*;b#s{a*9nW zkYx5Bq7@PGQ?#6ZidO3ZP4R;l*q10qq$VkZEK1SOfkR__-cCB5+w`vfP>hbM=t9dQ z)var>^XKx^H1CyULyRvCPa^gclIH|tMHGqnA3#o0cc%J8pz z8Go2yFMT%IWMpJyKN-bgYg>R-+6A8;SGyW&5$H{(36%)>pZ9r%du;nk5k&gc?A}n+ zhCbvOmdzzDfaj+8KN>Euv#B2p@El1Vm^x%{N=or*sGWEjQ8E+j{m zb~`z$!tVgWlc6u=B+*~tUH|>Nu<6_Bx_x7CI-yj>9Q`TeF*)wNi5=FRkPkU1Jh$du zud_jTa61oSD~_~8Ao3g?#ao*`GnFs(&cp8Dx&EJ*6=lH3rr~B{?p90Aoz53X?iP8a z$@A?=vGSn$9yvk7mV{YduV&f_!iu1-RCSYs+N!f+`f8Yl#{H{kl& z-9*6t^tHQ?a2sj8Q^ct4oSAJwx@k3eqp2TJK{76#7~Ezg)OE9cI`k^_6>grdG_vFj z1ly^@Mp?VdiWZ({53;&73{7pP>Ff&qBbgmS){53jRT-?e^b_#B;duf@bHO1sVYa%a|9$i;2vu~K5*l53UmHRpOKC}|Bq$j(~?hB5obYS+c>YCD@#Ze$c@ zitnR4p6|JZhNW4??+2$HlfB!r)xNxuYE?56aMteUywR1ay|;KybtUkb?-(2FeDeZL zzSdL->j0kOz<3(l&gaeGa%ZC#(MBZT_%uFsgfd3!F_RDb33UD)oqS0?v{4k#w!DUV3hp@xjCeQb>44Me2@sLTLxxgZezS3qt5uCMq< zs}U#jGZ)Rhw)VO86;Pql`fvCCdt35vI+g!3^k3Sz|Fb#&-)+vnz~%U}*YA1m#ZS%p z`j29;%(29uW#WhB*0G;;%qsq<89)3YGYZ`EE8x5MKp>Hy$@Ed?1bXKMFi4tjtp9O@ z67(Gy{dPq^^mh|<)xH;J!+}eYT4FbS*}1a6N?dH>E_m=6g38GKrz!@(a8DMk6DFy- zRS6Mq`brN+6Q~hU$O4Z{FI^U=F{p(oUnjja8>5079u-)uos|Tn8fPPRGbv;|U|2pJ zjoMs_;N!)b7+Nv&wOfcizetTUx||~jWCCQI@ya=j)T^!-%=!2%>b}=bH8G;TSM74} zn(5B9V0ySbKX2A*qUW-sJl{?r87ai@jrIth5(hE>H~h61GHW%c9gj$C)BI^FeO6t) zfz=gx!S(LXS-K(HQ&gYc5O-FyPo_}Q`g7qh`Rx^Oq^ago0Cl32fVw@K){92fFwe^$ zn7H)3zY)EzN6oY}6#xf(+tXr~Y_C>WnHD^K83y>_`}G*DM}6(CrNQ>Bnag0&rhvuV zW?17lVuDgkJEA=zCP76vwvHYDXknB(k`Q+=4_%82F;L}p&7a!3wKiyhnV?0!2|O(VFZnsO=qq}+t+rST^Qq`G(VsFmn{Y|N@Z z6GFJY#SxbTFKR5YmX~HFg-W@@)nCTGn@7B#J9X`lQG(XoJl|Wb$87V+2vwi;s5AU0 zgU6?ulcNu>zd3U*WGc0nA10eQvH3zTEGQbuJaPIG&-)UP%k6c?a1rNA}_PL@3dT}o^wNS*HpI#fKV{iWY$vGq%uXC z0$0CK#Z3b(STl2Zk@ZPuWz;@%my_ZLl~Bh z`s~l*6DfqK`gCJ8<#7tG)SFZO<$SF}Z)r!7rw_Y47de}SGN>Sb$26E9E^@*~Od|<^ z3q9#K4pfsr`Wl7)Ufg~MMz|V=f1+K(QlKspHDsUzt1q+)l(yb08Y@T(EZMz%8_=OX zw)-=_wB}6k_J~6ZB$(+JiABx&MPTnVu|gptavIdI2Zczi~U%*eu{W-sWu;7)SxwT6{?S!-j>{%^}*E7-|J*&F><=pT`7r{{tQD#4q2 z=qi2#zuvU zs1*siih9_cBC9vkRO7e8;xruv!i zm=npXeqSQKX^GQ(vi+p+p>)q1s;mTvsOH_U_kt%V!g~fnC66ttapt!O-|V_Ys<(%1 zY^`8#FNgN*)`HJq@cZPxRM$)=+58ocQE_V%%+y;7 zoQMV=1;_?s@>p)!W$U_Og5s?Ntpb_a?0l88GDGjEHoXBRESliX@=g6Z1`(~19t7Zn ziw$;U%>zUex6{=d1|W8>u30HuaEUCwsJ_;SZhn$!cy2q4xiLn)PZdf-8*gOvTJ< zssw*O61N|V9vf($IKGR%&1`Tkq_}(9t=RuOK_}oxgBqwCn!F?g{dE^y_K-j zOJI~xJ>;5H$C;&m#>GZtAI1=M_>6wYM!%6CLjPrHayx}{7b0qHr#uqO9 S0Nw)!8R(eYEWL66`TqjNq1aXc literal 11544 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 CV=Kx4 diff --git a/ProgramScreenshots/SettingsGlobalDownloader.png b/ProgramScreenshots/SettingsGlobalDownloader.png new file mode 100644 index 0000000000000000000000000000000000000000..a5b26fff304ba4aaafebe2d6003ffced5a4f59d7 GIT binary patch literal 15581 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 literal 10576 zcmeHtXIPWn)@BqDK>-yIrK*4iP(ToYASEKwqy>=Ur6f^MQ9()wy-JmqNSCf4 z(m_gybfpL(1QbXDfx!2~_nb5D%$e`}n)x;JgNr=5_OongORLP-T8o#H2*CTV!rXc zhik~QF|d6KCyzOBhza`Z#<}CqKif{v+rBzpzzU92U}8;H*FGqI^6Zta&!VEDPfs5d zJlpsp=_B|Vo;mE$s@>?mN@1}9nXv3z+)(KJA~R);u&7aQkO48n*$!`{`MSRN{tkIY z3~=xrFH6362=XBC@l@DJ5a1xs7JL8%dc3J)lzN>cQTHp= zvELt99d3pV8ycMZ(!Fgy1?~{=$rh*|Se##fm|?R`?DN@bSrv&#{9;mC>wdnDY%Y)S zwFYc|$}xu$KD2Qrlr*fxJ)F|;>G|SOL7R4V*q&;M@9`W5CrLHh7T6+71pI#;==T7& zJsg=BHWasm)Q{eyv~=f*a3t`1g=kAm+)eC@X#xkT(sD|Xk0LHL5Lqoh{CN7<&t-`7 zLidjygnsh|Sz37qaDT}y#&ha?((cyHMTkCj*u#cslggnx*Rpx0F7$_oERw{uo~9c! ziLW%dGu>qCbZHE{xaV4zu3Tb`v8uQHiTPliBX8|Uo7Erk?~_p4YYpn(lXb?TTrI_T z8^*5^-=A5px_&N_!?%pMus4Y}fcmh|yM33W9h3?r2;aE*#O>uUWRW$8Wi}Frn`=>8#TCPEZ zW1}>P8cJUt3T`}Mc7^+cX%!2 zuRHFvP-9lNAe>K6`Hog?DS%X)R|=Qx7h+TFJ!-L+f+cLDd3e!J)glWWK8BR99T#e(TsZWA+O; zBNV?lB*cu*$A|p9hgK)y{RGzYyUEBYRoc>-5;!Dp_%7d1d8*$X$i(5Y1MIrvesh^+4+~caSa7r5u&YkaE3wCGOtj~knmE5u=$$)WLApP&&E|+J zUimhP&M7vH_nK_{=vH;XU4pV6du2Y$Vv+M9YJ2%nlJKFi#SfLv7C%H&!b%Lv>u&k8 zwHi+Of&beI)Ju$k59nYRs{PWRd9DN~y*ho)$Y#fOLdddQ$@k z)4LxoHPcU6zW=FO7dm)21>&5)J7HMRWzT}p`?A)ORf)ZM(xT%23_m&xp@+8(wI1}T zgzA*EJDuFG19@Oi-uk#ZaikH}xc7Vujf(6k4p}m0oNB%p`SabadK>r{iOQYb01oL0l zg6HG3z8=E6v4GT(=fM6)1P_ASp99Q!`k_CxYmXqv6bZAj!Jc`tXOGMUy^wP@s^Is_ zdAp@qI`9f+^P+X9Um^SFF*3ercp!$z{q8(q0d9bvmF1oeY(60G{I10y1A(w9ROf*f zX)O6lT#7M>AN=$%h4QezG!nzFqQ6q?)Lp@vD|X~y%KJ-Yr$rsXjccJyuKh;H3cy>{`5y)dp5VOLt?I7D40CRVmZf|aw@tqCc0<}v8lv!ipyXn z78Rmu???Ltv5GmZA~9UySUI)iU@B2CKH}a|S`W`|v)~I))PG;2vrOC}3u?d=tW@v^ z0FUh6np^tI`@UXfxQh)DLt%{HG?(8zIgZzs?A;6AMkq7}SgR)4xo!maRc!}vaW@$x zNRi*>Q+QH@pNtT>i;c!`X#;u9Un70J2#}?tmnoKyLqEz4?_`hkE2LeM-;H%yP~4f* zDB@)}DjB;J5;gli4kz}b{aKpglcmFT$GOx3aVaEyi@~?zNHw%n-{udPJa30}jy}So05^Ev&T$dq@a<{7Q-%bF=0sfA zEK8FakJ<(IQ3l`Hsm#WStT+ObP>>W5Z$scN7eEVmHP7GWm>jTL0cj-=9LWM+HKV^Z4Ur}3d z!%|(SixYP}m8o@ic$y5RNsT)JRg#d=R$p*^ChZsG-S)9z`eTZ%#oFVnUR6Hub=7;x z1o}%S~*X9zclXP#*^_Q*0L&zW`=3sg^gw5+=Ag!V-;gsHbpi5 zx5*2yH{gNIn9+y26g&P^YU3)rpX!#lvHyjb){@C@*FaHyFk1H^T1y50$u>d+9v*C- zRE&peA$>6hzv0^-f06DpC4wR_;L2`+24a3GdV82ldFeT!%d^Wt$vlK6p?~Hf)%AE1Ucc z<2bZ_I(fq79uL*#&g6O1uY4o=mnXW>Jkcmq0InE34$;ToyqHNELIeBW&iZ^Cy_<;Dhq|G*QOl|9HFWXOscZ1l~9MU_A zsJV#sYgG1RPL2)il&bPAd{D}Qad8G}ZD8dh-3XJFsz(K^0bL?}Eo5pZX}7?_zrK1f z#182AWJ*<3Wz0{^;tRazXrWy|y53&>>xpH$d13gR8eB$#&i9Tehum#Oz_F3JO;cH3 z$U0Kv@cp73eAi0l53eT29ZGuSngxGGI5l{=ka+gzSYR$~cLz;2Fa2jVMW6aSlx2Z~ z&_6vG9aCT*9U)3P5Sg+)GsXxB&8A|=(1+CA zOmnJ9S;?4TwCY7&Zy?aWcVe4Iuh0?JcoVL*x_z42fPU6n*IZ(yNBr>zx7bsgGOb$z zNWR`S=2tfKuh@=KbCZuJDC6D7Fw{+A(;m5|!0vfP>fQ=M-?}4!G}&7=){)p`z`PTX z8Ht5M)!twVRU0@~=+5M5IPYi)qPIMm+O>*R&&3O$Hb~{v6?6b2P7r-_xRc)gYTjT6 zpO~>pfAbQ@zl=30%o4_n8)+IXAE%{*+ZoNXZJR%K+il5TM3_T^?!Uab!QYP;kJb9y zivLY}y(uoG@dLidZ{+)eMjRn5cKEZIIzn}j_TNdG;!+s=G0>B}@5gkx*`D8cYXoj< zy2^ug8pvb^fn-XThWw0V-~1=-@lX={MnI{;kwO7hWt5V#M|95t3Ep<**Nh7=9c`7j9?NMN|)Zp8ML)O{lcLO z*_|&~>J3(&ziIE;P}LMyGeC)($zt_%K_*l{FfyKy;WeDxbG^=jc$Q}77kG0ShAt}Yw6Y|Go0)R!-pm-Cn`f1NBH7U9Ppiy-NujZJ> zfg99x1{Dk^sK;>6QaXL*YI$x~EPy#=popr%0=4~(WrPpBu)`A8VdJr;C{+n$5O5dF z7>@lCYr6$s@WraVbD)Oo*EJkG_jO^P^lV#5J?fYFSyS*}?L8>f;HRf3b`j z&CGa5jx>jm)M+t(=n+4Hr@GO8rDrWBOFEWd(pNb_piQd@sE%O~K|L=#P8<`Pr{`@1 zGyU+MN&Px|VK`Lv;b-W1_B$?cfs?J;DtI!XrnqcXlH+6msEdNXKNBGQv#vzyDBPt5 zay4aRu;-Q>{6qAopx!$1kD*{7=$GA;!GyDUOmVDw&4B5)Y=@$y*m%wpsg@L6$)a)c zGZA>ae_iRa|HrRS&#QG0xy}T@EB%99ZQq62oIFg{Hjam1EjA2KXl8BpH01!C)dig8 zRlc-+10_{-^0x5QJ{8rPJ3BQK zX^R-h+KBbZy#O z;~sY4GJX>n=|G^WvTz1`)tz0G!PVW zQ5*CBO>JmdLtW-O<1Sa$vH)On@L+t}xXn^1>c=c`^Z9~ywcZW@VeM*Qd3hU3T9mL(Va^X5%+VEflq*}b0&3?)nJTMp zee(PHqdN6~)nFBJLCqEFSx0j4a^LN)g@Q%EU$p8`-99uW99Ch|bVa$Wb|QIE-d}>- zapIeTZ^T%Y&8xJ!10Z#SMFwp>;TQ&JC!t9NNd84PNk;mPIsO};{d?Z41pa`7%l5P} za1__cXkDPk|CaZdny{hHI1y_nFTnM$UE>_OW!#K}oW2c_25Y`O1l8&0>8PK6htB6d zRBV_lYH#XY>yb2VUUZ2EUw>!QObx){Wx5bLI5^m$H~GpCKy;=?uW#@;v>NFy9JVhQ zlZo>(f0bv$#z&puXL$VrjbPWkAgZ6PXdFNq$K5n4es`QpcT-o?J~#X8MD~0M@#sI6 z9o60bLl1dOH%=EVLS*uXy|%v|myVFo&Np#!$|%N;65c!34n%P9d=9I6lAcLFeRF0dF(Uahn-!G@c~PKD&vvd?~WtX7zCCix%V@Nj0X-pUl8`m(II z?>0lsYcuS-!XpsVhqqgkE2&+@cavJ&DL^1;ngAuSoD+O`+hYB>sO{@;*vV6>cGo9ivxp+R&g_`SxnqS~y*qz&RqeGX~XC{N>!oxy5~MA`}I znE>`3+<`tmR@V``ePXIr52L8*Ifh{Vz2p}S&p7pn7`bn`N`DW z_J|IGcsXTO@|zTgJZ>g>SVT{rDudfhk#Z(CBjV#qS8Tc|+}4;TTQ9q$dV1c@AF~5t zFu8VNwBK2<*;@ORA@$>g@398T?MTR34pyD-U7k*NJW3Q)tk2z`#_3zTj5cX%PB{J$ z12iXlP+OA7$Hz3>k4^!@y#i7xpgCRDg0J>$KA7pXB>{qCGlqke!Co1KfCiKJM=QR= zI3GXV{nw$;e=LWGN`GqRV!ccbP`a@ie>+(JdpUS8=YOw?u=@8xI4lY-8>i{+dgW%D zibufM4zgCkbXwn?$C`G-BogvxAI@N>G4z9=C((ld2fF>1^KKvwG+CZTODv$+v>aba zxY&xJWWYK~=NTketJQK?j0?pV12)UEfxYc~Cdw}Yyxez>p#Y;H5lyS+c@31*bhatC z?yUb?bQ?@ojCV=A&d=6CAD0q*cP9riQvV)J$(>mG=p<-zecZ7|+}t$=fZQ=lfUQlu zwwglM)3sb%QIevBA!0UnMC#S-rAPCwK!>lUQsVjhDt2DsiQ*rP&R6zd2NI`YHvuJ( z754R-={+RNKWGJxqVFOpR+c#Ie2_xo`EomJ!R#J;|1!jU+^P#aR@vTg27e^RdB_@6HOBJzJQ<5oEA zXbU-!IoUzC_n9QUkU3aB2OWLdkt&3%HjELOHiTv~4)qPPnDd= z{q7hQGf48}!+aE4L@TX1hiWFF*Io?>6mpY3pC=<_QsM?A*19qN+JbePe8uYUD{iVLIs#D$EDwd=UG zEu!=8-%5xAml||2i?Pm$*M*$`ldqe?#Y`UC>+_r3l}H$=x5<8`VPH||aV;l-I6PwB zT`cTtv;2*$Bu8R;t=@jE;8sgv6r=zEG_aUPzlt*-Ao4;tvFEx**~ zF8&z*B1SRxSzIuN$y`dkoiu#*R6faU%jd8dPfc%4HQZ{#i3}+DV_7h#4wca zy{`b4q1r6x|58*hpj|L72h+|YjSr1UB@A#X{tcrIv7&%e#BorIonk5jZWdWp}}<8*&HU6Sz%VgEl<}P-%GsJ zE>#}aP9_Q)`;^uK`>Sga=4_D)KmzK<@-<0Cge6@LNcRMeDB#gsJv{5Jc@$#cozR?ITK zkPei8G#&~qV)_gf=gkDhi1)ZZtsUO+r#tKYA0J=-&Ts#bBmcT*{Qt}UyBpa5ha&SY z-I&VuuY;5F<7n_-hbY(E`5ChE`w`s&fIvX3e@2gmvfAlxMu<+|b7L9Q80EOj0NeE8 zfa8SC2uby0y#LE1wq*|j@Y<87PAqY|bM+0F`1PoY^iW!t&n|06&wS*Co&-q@dO=#c z){1?yje}$0u3wxxZFg~@$j5STW*H|>beGQZL+v6q9|rv%cUy>Y8&#Y*b{N}CllX_( z6B{2qH~cH^wX;{;?>TQ-1FyOCn06y&d!)#RBw`{uzOGm2jSEc~gX5mCT;SrfOTMNr zq`bf#R8gWQq`d7A^l057sHvghf<XkDXEztZ};A>uq%KjplN+V0!U-4*9`q&cS4 zMZuzRb4ZWzdWTa5Z*I=L<}vR~)h}@v=2y_guW=@c2=Ak!knr0i?9^xkDPJtlv~V{G|UP}o!p-j?m=R#5~_o? zS1KJBnRz?iB*3KI!I*j6Ty(rLb=q1eO+ngCF8Mlj()XWE{B+M0aZ@NuOv`Gqh@RRr zltoPNI!w|-ik@7(L7CbNT=(Zeao9A}mX3SJ`+3kZZwB_qMq*nm?lhHmFSiG~pOO4D z2zB~AR5vunpkkJ3V*hS& zYi)K-hQ~vS?@?0^c{3kl+&&uCN zHGX%;3m0^^-g{P>Mzl(LOd}LBq~5Ws$}f9uWzAP7tFbzRr5IfLT06Li5B*b0&{z@WAQbRut8IP3XFtWco1(#)uo$CiWk=hiE-dK8 z&nG;HNic6SvNL;1NMPbb%kQj8nwV)9_oiFQ$(tuciQ=B+9l$GsRepcw`U@Sb=V-MP zr_>%-ue1=5-S}&vHhV7Q)%AtdmN?{x1?<8wem^BNX!S@Ce37_58!6o#f}{ztB80Rb zLC?-4?&q)2t0%<&w3@&fEI2|h2{!32_aXRM@;xSMPB0jT5plHxGRTSN>oK(m(_7N}dfRs+ z)kH(29%Dnlr>d*^;3D$ZEi^OXo)NqBP2=^8CQLycVo!Iw+MllYw>LQSw@&I$LBL9F zF)BlO;V_>y*i>iy;t#gt*vnR;6CKeB&NFn+puLPWG%w5$IWHo+7OOE&>5-D$(^n9* zNo^MH;Qvbtp?z6nFSy?tF=3lff?OVHGVqS#AayUE-Mr25W z<&|@)G;8Jk+d0jjJSKhSS{R!OC}GtZ5N|OFz7-X#)lh%k#IxwaEi~%5!>y+)*$xr> zZew`ozK1Xg4y!leLvZSp7BJ`B%T76-v!6VVy_T@>ta@%Pts1$rA}D*GQs_w4Q@doi z^4LabE$BAo!+c9mmMvGO=f=Tpb+qEWE+N^qL?r;4xz!ZcaExW2h#uQDfDtCR#u|CJTv}nz1Z)eG~)JUmEn7VAsVMjVgjY z#wr+>^B>ja&fVN^Co$Ldp#$$8?_8kn$#Upf2e`-aa0d3IAs=g~2JA&F7#6au^GXQ7 zAG(}HHp#|LA-OuG*Ocs>q`PK%%Y^)YTugD@ppROIY6OOdB&>YTMwXDqs7+WtRKSFO z4XLHYPuS<>W+-jgCx%U@R9!*yfx?pX64eq(+|0j{}se|cxY+}f$bo)F_jH~u-PB-t!YVA^2XF7f1v zy>_Ugqv>hrMy+tt#H!u7%2DMo6k>ia8b&%|;$igK&8awm*}m)93w*z#M&=Bhni}gz zHE)qf5`x$P!^k0tX+JTJ?6f=`6t~MNM}eHUf=$(OJB%}{hp_OYBKPFKw@(!E@_@= z!&KWU^;w>w3U{YnAhXYuhKWPBrisItUQekVZO#5h8&beGs&VTR=Y?|w_*#4ihoZWa z**TXUGxK%@Pq!GL+a(Mju5swpf=j61G!P+6Z-39gDwsO&l_TiEjLy&0$8kQ`l+c}q zMW=aXNwW0rv20HqRg-vtAo~X`K7ZP%#x^ffa&T;;U!Bqv5}2~{E2!e$LTDYKE3H(B zMg=@5F&0(+@+O{Fm2#yhPedoJz!1Alx<(I76jENhKK6k+U7=eY*-XBi;y?RpeV$#x z!Q*heyxZtsyq0@~};J053JxN@D=Yx4jA diff --git a/ProgramScreenshots/SettingsSiteMastodon.png b/ProgramScreenshots/SettingsSiteMastodon.png new file mode 100644 index 0000000000000000000000000000000000000000..5c2b069de8c6b9f24bfcb122de840752f9df34bc GIT binary patch literal 22314 zcmd?RXIN9++BF&lsnSFc=}kaUiqdI5NU$a1Vj`B1f-XU zAibjm2oR)54WTAL2staL&*QWAxA*&=bDit_IX}>pwdR^@t~u{}++&QHsB4Bg40N1y zAP|T_PZxY01Uf7T{Atrr0iVcm!@dFk9rC!YqXjDJ;a&jVP}*r4Xo5f`aQeNwM}YUV z4|Q*OfIy5*dn-E8K}@o`OEapvdF_EM1sN7dytH5)CKA8AcLqhX7@VsqrNhT+E;q2n5I zcZ9fzA+%;phjbWyZLV-yp0f#P`eD1h--A~P+*KU!U6?z+_#(rh9+6=BhOoIXsA`?m zqmu^>@4*)BvK<0}>_Zp?k$a0U;AP4UN)`~Pk0XQz`1q(MB?u%F&VLvL;?!mazKwaI z61g{e&+p5q<$Atj!)atIynU-U;BpaSUsQE%di~5GF5i(aejE12ecZK}2@afcNB=mu z$b9{}-?vVLLrU-|| zY43Desz1xLHBjHnYC8wJr=>eYs=03-NYG5VR;?B$!OjVR zV&9R3Av78<#HEA_Tqdj|Gq)S|wp#_&w;Qj{-pJew4%i&Rc6#(iaYH9t{YXpCUFl=} zx{Dk1R**C6xvSHu^4P#;?f@`j!ybu_HcQFuc%=q%cAvpN70+U_HP!}BTbTlD(wyNN5OZFjreGa9M|yaDOZi{&H;~aws zTke>rJ!W~l(v3}&ES02P(~M+!3)jOs6{NPWOa{yge25>FXlC9)^T57maaY%sb~0;> zCoHO!zL%qt64sSPW;Z+$GC@IgzVElKI_5Xl9hU-+e?ATZVdN?`XXLF;A>XUr9}jFD zTqp7DTQxv%VZAEn@PP|2f>6ch=-~_0;VJ>!Shn|j0{)_3?ok8th+V08j2tDz~H51fIxEm!Dk<1RUTg12{F6ua!!)g2`DmhQ+( zzX5CjTWE9hF(rA~l+zx8zX*?83vDY_yDE2%P_fqM8#KpfGw52cQ&hc3qK%$S6u~-u z$wAb7=RsOaNDV)dQdn^0SV-rhqm|G|A!>aRzYQKJmfs7}f+#2Y1|^TkHrcob1fc{WKXPy?5W3qyqGMvSV;qQsxHGo}LvB#l%6f zw(hJ_D@euitt}ko8h@6+~{oMgBY_E}6KS_dW&@RJ^t`=?ARm z`>fO~gdOaQ=pt+9`SChbAhIiy7H;9WGrahZ(h0ks7U9*2n zC#dAD*f-x$L2zc&e0}62ouxsRJX}}sq}Z~Pq{J@CK3KRv;OL9)e8-bYk334kztl`Bx=?p%&# z7fhD4Q&1qXo3SH1(|i1)?6szlw)TY!>$BE_?yKTVozD26D?xWRoYyysCq!ZUu-G;O z*dF7^ddsErQcr*+qCG$8WGCam(uBUxxV{W$V#f1t3?Z|LmXCG&^kT$19kz;K8 zhvCA86hy7fF|jz8d9$b?4dykHV7ihG=@_KWdbPOi9Z&FwJG~GKPN7upaN-fwhvP5y z!_?&2_C{x)Imxj)?-!Or@V0|WcVl)(>!ZFWCu6w_uaa+)1z$TyslUsG^e`mA&C|z{ivz; zHlCTv6}3S(pk|6{SDxeRu|#WXmdXzI)k)2fa>rx_eoozw&gh1~QoCMWE^x=)$NcA0 zJMP}QS+xpBo&7zjQ(RxuOY^ND-A+2^~hm3c; zHX*yRVX@Z?_O_yTR8PvT3?mEI$UWWPH2`{AcNoHSQTC>>CYa*$cUS$+r}{LCN?um` z&5xNZ8T`7bu}KkzmWiUgF!xKE&8k5oHC_d0Aq^JZD`O6P8_Q?}WT?_BivuY zqUxFg9Oxz0XYd&}(dS>W)2ZG3*)Yu?S)b5DE4i>Q)f-!qBqtGt*9o7qtOx?c6LmhH zv%1qKc5;_SH@wgluFtz|qhqa*%dSKBy$O;Ql5dZXLJKG<<`z@Dz4Y$mE$4Gc*X2p~ zDpCG*KJvS=KjwVP_9qc{pm31f(lO{7msyQEsljV~4cne(k3CJWx;ars*xf~!X|XjF z2|%E$2*(Nx+_i#8s6>aa%R1dyfKI{Vl6?w;2_3vS!%IhZcx_}CG7)#D*+|NG6BjzX z(Jt>y^46%AtgSr^3j0AwCi-#hY+bvlEqgX{Y@^#@r?3)si7~Zz@d6|e+>jK;jD9UH zxVt^O`n{<%i0FT-%r?epr1T5bvm|lT(WGOlRzarZ=Xm*Uc4`YTaJ&K*bvn>yFiE%r zR}NQ|e=Zzbr#3OnTiCB@GmNP765QIY-Pmv|oeqN9ua{v~9MA8y_w3!TZwe1_%EnWK z5Y20ct*RpS#;zw3wvq0rm3?~ryn%*)7jEq`pQ>+yJ{BrB)i5qUq-wGzYJk{-;?(UT z==Ho&mcPvKP?} zJ7tI1c&xJ1e1k&3W`I9i#%BIWYn+gd&9{8xQqP6!w@U7XaVtF3kg%##VAeXWX(Q*5 zvMOrJz&`#Y$ZxwXdlGv1{H$m&32RD6P2qg6Vi)eEU6xwlDF|E9?H2i z&}G9MYFR^(va^-RgeoHKsQePbY#je_Otfk|_1f~4SHVfKpOIGi0oUoqBE}^vYtM}9 z%&MBE7aOawbGFLVlGwe!-HA}ls~j{pSS_CJ>2v_mLe%%>8jGi1t8XHrOx|Ag)^!|E zhkai=OiI5pmxyg&L4M1?JYXPPxM<{I3@jYjO;hd9I1FmGk(w_N8;FIL+O)@qlsiIH za)8@@$gS+HZ&GaK+&b`$+459B-wDPo(a0_lbu-G?FLCA=DJ{)rb)Pn~+d`qbZws$Y zAg1aXc2DF>7^4t3iPEK@d!p*{_TOapaPEAS>L#^@K@*4x?ZEts^7W)xcYZFMw=;6;qIQ8C~pHO^c-! z)@qCEebiU{Z6OrNJl4&DbMmtydZf`N>`3hWit%JMg>aKE3f@=YTd*e=o9?a!QejLR zdf$(ef|5D{ukKpg7;kUL>@INyVTp8BS1Y@#zZB;_P;r(d`bY#Y>FmjGqA-eVMCt~< zT;lun;)ra{{CxiP`5gFY<^$Rz%sLP`;&3ajXZ!xQ`<>6*E1XeX=p#2up?DWU1C3;X z++BO-1@$)zB5p)+L6_Vw0|;g!LIetnW7_9&7~PWm`UGLt3H!b%6#NvU zzUdy)mX^4Gii_^TR-$FpWM%?@OfD4V^Jnw=P9CbpjXH)3H2Yy3A>#54J1tQYEN%!< zFh!Kw8#m{g;!U3?iM;}mA0G{+QL!-4?n<^!c6{zCs5@q~u?jR}dJ{l6yOA@6u7QqH zj}&$@A{)@sddx5pCn&M-E%pGd*d2+~KJ%)$3*e_}i>1Q4Es_TdikEwjlz)w9Z;C82N1u_{=s( zvAXYVucc-1(J=sVt5t%Ua~$dYa}?P%9|ItD-O-F$Q8DmRLx9t5#-Q1|)tCv_c1foO zgP{FygqdDaFH7|T94%lzQT3%}cd=l0pMYfw55LCT|G{l#Oi>ZGUzeun(Nf_+)8Cha zDe9K5)O?+)Ya^nUE~acGtj9Tj<3znlqOQEBlR(PkemRXGFbs(h0Qw>Ml*Gl+ei82t z)I^ckI-Q`ES?DaJ-nsX#x<6%1%DqE1@M{?(6-V#?1YX9&U3D zt_!ki#Tuq3t4n{^Hr2M;XoSvY-8eBD)tZ@R#Owx#RJe?v=}eYPPFMCBZB-%QvB_Rb zL+vdwT$r2;`V$hzds1tGRL<&4b1-x@A;8#G&!rBoo4b;tx#cpy*){A&28h!OH0YUv zH`hA9H$~hh?rxRZc5{xD-hax#FK$}&QT&vD-Cj}K*pWEVns_izRKm>~7vpBV>3yM8 zL*bG@n+rBazBskozMl}vsOoDi*Da_IOl1{7w#_rkm*oBZ9rN#l)PjQ=LZ{6(aM#&!CYJxLp zO%RX3Q8@kCtvatu|86 zLeYebv$slL-@%NY=oO0;W8iL$EXvO%jLG` z&qNhHm)W*|(^V*91HUtd*rR7HqUX=0t*Kj|9Mv%xGY-6z4qPnJ6nwaq9%DRh63`t) z>hLHFg#U4^-T53`-fgWz&Hc#+>bR2n3qiNMfN4_djFNlt@@4#Ztq%%+krQ^S9B-Db z9)~jp`v<=!ZD()mUhEyjtBwi1+^-dxos2Ihz)d??D{<#6u3#O$=Dct^)e@Hdx#P-Y z72?cwzxpS|BTQVglSUJGdloRc&P3#gL=xY~)%u@A z#@72_t@Pr-h8Zd^iXY8i?VHcxA9q=8^{I{jfsL5Yp|P}_=NXI|R&_b)+y2$?$1LrH z{>-kfFqf#hEx~p=#gRdO%=s4R53{Lm@RpfGvbMLtc zzJnFSSqXH;e=?YQX4J8mA)I-xyg0CMZ2Je7sJ~7#F@u>wcsHe`e4$|gefqkZ9b;&l zij>XyOg0Zs)n5|_`GG$ht~;?iR7ph~;*lQ{{|>C2mC+H6~9(@G8dRv&0GIX&A1j9 zIny=rpO?)g=>VC@GFWzy-+g^XZwDujIPHV@&anPP9&u}bp@Fb&Jxv+6u;6gO8W`Zq z$6Y;W1kb$!l2e4787DFVG5dYON3n(An5t z*lphZ)rgrkM=K%7Xt}t$+L=iEVFjO+v@~^?RZp6tHA?0&keG%lYUTJeZ63QEyJ91< z5&J|iYS85a`r0@+`=CH)0QiR2%*=0WTBp90TFVV+88ZvK0+B`;J zqBf*`VyVlx(nytC4!3%c#jm3H`a5wwnb1C#^R427Hxu5)!lIe3U+Stqz*Wc8T+<{OJU)%P%| zv+t&{348%_Ce>T%$Tmy-m;jS{KwY1!8IZj0GxDjOT|yQo%Q_CVJwIpe1iDr2o@;r! ze|pyEB8{N&r7&s_mDo~RxhJQ1hO9WmWn0U1RDz%jDkmN-=SQc1=vLI*E_pU&qY36i zdhQ<5(2k;|RDM&6JDWKC;ZE}PGozN1AF^7YdkNHW1J+q}mc?^nCo2triT2H8nu^7; zyb7OF{2Z(Htc^)BKm&)ci{Y*&xYCOY^Z-lrf2ssuUlveFTzH@-3WGFK0p@;LG<1!WXLjo{VFhW7giI&2JmoiYaDMf`)y~rwx?I_ z2dk{_Aluv#&z+5h8%p0Abqkom`Z0T|g4Xr#7e7b8 zy9VVw>wll($qE6p??oqB;L^i~I$EcDg&d8%0bCEM$Cs@aBI@-T@Z~<8>VZFG-3cqH zv*skv_kE%^X3YJ}*>`QQ6ZP@JQAD>pmET@cu9z=or}Tl9NYwiN5diol-2%W53cX(uu*Qmgu7mZ> z3&2dfKCi_gAPrlE)b!t9bpVrd3`^UukqvjSNL>*prVGv<#6q^C2hfqt9B4rZR&CB$ zPhu#=UPbf!z-$o#&@Sd|@*Mg*&*;+sonG7MA=`p}*mKxKNuCCC-k|O8T4Egc)A-My z4*ADnv;2Rf_ydl2K>Z;IwEfXDqp!U|!exb)C-t=a8*S!WcgT4=KVNI3B|zsNJoEpN zmHuZMx^;*B+-;eXJ_Qh{i<(R-hkg+zlhs{zz?(Ni`~wEUivZXARM@cgdqeV-VzyHQ z3_Ph|%jsyxqGD>0DF;v*T`**Rk&*G7P28j{-Dg5kDRMi(Nj^zfnGB@b%6iq5Z!y1nP@ru&ZK%{UYvuEGr)-OO_>njzz(sYs_Uh>%NTqXY^ru`ob2Vg>m;%B z(^Mk+E>o6QO+8Bq^AOPh%xiP**8G}@Ped;a-SxYB`NfAo%wS;PWX$G;PB3tDs#t_+ zMGnnqf<=*S`hE3!L-iN#iS8U_s=}wyPh;?NC#--ST zLv4=_SH_<&g;ih&lbta_Sx?wl#=XTwjn5Rz_YjRY-K-CRsF(o_;sp4k1;;x&0df#s z&a?H$${vM2&SrHX{2=kN>=3%u&G=mXO(cR2&bV@5rEgf>%W` z)~M>)^^CFc2DFuQk>S`%5S?VyVs+8@3kfvH~LMoAiU^A z17d1B2|@t^7Dwr)ubhDLV$VUAN$yC$`!$LGn;iTbas97Wh88KuJLho=AZM!aN1m19 zJSzeqK;VrK@WzWe&V}>J*9Bcs)-O?>6vW$Esk+Cz*OBrkkf2BG0>BYJF%+599P^t? zZwieJej2Ks~x62uv2+o6K$kGvQn=&+IY-y>*!v zE+4xX?<-())|o~y%i0U*`X?~JBy@e?zs@LdJ4w?fjdpM|DrdRG;CO0Z_V+KC^6IGD|S$rpx7gY3tGugq~p%DewX!-#qHifoY zK5NmY0F%qcibMot+;Hi|--xn&+b376YU6YvOH{&Q+uEU)UVOiz{?ZrU3QGzNSCv3M zTmH!H5KOML#izCvAto)Hbh3Pf>dNOtyebHEoID8`*g0in9#%f*yaTG`Fw?R83vZ)zW~ym=|ol6K2|xT@(LqH}H0P5V0w>Jz6o znaUZWrC+Pk^S|4vp>F5}ft~{MtcpK-_Oq@rO@BV4V0d8Kt44m5{9S<_(<7xi$0n3{ z8fg`f(7j0!3xaiH1w?KH<$WSZgd63`=lw*I2CKU?zpt_-8}Dm5_S5EE$fiWF_z2= z2CFNaM!f8Yt&>H4T3w6B7{?ulS}m1h{fYG(boe)L`!Qd&L4>;ZQ#pSaG;=UGPxZoD8qW^mwLO!76P;#Wg!r!p4*(`O0K6oF${}MhDCU zRb3WRLXn$%Tnjm;P_32}AgN-2DEzzqS^j|};V1szweijzx~~f|4K&)cqY&wQy;@Hd zi&Ilp?}UcN=4CZLWT|@&a z&-X*JbO+9ISEuACA1nlvJ`>q#?NGam<1IY+V!T;mvzML1a&F#;wF^>-Q+NMSEcFzdp_%IPY3?{`OgIj|> z26WDVx8xCdT-!MEY}vulE^^;dI>P&%`L{qc&ZyovpNtdiT_wTZu%p^d5m4dR(~m=S zv1|G>TE{?l8i8l@X_+$aC8d@&QHK8rFV`W^4^=X9`H*tcKJAt?N)wD`>i>4kL>qKR zQ}Dm(<9{NN3*KbplEM)5@Ux zTm+5sgwid9-9{Pk^%nkx(-O>YC=?Y5=+LZpp@i zhI6-CWo=GCv)EX+s5aKq?p^B@x6B#Nh%vI43@;Coq5vJ${@%Y;NiGpBB^|6E&?d5qOBD<}N?A({v=To&;%(rH`8`~UMO6G3 z+LKLBogvr}i~~batXA6UOy-!rWGE(w&jiSQ+K}|vT-GP?9L3X!^r8763>Hm};!bd%m_4_6M<48?<1KD*r;l(H)xj+v zQn!h|0Z3>ICE98y?OF+v4{Q@^W<^$fiFy;{R7lNMd70!mVAB70_mc4^=^QV z%EOpGoB~p;;r#PmZ$ z2a+zPU|<+}GcnzCk?*4I*rcjXyeEN(Q8lqBle)BEwI{8GA2z5fc9b)T1x!|dUbr*! zt%s#v7dYK>%R>}q*KGL7ZIMSl(BNU8UNr!~%Sc60;q-t^{nw=Tn9Rt0CbOb`y`z;| zF&tk$uONgi;TYHrJ_?dz1RhtS_9Zsc8XnHp4pA$__|I9dwBWd{YkeU~^o^yc1AzP~ z^Z!EMCc|vhmQfu2=;TW1_s`|VFrm^l0zgQCg@iA6tCr@L!juegMbRa z=^xC1^GC)^|IDw9{XgP_l#);y5MbR~z0hg~h}nPvRVwOkb@utlZ2~aoz4-rES>dB4 z=aoEGy#WU90-y?ga%>?y_K-#lji5$nuA8tM_mxD?&{mJ2?JA&hPViW&k$ZF`g8%Tx z+othi&o`rPP8lUY6Yf4*J!DY=oRYdg$(LtqPN9eMeWS8~A`Vj~#d<1*^LCELD=e0K z0jy&XDG0)#IKfc_lMCXsBffv{%6TQRa-quKbtEVVv%DW}A@1u6V&@K_>AN@nT3v6c zB2Z=e>ShNf`LIS}7$r)lDO7cco}tLHb=khJeOGkUiWiNYE5Buocx~u4fNBi3i`swKY-^b>z>qt{LN6}Ka?N8TP;_aI<`@}^F0`Mz` z=NQ%E`}wCVm7@ww!7bG$S+EMGJDNa@DNHO3N`(37$~ojIy+i2;8DVvx7;wgD6uRN2 zl2fTXD(r+gMJp6l&mX5A(x1faq0Xr`)|nrtmR%AkUZlEsJ1Y=GPxo(b+y6uK`nwwI z8YUlqM{fH?C>!vpl-)lJbGq~ZooV#d2@p)*v678uE_G{{K%fV?xf@-WO0-q#IyX7? zox@WfuFLo4sPxwPh99V#&uv{9TOojor9`tB5g;O+A z|LWDwg~2>}Kw`!Or2-l8G$0XxUdg=ycp3(-xt{cdjk(^=7Z>cDQp2B>+4YWkBY)JE zhU&`4y&a4*O!LQB>In|ApBmUA#A>&uw)>R~_FlE)6 zWbu(1oc)%A*FxdNRZSbJ=?6c12I$WJWQoZ-Y$=QsbgR-O-D#}KD3(uM;)H~SSeg#A zG;6>yAcB@v$UGO5`*DN$#i8H*E{vR?PHu`|>fGI0y5Id$w)B1{JD0|R{B?QLcgK5H< zizbn|pI{2=nW-;NDFnbQ;B{In3h;x^p_Rp%%C{o>tamB~p_$ELGz>EJ{MR4&1h>Q{ z7d9kTV*K`pt!hct+_$ynPX1~>hEDJ@MQe$5tIgKpH^4Y^td=voKsZ^#k`5;tIn&tq z_$a|lg#PFVWhPMVwTabzUCe}x^5q4#dA}DI2c%N5x?Oabvh-4OcF+|+Wj z|60YAky@{Mp2{Ua2&4pEFQ+2 zxEm>Lo#WKsPpIQx<*~UAMnUU-wz%~x`5T$P?j$E9a$wSkkz3-`Hel|w;RsX*n~LO{ zX#bG@HOX3N1rr1hM6iQ9r(}sX5N9GYpT~{00ZW1$pny?;G|~Y|vL9rW{Bwl@T;!9N zg>>D@UjGjM0Y4)Ebw(!b7y;l1`jqz1cyUgH%t)6RXZhtBlED=n@sbJ%BoXnv=Ry|x znbM6}#b_WS-CkijuvUCCn&mΞjJ9T=RAA$93Q}#E24f=hzz>L5|d4H%RXAuZiFm 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 VPmq9BL+H%1-D&$$dE_t8{})@<+yMXp literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/SettingsSiteMastodonAdditional.png b/ProgramScreenshots/SettingsSiteMastodonAdditional.png new file mode 100644 index 0000000000000000000000000000000000000000..127e5ebe6fb7c5c5f4299c2115238dc970eeb24f GIT binary patch literal 13213 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 literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/SettingsSiteRedGifs.png b/ProgramScreenshots/SettingsSiteRedGifs.png index bc718283220eb3e86ac64d45b3e08a24cb118416..328405cf958ac0fa4f516b12f19b17f35c0e9441 100644 GIT binary patch literal 19080 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 E0Q~4H=l}o! literal 15826 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 literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/SettingsSiteTwitter.png b/ProgramScreenshots/SettingsSiteTwitter.png index fb193df9d33c77f60ccf2841d20b599a76ee9a15..a297c34ee8de5792c9ee03fc51b952740a13ae5c 100644 GIT binary patch literal 19424 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 literal 20578 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 WEw!No2E1Yq0_kcQK;Rnp!v7ZvN6SzE literal 0 HcmV?d00001 diff --git a/ProgramsComparison.md b/ProgramsComparison.md index 471d406..78f2fa2 100644 --- a/ProgramsComparison.md +++ b/ProgramsComparison.md @@ -1,6 +1,55 @@ +# yt-dlp + +https://github.com/yt-dlp/yt-dlp/ + +**Great powerful CLI tool that supports hundreds of sites.** + +SCrawler has advanced user management, collections, labels, groups, automatic downloads, a beautiful view, GUI, the ability to add plugins for other sites and much more. Just try it and compare. + +# 4K Video Downloader + +https://www.4kdownload.com/-plbrz/video-downloader + +| Option | SCrawler | 4K Stogram | +| ---- | ---- | ---- | +| User managament | **Advanced** | No | +| Automatic downloads | **Yes** | No | +| Downloading groups | **Yes** | No | +| Labeling users | **Yes** | No | +| Filtering | **Yes** | No | +| Collections | **Yes** | No | +| Specific user folders | **Yes** | No | +| Favorite / Temporary user options | **Yes** | No | +| Plugins support | **Yes** | No | +| Download single video | **Unlimited** | 30 videos per day *(unlimited starts from 12 EUR)* | +| Download videos per channel | **Unlimited** | 5 free *(unlimited starts from 12 EUR)* | +| Download videos per playlist | **Unlimited** | 10 free *(unlimited starts from 12 EUR)* | +| Download video subtitles | **Any for free**: single video, playlist, user/channel, album, etc| Free for single video | +| The number of subtitles you can download for a video | **All of them** | Up to 10 | +| Convert subtitles to additional formats | **Yes** | No | +| Support LRC format | **Yes** | No | +| Select audio codec for audio/video | **Yes** | No | +| Extract and convert additional audio tracks for video | **Yes** | No | +| Simultaneous downloads | **Unlimited** | 1 free, 3 for 12 EUR, 7 for 43 EUR| +| Private YouTube content download | **Free** | Only in paid plans *starts from 12 EUR* | +| **Paid** | **No** | Yes | +| **Free options** | **The program is completely free** | Only **30** videos per day, 5 from a channel, 10 from a playlist | +| Permitted Commercial Use | **Yes** | Starting from 43 EUR | +| Automatic Subscriptions Update | **Free** | Paid (43 EUR) | +| Posts and Captions Export | No | Paid (43 EUR) | +| Advertisements free | **No ADs at all for free** | Paid (43 EUR) | +| Operating Systems | Windows 10+ | Windows 7+, MacOS 10.13+, Ubuntu x64 | +| Select want content type to download | **Yes** | No | +| Instagram support | **Yes** | No | +| Twitter support | **Yes** | No | +| Reddit support | **Yes** | No | +| Other sites support | **Yes** | No | +| Still supported | Yes | Yes | + # 4K Stogram -https://www.4kdownload.com/products/product-stogram + +https://www.4kdownload.com/-ad0p9/stogram | Option | SCrawler | 4K Stogram | | ---- | ---- | ---- | @@ -27,10 +76,10 @@ https://www.4kdownload.com/products/product-stogram | Export and import subscriptions | No | **Yes** | | **Paid** | **No** | Yes | | **Free options** | **The program is completely free** | Only **ONE** profile downloading and only **200 posts** per day | -| Permitted Commercial Use | **Yes** | Starting from 43.56 EUR | -| Automatic Subscriptions Update | **Free** | Paid (43.56 EUR) | -| Posts and Captions Export | No | Paid (43.56 EUR) | -| Advertisements free | **No ADs at all for free** | Paid (14.52) | +| Permitted Commercial Use | **Yes** | Starting from 43 EUR | +| Automatic Subscriptions Update | **Free** | Paid (43 EUR) | +| Posts and Captions Export | No | Paid (43 EUR) | +| Advertisements free | **No ADs at all for free** | Paid (18 EUR) | | Operating Systems | Windows 10+ | Windows 7+, MacOS 10.13+, Ubuntu x64 | | Select want content type to download | **Yes** | No | | Instagram support | Yes | Yes | @@ -68,7 +117,7 @@ https://github.com/RipMeApp/ripme | **Free options** | The program is completely free | The program is completely free, but site limits are not declared | | Operating Systems | Windows 10+ | Windows, MacOS, Linux | | Select want content type to download | Yes | Yes | -| Suported sites | 9 internal and any site using plugins | 86+ sites (declared) | +| Suported sites | 15 internal and any site using plugins | 86+ sites (declared) | | Other sites support | **Yes** | No | | Still supported | **Yes** | **No (last release date May 4, 2021)** | @@ -76,7 +125,6 @@ https://github.com/RipMeApp/ripme https://github.com/mikf/gallery-dl - -**CLI tool**! Configured with JSON files only. Users need to learn complex configuration options, JSON, commands to use that tool. Very difficult to configure. +**CLI tool** SCrawler has advanced user management, collections, labels, groups, automatic downloads, a beautiful view, GUI, the ability to add plugins for other sites and much more. Just try it and compare. \ No newline at end of file diff --git a/README.md b/README.md index 0e5a223..aedabcb 100644 --- a/README.md +++ b/README.md @@ -6,32 +6,39 @@ [![FAQ](https://img.shields.io/badge/FAQ-green)](FAQ.md) [![GUIDE](https://img.shields.io/badge/GUIDE-green)](https://github.com/AAndyProgram/SCrawler/wiki) [![How to support](https://img.shields.io/badge/HowToSupport-green)](HowToSupport.md) +:eu: +:greece: -A program to download photo and video from [any site](#supported-sites) (e.g. Reddit, Twitter, Instagram, TikTok, RedGifs, PornHub, XHamster, XVIDEOS, LPSG). +A program to download photo and video from [any site](#supported-sites) (e.g. YouTube, YouTube Music, Reddit, Twitter, Mastodon, Instagram, TikTok, RedGifs, PornHub, XHamster, XVIDEOS, ThisVid, LPSG, Pinterest). **If you like SCrawler, please like the program on [this site](https://alternativeto.net/software/scrawler/about/) and/or [this](https://www.softpedia.com/get/Internet/Download-Managers/Social-networks-crawler.shtml)** - -Do you like this program? Consider adding to my coffee fund by making a donation to show your support. :blush: - -[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/andyprogram) - + **Bitcoin**: BC1Q0NH839FT5TA44DD7L7RLR97XDQAG9V8D6N7XET ![Main window](ProgramScreenshots/MainWindow.png) ![Channels window](ProgramScreenshots/Channels.png) +[**YouTube standalone application:**](https://github.com/AAndyProgram/SCrawler/wiki/YouTube%20downloader) + +![YouTube application](ProgramScreenshots/AppYouTube.png) + # What can program do: - Download pictures and videos from users' profiles and subreddits: + - YouTube videos, shorts, users, artists, playlists, music, tracks; - Reddit images, galleries of images, videos, saved posts; - Redgifs videos (https://www.redgifs.com/); - Twitter images and videos, saved (bookmarked) posts; + - Mastodon images and videos, saved (bookmarked) posts; - Instagram images and videos, tagged posts, stories, saved posts; - - TikTok videos ([limited](https://github.com/AAndyProgram/SCrawler/wiki/Settings#tiktok-limits)); + - TikTok videos (*currently broken*; [limited](https://github.com/AAndyProgram/SCrawler/wiki/Settings#tiktok-limits)); + - Pinterest boards, users, saved posts; - Imgur images, galleries and videos; - Gfycat videos; - PornHub images, videos, save (liked) posts; - XHamster images, videos, saved posts; - - XVIDEOS videos; + - XVIDEOS videos, saved posts; + - ThiVid images, videos, saved posts; - [Other](#supported-sites) supported sites - Parse [channel and view data](https://github.com/AAndyProgram/SCrawler/wiki/Channels) - Download [saved Reddit, Twitter and Instagram posts](https://github.com/AAndyProgram/SCrawler/wiki/Home#saved-posts) @@ -52,18 +59,22 @@ Do you like this program? Consider adding to my coffee fund by making a donation - ...and many others... # Supported sites - +- **YouTube** +- **YouTube Music** - **Reddit** - **Twitter** +- **Mastodon** - **Instagram** -- **TikTok** ([limited](https://github.com/AAndyProgram/SCrawler/wiki/Settings#tiktok-limits)) +- TikTok (*currently broken*; [limited](https://github.com/AAndyProgram/SCrawler/wiki/Settings#tiktok-limits)) - RedGifs +- Pinterest - Imgur - Gfycat - LPSG - **PornHub** - **XHamster** - **XVIDEOS** +- **ThisVid** - [Other sites](Plugins.md) **[SITES REQUIREMENTS](https://github.com/AAndyProgram/SCrawler/wiki/Settings#sites-requirements)** @@ -82,7 +93,8 @@ The program parses user posts and compares file names with existing ones to remo ## How to request a new site -Read [here](CONTRIBUTING.md#how-to-request-a-new-site) about + +**I'm currently not accepting requests to develop new sites.** # Requirements @@ -108,12 +120,16 @@ Read [here](CONTRIBUTING.md#how-to-request-a-new-site) about - **[SITES REQUIREMENTS](https://github.com/AAndyProgram/SCrawler/wiki/Settings#sites-requirements)** - [Reddit](https://github.com/AAndyProgram/SCrawler/wiki/Settings#reddit) - [Twitter](https://github.com/AAndyProgram/SCrawler/wiki/Settings#twitter) + - [Mastodon](https://github.com/AAndyProgram/SCrawler/wiki/Settings#Mastodon) - [Instagram](https://github.com/AAndyProgram/SCrawler/wiki/Settings#instagram) - [TikTok](https://github.com/AAndyProgram/SCrawler/wiki/Settings#tiktok) - [RedGifs](https://github.com/AAndyProgram/SCrawler/wiki/Settings#redgifs) + - [YouTube](https://github.com/AAndyProgram/SCrawler/wiki/Settings#YouTube) + - [Pinterest](https://github.com/AAndyProgram/SCrawler/wiki/Settings#Pinterest) - [PornHub](https://github.com/AAndyProgram/SCrawler/wiki/Settings#pornhub) - [XHamster](https://github.com/AAndyProgram/SCrawler/wiki/Settings#xhamster) - [XVIDEOS](https://github.com/AAndyProgram/SCrawler/wiki/Settings#xvideos) + - [ThisVid](https://github.com/AAndyProgram/SCrawler/wiki/Settings#ThisVid) - [LPSG](https://github.com/AAndyProgram/SCrawler/wiki/Settings#lpsg) **Full guide you can find [here](https://github.com/AAndyProgram/SCrawler/wiki)** @@ -142,20 +158,11 @@ The program has an intuitive interface. Just add a user profile and **click the ```Download``` button**. -Read more about adding users and subreddits [here](https://github.com/AAndyProgram/SCrawler/wiki/Users) +Read more about adding users and subreddits [here](https://github.com/AAndyProgram/SCrawler/wiki#Add%20user) ![Add user](ProgramScreenshots/CreateUserClear.png) -# Using program as just video downloader - -Create a shortcut for the program. Open shortcut properties. In the ```Shortcut``` tab, in the ```Target``` field, just add the letter ```v``` at the end across the space. - -Example: ```D:\Programs\SCrawler\SCrawler.exe v``` - -![Separate video downloader](ProgramScreenshots/SeparateVideoDownloader.png) - # Contact me -[![matrix](https://img.shields.io/badge/Matrix-%40andyprogram%3Amatrix.org-informational)](https://matrix.to/#/@andyprogram:matrix.org) - -[![discord](https://img.shields.io/badge/discord-AndyProgram%233804-yellowgreen)](https://discordapp.com/users/1012768226679206009) AndyProgram#3804 \ No newline at end of file +Matrix (Element): https://matrix.to/#/@andyprogram:matrix.org +Discord: AndyProgram#3804 \ No newline at end of file diff --git a/SCrawler.PluginProvider/Interfaces/IDownloadableMedia.vb b/SCrawler.PluginProvider/Interfaces/IDownloadableMedia.vb new file mode 100644 index 0000000..76de874 --- /dev/null +++ b/SCrawler.PluginProvider/Interfaces/IDownloadableMedia.vb @@ -0,0 +1,34 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace Plugin + Public Interface IDownloadableMedia : Inherits IUserMedia, IDisposable + Event CheckedChange As EventHandler + Event ThumbnailChanged As EventHandler + Event StateChanged As EventHandler + ReadOnly Property SiteIcon As Drawing.Image + ReadOnly Property Site As String + ReadOnly Property SiteKey As String + Property ThumbnailUrl As String + Property ThumbnailFile As String + Property Title As String + Property Size As Integer + Property Duration As TimeSpan + Property Progress As Object + ReadOnly Property HasError As Boolean + ReadOnly Property Exists As Boolean + Property Checked As Boolean + Property Instance As IPluginContentProvider + Sub Download(ByVal UseCookies As Boolean, ByVal Token As Threading.CancellationToken) + Sub Delete(ByVal RemoveFiles As Boolean) + Sub Load(ByVal File As String) + Sub Save() + Overloads Function ToString() As String + Overloads Function ToString(ByVal ForMediaItem As Boolean) As String + End Interface +End Namespace \ No newline at end of file diff --git a/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb b/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb index 0a38d14..0385313 100644 --- a/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb +++ b/SCrawler.PluginProvider/Interfaces/IPluginContentProvider.vb @@ -8,8 +8,8 @@ ' but WITHOUT ANY WARRANTY Namespace Plugin Public Interface IPluginContentProvider : Inherits IDisposable - Event ProgressChanged(ByVal Count As Integer) - Event TotalCountChanged(ByVal Count As Integer) + Event ProgressChanged(ByVal Value As Integer) + Event ProgressMaximumChanged(ByVal Value As Integer, ByVal Add As Boolean) Property Thrower As IThrower Property LogProvider As ILogProvider Property Settings As ISiteSettings @@ -32,7 +32,8 @@ Namespace Plugin Sub ExchangeOptionsSet(ByVal Obj As Object) Sub XmlFieldsSet(ByVal Fields As List(Of KeyValuePair(Of String, String))) Function XmlFieldsGet() As List(Of KeyValuePair(Of String, String)) - Sub GetMedia() - Sub Download() + Sub GetMedia(ByVal Token As Threading.CancellationToken) + Sub Download(ByVal Token As Threading.CancellationToken) + Sub DownloadSingleObject(ByVal Data As IDownloadableMedia, ByVal Token As Threading.CancellationToken) End Interface End Namespace \ No newline at end of file diff --git a/SCrawler.PluginProvider/Interfaces/ISiteSettings.vb b/SCrawler.PluginProvider/Interfaces/ISiteSettings.vb index 4164e96..0563589 100644 --- a/SCrawler.PluginProvider/Interfaces/ISiteSettings.vb +++ b/SCrawler.PluginProvider/Interfaces/ISiteSettings.vb @@ -12,17 +12,17 @@ Namespace Plugin Enum Download As Integer Main = 0 SavedPosts = 1 - Channel = 2 + SingleObject = 2 End Enum ReadOnly Property Icon As Icon ReadOnly Property Image As Image ReadOnly Property Site As String Property Logger As ILogProvider - Function GetUserUrl(ByVal User As IPluginContentProvider, ByVal Channel As Boolean) As String + Function GetUserUrl(ByVal User As IPluginContentProvider) As String Function IsMyUser(ByVal UserURL As String) As ExchangeOptions Function IsMyImageVideo(ByVal URL As String) As ExchangeOptions - Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable Function GetInstance(ByVal What As Download) As IPluginContentProvider + Function GetSingleMediaInstance(ByVal URL As String, ByVal OutputFile As String) As IDownloadableMedia Function GetUserPostUrl(ByVal User As IPluginContentProvider, ByVal Media As IUserMedia) As String #Region "XML Support" Sub Load(ByVal XMLValues As IEnumerable(Of KeyValuePair(Of String, String))) diff --git a/SCrawler.PluginProvider/My Project/AssemblyInfo.vb b/SCrawler.PluginProvider/My Project/AssemblyInfo.vb index 765443a..3a3c5ee 100644 --- a/SCrawler.PluginProvider/My Project/AssemblyInfo.vb +++ b/SCrawler.PluginProvider/My Project/AssemblyInfo.vb @@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices ' by using the '*' as shown below: ' - - + + diff --git a/SCrawler.PluginProvider/Objects/ExchangeOptions.vb b/SCrawler.PluginProvider/Objects/ExchangeOptions.vb index b3667cf..e761d6a 100644 --- a/SCrawler.PluginProvider/Objects/ExchangeOptions.vb +++ b/SCrawler.PluginProvider/Objects/ExchangeOptions.vb @@ -11,15 +11,11 @@ Namespace Plugin Public UserName As String Public SiteName As String Public HostKey As String - Public IsChannel As Boolean Public Exists As Boolean Public Sub New(ByVal Site As String, ByVal Name As String) UserName = Name SiteName = Site - End Sub - Public Sub New(ByVal Site As String, ByVal Name As String, ByVal IsChannel As Boolean) - Me.New(Site, Name) - Me.IsChannel = IsChannel + Exists = Not String.IsNullOrEmpty(Name) And Not String.IsNullOrWhiteSpace(Name) End Sub End Structure End Namespace \ No newline at end of file diff --git a/SCrawler.PluginProvider/Objects/PluginUserMedia.vb b/SCrawler.PluginProvider/Objects/PluginUserMedia.vb index a5d59c8..a16a893 100644 --- a/SCrawler.PluginProvider/Objects/PluginUserMedia.vb +++ b/SCrawler.PluginProvider/Objects/PluginUserMedia.vb @@ -7,12 +7,15 @@ ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY Namespace Plugin + Public Delegate Sub ProgressChange(ByVal Value As Double?, ByVal Maximum As Double?, ByVal Information As String) Public Enum UserMediaTypes As Integer Undefined = 0 - [Picture] = 1 - [Video] = 2 - [Text] = 3 + Picture = 1 + Video = 2 + Audio = 200 + Text = 4 VideoPre = 10 + AudioPre = 215 GIF = 50 m3u8 = 100 End Enum @@ -24,12 +27,12 @@ Namespace Plugin Missing = 4 End Enum Public Structure PluginUserMedia : Implements IUserMedia - Public Property ContentType As Integer Implements IUserMedia.ContentType + Public Property ContentType As UserMediaTypes Implements IUserMedia.ContentType Public Property URL As String Implements IUserMedia.URL Public Property URL_BASE As String Implements IUserMedia.URL_BASE Public Property MD5 As String Implements IUserMedia.MD5 Public Property File As String Implements IUserMedia.File - Public Property DownloadState As Integer Implements IUserMedia.DownloadState + Public Property DownloadState As UserMediaStates Implements IUserMedia.DownloadState Public Property PostID As String Implements IUserMedia.PostID Public Property PostDate As Date? Implements IUserMedia.PostDate Public Property SpecialFolder As String Implements IUserMedia.SpecialFolder @@ -37,12 +40,12 @@ Namespace Plugin Public Property [Object] As Object Implements IUserMedia.Object End Structure Public Interface IUserMedia - Property ContentType As Integer + Property ContentType As UserMediaTypes Property URL As String Property URL_BASE As String Property MD5 As String Property File As String - Property DownloadState As Integer + Property DownloadState As UserMediaStates Property PostID As String Property PostDate As Date? Property SpecialFolder As String diff --git a/SCrawler.PluginProvider/SCrawler.PluginProvider.vbproj b/SCrawler.PluginProvider/SCrawler.PluginProvider.vbproj index 6b5a7f6..a45fa8b 100644 --- a/SCrawler.PluginProvider/SCrawler.PluginProvider.vbproj +++ b/SCrawler.PluginProvider/SCrawler.PluginProvider.vbproj @@ -102,6 +102,7 @@ + diff --git a/SCrawler.YouTube/.editorconfig b/SCrawler.YouTube/.editorconfig new file mode 100644 index 0000000..18ddd08 --- /dev/null +++ b/SCrawler.YouTube/.editorconfig @@ -0,0 +1,3 @@ +[*.vb] +# Modifier preferences +file_header_template = Copyright (C) 2023 Andy https://github.com/AAndyProgram\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see \ No newline at end of file diff --git a/SCrawler.YouTube/App.config b/SCrawler.YouTube/App.config new file mode 100644 index 0000000..5534e28 --- /dev/null +++ b/SCrawler.YouTube/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SCrawler.YouTube/Attributes/GridVisibleAttribute.vb b/SCrawler.YouTube/Attributes/GridVisibleAttribute.vb new file mode 100644 index 0000000..5aa85fd --- /dev/null +++ b/SCrawler.YouTube/Attributes/GridVisibleAttribute.vb @@ -0,0 +1,30 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Attributes + + Public Class GridVisibleAttribute : Inherits Attribute + Private ReadOnly NonAppMode As Boolean = True + Public Sub New() + End Sub + Public Sub New(ByVal NonAppMode As Boolean) + Me.NonAppMode = NonAppMode + End Sub + Public Overrides Function Equals(ByVal Obj As Object) As Boolean + If Not Obj Is Nothing AndAlso TypeOf Obj Is GridVisibleAttribute Then + If NonAppMode Then + Return DirectCast(Obj, GridVisibleAttribute).NonAppMode + Else + Return True + End If + Else + Return False + End If + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Base/Structures.vb b/SCrawler.YouTube/Base/Structures.vb new file mode 100644 index 0000000..58b168b --- /dev/null +++ b/SCrawler.YouTube/Base/Structures.vb @@ -0,0 +1,81 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Base + Public Structure Thumbnail : Implements IIndexable, IComparable(Of Thumbnail) + Public ID As String + Public Width As Integer + Public Height As Integer + Public URL As String + Public Property Index As Integer Implements IIndexable.Index + Private Function SetIndex(ByVal Obj As Object, ByVal Index As Integer) As Object Implements IIndexable.SetIndex + Dim t As Thumbnail = Obj + t.Index = Index + Return t + End Function + Private Function CompareTo(ByVal Other As Thumbnail) As Integer Implements IComparable(Of Thumbnail).CompareTo + Return Width.CompareTo(Other.Width) * -1 + End Function + End Structure + Public Structure Subtitles : Implements IIndexable, IComparable(Of Subtitles) + Public ID As String + Public Name As String + Public Formats As String + Public ReadOnly Property FullID As String + Get + Return IIf(ID = "en", "en.*", ID) + End Get + End Property + Public Property Index As Integer Implements IIndexable.Index + Private Function SetIndex(ByVal Obj As Object, ByVal Index As Integer) As Object Implements IIndexable.SetIndex + Dim s As Subtitles = Obj + s.Index = Index + Return s + End Function + Private Function CompareTo(ByVal Other As Subtitles) As Integer Implements IComparable(Of Subtitles).CompareTo + Return Name.CompareTo(Other.Name) + End Function + End Structure + Public Enum YouTubeMediaType As Integer + Undefined = 0 + [Single] = 1 + Channel = 2 + PlayList = 3 + End Enum + Public Structure MediaObject : Implements IIndexable, IComparable(Of MediaObject) + Public Type As Plugin.UserMediaTypes + Public ID As String + Public Extension As String + Public Width As Integer + Public Height As Integer + Public FPS As Integer + Public Bitrate As Integer + '''

    Kb + Public Size As Double + Public Codec As String + Public Info As String + Public URL As String + Public Property Index As Integer Implements IIndexable.Index + Private Function SetIndex(ByVal Obj As Object, ByVal Index As Integer) As Object Implements IIndexable.SetIndex + Dim m As MediaObject = Obj + m.Index = Index + Return m + End Function + Private Function CompareTo(ByVal Other As MediaObject) As Integer Implements IComparable(Of MediaObject).CompareTo + If Type = Other.Type Then + If Width.CompareTo(Other.Width) = 0 Then + Return Size.CompareTo(Other.Size) * -1 + Else + Return Width.CompareTo(Other.Width) * -1 + End If + Else + Return CInt(Type).CompareTo(CInt(Other.Type)) + End If + End Function + End Structure +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Base/TableControlsProcessor.vb b/SCrawler.YouTube/Base/TableControlsProcessor.vb new file mode 100644 index 0000000..a24f533 --- /dev/null +++ b/SCrawler.YouTube/Base/TableControlsProcessor.vb @@ -0,0 +1,38 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + Friend Class TableControlsProcessor + Private ReadOnly Property TP_CONTROLS As TableLayoutPanel + Friend Sub New(ByRef TP As TableLayoutPanel) + TP_CONTROLS = TP + End Sub + Private _LatestSelected As Integer = -1 + Friend Sub MediaItem_Click(ByVal Sender As Object, ByVal e As EventArgs) + Try + _LatestSelected = TP_CONTROLS.GetPositionFromControl(Sender).Row + DirectCast(Sender, Control).Focus() + Catch ex As Exception + _LatestSelected = -1 + End Try + End Sub + Friend Sub MediaItem_KeyDown(ByVal Sender As Object, ByVal e As KeyEventArgs) + Try + If e.KeyCode = Keys.Down Or e.KeyCode = Keys.Up Then + Dim newPosition% = _LatestSelected + IIf(e.KeyCode = Keys.Down, 1, -1) + If newPosition < 0 Then newPosition = 0 + If newPosition <> _LatestSelected Then + Dim cnt As DownloadObjects.STDownloader.MediaItem = TP_CONTROLS.GetControlFromPosition(0, newPosition) + If Not cnt Is Nothing Then cnt.PerformClick() + End If + End If + Catch + End Try + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Base/YouTubeFunctions.vb b/SCrawler.YouTube/Base/YouTubeFunctions.vb new file mode 100644 index 0000000..b686a33 --- /dev/null +++ b/SCrawler.YouTube/Base/YouTubeFunctions.vb @@ -0,0 +1,165 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Forms.Toolbars +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.RegularExpressions +Imports SCrawler.API.YouTube.Objects +Namespace API.YouTube.Base + Public NotInheritable Class YouTubeFunctions + Public Const YouTubeCachePathRoot As String = "_CacheYouTube\" + Public Const UserChannelOption As String = "channel" + Public Const TrueUrlPattern As String = "https?://[^/]*?youtube.com/[^\?/&]+((\??[^\?/&]+|/[^\?/&]+))" + '2 - type; 5 - id + Public Const UrlTypePattern As String = "(?<=https?://[^/]*?youtube.com/)((@|[^\?/&]+))([/\?]{0,1}(list=|v=|)([^\?/&]*))(?=(\S+|\Z|))" + Private Sub New() + End Sub + Public Shared Function IsMyUrl(ByVal URL As String) As Boolean + Return Not Info_GetUrlType(URL) = YouTubeMediaType.Undefined + End Function + Public Shared Function Info_GetUrlType(ByVal URL As String, Optional ByRef IsMusic As Boolean = False, + Optional ByRef IsChannelUser As Boolean = False, Optional ByRef Id As String = Nothing) As YouTubeMediaType + If Not URL.IsEmptyString Then + IsMusic = URL.Contains("music.youtube.com") + IsChannelUser = False + Dim data As List(Of String) = RegexReplace(URL, RParams.DMS(UrlTypePattern, 0, RegexReturn.ListByMatch, EDP.ReturnValue)) + If data.ListExists Then + If data.Count >= 6 Then Id = data(5) + If data.Count >= 3 And Not data(2).IsEmptyString Then + Select Case data(2).ToLower + Case "watch" : Return YouTubeMediaType.Single + Case "playlist" : Return YouTubeMediaType.PlayList + Case UserChannelOption, "@" : IsChannelUser = data(2).ToLower = UserChannelOption : Return YouTubeMediaType.Channel + End Select + End If + End If + End If + Return YouTubeMediaType.Undefined + End Function + ''' '--no-cookies-from-browser --cookies CookiesFile' + Public Shared Function GetCookiesCommand(ByVal UseCookies As Boolean, ByVal CookiesFile As SFile) As String + If UseCookies And CookiesFile.Exists Then + Return $"--no-cookies-from-browser --cookies ""{CookiesFile}""" + Else + Return String.Empty + End If + End Function + ''' Data with upload date 'more than or equal to' date will be downloaded + ''' Data with upload date 'less than or equal to' date will be downloaded + ''' + ''' + ''' + Public Shared Function Parse(ByVal URL As String, Optional ByVal UseCookies As Boolean? = Nothing, + Optional ByVal Token As Threading.CancellationToken = Nothing, Optional ByVal Progress As IMyProgress = Nothing, + Optional ByVal GetDefault As Boolean? = Nothing, Optional ByVal GetShorts As Boolean? = Nothing, + Optional ByVal DateAfter As Date? = Nothing, Optional ByVal DateBefore As Date? = Nothing) As IYouTubeMediaContainer + If URL.IsEmptyString Then Throw New ArgumentNullException("URL", "URL cannot be null") + If Not MyYouTubeSettings.YTDLP.Value.Exists Then Throw New IO.FileNotFoundException("Path to 'yt-dlp.exe' not set or program not found at destination", MyYouTubeSettings.YTDLP.Value.ToString) + Dim urlOrig$ = URL + URL = RegexReplace(URL, TrueUrlRegEx) + If URL.IsEmptyString Then Throw New ArgumentNullException("URL", $"Can't get true URL from [{urlOrig}]") + Dim isMusic As Boolean = False + Dim objType As YouTubeMediaType = Info_GetUrlType(URL, isMusic) + If Not objType = YouTubeMediaType.Undefined Then + Dim __GetDefault As Boolean = If(GetDefault, True) + Dim __GetShorts As Boolean = If(GetShorts, True) + If isMusic Then __GetShorts = False + Dim container As IYouTubeMediaContainer + Dim pattern$ = "%(channel_id)s_%(id)s_%(playlist_index)s" + + Select Case objType + Case YouTubeMediaType.Single + __GetShorts = False + If isMusic Then container = New Track Else container = New Video + Case YouTubeMediaType.PlayList : container = New PlayList : pattern = "%(playlist_index)s_%(id)s" : __GetShorts = False + Case YouTubeMediaType.Channel + container = New Channel + If isMusic Then pattern = "%(playlist_id)s/%(channel_id)s_%(id)s_%(playlist_index)s" + Case Else : Throw New InvalidOperationException($"Type '{objType}' is not supported by YouTubeDownloader") + End Select + + If UseCookies.HasValue Then container.UseCookies = UseCookies.Value + Dim result As Boolean = False + Dim cookiesExists As Boolean = YouTubeCookieNetscapeFile.Exists + Dim _CachePathDefault As SFile = MyCache.NewPath(, EDP.ReturnValue) + If _CachePathDefault.IsEmptyString Then _CachePathDefault = $"{YouTubeCachePathRoot}{SFile.GetDirectories(YouTubeCachePathRoot,,, EDP.ReturnValue).Count + 1}" + _CachePathDefault.Exists(SFO.Path, True, EDP.ThrowException) + pattern = $"{_CachePathDefault.PathWithSeparator}{pattern}" + + Dim withCookieRequested As Boolean = False + Dim useCookiesForce As Boolean = UseCookies.HasValue AndAlso UseCookies.Value AndAlso cookiesExists + If UseCookies.HasValue AndAlso UseCookies.Value Then + withCookieRequested = True + result = Parse_Internal(URL, pattern, _CachePathDefault, True, YouTubeCookieNetscapeFile, DateAfter, DateBefore, __GetDefault, __GetShorts) + End If + If Not result And Not withCookieRequested Then + If Not UseCookies.HasValue OrElse Not UseCookies.Value Then result = Parse_Internal(URL, pattern, _CachePathDefault, False, YouTubeCookieNetscapeFile, DateAfter, DateBefore, __GetDefault, __GetShorts) + If Not result And Not UseCookies.HasValue And cookiesExists Then result = Parse_Internal(URL, pattern, _CachePathDefault, True, YouTubeCookieNetscapeFile, DateAfter, DateBefore, __GetDefault, __GetShorts) + End If + + If result Then + container.Parse(Nothing, _CachePathDefault, isMusic, Token, Progress) + If Not container.HasError Then container.URL = URL : Return container + End If + container.Dispose() + End If + Return Nothing + End Function + Private Shared Function Parse_Internal(ByVal URL As String, ByVal OutputPattern As String, ByVal OutputPath As SFile, + ByVal UseCookies As Boolean, ByVal CookiesFile As SFile, + ByVal DateAfter As Date?, ByVal DateBefore As Date?, + ByVal GetDefault As Boolean, ByVal GetShorts As Boolean) As Boolean + Try + Dim command$ = "yt-dlp --write-info-json --skip-download" + command.StringAppend(GetCookiesCommand(UseCookies, CookiesFile), " ") + If DateAfter.HasValue Then command.StringAppend($"--dateafter {DateAfter.Value:yyyyMMdd}", " ") + If DateBefore.HasValue Then command.StringAppend($"--datebefore {DateBefore.Value:yyyyMMdd}", " ") + command.StringAppend("{0}" & $" -o ""{OutputPattern}""", " ") +#If DEBUG Then + Debug.WriteLine(String.Format(command, URL)) +#End If + Using batch As New BatchExecutor(True) + With batch + .CommandPermanent = BatchExecutor.GetDirectoryCommand(MyYouTubeSettings.YTDLP.Value) + If GetDefault Then .Execute(String.Format(command, URL)) + If GetShorts Then .Execute(String.Format(command, $"{URL.StringTrimEnd("/")}/shorts")) + End With + End Using + Return SFile.GetFiles(OutputPath,, IO.SearchOption.AllDirectories, EDP.ReturnValue).Count > 0 + Catch ex As Exception + Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, + $"[API.YouTube.Base.YouTubeFunctions.Parse_Internal({URL}, {UseCookies})]", False) + End Try + End Function + Friend Shared Function CreateContainer(ByVal f As SFile) As IYouTubeMediaContainer + Dim c As IYouTubeMediaContainer = Nothing + If f.Exists(SFO.File, False) Then + Using x As New XmlFile(f, Protector.Modes.All, False) With {.AllowSameNames = True, .XmlReadOnly = True} + x.LoadData() + If x.Value(YouTubeMediaContainerBase.Name_SiteKey) = YouTubeSiteKey Then + Select Case x.Value(YouTubeMediaContainerBase.Name_ObjectType).FromXML(Of Integer)(YouTubeMediaType.Undefined) + Case YouTubeMediaType.Channel : c = New Channel + Case YouTubeMediaType.PlayList : c = New PlayList + Case YouTubeMediaType.Single + If x.Value(YouTubeMediaContainerBase.Name_IsMusic).FromXML(Of Boolean)(False) Then + c = New Track + Else + c = New Video + End If + Case Else : Throw New ArgumentException($"Object type '{x.Value(YouTubeMediaContainerBase.Name_ObjectType)}' is not identified", + "ObjectType") With {.HelpLink = NameOf(CreateContainer)} + End Select + End If + End Using + If Not c Is Nothing Then c.Load(f) + End If + Return c + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Base/YouTubeSettings.vb b/SCrawler.YouTube/Base/YouTubeSettings.vb new file mode 100644 index 0000000..58b604b --- /dev/null +++ b/SCrawler.YouTube/Base/YouTubeSettings.vb @@ -0,0 +1,337 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Globalization +Imports System.Drawing.Design +Imports System.ComponentModel +Imports SCrawler.API.YouTube.Attributes +Imports SCrawler.DownloadObjects.STDownloader +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Imports PersonalUtilities.Functions.XML.Objects +Imports PersonalUtilities.Functions.XML.Attributes.Specialized +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Tools.Grid.Base +Imports PersonalUtilities.Tools.Grid.Attributes +Imports PersonalUtilities.Tools.Grid.Collections +Imports PersonalUtilities.Tools.Grid.Specialized +Imports PersonalUtilities.Tools.Web.Cookies +Namespace API.YouTube.Base + + Public Class YouTubeSettings : Implements IXMLValuesContainer, IGridValuesContainer, IDownloaderSettings +#Region "Events" + Private Event OnBeginUpdate As EventHandler Implements IXMLValuesContainer.OnBeginUpdate + Private Event OnEndUpdate As EventHandler Implements IXMLValuesContainer.OnEndUpdate +#End Region +#Region "Declarations" + Private ReadOnly Property XML As XmlFile Implements IXMLValuesContainer.XML + Friend ReadOnly Property DesignXml As XmlFile + Private Property Mode As GridUpdateModes = GridUpdateModes.OnConfirm Implements IGridValuesContainer.Mode + Friend ReadOnly Property PlaylistFormSplitterDistance As XMLValue(Of Integer) +#Region "Environment" + + Public ReadOnly Property YTDLP As XMLValue(Of SFile) + + Public ReadOnly Property FFMPEG As XMLValue(Of SFile) + + Public ReadOnly Property Cookies As CookieKeeper + Private Function ShouldSerializeCookies() As Boolean + Return Cookies.Count > 0 + End Function + Private Sub ResetCookies() + Cookies.Clear() + End Sub + Private Class CookieListForm2 : Inherits CookieListForm + Public Sub New() + ShowGrid = False + End Sub + End Class + + Public ReadOnly Property OutputPath As XMLValue(Of SFile) + + Public ReadOnly Property OutputPathAutoChange As XMLValue(Of Boolean) + + Public ReadOnly Property OnItemDoubleClick As XMLValue(Of DoubleClickBehavior) + Private ReadOnly Property IDownloaderSettings_OnItemDoubleClick As DoubleClickBehavior Implements IDownloaderSettings.OnItemDoubleClick + Get + Return OnItemDoubleClick + End Get + End Property + + Public ReadOnly Property OpenFolderInOtherProgram As XMLValueUse(Of String) + + Private Property IDownloaderSettings_OpenFolderInOtherProgram As Boolean Implements IDownloaderSettings.OpenFolderInOtherProgram + Get + Return OpenFolderInOtherProgram.Use + End Get + Set(ByVal use As Boolean) + OpenFolderInOtherProgram.Use = use + End Set + End Property + + Private Property IDownloaderSettings_OpenFolderInOtherProgram_Command As String Implements IDownloaderSettings.OpenFolderInOtherProgram_Command + Get + Return OpenFolderInOtherProgram + End Get + Set(ByVal command As String) + OpenFolderInOtherProgram.Value = command + End Set + End Property +#End Region +#Region "Defaults" + + Public ReadOnly Property DefaultUseCookies As XMLValue(Of Boolean) + + Public ReadOnly Property ItemsListLimit As XMLValue(Of Integer) + + Public ReadOnly Property RemoveDownloadedAutomatically As XMLValue(Of Boolean) + Private ReadOnly Property IDownloaderSettings_RemoveDownloadedAutomatically As Boolean Implements IDownloaderSettings.RemoveDownloadedAutomatically + Get + Return RemoveDownloadedAutomatically + End Get + End Property + + Public ReadOnly Property DownloadAutomatically As XMLValue(Of Boolean) + Private ReadOnly Property IDownloaderSettings_DownloadAutomatically As Boolean Implements IDownloaderSettings.DownloadAutomatically + Get + Return DownloadAutomatically + End Get + End Property + + Public ReadOnly Property MaxJobsCount As XMLValue(Of Integer) + Private ReadOnly Property IDownloaderSettings_MaxJobsCount As Integer Implements IDownloaderSettings.MaxJobsCount + Get + Return MaxJobsCount + End Get + End Property + + Public ReadOnly Property ShowNotifications As XMLValue(Of Boolean) + Private ReadOnly Property IDownloaderSettings_ShowNotifications As Boolean Implements IDownloaderSettings.ShowNotifications + Get + Return ShowNotifications + End Get + End Property + + Public ReadOnly Property ShowNotificationsEveryDownload As XMLValue(Of Boolean) + Private ReadOnly Property IDownloaderSettings_ShowNotificationsEveryDownload As Boolean Implements IDownloaderSettings.ShowNotificationsEveryDownload + Get + Return ShowNotifications And ShowNotificationsEveryDownload + End Get + End Property + Private Sub ShowNotificationsEveryDownload_TempValueChanged(ByVal Sender As Object, ByVal e As EventArgs) + If ShowNotificationsEveryDownload.ValueTemp Then ShowNotifications.ValueTemp = True + End Sub + + Public ReadOnly Property CloseToTray As XMLValue(Of Boolean) + + Public ReadOnly Property ExitConfirm As XMLValue(Of Boolean) +#End Region +#Region "Defaults Video" + + Public ReadOnly Property DefaultVideoFormat As XMLValue(Of String) + Private Function AvailableVideoFormats_Impl() As String() + Return AvailableVideoFormats + End Function + + Public ReadOnly Property DefaultVideoDefinition As XMLValue(Of Integer) +#End Region +#Region "Defaults Audio" + + Public ReadOnly Property DefaultAudioCodec As XMLValue(Of String) + Private Function AvailableAudioFormats_Impl() As String() + Return AvailableAudioFormats + End Function + + Public ReadOnly Property DefaultAudioCodecMusic As XMLValue(Of String) + + Public ReadOnly Property DefaultAudioCodecAddit As XMLValuesCollection(Of String) +#End Region +#Region "Defaults Subtitles" + + Public ReadOnly Property DefaultSubtitles As XMLValuesCollection(Of String) + + Private Property DefaultSubtitles_Impl As String + Get + If DefaultSubtitles.ValueTemp.Count > 0 Then + Return DefaultSubtitles.ValueTemp.ListToString(",") + Else + Return String.Empty + End If + End Get + Set(ByVal s As String) + If s.IsEmptyString Then + DefaultSubtitles.ValueTemp = Nothing + Else + DefaultSubtitles.ValueTemp = ListAddList(Nothing, s.Split(","), LAP.NotContainsOnly, + CType(Function(Input$) Input.StringTrim, Func(Of Object, Object))) + End If + End Set + End Property + Private Function ShouldSerializeDefaultSubtitles_Impl() As Boolean + Return DirectCast(DefaultSubtitles, IGridValue).ShouldSerializeValue + End Function + Private Sub ResetDefaultSubtitles_Impl() + DirectCast(DefaultSubtitles, IGridValue).ResetValue() + End Sub + + Public ReadOnly Property DefaultSubtitlesFormat As XMLValue(Of String) + Private Function AvailableSubtitlesFormats_Impl() As String() + Return AvailableSubtitlesFormats + End Function + + Public ReadOnly Property DefaultSubtitlesFormatAddit As XMLValuesCollection(Of String) +#End Region +#End Region +#Region "Initializer" + Public Sub New() + XML = New XmlFile(YouTubeSettingsFile,, False) With {.AutoUpdateFile = True} + XML.LoadData(EDP.None) + DesignXml = New XmlFile("Settings\DesignDownloader.xml", Protector.Modes.All, False) + DesignXml.LoadData(EDP.None) + InitializeXMLValueProperties(Me) + AddHandler ShowNotificationsEveryDownload.TempValueChanged, AddressOf ShowNotificationsEveryDownload_TempValueChanged + Cookies = New CookieKeeper + Grid.Abstract.DesignerXmlSource.Add(New Grid.Abstract.DesignerXmlData(GetType(CookieListForm2), DesignXml, "CookiesListForm")) + If YouTubeCookieNetscapeFile.Exists Then Cookies.AddRange(CookieKeeper.ParseNetscapeText(YouTubeCookieNetscapeFile.GetText(EDP.ReturnValue), EDP.None),, EDP.None) + If Not YTDLP.Value.Exists Then YTDLP.Value = ProgramPath("yt-dlp.exe") + If Not FFMPEG.Value.Exists Then FFMPEG.Value = ProgramPath("ffmpeg.exe") + If Not OutputPath.Value.Exists(SFO.Path, False) Then OutputPath.Value = YouTubeDownloadPathDefault + If XML.ChangesDetected Then XML.UpdateData() + End Sub + Private Function ProgramPath(ByVal Program As String) As SFile + If Program.CSFile.Exists Then + Return Program.CSFile + ElseIf $"Environment\{Program}".CSFile.Exists Then + Return $"Environment\{Program}" + Else + Return SystemEnvironment.FindFileInPaths(Program).ListIfNothing.FirstOrDefault + End If + End Function +#End Region +#Region "Edit, Update" + Protected Overridable Sub BeginUpdate() Implements IXMLValuesContainer.BeginUpdate, IGridValuesContainer.BeginUpdate + XML.BeginUpdate() + End Sub + Protected Overridable Sub EndUpdate() Implements IXMLValuesContainer.EndUpdate, IGridValuesContainer.EndUpdate + XML.EndUpdate() + If XML.ChangesDetected Then XML.UpdateData() + End Sub + Protected Overridable Sub Apply() Implements IGridValuesContainer.Apply + XMLValuesApply(Me) + ApplyCookies() + End Sub + Protected Sub ApplyCookies() + If Cookies.Count > 0 Then Cookies.SaveNetscapeFile(YouTubeCookieNetscapeFile) Else YouTubeCookieNetscapeFile.Delete(,, EDP.None) + End Sub + Private Sub BeginEdit() Implements IGridValuesContainer.BeginEdit + XMLValuesBeginEdit(Me) + End Sub + Protected Overridable Sub EndEdit() Implements IGridValuesContainer.EndEdit + XMLValuesEndEdit(Me) + Cookies.Clear() + If YouTubeCookieNetscapeFile.Exists Then Cookies.AddRange(CookieKeeper.ParseNetscapeText(YouTubeCookieNetscapeFile.GetText(EDP.ReturnValue), EDP.None),, EDP.None) + End Sub + Public Sub ShowForm(ByVal AppMode As Boolean) + Using f As New SimpleGridForm(Me) With { + .GridShowToolbar = False, + .InitialOkValue = True, + .ShowIcon = True, + .Icon = My.Resources.SiteYouTube.YouTubeIcon_32, + .Text = "YouTube Settings", + .DesignXML = DesignXml, + .DesignXMLNodeName = "YouTubeSettingsForm" + } + f.GridBrowsableAttributes = New AttributeCollection(New BrowsableAttribute(True), New GridVisibleAttribute(Not AppMode)) + f.ShowDialog() + End Using + End Sub +#End Region +#Region "Close" + Friend Sub Close() + DesignXml.Dispose() + XML.Dispose() + Cookies.Dispose() + End Sub +#End Region +#Region "Grid Support" + Private Class ValueCollectionConverter : Inherits TypeConverter + Public Overrides Function ConvertTo(ByVal Context As ITypeDescriptorContext, ByVal Culture As CultureInfo, ByVal Value As Object, ByVal DestinationType As Type) As Object + If TypeOf Value Is IEnumerable Then + Return DirectCast(Value, IEnumerable).ToObjectsList(Of String).ListToString + Else + Return String.Empty + End If + End Function + End Class + Private Class ValueCollectionEditor : Inherits GridStructureCollectionEditor + Public Overrides Function EditValue(ByVal Context As ITypeDescriptorContext, ByVal Provider As IServiceProvider, ByVal Value As Object) As Object + Dim eObj As IEnumerable(Of String) = Nothing + Select Case Context.PropertyDescriptor.Name + Case NameOf(DefaultSubtitlesFormatAddit) : eObj = AvailableSubtitlesFormats + Case NameOf(DefaultAudioCodecAddit) : eObj = AvailableAudioFormats + End Select + Using f As New SimpleListForm(Of String)(eObj) With { + .Mode = SimpleListFormModes.CheckedItems, + .DesignXML = MyYouTubeSettings.DesignXml, + .DesignXMLNodeName = "YouTubeSettingsFormList", + .FormText = DirectCast(Context.PropertyDescriptor.Attributes.Cast(Of Attribute).First(Function(a) a.GetType Is GetType(DisplayNameAttribute)), DisplayNameAttribute).DisplayName, + .Icon = My.Resources.SiteYouTube.YouTubeIcon_32 + } + f.DataSelected.ListAddList(Value) + If f.ShowDialog() = DialogResult.OK Then + eObj = f.DataResult.ToList + With DirectCast(Value, List(Of String)) : .Clear() : .ListAddList(eObj) : End With + End If + End Using + Return Value + End Function + End Class +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Content/Icons/YouTubeIcon_32.ico b/SCrawler.YouTube/Content/Icons/YouTubeIcon_32.ico new file mode 100644 index 0000000000000000000000000000000000000000..4e4c0762570a57e1f82d27a765c1ce198a7373b2 GIT binary patch literal 4286 zcmeHKO-NKx7(H)h9Cg&jv1~9CM+`CqLC{5Qd?1L6aZ#3FA+(5SA=DxUmo0({SG7n8 z+qn=F3aOxw(y~M(i9$#h;}2uyPwF@`HtINE=Z(DGDD$J!BJbn8``*3pe&^nEzk9#? z0EL&s0qu_NT;Mo>GJ-`=39USmSa+BX71&*Ye630WE~Wv!*+5^O!7ysK*|rNyOT|%9 zA*ZUUawJ9);e^vhicU{AIxV5b!@E-tl>q z`8P$838PWm#H`6Ab9;f`%oq&fW}EL?+Z`Le0l@d1?~j3{YrtF~AmltxgbD8%VB-~^ zHwjkA0U{g%HitRZvpanlwgEwo_aHf=+1?le+$X6;JrG(=J%2a|EL^1yE^5C05BNF9 zi;sX`^oiK#oc<+db2L2@DGR)yPxi($_M#$@_I8o#Y7zQ*l78yb*4Nv3__?-aYD&c8 z5ov1^vD=gBLtp|}{v0pA+bxl_wIwn*DB^S`Iwxx$;P=-=`FDCDva%x5(IHY+7Oxho zi@-|K{CYnoCq-IX;_-{MrTrMc#;?yseSJLq-hJVpo)&SrMDp|F)!#c${kaBWZ>Rac z?tgy2NLQCgWu*w?O#*AtHvT8T-29;V&oAoLF4k=% zllwk$|7E<7egD;TPJZ9h`|Rss{JBMb#$>%LRk=3i#e2YP4Y0+1AXU6NPsj&&E&y*V z^uf;5^9LFGMvij-=lN^u1mn;Xo@O4B*E( z`eQ5b^MRrSJ58owkJ-HbG9x40&vo`bD@#6QXUphrVEqWc8_LX-f%Npy>$J4>KBIA? zTTucW+mHLSdz0s_I^YY>dOkrs1D2ia|QIXgP!w8z&ZdJ??nCs_eUJfmS&TiY>m#N G=YIhqjqaNO literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Icons/YouTubeMusicIcon_32.ico b/SCrawler.YouTube/Content/Icons/YouTubeMusicIcon_32.ico new file mode 100644 index 0000000000000000000000000000000000000000..e341bc91f1e592982d11bb0b526f1b2ddd1c5a2b GIT binary patch literal 4286 zcmc(jPfJxn7{=$Cq~M|y30x(l5UvXqg`{lX&hMap0ox?HRk!s43R>vSMR(^yQK9?* zF1$y`jUc%Z3d*PVcgDG#dp&Bw_un0epld1uZ$e~!-8>~C<;@#{W6bncOJ?y13= z+?0X#__4me9uMD8=2vEU21!-?UK zA!ufm*kFqfz7^w$b9$kHlm#}^K`;LI3nXbv`dX|~@CMf_#^;es+J1G$H&safmE-1P^XPPV!<|#>q-mFp#R$HnRz}kB5iJZ zjn!3YY)tC!m%6(>sMAItabSy2P7G+Fi9YM|qaoEEp8b7kXUA)FbfnwiPakn$i%&re zXrlex@E!klFprF7ds`YB^8IMkYS(R(zS!b}Z_c|w6YaN#)P0S2*=YDN1%Adv?hOp& z$G|)s9r=6+zHKF%XxH1?<9Q&LcX$0eO^$bRQaU^Hxi&K+_4K62fzQf{=g3(XoEK4= zK5HzBr19}qe)P}J6+agj(#D2FTW4ph9h~^W0au!awKWXMbF68{+_SF8?WAu~-$x;x zo=R(L((rJrExvHTmE=g&Q?==5UtxW-#uE1NSi|^(ekczQy;ks{gafW5N11w6{mV<~ zhWe>znnOljqI+H7)LViThVm zpEzC_?&9tn^dsX>xqr6A@E`8q=<`^ZPBjDbaNFM>aKarcW&gx65C`vwZ>P+Asr7|< z%_=K?eYwKVojIM qz5DIli=WOtK6maxF;*I`X2bVjEEe8EP3KQEREB)~Zk~T3gFdwT@|; z1hqD{qR14jvBXY;jA2YI#Y5E=Pyc!6yg%mLbMNPz`#qm?|GTLU*R8}wI&oK$w*2Pb-tRNhp^qG;3LrK z_mdA@a+_#Fd8&3J+Y7Jk3JMnrx45DAqKfrxYWq_opywsfXpiH(m6qdOlK-OdMuCoC zPXgayU7Sjla}WsXVf4pmzD4g?DY0x&X%477g{2=Zbk5&Tl9l3u z5ld-s4!<92wkyI(6P7A_Dlp^FZtnk+nO!4HAfbeAzwl6!> zbB|LTisR-OQNu?1zRqg-)*Xbow>|mtnLDc@>gXUn3L%I|vOS6+O#iq?;3zSzfRFFOka+>aYsI?>6}vXf;nQt-3RI!t<&#i=Jj>dv@HTK(btZ*s6$=Upi3(BOeaf}I{H=60y7ry%# zp-9((-(H+~+qTN*&%ed*%v9$IX%u}OnELB*EVLvhZZe)>o}$6omi*A33=?&DTgH8p zICL@f?!Be9R=wh9&kMhgUE5>pVbH{s8t(KyQwhw#b#Ox&{B=k2fXO1*A7HN9_tZmxS4sD4c;i>tm`gZcpT@yiO#C$LI3V)XWSY@IB z8Ub0;fQ4K2#xZ9(AN`_&V|C~{2G6cU?(eeB4z}VG2j@@}1vLP6v&2+?vI)60vmB*2 z$5Hdw#9jWYSR(a2w=F-iBDX@%$v4~dXw@Ds?%D3OiLb7T+G|dZcc~cjVh%D77G!M0 zCC;KVO(#1?ugPYUANO+<*N^a1TW+Ohq6M3lU+8jvI_-W3(DR14Pe-mgtE(UWZe0?1 zSe{P#@}N3S#dB{L;Xo>CKh}`|Y7iUAmRnu!rve}A&W+4wgv@nyV2b)eTkmR%6`|Qb z3_rjJJ;$4R*u^Yy8Wn#ohL*f`8#gvGqy)}S(&Hf>FMIb|rB{v>1%ctIzJx*-iiMO&vh}{ literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/AudioMusic_32.png b/SCrawler.YouTube/Content/Pictures/AudioMusic_32.png new file mode 100644 index 0000000000000000000000000000000000000000..de3cfcc5b23b93d5f3612237ea5d550599c0a451 GIT binary patch literal 1322 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dyEa{HEjtmSN`?>!lvI6;x#X;^) z4C~IxyacIC_6YK2V5m}KU}$JzVE6?TYIwoGP-?)y@G60U!DT^zebD+^jiuqx-ArB*~bj!)x8_!8@uI08)^A2aLB zon8q1S*KpKBg&1}yt08Se&5ATIT4K=tF{AeV@&dPcS(-fr@IQs;VkfoEM{ORlL2AI z<%cR>0R?|~x;TbdoK8-Va5!+H#edq&)~UU+(^c5j#m)Kk?p3N{Z{`2@K1aEGuWo%x_vGl*f8XYgN`OsaZEfKV)XUe(~xhvt@*+ zhO~r~q%)Awk(GVGa*C!w;x*%=%MYBnbLi43sqIYC(Q_DC0~6X!Ro=?V%$;+PD<)>6 zWh84Ruq@J?>rQna!C8<`)MX5lF!N|bSLe~I@j6)2KtV|89OwF_n zjI0a{7}spjLD7(#pOTqYiCaU#M~(?V4Q?PCiu2P-$`gxH89Z|n(^GvD(=(H^70mR^ z^(=HP6@XTn=^B{n8k#8t0&zx3Nr9EVetCJhUM5I25Cc`~nwg$a!eDA>bl^fjAW)4sk~vT{ z;h8BV86YJxHOzN_N+gk#_-5v&mQ)s`GJsvBUyzq>zbVWQ%^cqlpehDKQ)6QzOJh@@ zZ>&=;1C8@mC9hQXJF~=S9Zc720&*16m=d#Wzp$Pz{{Z^I$ literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/ClockPic_16.png b/SCrawler.YouTube/Content/Pictures/ClockPic_16.png new file mode 100644 index 0000000000000000000000000000000000000000..03ea118846c5bb3ee3a0849384f62a6f95e3eded GIT binary patch literal 457 zcmV;)0XF`LP))k_xs;n-d%*Y6o%m?5fyn@ zTcrVyj;3jkDiBq`1ds7#GHE-GW7)P%Y9NOideFOJuu6;@jYg_gtI_gzNtL?_)$i*F ze2W}vuIr{aB28i|BcB)Iy^*y;7Yx`a?shVn{L1BW)M~fTuow2cCjo;2yPiU!TCFZnK*P=LwP+rVKCxXGBPU^g z3m0GjMnEg?!C&7>%vll6ELR51SN{R`@8|pn27bdDOjsg&00000NkvXXu0mjf$!Wsp literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/HeartPic_32.png b/SCrawler.YouTube/Content/Pictures/HeartPic_32.png new file mode 100644 index 0000000000000000000000000000000000000000..c76b691dbaac542a5e0f927f60c75054f6115cfd GIT binary patch literal 525 zcmV+o0`mQdP)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0isDnK~z{r?N=>R z!$1%{heRTgNMwiJg-E8&b@~&KNF)-+Pe3A(m6b?jB$66(2?iMpyO+&ClFbL?uE0#+ z%$sQ@yZiQ<%e#%(VTU%DxjIWDd7WyxHd;Nv?-gEW3pt^X2GV7Cm}q&GX?c%1$ShJf z=^{Aa1i>DKb>npw9-wHd)q5ME@ZDy4x=$$ri&L{X7jGG^ELphyUOV++)1v@|dC z=`pSvi*qp?jh6Wr58>XBp%;Pd1H zy%HWb$zfdP_Q?kNVcf80 zYrz9ua4cIb8EukFa7r*dlH*}dFD}su<$BPmi0>$0UG_0l;#$=s?H{S zikB#cP|mVpBCa1WzF?=}N)s~2Bb{z<2et|N4%oVQ2|LKZR}WqOM+or&Q2iTO84x}F P00000NkvXXu0mjfz}()o literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/ImagePic_32.png b/SCrawler.YouTube/Content/Pictures/ImagePic_32.png new file mode 100644 index 0000000000000000000000000000000000000000..dd889315869cf94598ad9fb1878e1e5cbaa1d36a GIT binary patch literal 569 zcmV-90>=G`P)q$gGRCr$PSG`UGK@grJRCkRPwbW8rVe1iIKutVL>?I*mNFypX zSYSx7hte}qUVw14()b8a)8A3z&cw-GvchsKKMh~9+04#&`%Pxad_E7QQi;vBcy=r@ zF;HYcTbzM@;ziIEdmJk=piFZpt_Jfs69;1ah`pnfj>yV2XFyk6oe3F*KRuvMIGs+V zG;aj*JJO*N#1(g(E3i*(NTpK1m`o<%4Loq&_c-M#BqZkoSe6Bi#w$3N4vRC;#&z@) zFN&|vE)UjOtrOO2&yYS$lQA5AL;a-=)#^RYWFydzJcdRekkiiMt+y610H;QMNS81(uBFfB{% z4?j)q0Hn=ww!MhPe&(+&0BSObZ1#lSb{T-{=x6>~1E3B!8jsMRPpxuSA+gL+24}Qs z9f3UL>-7N{*mSrVp(u?ASR8qqxfA-n59RVLxF4=mW3+J{{m6^m3HhX=KCLbX4xJNG z%0P!tD0Xh^IHvo6VHn|sWJ};%QGzWraB-XBXzc$D_W}F@t}+KLS=t#;00000NkvXX Hu0mjfyG-&& literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/InfoPic_32.png b/SCrawler.YouTube/Content/Pictures/InfoPic_32.png new file mode 100644 index 0000000000000000000000000000000000000000..872abf351dbf1bf0229265f5c84f8e205a4a5e86 GIT binary patch literal 368 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 literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/SettingsPic_16.bmp b/SCrawler.YouTube/Content/Pictures/SettingsPic_16.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ab2e01ddbb65568b088cbd70847eae71c024919e GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C7H0y=3voj*SO7@;XZTMc+OlX-v4usMwM~MaJ|3fb zl2fW&T>71y`>Lw?yuFVkCM4+U0@ZI#NXRoWJ-vE0T<@D(x96s394sq8SzVo?rZiaxVR^iCqL-vIbBn8w6x@6Y3Z4~ zyc6l^i-Uu=M1&uVjoB3ySzuuaQUC1pnNz*JdrC{6g0VNGHOdq?E6KF4^~vR2L`@4d-nGB9s63_cBH36Z0WVO2D&HD%>3BKjdxqy{%qUv zbK|Bjt5(BfYis*^;Nb7A+kR}^ zG{MvJ*5oNoF0Or{VTbDKXNE^ys{{GvXlB;Dz@P>PN1&^o&YbmV@sbBqr~TTp?e?U} zEA#T7FIoCz{`?JwW#*9-bZU z9v9o%A%T3jv^3S&1g;Xu*b^A|W7FpUyZ8Lvx$Dn%s9)YKUi@O_%%_v50OMs}VNsQb z2VBe3DU*L~-S%(iu2WT2hf7K!-ak-Py(lSpM{x--kPlZ>EKN;=s|PY(ELwE0e*%zk xzN;5#(VuPG7lcOvxwqDDSecozDl-dNF*5sHN9Xkk6VEm_ovp4x7KgAI7yv@k%7p*` literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/VideoCamera_32.png b/SCrawler.YouTube/Content/Pictures/VideoCamera_32.png new file mode 100644 index 0000000000000000000000000000000000000000..ac7ada391e35e10eac0461b0244bc2965cdf78e8 GIT binary patch literal 402 zcmV;D0d4+?P)DY`s841S<;*1?|LZF$sj##Hg9XVt+WW z%+AjFnVmTtQc}81Pu=qffwQAZ{S2I zy&V-G+=4M2V^=7_g-!~<4VEz>l;Dz3lJlJss8JD0a1n!8uL(RN7a?J;x^|K>rZ9_h wJ~t%2e~kKuP%2) zf_Q_-6V#cY&Ia}bd=DbkC?us&BtX$NpF1>H$)ZR+B)~6Fg%ARgL{XFgFjZ2jEM7Tg zb*=tZ0I&mq4G2*gze)h<5Fi!ah^wN9Vy#H2QXynq0;JM!0AL0H=f-_r>;Yf}0LwAS zh)+OKOaS0cDgKjje;mBlL;x&6h+WhiAOTSXU@8EX0$-W7EbF{R(u;9{Tn!5-3@0EfzYAU@3?o1~ovi$B zAPIx~fx8oy-L5C&dWH?(K!}y)x&b!=ji}9$Y=|;DbjZWu&_+k#*x`fh{ZyP%|;9cK7-F<8sklAFARMS zS`PpyEs65VbSlJhDa39!TUm~D?WWN8~*qRaCc{>!-Jol zi#j$|)H34E!X~YteeN?gSX*yy{7eTvgU|N0DtvbwAF=CN-ZU;K0{GW((I$6++Fmz) zr3-k^_8hG&4Lw^4khPyLBa=yYuZG)E_7Q=3!o1m@qZ6YQ*a+aO{awo6Zlx2pJ;NdR zE_`Qv5{y=$D}Q2nh*$Y&qxox|DfmvCf8Zx!(nrAD@&Jw4lpn|T_KVFVVP5&(IqxHY zr{8ZYWD3e3Cx2Tz0bHYA?B=Dc{5}%+x~Qd%0D9+(wp8!uqusUrHU-gr0_L5^A1rMI z&=0)yT7^9i*8<^mL2T!Y@6&rnJgs&2o&XN>g9_9!q^mifOJ(3_)^?vuFpoNT=8noX z0rb5ey*Ur9Nk~B3Y{eWWtX471=DbZnW%+@>;D}Sl&=GE%fHsLS%o0WZKbERZ0Bz#B z?f0>xHUb*Qi1%#Iq*jKHr2dz~{_8y-pW4$!Km+s-oHLnvE5I_@eitU?nF<4?!)yj{ z-dej~ujL#*HE_QF=}E2;bgdDG0Q0nRG zU%%u#DU^ljU>O=*(C^GSBaKQQtM=wcm!XUjhF9 z1(?sxytzUz1F@f>B|z_uY)HbW!f}5u6?x+kN1>O2xW`5B1KNnQ<--l=AAbLq3LRob zn9IORK#D*|iv9t+n4$iG0H;Sl1L`q@C;m$!7cuejBF748?4V{QAdy;DW51V*A38E; zE(5Iz(rXpR9E}ei9;ChZnHJH@K-_Y8Tfo>(4ky#F>lrd0;VuLDVvIgzZ2JwxbaWpm zciQP|#J1n~OGV!cLMPV%rvzfQ0<`l#e#m!Hm6EzuSYhvD zoO*vf7n(8i%0+}f_qP(vdTUZ%vdRtjQ#p z@cNjK-`>znf)4L5Dfw^C%_KA!=`&XFkAzcp%(o%R2a*UG>2$uSzVE4Euf7u#d=d#w ze1PnBmFh#6$Bq{@$RgP+N*ADk0}na$elYGG4<70=5`9S(@&}!raX=q*hJV|xr}aX_ zVf%)?T1=U;Y~u%)uPB8|{-}Si^xiyCBBCgM*m?jZiULE=@6_ySDn8;>`K$#7SQ&R2 z5uboIq_ajMW+SXMpnYG}is!QL4j7k!HY6F1&>f@p9&5F*RE??hHqs$Lha?1hQYcNp pt}8K3d=x4fV~e(_DEu7&;D1lCMbV*)iFE(~002ovPDHLkV1jbjfm8qh literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Content/Pictures/YouTubePic_96.png b/SCrawler.YouTube/Content/Pictures/YouTubePic_96.png new file mode 100644 index 0000000000000000000000000000000000000000..358e8dac041ca064e811a5094ef950080aca6c01 GIT binary patch literal 2576 zcmaJ@c{r47AAV-Pd!y8D1VcmueYl z0RXU+>O!F_)==dOQ&)V4Pf9Emi=oIVK(vb&E|M?=AYjkpg@FhvhY;2po`a?ed75bjt&ZWu>z)IaN!%3q4;D+0mu;X1U@`o^kNsiB6%X7Fp|ed zI65l3W`RJ{7)&--Y25M!MWd0aT%m};Wr9?SHB!NYWwTjif~lDq!P&vd(%HMlv@?9)Wkqk~b-v1f*qDi44<@TF+6^C!~2e}H(3l!qs7~Hr30P0Cp zioH+V!`H#V(JOsdE{Lz^rVR9@cxf$jx39gL5{Ytw(Q+vS3QAu3tOVVX@$8q$P?#-z zG7R<%vRo6g31x?ZWFS(DOVm?zkxOm&rW^}N_IuTL^+a86*|bX5@-dWrDvz+RRMUW z0cB-n?1@J%3BBEQ8MP3*kR&VGIIXjcG-GN7ZQr7i#t&S{E780AabP<2v435};Pjic zH7y7GL-T&GOYv$eMd*MGP)E{~q&`)K=$@dx7`jt8sC?(oNUg#2{{x`aC2r zFX>Ww%0#t1#CQ4nC+#EGD)01^(rkRUjlfSIzrocj$wq3wIXc&zHuvH9TxJG8)U5Yn zFl69C*7GcjVpwb1~wr$l|kJ;?r{;f8UE8ylE;ndAnEvi5j;(6W|fR_OGY~_Q_+yc zj6lz}wzY<~$v-c&Lz?D^{P*bUy~ks`ZQ5K0HQko9Pak<%I=;J3+fuB3a%|!`r-lPr zpinid6D+DZtG#x=9!#=RJ-Y1}saY?qfBVzhnxSvQC6JXqv9>7x=}!V zFX}ytGVN-Rd#|G{H<u^)C8@Z2Km0S3*o=amVFK)f3wZXPK9ERH4+w;I-9K^XepP zuaJqcnJrM&PPN}3f>ruwY$p!%`DFl8=ySV2bxxVT)yD#EdC(n_kLT}yaFQ9F)W}|v zNKIs(?MQMZUq2`3m9-STR+$@1L!Ha>=yzm*5kD~v(;t_av)!L;Oh0hxQ^Q3<)2DXz zh3r*`6~1x%%o0D=QZcvyUDy<7>oD&Ct++o2WA`Q{$4szYy)ppeUZ}ms%Ag|sk8O3b zNw}S9Q{?>k9+Ac1HI>ywAfj6}rrw}@$p!cmXb?Bw?`c;SvHs`=X{Gtvu4MyDd-N`s zsX=yP;xETW^Agu;`Fm?TYVLyFztZ&9sqA*}nXv5=KWzKJ(Ck(EF!m$R(pk>*XI0f0 zmHpfL=zItr+NQg2!ryDBhK&|V|J#te+^*Flv}kHrCrKr% zXz$Y0hp5&ZB)H|+on%$#0E^c&$zY7oYIEZ{sPvzy-a%)Z}Bwza|K3AR&Fcw~!F4X&JCT=hK z{5bl00roM3WBg`aSIkZ4b(OA~w80>kIq&Od;K(EO&o<;%>8?m-yl5?5W;X_yj-9#l z;}&?lKH}AfJaMK0MhdSWaRqyg>!3RgUKWw_kX&@eZw5fqpR;_qC}xhVe-V%xDpf!^HoJ-<0HZtCgj6?n3WWB=81()yU_v&A1K8aNogO-omL z5^xzI7EFDvj3|lP)%|J*OKPCJ*`eqq|9rvcO&$SMfRuY?E`h{mhVmbc>f}MGatKZN E6R997fB*mh literal 0 HcmV?d00001 diff --git a/SCrawler.YouTube/Controls/MusicPlaylistsForm.Designer.vb b/SCrawler.YouTube/Controls/MusicPlaylistsForm.Designer.vb new file mode 100644 index 0000000..5589941 --- /dev/null +++ b/SCrawler.YouTube/Controls/MusicPlaylistsForm.Designer.vb @@ -0,0 +1,469 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Friend Class MusicPlaylistsForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Me.components = New System.ComponentModel.Container() + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim TP_BUTTONS As System.Windows.Forms.TableLayoutPanel + Dim TP_PLS As System.Windows.Forms.TableLayoutPanel + Dim TP_PLS_BUTTONS As System.Windows.Forms.TableLayoutPanel + Dim TP_PLS_ITEMS As System.Windows.Forms.TableLayoutPanel + Dim TP_SETTINGS As System.Windows.Forms.TableLayoutPanel + Dim TP_FORMATS As System.Windows.Forms.TableLayoutPanel + Dim ActionButton1 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(MusicPlaylistsForm)) + Dim ActionButton2 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton3 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim LBL_FORMAT As System.Windows.Forms.Label + Dim TP_LYRICS As System.Windows.Forms.TableLayoutPanel + Dim ActionButton4 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton5 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton6 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton7 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton8 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim TT_MAIN As System.Windows.Forms.ToolTip + Me.BTT_DOWN = New System.Windows.Forms.Button() + Me.BTT_CANCEL = New System.Windows.Forms.Button() + Me.SPLITTER_MAIN = New System.Windows.Forms.SplitContainer() + Me.LIST_PLAYLISTS = New System.Windows.Forms.CheckedListBox() + Me.BTT_PLS_ALL = New System.Windows.Forms.Button() + Me.BTT_PLS_NONE = New System.Windows.Forms.Button() + Me.LIST_ITEMS = New System.Windows.Forms.CheckedListBox() + Me.TXT_FORMATS_ADDIT = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.CMB_FORMATS = New System.Windows.Forms.ComboBox() + Me.TXT_SUBS = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.CH_DOWN_LYRICS = New System.Windows.Forms.CheckBox() + Me.TXT_OUTPUT_PATH = New PersonalUtilities.Forms.Controls.TextBoxExtended() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_BUTTONS = New System.Windows.Forms.TableLayoutPanel() + TP_PLS = New System.Windows.Forms.TableLayoutPanel() + TP_PLS_BUTTONS = New System.Windows.Forms.TableLayoutPanel() + TP_PLS_ITEMS = New System.Windows.Forms.TableLayoutPanel() + TP_SETTINGS = New System.Windows.Forms.TableLayoutPanel() + TP_FORMATS = New System.Windows.Forms.TableLayoutPanel() + LBL_FORMAT = New System.Windows.Forms.Label() + TP_LYRICS = New System.Windows.Forms.TableLayoutPanel() + TT_MAIN = New System.Windows.Forms.ToolTip(Me.components) + TP_MAIN.SuspendLayout() + TP_BUTTONS.SuspendLayout() + CType(Me.SPLITTER_MAIN, System.ComponentModel.ISupportInitialize).BeginInit() + Me.SPLITTER_MAIN.Panel1.SuspendLayout() + Me.SPLITTER_MAIN.Panel2.SuspendLayout() + Me.SPLITTER_MAIN.SuspendLayout() + TP_PLS.SuspendLayout() + TP_PLS_BUTTONS.SuspendLayout() + TP_PLS_ITEMS.SuspendLayout() + TP_SETTINGS.SuspendLayout() + TP_FORMATS.SuspendLayout() + CType(Me.TXT_FORMATS_ADDIT, System.ComponentModel.ISupportInitialize).BeginInit() + TP_LYRICS.SuspendLayout() + CType(Me.TXT_SUBS, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_OUTPUT_PATH, System.ComponentModel.ISupportInitialize).BeginInit() + Me.SuspendLayout() + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Controls.Add(TP_BUTTONS, 0, 2) + TP_MAIN.Controls.Add(Me.SPLITTER_MAIN, 0, 1) + TP_MAIN.Controls.Add(TP_SETTINGS, 0, 0) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Margin = New System.Windows.Forms.Padding(0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 3 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 84.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_MAIN.Size = New System.Drawing.Size(434, 261) + TP_MAIN.TabIndex = 0 + ' + 'TP_BUTTONS + ' + TP_BUTTONS.ColumnCount = 3 + TP_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100.0!)) + TP_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 100.0!)) + TP_BUTTONS.Controls.Add(Me.BTT_DOWN, 1, 0) + TP_BUTTONS.Controls.Add(Me.BTT_CANCEL, 2, 0) + TP_BUTTONS.Dock = System.Windows.Forms.DockStyle.Fill + TP_BUTTONS.Location = New System.Drawing.Point(0, 236) + TP_BUTTONS.Margin = New System.Windows.Forms.Padding(0) + TP_BUTTONS.Name = "TP_BUTTONS" + TP_BUTTONS.RowCount = 1 + TP_BUTTONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_BUTTONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_BUTTONS.Size = New System.Drawing.Size(434, 25) + TP_BUTTONS.TabIndex = 2 + ' + 'BTT_DOWN + ' + Me.BTT_DOWN.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_DOWN.Location = New System.Drawing.Point(236, 2) + Me.BTT_DOWN.Margin = New System.Windows.Forms.Padding(2) + Me.BTT_DOWN.Name = "BTT_DOWN" + Me.BTT_DOWN.Size = New System.Drawing.Size(96, 21) + Me.BTT_DOWN.TabIndex = 0 + Me.BTT_DOWN.Text = "Download" + Me.BTT_DOWN.UseVisualStyleBackColor = True + ' + 'BTT_CANCEL + ' + Me.BTT_CANCEL.DialogResult = System.Windows.Forms.DialogResult.Cancel + Me.BTT_CANCEL.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_CANCEL.Location = New System.Drawing.Point(336, 2) + Me.BTT_CANCEL.Margin = New System.Windows.Forms.Padding(2) + Me.BTT_CANCEL.Name = "BTT_CANCEL" + Me.BTT_CANCEL.Size = New System.Drawing.Size(96, 21) + Me.BTT_CANCEL.TabIndex = 1 + Me.BTT_CANCEL.Text = "Cancel" + Me.BTT_CANCEL.UseVisualStyleBackColor = True + ' + 'SPLITTER_MAIN + ' + Me.SPLITTER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + Me.SPLITTER_MAIN.Location = New System.Drawing.Point(3, 87) + Me.SPLITTER_MAIN.Name = "SPLITTER_MAIN" + ' + 'SPLITTER_MAIN.Panel1 + ' + Me.SPLITTER_MAIN.Panel1.Controls.Add(TP_PLS) + Me.SPLITTER_MAIN.Panel1MinSize = 110 + ' + 'SPLITTER_MAIN.Panel2 + ' + Me.SPLITTER_MAIN.Panel2.Controls.Add(TP_PLS_ITEMS) + Me.SPLITTER_MAIN.Size = New System.Drawing.Size(428, 146) + Me.SPLITTER_MAIN.SplitterDistance = 142 + Me.SPLITTER_MAIN.TabIndex = 0 + ' + 'TP_PLS + ' + TP_PLS.ColumnCount = 1 + TP_PLS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_PLS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_PLS.Controls.Add(Me.LIST_PLAYLISTS, 0, 0) + TP_PLS.Controls.Add(TP_PLS_BUTTONS, 0, 1) + TP_PLS.Dock = System.Windows.Forms.DockStyle.Fill + TP_PLS.Location = New System.Drawing.Point(0, 0) + TP_PLS.Margin = New System.Windows.Forms.Padding(0) + TP_PLS.Name = "TP_PLS" + TP_PLS.RowCount = 2 + TP_PLS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_PLS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_PLS.Size = New System.Drawing.Size(142, 146) + TP_PLS.TabIndex = 0 + ' + 'LIST_PLAYLISTS + ' + Me.LIST_PLAYLISTS.Dock = System.Windows.Forms.DockStyle.Fill + Me.LIST_PLAYLISTS.FormattingEnabled = True + Me.LIST_PLAYLISTS.Location = New System.Drawing.Point(3, 3) + Me.LIST_PLAYLISTS.Name = "LIST_PLAYLISTS" + Me.LIST_PLAYLISTS.Size = New System.Drawing.Size(136, 115) + Me.LIST_PLAYLISTS.TabIndex = 0 + Me.LIST_PLAYLISTS.ThreeDCheckBoxes = True + ' + 'TP_PLS_BUTTONS + ' + TP_PLS_BUTTONS.ColumnCount = 3 + TP_PLS_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50.0!)) + TP_PLS_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50.0!)) + TP_PLS_BUTTONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_PLS_BUTTONS.Controls.Add(Me.BTT_PLS_ALL, 0, 0) + TP_PLS_BUTTONS.Controls.Add(Me.BTT_PLS_NONE, 1, 0) + TP_PLS_BUTTONS.Dock = System.Windows.Forms.DockStyle.Fill + TP_PLS_BUTTONS.Location = New System.Drawing.Point(0, 121) + TP_PLS_BUTTONS.Margin = New System.Windows.Forms.Padding(0) + TP_PLS_BUTTONS.Name = "TP_PLS_BUTTONS" + TP_PLS_BUTTONS.RowCount = 1 + TP_PLS_BUTTONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_PLS_BUTTONS.Size = New System.Drawing.Size(142, 25) + TP_PLS_BUTTONS.TabIndex = 1 + ' + 'BTT_PLS_ALL + ' + Me.BTT_PLS_ALL.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_PLS_ALL.Location = New System.Drawing.Point(2, 2) + Me.BTT_PLS_ALL.Margin = New System.Windows.Forms.Padding(2) + Me.BTT_PLS_ALL.Name = "BTT_PLS_ALL" + Me.BTT_PLS_ALL.Size = New System.Drawing.Size(46, 21) + Me.BTT_PLS_ALL.TabIndex = 0 + Me.BTT_PLS_ALL.Tag = "a" + Me.BTT_PLS_ALL.Text = "All" + TT_MAIN.SetToolTip(Me.BTT_PLS_ALL, "Select all") + Me.BTT_PLS_ALL.UseVisualStyleBackColor = True + ' + 'BTT_PLS_NONE + ' + Me.BTT_PLS_NONE.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_PLS_NONE.Location = New System.Drawing.Point(52, 2) + Me.BTT_PLS_NONE.Margin = New System.Windows.Forms.Padding(2) + Me.BTT_PLS_NONE.Name = "BTT_PLS_NONE" + Me.BTT_PLS_NONE.Size = New System.Drawing.Size(46, 21) + Me.BTT_PLS_NONE.TabIndex = 1 + Me.BTT_PLS_NONE.Tag = "n" + Me.BTT_PLS_NONE.Text = "None" + TT_MAIN.SetToolTip(Me.BTT_PLS_NONE, "Select none") + Me.BTT_PLS_NONE.UseVisualStyleBackColor = True + ' + 'TP_PLS_ITEMS + ' + TP_PLS_ITEMS.ColumnCount = 1 + TP_PLS_ITEMS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_PLS_ITEMS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_PLS_ITEMS.Controls.Add(Me.LIST_ITEMS, 0, 0) + TP_PLS_ITEMS.Dock = System.Windows.Forms.DockStyle.Fill + TP_PLS_ITEMS.Location = New System.Drawing.Point(0, 0) + TP_PLS_ITEMS.Margin = New System.Windows.Forms.Padding(0) + TP_PLS_ITEMS.Name = "TP_PLS_ITEMS" + TP_PLS_ITEMS.RowCount = 2 + TP_PLS_ITEMS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_PLS_ITEMS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_PLS_ITEMS.Size = New System.Drawing.Size(282, 146) + TP_PLS_ITEMS.TabIndex = 1 + ' + 'LIST_ITEMS + ' + Me.LIST_ITEMS.Dock = System.Windows.Forms.DockStyle.Fill + Me.LIST_ITEMS.FormattingEnabled = True + Me.LIST_ITEMS.Location = New System.Drawing.Point(3, 3) + Me.LIST_ITEMS.Name = "LIST_ITEMS" + Me.LIST_ITEMS.Size = New System.Drawing.Size(276, 115) + Me.LIST_ITEMS.TabIndex = 0 + ' + 'TP_SETTINGS + ' + TP_SETTINGS.ColumnCount = 1 + TP_SETTINGS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_SETTINGS.Controls.Add(TP_FORMATS, 0, 1) + TP_SETTINGS.Controls.Add(TP_LYRICS, 0, 0) + TP_SETTINGS.Controls.Add(Me.TXT_OUTPUT_PATH, 0, 2) + TP_SETTINGS.Dock = System.Windows.Forms.DockStyle.Fill + TP_SETTINGS.Location = New System.Drawing.Point(0, 0) + TP_SETTINGS.Margin = New System.Windows.Forms.Padding(0) + TP_SETTINGS.Name = "TP_SETTINGS" + TP_SETTINGS.RowCount = 3 + TP_SETTINGS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333!)) + TP_SETTINGS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333!)) + TP_SETTINGS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 33.33333!)) + TP_SETTINGS.Size = New System.Drawing.Size(434, 84) + TP_SETTINGS.TabIndex = 1 + ' + 'TP_FORMATS + ' + TP_FORMATS.ColumnCount = 3 + TP_FORMATS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 50.0!)) + TP_FORMATS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 65.0!)) + TP_FORMATS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_FORMATS.Controls.Add(Me.TXT_FORMATS_ADDIT, 2, 0) + TP_FORMATS.Controls.Add(LBL_FORMAT, 0, 0) + TP_FORMATS.Controls.Add(Me.CMB_FORMATS, 1, 0) + TP_FORMATS.Dock = System.Windows.Forms.DockStyle.Fill + TP_FORMATS.Location = New System.Drawing.Point(0, 28) + TP_FORMATS.Margin = New System.Windows.Forms.Padding(0) + TP_FORMATS.Name = "TP_FORMATS" + TP_FORMATS.RowCount = 1 + TP_FORMATS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_FORMATS.Size = New System.Drawing.Size(434, 28) + TP_FORMATS.TabIndex = 1 + ' + 'TXT_FORMATS_ADDIT + ' + ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image) + ActionButton1.Enabled = False + ActionButton1.Name = "Open" + ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open + ActionButton2.BackgroundImage = CType(resources.GetObject("ActionButton2.BackgroundImage"), System.Drawing.Image) + ActionButton2.Enabled = False + ActionButton2.Name = "Refresh" + ActionButton2.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Refresh + ActionButton3.BackgroundImage = CType(resources.GetObject("ActionButton3.BackgroundImage"), System.Drawing.Image) + ActionButton3.Enabled = False + ActionButton3.Name = "Clear" + ActionButton3.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + Me.TXT_FORMATS_ADDIT.Buttons.Add(ActionButton1) + Me.TXT_FORMATS_ADDIT.Buttons.Add(ActionButton2) + Me.TXT_FORMATS_ADDIT.Buttons.Add(ActionButton3) + Me.TXT_FORMATS_ADDIT.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.CheckBox + Me.TXT_FORMATS_ADDIT.CaptionText = "Additional formats" + Me.TXT_FORMATS_ADDIT.CaptionToolTipEnabled = True + Me.TXT_FORMATS_ADDIT.CaptionToolTipText = "Convert every downloaded track to the formats you choose." + Me.TXT_FORMATS_ADDIT.CaptionWidth = 115.0R + Me.TXT_FORMATS_ADDIT.ClearTextByButtonClear = False + Me.TXT_FORMATS_ADDIT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_FORMATS_ADDIT.Location = New System.Drawing.Point(118, 3) + Me.TXT_FORMATS_ADDIT.Name = "TXT_FORMATS_ADDIT" + Me.TXT_FORMATS_ADDIT.Size = New System.Drawing.Size(313, 22) + Me.TXT_FORMATS_ADDIT.TabIndex = 2 + Me.TXT_FORMATS_ADDIT.Tag = "a" + Me.TXT_FORMATS_ADDIT.TextBoxReadOnly = True + ' + 'LBL_FORMAT + ' + LBL_FORMAT.Dock = System.Windows.Forms.DockStyle.Fill + LBL_FORMAT.Location = New System.Drawing.Point(3, 0) + LBL_FORMAT.Margin = New System.Windows.Forms.Padding(3, 0, 0, 0) + LBL_FORMAT.Name = "LBL_FORMAT" + LBL_FORMAT.Size = New System.Drawing.Size(47, 28) + LBL_FORMAT.TabIndex = 0 + LBL_FORMAT.Text = "Format:" + LBL_FORMAT.TextAlign = System.Drawing.ContentAlignment.MiddleRight + TT_MAIN.SetToolTip(LBL_FORMAT, "Output files format") + ' + 'CMB_FORMATS + ' + Me.CMB_FORMATS.Dock = System.Windows.Forms.DockStyle.Fill + Me.CMB_FORMATS.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList + Me.CMB_FORMATS.FormattingEnabled = True + Me.CMB_FORMATS.Location = New System.Drawing.Point(53, 3) + Me.CMB_FORMATS.Name = "CMB_FORMATS" + Me.CMB_FORMATS.Size = New System.Drawing.Size(59, 21) + Me.CMB_FORMATS.TabIndex = 1 + ' + 'TP_LYRICS + ' + TP_LYRICS.ColumnCount = 2 + TP_LYRICS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 115.0!)) + TP_LYRICS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_LYRICS.Controls.Add(Me.TXT_SUBS, 1, 0) + TP_LYRICS.Controls.Add(Me.CH_DOWN_LYRICS, 0, 0) + TP_LYRICS.Dock = System.Windows.Forms.DockStyle.Fill + TP_LYRICS.Location = New System.Drawing.Point(0, 0) + TP_LYRICS.Margin = New System.Windows.Forms.Padding(0) + TP_LYRICS.Name = "TP_LYRICS" + TP_LYRICS.RowCount = 1 + TP_LYRICS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_LYRICS.Size = New System.Drawing.Size(434, 28) + TP_LYRICS.TabIndex = 0 + ' + 'TXT_SUBS + ' + ActionButton4.BackgroundImage = CType(resources.GetObject("ActionButton4.BackgroundImage"), System.Drawing.Image) + ActionButton4.Enabled = False + ActionButton4.Name = "Open" + ActionButton4.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open + ActionButton5.BackgroundImage = CType(resources.GetObject("ActionButton5.BackgroundImage"), System.Drawing.Image) + ActionButton5.Enabled = False + ActionButton5.Name = "Refresh" + ActionButton5.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Refresh + ActionButton6.BackgroundImage = CType(resources.GetObject("ActionButton6.BackgroundImage"), System.Drawing.Image) + ActionButton6.Enabled = False + ActionButton6.Name = "Clear" + ActionButton6.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + Me.TXT_SUBS.Buttons.Add(ActionButton4) + Me.TXT_SUBS.Buttons.Add(ActionButton5) + Me.TXT_SUBS.Buttons.Add(ActionButton6) + Me.TXT_SUBS.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.CheckBox + Me.TXT_SUBS.CaptionText = "Additional lyrics" + Me.TXT_SUBS.CaptionToolTipEnabled = True + Me.TXT_SUBS.CaptionToolTipText = "Convert all downloaded lyrics to the formats you choose." + Me.TXT_SUBS.CaptionWidth = 115.0R + Me.TXT_SUBS.ClearTextByButtonClear = False + Me.TXT_SUBS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_SUBS.Location = New System.Drawing.Point(118, 3) + Me.TXT_SUBS.Name = "TXT_SUBS" + Me.TXT_SUBS.Size = New System.Drawing.Size(313, 22) + Me.TXT_SUBS.TabIndex = 1 + Me.TXT_SUBS.Tag = "s" + Me.TXT_SUBS.TextBoxReadOnly = True + ' + 'CH_DOWN_LYRICS + ' + Me.CH_DOWN_LYRICS.AutoSize = True + Me.CH_DOWN_LYRICS.CheckAlign = System.Drawing.ContentAlignment.MiddleRight + Me.CH_DOWN_LYRICS.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_DOWN_LYRICS.Location = New System.Drawing.Point(3, 3) + Me.CH_DOWN_LYRICS.Name = "CH_DOWN_LYRICS" + Me.CH_DOWN_LYRICS.Size = New System.Drawing.Size(109, 22) + Me.CH_DOWN_LYRICS.TabIndex = 0 + Me.CH_DOWN_LYRICS.Text = "Download lyrics" + Me.CH_DOWN_LYRICS.TextAlign = System.Drawing.ContentAlignment.MiddleRight + TT_MAIN.SetToolTip(Me.CH_DOWN_LYRICS, "Download lyrics if available") + Me.CH_DOWN_LYRICS.UseVisualStyleBackColor = True + ' + 'TXT_OUTPUT_PATH + ' + ActionButton7.BackgroundImage = CType(resources.GetObject("ActionButton7.BackgroundImage"), System.Drawing.Image) + ActionButton7.Name = "Open" + ActionButton7.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open + ActionButton8.BackgroundImage = CType(resources.GetObject("ActionButton8.BackgroundImage"), System.Drawing.Image) + ActionButton8.Name = "Clear" + ActionButton8.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + Me.TXT_OUTPUT_PATH.Buttons.Add(ActionButton7) + Me.TXT_OUTPUT_PATH.Buttons.Add(ActionButton8) + Me.TXT_OUTPUT_PATH.CaptionText = "Output path" + Me.TXT_OUTPUT_PATH.CaptionWidth = 112.0R + Me.TXT_OUTPUT_PATH.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_OUTPUT_PATH.Location = New System.Drawing.Point(3, 59) + Me.TXT_OUTPUT_PATH.Name = "TXT_OUTPUT_PATH" + Me.TXT_OUTPUT_PATH.Size = New System.Drawing.Size(428, 22) + Me.TXT_OUTPUT_PATH.TabIndex = 2 + ' + 'MusicPlaylistsForm + ' + Me.AcceptButton = Me.BTT_DOWN + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.CancelButton = Me.BTT_CANCEL + Me.ClientSize = New System.Drawing.Size(434, 261) + Me.Controls.Add(TP_MAIN) + Me.Icon = Global.SCrawler.My.Resources.SiteYouTube.YouTubeMusicIcon_32 + Me.KeyPreview = True + Me.MinimumSize = New System.Drawing.Size(450, 300) + Me.Name = "MusicPlaylistsForm" + Me.Text = "Albums" + TP_MAIN.ResumeLayout(False) + TP_BUTTONS.ResumeLayout(False) + Me.SPLITTER_MAIN.Panel1.ResumeLayout(False) + Me.SPLITTER_MAIN.Panel2.ResumeLayout(False) + CType(Me.SPLITTER_MAIN, System.ComponentModel.ISupportInitialize).EndInit() + Me.SPLITTER_MAIN.ResumeLayout(False) + TP_PLS.ResumeLayout(False) + TP_PLS_BUTTONS.ResumeLayout(False) + TP_PLS_ITEMS.ResumeLayout(False) + TP_SETTINGS.ResumeLayout(False) + TP_FORMATS.ResumeLayout(False) + CType(Me.TXT_FORMATS_ADDIT, System.ComponentModel.ISupportInitialize).EndInit() + TP_LYRICS.ResumeLayout(False) + TP_LYRICS.PerformLayout() + CType(Me.TXT_SUBS, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_OUTPUT_PATH, System.ComponentModel.ISupportInitialize).EndInit() + Me.ResumeLayout(False) + + End Sub + Private WithEvents BTT_DOWN As Button + Private WithEvents BTT_CANCEL As Button + Private WithEvents LIST_PLAYLISTS As CheckedListBox + Private WithEvents LIST_ITEMS As CheckedListBox + Private WithEvents TXT_SUBS As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents BTT_PLS_ALL As Button + Private WithEvents BTT_PLS_NONE As Button + Private WithEvents TXT_FORMATS_ADDIT As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents CMB_FORMATS As ComboBox + Private WithEvents SPLITTER_MAIN As SplitContainer + Private WithEvents CH_DOWN_LYRICS As CheckBox + Private WithEvents TXT_OUTPUT_PATH As PersonalUtilities.Forms.Controls.TextBoxExtended + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/MusicPlaylistsForm.resx b/SCrawler.YouTube/Controls/MusicPlaylistsForm.resx new file mode 100644 index 0000000..0a4b79a --- /dev/null +++ b/SCrawler.YouTube/Controls/MusicPlaylistsForm.resx @@ -0,0 +1,243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + + 17, 17 + + + False + + + False + + + False + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP + WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP + aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+ + 5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8 + vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB + cMaRN0UdBBkAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6 + JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOElE + QVQ4T2P4//8/QczOJyyqHpzfiE0OQwAZC8iqszAzs7CJ69o4BR768V/W2jcGXQ0KB4aFNS3dDQtnrbCb + ePCK48wTN1wXXXzge/jXf/clV55zC4hIIatF0cjIyMikElzc57z0wX+XHd/+2+//99/ywP//xlu//tdb + +eK/4Zp3/1WTOhYzARViNUAluKjTdf37/0ZTTn9TbdhwXblhwwW1/qOP1Ja9+K8w+95/6cm3/6v2Xvkv + qKjniGGAoIqRpW3/4e8S9uGdzFz82gwMDFxAzCxm4ZegtuLDf+VJ1/8rZM25IqLvnM/CximCYYCic1QN + v7x2JIwPwyrJ3XNUylddE9G2TWNmZOBDl4czmJiZMSRBmFdSyYyJgUEQmxwIYxWEYXZBCUls4sgYq6CA + prWNbtG8nXKeaVPR5XiVjSxEzf0yYXy4BBMLO6eQjoOXZvrkbbazrv53Xf/2v4CSbjBMXkhBl1/CMyNZ + qWnvGy5pNQ+YONwAfjXzAOupl/47LLr333L50/96q9/8l23YdES6cO5KuYqVW+R7Tj6SnfP0v4hryjyY + HhQDmFjYeHVKFp7WX/Xuv9Kq9/+Vd/z7r7rv/3+l7f//y676DEwDN/9L+BVvYkKLCTgDhNkkVUyVlr74 + qbbz73/VOTc/qsy89kWx+9h7qbQpJwS1bbOAscGGrB6EUTggLOqf16C55ft/HlnNAFZOXgVWdi4FRgYG + VnR1MIwhwMTCyqEQ37qEmZVDFF0OE/9nAACtFF4Ey6OP+wAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + False + + + False + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP + WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP + aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+ + 5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8 + vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB + cMaRN0UdBBkAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6 + JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOElE + QVQ4T2P4//8/QczOJyyqHpzfiE0OQwAZC8iqszAzs7CJ69o4BR768V/W2jcGXQ0KB4aFNS3dDQtnrbCb + ePCK48wTN1wXXXzge/jXf/clV55zC4hIIatF0cjIyMikElzc57z0wX+XHd/+2+//99/ywP//xlu//tdb + +eK/4Zp3/1WTOhYzARViNUAluKjTdf37/0ZTTn9TbdhwXblhwwW1/qOP1Ja9+K8w+95/6cm3/6v2Xvkv + qKjniGGAoIqRpW3/4e8S9uGdzFz82gwMDFxAzCxm4ZegtuLDf+VJ1/8rZM25IqLvnM/CximCYYCic1QN + v7x2JIwPwyrJ3XNUylddE9G2TWNmZOBDl4czmJiZMSRBmFdSyYyJgUEQmxwIYxWEYXZBCUls4sgYq6CA + prWNbtG8nXKeaVPR5XiVjSxEzf0yYXy4BBMLO6eQjoOXZvrkbbazrv53Xf/2v4CSbjBMXkhBl1/CMyNZ + qWnvGy5pNQ+YONwAfjXzAOupl/47LLr333L50/96q9/8l23YdES6cO5KuYqVW+R7Tj6SnfP0v4hryjyY + HhQDmFjYeHVKFp7WX/Xuv9Kq9/+Vd/z7r7rv/3+l7f//y676DEwDN/9L+BVvYkKLCTgDhNkkVUyVlr74 + qbbz73/VOTc/qsy89kWx+9h7qbQpJwS1bbOAscGGrB6EUTggLOqf16C55ft/HlnNAFZOXgVWdi4FRgYG + VnR1MIwhwMTCyqEQ37qEmZVDFF0OE/9nAACtFF4Ey6OP+wAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP + WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP + aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+ + 5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8 + vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB + cMaRN0UdBBkAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/MusicPlaylistsForm.vb b/SCrawler.YouTube/Controls/MusicPlaylistsForm.vb new file mode 100644 index 0000000..9247bca --- /dev/null +++ b/SCrawler.YouTube/Controls/MusicPlaylistsForm.vb @@ -0,0 +1,262 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.ComponentModel +Imports SCrawler.API.YouTube.Objects +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Controls +Imports PersonalUtilities.Forms.Controls.Base +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Imports ADB = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons +Namespace API.YouTube.Controls + Friend Class MusicPlaylistsForm : Implements IDesignXMLContainer +#Region "Declarations" + Private MyView As FormView + Friend Property DesignXML As EContainer Implements IDesignXMLContainer.DesignXML + Private Property DesignXMLNodes As String() Implements IDesignXMLContainer.DesignXMLNodes + Private Property DesignXMLNodeName As String Implements IDesignXMLContainer.DesignXMLNodeName + Private ReadOnly MyContainer As IYouTubeMediaContainer + Private Initializing As Boolean = True + Private ReadOnly Property Current As IYouTubeMediaContainer + Get + With MyContainer + If .ObjectType = Base.YouTubeMediaType.Channel Then + If _LatestSelected.ValueBetween(0, .Count - 1) Then Return .Elements(_LatestSelected) + Else + Return .Self + End If + End With + Return Nothing + End Get + End Property +#End Region +#Region "Initializer" + Friend Sub New(ByVal Container As IYouTubeMediaContainer) + InitializeComponent() + MyContainer = Container + End Sub +#End Region +#Region "Form handlers" + Private Sub MusicPlaylistsForm_Load(sender As Object, e As EventArgs) Handles Me.Load + If Not DesignXML Is Nothing Then + MyView = New FormView(Me) + MyView.Import() + MyView.SetFormSize() + End If + + CMB_FORMATS.Items.AddRange(AvailableAudioFormats) + If MyYouTubeSettings.PlaylistFormSplitterDistance > 0 Then SPLITTER_MAIN.SplitterDistancePercentageSet(MyYouTubeSettings.PlaylistFormSplitterDistance) + + With MyContainer + CH_DOWN_LYRICS.Checked = Not .OutputSubtitlesFormat.IsEmptyString + Dim i% + If Not .OutputAudioCodec.IsEmptyString Then + i = AvailableAudioFormats.ListIndexOf(Function(ff) ff.StringToLower = .OutputAudioCodec.StringToLower) + If i >= 0 Then CMB_FORMATS.SelectedIndex = i + End If + If CMB_FORMATS.SelectedIndex = -1 Then + Dim oac$ = MyYouTubeSettings.DefaultAudioCodecMusic.Value.IfNullOrEmpty("mp3").StringToLower + i = AvailableAudioFormats.ListIndexOf(Function(ff) ff.StringToLower = oac) + If i >= 0 Then CMB_FORMATS.SelectedIndex = i Else CMB_FORMATS.SelectedIndex = 0 + End If + + If .ObjectType = Base.YouTubeMediaType.Channel Then + If .HasElements Then + For Each elem In .Elements : LIST_PLAYLISTS.Items.Add(elem, elem.CheckState) : Next + End If + ElseIf .ObjectType = Base.YouTubeMediaType.PlayList Then + LIST_PLAYLISTS.Items.Add(.Self, .CheckState) + Else + Throw New InvalidOperationException($"The object type '{ .ObjectType}' is incompatible with 'MusicPlaylistsForm'.") + End If + LIST_PLAYLISTS.SelectedIndex = 0 + + TXT_OUTPUT_PATH.Text = MyYouTubeSettings.OutputPath.Value + + If Not .UserTitle.IsEmptyString Then + Text = .UserTitle + ElseIf Not .PlaylistTitle.IsEmptyString Then + Text = .PlaylistTitle + End If + + UpdateSizeText() + End With + RefillAddit() + Initializing = False + End Sub + Private Sub MusicPlaylistsForm_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing + MyYouTubeSettings.PlaylistFormSplitterDistance.Value = SPLITTER_MAIN.SplitterDistancePercentageGet + MyView.DisposeIfReady() + End Sub +#End Region +#Region "Form text" + Private _InitialFormText As String = String.Empty + Private Sub UpdateSizeText() + If _InitialFormText.IsEmptyString Then _InitialFormText = Text + Text = $"{_InitialFormText} ({MyContainer.SizeStr})" + End Sub +#End Region +#Region "Settings" + Private Sub TXT_SUBS_ActionOnButtonClick(ByVal Sender As ActionButton, ByVal e As ActionButtonEventArgs) Handles TXT_SUBS.ActionOnButtonClick, TXT_FORMATS_ADDIT.ActionOnButtonClick + Dim isLyrics As Boolean = DirectCast(e.AssociatedControl, Control).Tag = "s" + With DirectCast(MyContainer, YouTubeMediaContainerBase) + Select Case Sender.DefaultButton + Case ADB.Open + Using f As New SimpleListForm(Of String)(IIf(isLyrics, AvailableSubtitlesFormats, AvailableAudioFormats)) With { + .DesignXML = DesignXML, + .DesignXMLNodeName = SimpleArraysFormNode, + .FormText = DirectCast(e.AssociatedControl, TextBoxExtended).CaptionText, + .Icon = My.Resources.SiteYouTube.YouTubeMusicIcon_32, + .Mode = SimpleListFormModes.CheckedItems + } + f.DataSelected.ListAddList(IIf(isLyrics, .PostProcessing_OutputSubtitlesFormats, .PostProcessing_OutputAudioFormats)) + If f.ShowDialog = DialogResult.OK Then + If isLyrics Then + .PostProcessing_OutputSubtitlesFormats.Clear() + .PostProcessing_OutputSubtitlesFormats.ListAddList(f.DataResult) + Else + .PostProcessing_OutputAudioFormats.Clear() + .PostProcessing_OutputAudioFormats.ListAddList(f.DataResult) + End If + RefillAddit() + End If + End Using + Case ADB.Refresh + If isLyrics Then + .PostProcessing_OutputSubtitlesFormats_Reset() + Else + .PostProcessing_OutputAudioFormats_Reset() + End If + RefillAddit() + Case ADB.Clear + If isLyrics Then + .PostProcessing_OutputSubtitlesFormats.Clear() + Else + .PostProcessing_OutputAudioFormats.Clear() + End If + RefillAddit() + End Select + End With + End Sub + Private Sub RefillAddit() + With DirectCast(MyContainer, YouTubeMediaContainerBase) + TXT_SUBS.Text = .PostProcessing_OutputSubtitlesFormats.ListToString + TXT_FORMATS_ADDIT.Text = .PostProcessing_OutputAudioFormats.ListToString + End With + End Sub + Private Sub TXT_OUTPUT_PATH_ActionOnButtonClick(ByVal Sender As ActionButton, ByVal e As ActionButtonEventArgs) Handles TXT_OUTPUT_PATH.ActionOnButtonClick + If Sender.DefaultButton = ADB.Open Then + Dim f As SFile = SFile.SelectPath(TXT_OUTPUT_PATH.Text, "Select files destination", EDP.ReturnValue) + If Not f.IsEmptyString Then TXT_OUTPUT_PATH.Text = f + End If + End Sub +#End Region +#Region "Lists' handlers" + Private _LatestSelected As Integer = -1 + Private Sub LIST_PLAYLISTS_SelectedIndexChanged(sender As Object, e As EventArgs) Handles LIST_PLAYLISTS.SelectedIndexChanged + Dim i% = LIST_PLAYLISTS.SelectedIndex + If i >= 0 Then + _LatestSelected = i + LIST_ITEMS.Items.Clear() + With DirectCast(LIST_PLAYLISTS.SelectedItem, IYouTubeMediaContainer) + If .HasElements Then + For Each elem In .Elements : LIST_ITEMS.Items.Add(elem, elem.Checked) : Next + End If + End With + End If + End Sub + Private _CheckHandlersSuspended As Boolean = False + Private Sub LIST_PLAYLISTS_ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles LIST_PLAYLISTS.ItemCheck + If Not Initializing And Not _CheckHandlersSuspended Then + _CheckHandlersSuspended = True + + Dim checked As Boolean = Not e.NewValue = CheckState.Unchecked + + Dim current As IYouTubeMediaContainer = Me.Current + If Not current Is Nothing Then + With current + .Checked = checked + If LIST_ITEMS.Items.Count > 0 Then + _ListCheckHandlersSuspended = True + For i% = 0 To .Count - 1 + If i.ValueBetween(0, LIST_ITEMS.Items.Count - 1) Then LIST_ITEMS.SetItemChecked(i, checked) + Next + _ListCheckHandlersSuspended = False + End If + If LIST_PLAYLISTS.Items.Count.ValueBetween(0, _LatestSelected) Then LIST_PLAYLISTS.SetItemChecked(_LatestSelected, checked) + End With + UpdateSizeText() + End If + + _CheckHandlersSuspended = False + End If + End Sub + Private _ListCheckHandlersSuspended As Boolean = False + Private Sub LIST_ITEMS_ItemCheck(sender As Object, e As ItemCheckEventArgs) Handles LIST_ITEMS.ItemCheck + If Not Initializing Then + Dim current As IYouTubeMediaContainer = Me.Current + If Not current Is Nothing Then + With current + If e.Index.ValueBetween(0, .Count - 1) Then + .Elements(e.Index).Checked = e.NewValue + If Not _ListCheckHandlersSuspended And _LatestSelected.ValueBetween(0, LIST_PLAYLISTS.Items.Count - 1) Then + Dim checked As Boolean = .Elements(e.Index).Checked + _CheckHandlersSuspended = True + If .Elements.All(Function(ee) ee.Checked = checked) Then + LIST_PLAYLISTS.SetItemChecked(_LatestSelected, checked) + Else + LIST_PLAYLISTS.SetItemCheckState(_LatestSelected, CheckState.Indeterminate) + End If + _CheckHandlersSuspended = False + LIST_PLAYLISTS.Refresh() + UpdateSizeText() + End If + End If + End With + End If + End If + End Sub +#End Region +#Region "Selection buttons" + Private Sub BTT_PLS_Click(sender As Object, e As EventArgs) Handles BTT_PLS_ALL.Click, BTT_PLS_NONE.Click + Dim checked As Boolean = DirectCast(sender, Button).Tag = "a" + _CheckHandlersSuspended = True + If LIST_PLAYLISTS.Items.Count > 0 Then + For i% = 0 To LIST_PLAYLISTS.Items.Count - 1 : LIST_PLAYLISTS.SetItemChecked(i, checked) : Next + End If + MyContainer.Checked = checked + If MyContainer.Count > 1 Then MyContainer.Elements.ForEach(Sub(ee) ee.Checked = checked) + _CheckHandlersSuspended = False + UpdateSizeText() + End Sub +#End Region +#Region "Ok, Cancel" + Private Sub BTT_DOWN_Click(sender As Object, e As EventArgs) Handles BTT_DOWN.Click + If TXT_OUTPUT_PATH.IsEmptyString Then + MsgBoxE({"The output path cannot be null.", "Download music"}, vbCritical) + Else + With DirectCast(MyContainer, YouTubeMediaContainerBase) + .OutputSubtitlesFormat = IIf(CH_DOWN_LYRICS.Checked, "LRC", String.Empty) + If Not TXT_SUBS.Checked Then .PostProcessing_OutputSubtitlesFormats.Clear() + .OutputAudioCodec = CMB_FORMATS.Text + If Not TXT_FORMATS_ADDIT.Checked Then .PostProcessing_OutputAudioFormats.Clear() + .File = TXT_OUTPUT_PATH.Text.CSFileP + If MyYouTubeSettings.OutputPathAutoChange Then MyYouTubeSettings.OutputPath.Value = .File + End With + DialogResult = DialogResult.OK + Close() + End If + End Sub + Private Sub BTT_CANCEL_Click(sender As Object, e As EventArgs) Handles BTT_CANCEL.Click + DialogResult = DialogResult.Cancel + Close() + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/ParsingProgressForm.Designer.vb b/SCrawler.YouTube/Controls/ParsingProgressForm.Designer.vb new file mode 100644 index 0000000..96ab149 --- /dev/null +++ b/SCrawler.YouTube/Controls/ParsingProgressForm.Designer.vb @@ -0,0 +1,94 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Public Class ParsingProgressForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Me.PR_MAIN = New System.Windows.Forms.ProgressBar() + Me.LBL_MAIN = New System.Windows.Forms.Label() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_MAIN.SuspendLayout() + Me.SuspendLayout() + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Controls.Add(Me.PR_MAIN, 0, 0) + TP_MAIN.Controls.Add(Me.LBL_MAIN, 0, 1) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Margin = New System.Windows.Forms.Padding(0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 2 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_MAIN.Size = New System.Drawing.Size(334, 56) + TP_MAIN.TabIndex = 0 + ' + 'PR_MAIN + ' + Me.PR_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + Me.PR_MAIN.Location = New System.Drawing.Point(3, 3) + Me.PR_MAIN.Name = "PR_MAIN" + Me.PR_MAIN.Size = New System.Drawing.Size(328, 25) + Me.PR_MAIN.TabIndex = 0 + ' + 'LBL_MAIN + ' + Me.LBL_MAIN.AutoSize = True + Me.LBL_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_MAIN.Location = New System.Drawing.Point(3, 31) + Me.LBL_MAIN.Name = "LBL_MAIN" + Me.LBL_MAIN.Size = New System.Drawing.Size(328, 25) + Me.LBL_MAIN.TabIndex = 1 + Me.LBL_MAIN.TextAlign = System.Drawing.ContentAlignment.TopCenter + ' + 'ParsingProgressForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(334, 56) + Me.ControlBox = False + Me.Controls.Add(TP_MAIN) + Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow + Me.KeyPreview = True + Me.MaximizeBox = False + Me.MaximumSize = New System.Drawing.Size(350, 95) + Me.MinimizeBox = False + Me.MinimumSize = New System.Drawing.Size(350, 95) + Me.Name = "ParsingProgressForm" + Me.ShowIcon = False + Me.ShowInTaskbar = False + Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide + Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent + Me.Text = "Parsing progress" + Me.TopMost = True + TP_MAIN.ResumeLayout(False) + TP_MAIN.PerformLayout() + Me.ResumeLayout(False) + + End Sub + Private WithEvents PR_MAIN As ProgressBar + Private WithEvents LBL_MAIN As Label + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/PornHub/OptionsForm.resx b/SCrawler.YouTube/Controls/ParsingProgressForm.resx similarity index 96% rename from SCrawler/API/PornHub/OptionsForm.resx rename to SCrawler.YouTube/Controls/ParsingProgressForm.resx index be8e932..e5f5c30 100644 --- a/SCrawler/API/PornHub/OptionsForm.resx +++ b/SCrawler.YouTube/Controls/ParsingProgressForm.resx @@ -117,9 +117,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - False - False diff --git a/SCrawler.YouTube/Controls/ParsingProgressForm.vb b/SCrawler.YouTube/Controls/ParsingProgressForm.vb new file mode 100644 index 0000000..d9e249d --- /dev/null +++ b/SCrawler.YouTube/Controls/ParsingProgressForm.vb @@ -0,0 +1,48 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Threading +Imports PersonalUtilities.Forms.Toolbars +Namespace API.YouTube.Controls + Public Class ParsingProgressForm + Public ReadOnly Property MyProgress As MyProgress + Private ReadOnly TokenSource As CancellationTokenSource + Public ReadOnly Property Token As CancellationToken + Get + Return TokenSource.Token + End Get + End Property + Public Sub New() + InitializeComponent() + MyProgress = New MyProgress(PR_MAIN, LBL_MAIN, "Data parsing in progress") With {.ResetProgressOnMaximumChanges = False} + TokenSource = New CancellationTokenSource + End Sub + Public Sub SetInitialValues(ByVal Count As Integer, ByVal Info As String) + With MyProgress + .Maximum = Count + .Visible = True + .Perform(0.5) + .Value = 0 + .InformationTemporary = Info + End With + End Sub + Private _KeyDownDisabled As Boolean = False + Private Sub ParsingProgressForm_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown + If e.KeyCode = Keys.Escape AndAlso Not _KeyDownDisabled AndAlso MsgBoxE({"Data parsing in progress." & vbCr & + "Are you sure you want to stop parsing and cancel the operation?", + "Stop parsing"}, vbExclamation + vbYesNo) = vbYes Then + _KeyDownDisabled = True + TokenSource.Cancel() + End If + End Sub + Private Sub ParsingProgressForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + MyProgress.Dispose() + TokenSource.Dispose() + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/PlayListParserForm.Designer.vb b/SCrawler.YouTube/Controls/PlayListParserForm.Designer.vb new file mode 100644 index 0000000..1d6483e --- /dev/null +++ b/SCrawler.YouTube/Controls/PlayListParserForm.Designer.vb @@ -0,0 +1,171 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Friend Class PlayListParserForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Me.components = New System.ComponentModel.Container() + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim FRM_IN As System.Windows.Forms.GroupBox + Dim FRM_OUT As System.Windows.Forms.GroupBox + Dim ActionButton1 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(PlayListParserForm)) + Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer + Dim TT_MAIN As System.Windows.Forms.ToolTip + Me.TXT_IN = New System.Windows.Forms.RichTextBox() + Me.TXT_OUT = New System.Windows.Forms.RichTextBox() + Me.TXT_LIMIT = New PersonalUtilities.Forms.Controls.TextBoxExtended() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + FRM_IN = New System.Windows.Forms.GroupBox() + FRM_OUT = New System.Windows.Forms.GroupBox() + CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + TT_MAIN = New System.Windows.Forms.ToolTip(Me.components) + TP_MAIN.SuspendLayout() + FRM_IN.SuspendLayout() + FRM_OUT.SuspendLayout() + CType(Me.TXT_LIMIT, System.ComponentModel.ISupportInitialize).BeginInit() + CONTAINER_MAIN.ContentPanel.SuspendLayout() + CONTAINER_MAIN.SuspendLayout() + Me.SuspendLayout() + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Controls.Add(FRM_IN, 0, 1) + TP_MAIN.Controls.Add(FRM_OUT, 0, 2) + TP_MAIN.Controls.Add(Me.TXT_LIMIT, 0, 0) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Margin = New System.Windows.Forms.Padding(0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 3 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_MAIN.Size = New System.Drawing.Size(384, 261) + TP_MAIN.TabIndex = 0 + ' + 'FRM_IN + ' + FRM_IN.Controls.Add(Me.TXT_IN) + FRM_IN.Dock = System.Windows.Forms.DockStyle.Fill + FRM_IN.Location = New System.Drawing.Point(3, 31) + FRM_IN.Name = "FRM_IN" + FRM_IN.Size = New System.Drawing.Size(378, 110) + FRM_IN.TabIndex = 0 + FRM_IN.TabStop = False + FRM_IN.Text = "In" + TT_MAIN.SetToolTip(FRM_IN, "In your browser's DevTools, find the page starting with the following URL and cop" & + "y the response text into this window." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "https://music.youtube.com/youtubei/v1/bro" & + "wse?key=") + ' + 'TXT_IN + ' + Me.TXT_IN.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_IN.Location = New System.Drawing.Point(3, 16) + Me.TXT_IN.Name = "TXT_IN" + Me.TXT_IN.Size = New System.Drawing.Size(372, 91) + Me.TXT_IN.TabIndex = 0 + Me.TXT_IN.Text = "" + ' + 'FRM_OUT + ' + FRM_OUT.Controls.Add(Me.TXT_OUT) + FRM_OUT.Dock = System.Windows.Forms.DockStyle.Fill + FRM_OUT.Location = New System.Drawing.Point(3, 147) + FRM_OUT.Name = "FRM_OUT" + FRM_OUT.Size = New System.Drawing.Size(378, 111) + FRM_OUT.TabIndex = 1 + FRM_OUT.TabStop = False + FRM_OUT.Text = "Out" + ' + 'TXT_OUT + ' + Me.TXT_OUT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_OUT.Location = New System.Drawing.Point(3, 16) + Me.TXT_OUT.Name = "TXT_OUT" + Me.TXT_OUT.Size = New System.Drawing.Size(372, 92) + Me.TXT_OUT.TabIndex = 0 + Me.TXT_OUT.Text = "" + ' + 'TXT_LIMIT + ' + ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image) + ActionButton1.Name = "Clear" + ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + Me.TXT_LIMIT.Buttons.Add(ActionButton1) + Me.TXT_LIMIT.CaptionText = "Remove" + Me.TXT_LIMIT.CaptionToolTipEnabled = True + Me.TXT_LIMIT.CaptionToolTipText = "Remove playlists starts with..." + Me.TXT_LIMIT.CaptionWidth = 50.0R + Me.TXT_LIMIT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_LIMIT.Location = New System.Drawing.Point(3, 3) + Me.TXT_LIMIT.Name = "TXT_LIMIT" + Me.TXT_LIMIT.PlaceholderEnabled = True + Me.TXT_LIMIT.PlaceholderText = "e.g. ABCDE" + Me.TXT_LIMIT.Size = New System.Drawing.Size(378, 22) + Me.TXT_LIMIT.TabIndex = 2 + ' + 'CONTAINER_MAIN + ' + ' + 'CONTAINER_MAIN.ContentPanel + ' + CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) + CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(384, 261) + CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + CONTAINER_MAIN.LeftToolStripPanelVisible = False + CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) + CONTAINER_MAIN.Name = "CONTAINER_MAIN" + CONTAINER_MAIN.RightToolStripPanelVisible = False + CONTAINER_MAIN.Size = New System.Drawing.Size(384, 261) + CONTAINER_MAIN.TabIndex = 0 + CONTAINER_MAIN.TopToolStripPanelVisible = False + ' + 'PlayListParserForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(384, 261) + Me.Controls.Add(CONTAINER_MAIN) + Me.Icon = Global.SCrawler.My.Resources.SiteYouTube.YouTubeMusicIcon_32 + Me.KeyPreview = True + Me.MinimizeBox = False + Me.MinimumSize = New System.Drawing.Size(400, 300) + Me.Name = "PlayListParserForm" + Me.ShowInTaskbar = False + Me.Text = "Playlist parser" + TP_MAIN.ResumeLayout(False) + FRM_IN.ResumeLayout(False) + FRM_OUT.ResumeLayout(False) + CType(Me.TXT_LIMIT, System.ComponentModel.ISupportInitialize).EndInit() + CONTAINER_MAIN.ContentPanel.ResumeLayout(False) + CONTAINER_MAIN.ResumeLayout(False) + CONTAINER_MAIN.PerformLayout() + Me.ResumeLayout(False) + + End Sub + Private WithEvents TXT_IN As RichTextBox + Private WithEvents TXT_OUT As RichTextBox + Private WithEvents TXT_LIMIT As PersonalUtilities.Forms.Controls.TextBoxExtended + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/PlayListParserForm.resx b/SCrawler.YouTube/Controls/PlayListParserForm.resx new file mode 100644 index 0000000..bf08091 --- /dev/null +++ b/SCrawler.YouTube/Controls/PlayListParserForm.resx @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + 17, 17 + + + False + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + False + + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/PlayListParserForm.vb b/SCrawler.YouTube/Controls/PlayListParserForm.vb new file mode 100644 index 0000000..1d82e9e --- /dev/null +++ b/SCrawler.YouTube/Controls/PlayListParserForm.vb @@ -0,0 +1,60 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Imports PersonalUtilities.Functions.RegularExpressions +Namespace API.YouTube.Controls + Friend Class PlayListParserForm : Implements IDesignXMLContainer + Private WithEvents MyDefs As DefaultFormOptions + Friend Property DesignXML As EContainer Implements IDesignXMLContainer.DesignXML + Private Property DesignXMLNodes As String() Implements IDesignXMLContainer.DesignXMLNodes + Private Property DesignXMLNodeName As String Implements IDesignXMLContainer.DesignXMLNodeName + Friend ReadOnly Property PlayLists As List(Of String) + Get + If Not TXT_OUT.Text.IsEmptyString Then + Return TXT_OUT.Lines.ToList + Else + Return New List(Of String) + End If + End Get + End Property + Friend Sub New() + InitializeComponent() + MyDefs = New DefaultFormOptions(Me) + End Sub + Private Sub MyForm_Load(sender As Object, e As EventArgs) Handles Me.Load + With MyDefs + .MyViewInitialize() + .AddOkCancelToolbar() + .EndLoaderOperations() + End With + End Sub + Private Sub UpdatePlaylists(sender As Object, e As EventArgs) Handles TXT_IN.TextChanged, TXT_LIMIT.ActionOnTextChanged + Try + If Not TXT_IN.Text.IsEmptyString Then + Dim l As List(Of String) = ListAddList(Of String)(Nothing, RegexReplace(TXT_IN.Text, RParams.DMS("playlistId"": ""([^""]+)""", 1, + RegexReturn.List, EDP.ReturnValue)), + LAP.NotContainsOnly, EDP.ReturnValue) + If Not TXT_LIMIT.Text.IsEmptyString And l.ListExists Then l.RemoveAll(Function(id) id.StringToLower.StartsWith(TXT_LIMIT.Text.ToLower)) + If l.ListExists Then + TXT_OUT.Text = l.Select(Function(id) $"https://music.youtube.com/playlist?list={id}").ListToString(vbNewLine, EDP.ReturnValue) + Else + TXT_OUT.Text = String.Empty + End If + Else + TXT_OUT.Text = String.Empty + End If + Catch ex As Exception + TXT_OUT.Text = String.Empty + ErrorsDescriber.Execute(EDP.LogMessageValue, ex, "[PlayListParserForm.UpdatePlaylists]") + End Try + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/PornHub/OptionsForm.Designer.vb b/SCrawler.YouTube/Controls/PlaylistArrayForm.Designer.vb similarity index 55% rename from SCrawler/API/PornHub/OptionsForm.Designer.vb rename to SCrawler.YouTube/Controls/PlaylistArrayForm.Designer.vb index d1b0953..7bcfa45 100644 --- a/SCrawler/API/PornHub/OptionsForm.Designer.vb +++ b/SCrawler.YouTube/Controls/PlaylistArrayForm.Designer.vb @@ -6,9 +6,9 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY -Namespace API.PornHub +Namespace API.YouTube.Controls - Partial Friend Class OptionsForm : Inherits System.Windows.Forms.Form + Partial Friend Class PlaylistArrayForm : Inherits System.Windows.Forms.Form Protected Overrides Sub Dispose(ByVal disposing As Boolean) Try @@ -24,13 +24,16 @@ Namespace API.PornHub Private Sub InitializeComponent() Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel - Me.CH_DOWN_GIFS = New System.Windows.Forms.CheckBox() - Me.CH_DOWN_PHOTO_MODELHUB = New System.Windows.Forms.CheckBox() + Dim FRM_PLS As System.Windows.Forms.GroupBox + Me.CH_PLS_ONE = New System.Windows.Forms.CheckBox() + Me.TXT_URLS = New System.Windows.Forms.RichTextBox() CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + FRM_PLS = New System.Windows.Forms.GroupBox() CONTAINER_MAIN.ContentPanel.SuspendLayout() CONTAINER_MAIN.SuspendLayout() TP_MAIN.SuspendLayout() + FRM_PLS.SuspendLayout() Me.SuspendLayout() ' 'CONTAINER_MAIN @@ -39,80 +42,87 @@ Namespace API.PornHub 'CONTAINER_MAIN.ContentPanel ' CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) - CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(278, 52) + CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(384, 311) CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill CONTAINER_MAIN.LeftToolStripPanelVisible = False CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) CONTAINER_MAIN.Name = "CONTAINER_MAIN" CONTAINER_MAIN.RightToolStripPanelVisible = False - CONTAINER_MAIN.Size = New System.Drawing.Size(278, 77) + CONTAINER_MAIN.Size = New System.Drawing.Size(384, 311) CONTAINER_MAIN.TabIndex = 0 CONTAINER_MAIN.TopToolStripPanelVisible = False ' 'TP_MAIN ' - TP_MAIN.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] TP_MAIN.ColumnCount = 1 TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_MAIN.Controls.Add(Me.CH_DOWN_GIFS, 0, 0) - TP_MAIN.Controls.Add(Me.CH_DOWN_PHOTO_MODELHUB, 0, 1) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_MAIN.Controls.Add(Me.CH_PLS_ONE, 0, 0) + TP_MAIN.Controls.Add(FRM_PLS, 0, 1) TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill TP_MAIN.Location = New System.Drawing.Point(0, 0) TP_MAIN.Name = "TP_MAIN" - TP_MAIN.RowCount = 3 - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + TP_MAIN.RowCount = 2 TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_MAIN.Size = New System.Drawing.Size(278, 52) + TP_MAIN.Size = New System.Drawing.Size(384, 311) TP_MAIN.TabIndex = 0 ' - 'CH_DOWN_GIFS + 'CH_PLS_ONE ' - Me.CH_DOWN_GIFS.AutoSize = True - Me.CH_DOWN_GIFS.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_DOWN_GIFS.Location = New System.Drawing.Point(4, 4) - Me.CH_DOWN_GIFS.Name = "CH_DOWN_GIFS" - Me.CH_DOWN_GIFS.Size = New System.Drawing.Size(270, 19) - Me.CH_DOWN_GIFS.TabIndex = 0 - Me.CH_DOWN_GIFS.Text = "Download gifs" - Me.CH_DOWN_GIFS.UseVisualStyleBackColor = True + Me.CH_PLS_ONE.AutoSize = True + Me.CH_PLS_ONE.Checked = True + Me.CH_PLS_ONE.CheckState = System.Windows.Forms.CheckState.Checked + Me.CH_PLS_ONE.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_PLS_ONE.Location = New System.Drawing.Point(3, 3) + Me.CH_PLS_ONE.Name = "CH_PLS_ONE" + Me.CH_PLS_ONE.Size = New System.Drawing.Size(378, 19) + Me.CH_PLS_ONE.TabIndex = 1 + Me.CH_PLS_ONE.Text = "Playlists / Albums by one artist" + Me.CH_PLS_ONE.UseVisualStyleBackColor = True ' - 'CH_DOWN_PHOTO_MODELHUB + 'FRM_PLS ' - Me.CH_DOWN_PHOTO_MODELHUB.AutoSize = True - Me.CH_DOWN_PHOTO_MODELHUB.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_DOWN_PHOTO_MODELHUB.Location = New System.Drawing.Point(4, 30) - Me.CH_DOWN_PHOTO_MODELHUB.Name = "CH_DOWN_PHOTO_MODELHUB" - Me.CH_DOWN_PHOTO_MODELHUB.Size = New System.Drawing.Size(270, 19) - Me.CH_DOWN_PHOTO_MODELHUB.TabIndex = 1 - Me.CH_DOWN_PHOTO_MODELHUB.Text = "Download photo only from ModelHub" - Me.CH_DOWN_PHOTO_MODELHUB.UseVisualStyleBackColor = True + FRM_PLS.Controls.Add(Me.TXT_URLS) + FRM_PLS.Dock = System.Windows.Forms.DockStyle.Fill + FRM_PLS.Location = New System.Drawing.Point(3, 28) + FRM_PLS.Name = "FRM_PLS" + FRM_PLS.Size = New System.Drawing.Size(378, 280) + FRM_PLS.TabIndex = 0 + FRM_PLS.TabStop = False + FRM_PLS.Text = "URLs (new line as delimiter); Ctrl+O to parse playlists from response" ' - 'OptionsForm + 'TXT_URLS + ' + Me.TXT_URLS.DetectUrls = False + Me.TXT_URLS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_URLS.Location = New System.Drawing.Point(3, 16) + Me.TXT_URLS.Name = "TXT_URLS" + Me.TXT_URLS.Size = New System.Drawing.Size(372, 261) + Me.TXT_URLS.TabIndex = 0 + Me.TXT_URLS.Text = "" + ' + 'PlaylistArrayForm ' Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font - Me.ClientSize = New System.Drawing.Size(278, 77) + Me.ClientSize = New System.Drawing.Size(384, 311) Me.Controls.Add(CONTAINER_MAIN) - Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle - Me.Icon = Global.SCrawler.My.Resources.SiteResources.InstagramIcon_32 + Me.Icon = Global.SCrawler.My.Resources.SiteYouTube.YouTubeMusicIcon_32 Me.KeyPreview = True - Me.MaximizeBox = False - Me.MaximumSize = New System.Drawing.Size(294, 116) - Me.MinimizeBox = False - Me.MinimumSize = New System.Drawing.Size(294, 116) - Me.Name = "OptionsForm" - Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide - Me.Text = "Options" + Me.MinimumSize = New System.Drawing.Size(400, 350) + Me.Name = "PlaylistArrayForm" + Me.Text = "Playlists / Albums" CONTAINER_MAIN.ContentPanel.ResumeLayout(False) CONTAINER_MAIN.ResumeLayout(False) CONTAINER_MAIN.PerformLayout() TP_MAIN.ResumeLayout(False) TP_MAIN.PerformLayout() + FRM_PLS.ResumeLayout(False) Me.ResumeLayout(False) End Sub - Private WithEvents CH_DOWN_GIFS As CheckBox - Private WithEvents CH_DOWN_PHOTO_MODELHUB As CheckBox + Private WithEvents CH_PLS_ONE As CheckBox + Private WithEvents TXT_URLS As RichTextBox End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Instagram/OptionsForm.resx b/SCrawler.YouTube/Controls/PlaylistArrayForm.resx similarity index 97% rename from SCrawler/API/Instagram/OptionsForm.resx rename to SCrawler.YouTube/Controls/PlaylistArrayForm.resx index be8e932..58e44b8 100644 --- a/SCrawler/API/Instagram/OptionsForm.resx +++ b/SCrawler.YouTube/Controls/PlaylistArrayForm.resx @@ -123,4 +123,7 @@ False + + False + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/PlaylistArrayForm.vb b/SCrawler.YouTube/Controls/PlaylistArrayForm.vb new file mode 100644 index 0000000..0d137ed --- /dev/null +++ b/SCrawler.YouTube/Controls/PlaylistArrayForm.vb @@ -0,0 +1,54 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Namespace API.YouTube.Controls + Friend Class PlaylistArrayForm : Implements IDesignXMLContainer + Private WithEvents MyDefs As DefaultFormOptions + Friend Property DesignXML As EContainer Implements IDesignXMLContainer.DesignXML + Private Property DesignXMLNodes As String() Implements IDesignXMLContainer.DesignXMLNodes + Private Property DesignXMLNodeName As String Implements IDesignXMLContainer.DesignXMLNodeName + Friend ReadOnly Property URLs As List(Of String) + Get + If Not TXT_URLS.Text.IsEmptyString Then + Return ListAddList(Nothing, TXT_URLS.Text.StringFormatLines.StringToList(Of String)(vbNewLine), + LAP.NotContainsOnly, EDP.ReturnValue, CType(Function(Input$) Input.StringTrim, Func(Of Object, Object))).ListIfNothing + Else + Return New List(Of String) + End If + End Get + End Property + Friend ReadOnly Property IsOneArtist As Boolean + Get + Return CH_PLS_ONE.Checked + End Get + End Property + Friend Sub New() + InitializeComponent() + MyDefs = New DefaultFormOptions(Me) + End Sub + Private Sub PlaylistArrayForm_Load(sender As Object, e As EventArgs) Handles Me.Load + With MyDefs + .MyViewInitialize() + .AddOkCancelToolbar() + .EndLoaderOperations() + End With + End Sub + Private Sub PlaylistArrayForm_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown + If e.KeyCode = Keys.O And e.Control Then + Using f As New PlayListParserForm With {.DesignXML = DesignXML} + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then TXT_URLS.Text = f.PlayLists.ListToString(vbNewLine, EDP.ReturnValue) + End Using + e.Handled = True + End If + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOption.Designer.vb b/SCrawler.YouTube/Controls/VideoOption.Designer.vb new file mode 100644 index 0000000..15861b7 --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOption.Designer.vb @@ -0,0 +1,132 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Friend Class VideoOption : Inherits System.Windows.Forms.UserControl + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Me.OPT_CHECKED = New System.Windows.Forms.RadioButton() + Me.LBL_DEFINITION_INFO = New System.Windows.Forms.Label() + Me.LBL_DEFINITION = New System.Windows.Forms.Label() + Me.LBL_CODECS = New System.Windows.Forms.Label() + Me.LBL_SIZE = New System.Windows.Forms.Label() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_MAIN.SuspendLayout() + Me.SuspendLayout() + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 5 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 36.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 110.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 58.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 71.0!)) + TP_MAIN.Controls.Add(Me.OPT_CHECKED, 0, 0) + TP_MAIN.Controls.Add(Me.LBL_DEFINITION_INFO, 1, 0) + TP_MAIN.Controls.Add(Me.LBL_DEFINITION, 2, 0) + TP_MAIN.Controls.Add(Me.LBL_CODECS, 3, 0) + TP_MAIN.Controls.Add(Me.LBL_SIZE, 4, 0) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Margin = New System.Windows.Forms.Padding(0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 1 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Size = New System.Drawing.Size(459, 30) + TP_MAIN.TabIndex = 0 + ' + 'OPT_CHECKED + ' + Me.OPT_CHECKED.AutoSize = True + Me.OPT_CHECKED.CheckAlign = System.Drawing.ContentAlignment.MiddleCenter + Me.OPT_CHECKED.Dock = System.Windows.Forms.DockStyle.Fill + Me.OPT_CHECKED.Location = New System.Drawing.Point(3, 3) + Me.OPT_CHECKED.Name = "OPT_CHECKED" + Me.OPT_CHECKED.Size = New System.Drawing.Size(30, 24) + Me.OPT_CHECKED.TabIndex = 0 + Me.OPT_CHECKED.TabStop = True + Me.OPT_CHECKED.TextAlign = System.Drawing.ContentAlignment.MiddleCenter + Me.OPT_CHECKED.UseVisualStyleBackColor = True + ' + 'LBL_DEFINITION_INFO + ' + Me.LBL_DEFINITION_INFO.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_DEFINITION_INFO.Font = New System.Drawing.Font("Arial", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_DEFINITION_INFO.Location = New System.Drawing.Point(39, 0) + Me.LBL_DEFINITION_INFO.Name = "LBL_DEFINITION_INFO" + Me.LBL_DEFINITION_INFO.Size = New System.Drawing.Size(104, 30) + Me.LBL_DEFINITION_INFO.TabIndex = 1 + Me.LBL_DEFINITION_INFO.Text = "Ultra High Definition" + Me.LBL_DEFINITION_INFO.TextAlign = System.Drawing.ContentAlignment.MiddleLeft + ' + 'LBL_DEFINITION + ' + Me.LBL_DEFINITION.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_DEFINITION.Font = New System.Drawing.Font("Arial", 9.0!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_DEFINITION.Location = New System.Drawing.Point(149, 0) + Me.LBL_DEFINITION.Name = "LBL_DEFINITION" + Me.LBL_DEFINITION.Size = New System.Drawing.Size(52, 30) + Me.LBL_DEFINITION.TabIndex = 2 + Me.LBL_DEFINITION.Text = "1080p" + Me.LBL_DEFINITION.TextAlign = System.Drawing.ContentAlignment.MiddleLeft + ' + 'LBL_CODECS + ' + Me.LBL_CODECS.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_CODECS.Font = New System.Drawing.Font("Arial", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_CODECS.Location = New System.Drawing.Point(207, 0) + Me.LBL_CODECS.Name = "LBL_CODECS" + Me.LBL_CODECS.Size = New System.Drawing.Size(178, 30) + Me.LBL_CODECS.TabIndex = 3 + Me.LBL_CODECS.Text = "MKV - VP9 - Opus" + Me.LBL_CODECS.TextAlign = System.Drawing.ContentAlignment.MiddleRight + ' + 'LBL_SIZE + ' + Me.LBL_SIZE.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_SIZE.Font = New System.Drawing.Font("Arial", 8.25!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_SIZE.Location = New System.Drawing.Point(391, 0) + Me.LBL_SIZE.Name = "LBL_SIZE" + Me.LBL_SIZE.Size = New System.Drawing.Size(65, 30) + Me.LBL_SIZE.TabIndex = 4 + Me.LBL_SIZE.Text = "1 062 MB" + Me.LBL_SIZE.TextAlign = System.Drawing.ContentAlignment.MiddleRight + ' + 'VideoOption + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.Controls.Add(TP_MAIN) + Me.Name = "VideoOption" + Me.Size = New System.Drawing.Size(459, 30) + TP_MAIN.ResumeLayout(False) + TP_MAIN.PerformLayout() + Me.ResumeLayout(False) + + End Sub + Private WithEvents OPT_CHECKED As RadioButton + Private WithEvents LBL_DEFINITION_INFO As Label + Private WithEvents LBL_DEFINITION As Label + Private WithEvents LBL_CODECS As Label + Private WithEvents LBL_SIZE As Label + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOption.resx b/SCrawler.YouTube/Controls/VideoOption.resx new file mode 100644 index 0000000..e5f5c30 --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOption.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOption.vb b/SCrawler.YouTube/Controls/VideoOption.vb new file mode 100644 index 0000000..a730200 --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOption.vb @@ -0,0 +1,87 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.ComponentModel +Imports SCrawler.API.YouTube.Base +Namespace API.YouTube.Controls + + Friend Class VideoOption : Implements ISupportInitialize + Friend Event CheckedChanged As EventHandler + Friend Property MyMedia As MediaObject + Friend Property Checked As Boolean + Get + Return OPT_CHECKED.Checked + End Get + Set(ByVal _Checked As Boolean) + OPT_CHECKED.Checked = _Checked + End Set + End Property + Friend Sub New() + InitializeComponent() + End Sub + Friend Sub New(ByVal m As MediaObject, Optional ByVal SelectedAudio As MediaObject = Nothing) + Me.New + Const d$ = " " & ChrW(183) & " " + MyMedia = m + If m.Type = Plugin.UserMediaTypes.Audio Then + If m.Bitrate >= 320 Then + LBL_DEFINITION_INFO.Text = "High Quality" + ElseIf m.Bitrate >= 190 Then + LBL_DEFINITION_INFO.Text = "Medium Quality" + Else + LBL_DEFINITION_INFO.Text = "Low Quality" + End If + LBL_DEFINITION.Text = $"{m.Bitrate}k" + LBL_CODECS.Text = $"{m.Extension} {d} {m.Codec} {d} {m.Bitrate}k" + Else + If m.Height >= 1440 Then + LBL_DEFINITION_INFO.Text = "Ultra High Definition" + ElseIf m.Height >= 720 Then + LBL_DEFINITION_INFO.Text = "High Definition" + ElseIf m.Height >= 480 Then + LBL_DEFINITION_INFO.Text = "Medium Definition" + ElseIf m.Height >= 360 Then + LBL_DEFINITION_INFO.Text = "Normal Definition" + Else + LBL_DEFINITION_INFO.Text = "Low Definition" + End If + LBL_DEFINITION.Text = $"{m.Height}p" + LBL_CODECS.Text = $"{m.Extension.StringToUpper}{d}{m.Codec.StringToUpper}{d}{m.FPS}fps{d}{m.Bitrate}k" + If Not SelectedAudio.ID.IsEmptyString Then LBL_CODECS.Text &= $" / {SelectedAudio.Extension}{d}{SelectedAudio.Codec}{d}{SelectedAudio.Bitrate}k" + End If + + Dim sv% = m.Size / 1024 + If sv >= 1000 Then + LBL_SIZE.Text = AConvert(Of String)(sv / 1024, VideoSizeProvider) + LBL_SIZE.Text &= " GB" + Else + LBL_SIZE.Text = AConvert(Of String)(sv, VideoSizeProvider) + LBL_SIZE.Text &= " MB" + End If + End Sub + Private Sub OPT_CHECKED_CheckedChanged(sender As Object, e As EventArgs) Handles OPT_CHECKED.CheckedChanged + RaiseEvent CheckedChanged(Me, e) + End Sub + Private Sub Labels_Click(sender As Object, e As EventArgs) Handles LBL_DEFINITION_INFO.Click, LBL_DEFINITION.Click, LBL_CODECS.Click, LBL_SIZE.Click + OPT_CHECKED.Checked = True + End Sub + Private Sub BindedControl_CheckedChanged(sender As Object, e As EventArgs) + If Not sender Is Nothing AndAlso Not sender Is Me AndAlso DirectCast(sender, VideoOption).Checked Then Checked = False + End Sub + Private Sub BeginInit() Implements ISupportInitialize.BeginInit + End Sub + Private Sub EndInit() Implements ISupportInitialize.EndInit + If Not Parent Is Nothing AndAlso Parent.Controls.Count > 0 Then + For Each cnt As Control In Parent.Controls + If TypeOf cnt Is VideoOption And Not cnt Is Me Then _ + AddHandler DirectCast(cnt, VideoOption).CheckedChanged, AddressOf BindedControl_CheckedChanged + Next + End If + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb b/SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb new file mode 100644 index 0000000..633c461 --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOptionsForm.Designer.vb @@ -0,0 +1,749 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.YouTube.Controls + + Partial Friend Class VideoOptionsForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Me.components = New System.ComponentModel.Container() + Dim TP_HEADER As System.Windows.Forms.TableLayoutPanel + Dim TP_HEADER_INFO As System.Windows.Forms.TableLayoutPanel + Dim ICON_CLOCK As System.Windows.Forms.PictureBox + Dim ICON_LINK As System.Windows.Forms.PictureBox + Dim TP_FOOTER As System.Windows.Forms.TableLayoutPanel + Dim TP_DESTINATION As System.Windows.Forms.TableLayoutPanel + Dim TP_OK_CANCEL As System.Windows.Forms.TableLayoutPanel + Dim LB_SEP_1 As System.Windows.Forms.Label + Dim LB_SEP_2 As System.Windows.Forms.Label + Dim TP_WHAT As System.Windows.Forms.TableLayoutPanel + Dim LBL_WHAT As System.Windows.Forms.Label + Dim LBL_FORMAT As System.Windows.Forms.Label + Dim LBL_SUBS_FORMAT As System.Windows.Forms.Label + Dim TT_MAIN As System.Windows.Forms.ToolTip + Dim ActionButton1 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(VideoOptionsForm)) + Dim ActionButton2 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton3 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton4 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton5 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton6 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton7 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton8 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton9 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Me.ICON_VIDEO = New System.Windows.Forms.PictureBox() + Me.LBL_TITLE = New System.Windows.Forms.Label() + Me.TP_HEADER_INFO_2 = New System.Windows.Forms.TableLayoutPanel() + Me.LBL_TIME = New System.Windows.Forms.Label() + Me.LBL_URL = New System.Windows.Forms.LinkLabel() + Me.TXT_FILE = New System.Windows.Forms.TextBox() + Me.BTT_BROWSE = New System.Windows.Forms.Button() + Me.BTT_DOWN = New System.Windows.Forms.Button() + Me.BTT_CANCEL = New System.Windows.Forms.Button() + Me.OPT_VIDEO = New System.Windows.Forms.RadioButton() + Me.OPT_AUDIO = New System.Windows.Forms.RadioButton() + Me.LBL_AUDIO_CODEC = New System.Windows.Forms.Label() + Me.TP_HEADER_BASE = New System.Windows.Forms.TableLayoutPanel() + Me.TP_SUBS = New System.Windows.Forms.TableLayoutPanel() + Me.TXT_SUBS = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.CMB_SUBS_FORMAT = New System.Windows.Forms.ComboBox() + Me.TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + Me.TP_OPTIONS = New System.Windows.Forms.TableLayoutPanel() + Me.CMB_FORMAT = New System.Windows.Forms.ComboBox() + Me.CMB_AUDIO_CODEC = New System.Windows.Forms.ComboBox() + Me.NUM_RES = New System.Windows.Forms.NumericUpDown() + Me.TP_CONTROLS = New System.Windows.Forms.TableLayoutPanel() + Me.TXT_SUBS_ADDIT = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.TXT_EXTRA_AUDIO_FORMATS = New PersonalUtilities.Forms.Controls.TextBoxExtended() + TP_HEADER = New System.Windows.Forms.TableLayoutPanel() + TP_HEADER_INFO = New System.Windows.Forms.TableLayoutPanel() + ICON_CLOCK = New System.Windows.Forms.PictureBox() + ICON_LINK = New System.Windows.Forms.PictureBox() + TP_FOOTER = New System.Windows.Forms.TableLayoutPanel() + TP_DESTINATION = New System.Windows.Forms.TableLayoutPanel() + TP_OK_CANCEL = New System.Windows.Forms.TableLayoutPanel() + LB_SEP_1 = New System.Windows.Forms.Label() + LB_SEP_2 = New System.Windows.Forms.Label() + TP_WHAT = New System.Windows.Forms.TableLayoutPanel() + LBL_WHAT = New System.Windows.Forms.Label() + LBL_FORMAT = New System.Windows.Forms.Label() + LBL_SUBS_FORMAT = New System.Windows.Forms.Label() + TT_MAIN = New System.Windows.Forms.ToolTip(Me.components) + TP_HEADER.SuspendLayout() + CType(Me.ICON_VIDEO, System.ComponentModel.ISupportInitialize).BeginInit() + TP_HEADER_INFO.SuspendLayout() + Me.TP_HEADER_INFO_2.SuspendLayout() + CType(ICON_CLOCK, System.ComponentModel.ISupportInitialize).BeginInit() + CType(ICON_LINK, System.ComponentModel.ISupportInitialize).BeginInit() + TP_FOOTER.SuspendLayout() + TP_DESTINATION.SuspendLayout() + TP_OK_CANCEL.SuspendLayout() + TP_WHAT.SuspendLayout() + Me.TP_HEADER_BASE.SuspendLayout() + Me.TP_SUBS.SuspendLayout() + CType(Me.TXT_SUBS, System.ComponentModel.ISupportInitialize).BeginInit() + Me.TP_MAIN.SuspendLayout() + Me.TP_OPTIONS.SuspendLayout() + CType(Me.NUM_RES, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_SUBS_ADDIT, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_EXTRA_AUDIO_FORMATS, System.ComponentModel.ISupportInitialize).BeginInit() + Me.SuspendLayout() + ' + 'TP_HEADER + ' + TP_HEADER.BackColor = System.Drawing.SystemColors.Window + TP_HEADER.ColumnCount = 2 + TP_HEADER.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 130.0!)) + TP_HEADER.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_HEADER.Controls.Add(Me.ICON_VIDEO, 0, 0) + TP_HEADER.Controls.Add(TP_HEADER_INFO, 1, 0) + TP_HEADER.Dock = System.Windows.Forms.DockStyle.Fill + TP_HEADER.Location = New System.Drawing.Point(1, 1) + TP_HEADER.Margin = New System.Windows.Forms.Padding(0) + TP_HEADER.Name = "TP_HEADER" + TP_HEADER.RowCount = 1 + TP_HEADER.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_HEADER.Size = New System.Drawing.Size(599, 63) + TP_HEADER.TabIndex = 0 + ' + 'ICON_VIDEO + ' + Me.ICON_VIDEO.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom + Me.ICON_VIDEO.Dock = System.Windows.Forms.DockStyle.Fill + Me.ICON_VIDEO.Location = New System.Drawing.Point(1, 1) + Me.ICON_VIDEO.Margin = New System.Windows.Forms.Padding(1) + Me.ICON_VIDEO.Name = "ICON_VIDEO" + Me.ICON_VIDEO.Size = New System.Drawing.Size(128, 61) + Me.ICON_VIDEO.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom + Me.ICON_VIDEO.TabIndex = 0 + Me.ICON_VIDEO.TabStop = False + ' + 'TP_HEADER_INFO + ' + TP_HEADER_INFO.ColumnCount = 1 + TP_HEADER_INFO.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_HEADER_INFO.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_HEADER_INFO.Controls.Add(Me.LBL_TITLE, 0, 0) + TP_HEADER_INFO.Controls.Add(Me.TP_HEADER_INFO_2, 0, 1) + TP_HEADER_INFO.Dock = System.Windows.Forms.DockStyle.Fill + TP_HEADER_INFO.Location = New System.Drawing.Point(130, 0) + TP_HEADER_INFO.Margin = New System.Windows.Forms.Padding(0) + TP_HEADER_INFO.Name = "TP_HEADER_INFO" + TP_HEADER_INFO.RowCount = 2 + TP_HEADER_INFO.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_HEADER_INFO.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_HEADER_INFO.Size = New System.Drawing.Size(469, 63) + TP_HEADER_INFO.TabIndex = 0 + ' + 'LBL_TITLE + ' + Me.LBL_TITLE.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_TITLE.Font = New System.Drawing.Font("Arial", 9.0!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_TITLE.Location = New System.Drawing.Point(3, 0) + Me.LBL_TITLE.Name = "LBL_TITLE" + Me.LBL_TITLE.Size = New System.Drawing.Size(463, 31) + Me.LBL_TITLE.TabIndex = 0 + Me.LBL_TITLE.Text = "Video title" + Me.LBL_TITLE.TextAlign = System.Drawing.ContentAlignment.MiddleLeft + ' + 'TP_HEADER_INFO_2 + ' + Me.TP_HEADER_INFO_2.ColumnCount = 4 + Me.TP_HEADER_INFO_2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + Me.TP_HEADER_INFO_2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 62.0!)) + Me.TP_HEADER_INFO_2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + Me.TP_HEADER_INFO_2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_HEADER_INFO_2.Controls.Add(ICON_CLOCK, 0, 0) + Me.TP_HEADER_INFO_2.Controls.Add(Me.LBL_TIME, 1, 0) + Me.TP_HEADER_INFO_2.Controls.Add(ICON_LINK, 2, 0) + Me.TP_HEADER_INFO_2.Controls.Add(Me.LBL_URL, 3, 0) + Me.TP_HEADER_INFO_2.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_HEADER_INFO_2.Location = New System.Drawing.Point(0, 31) + Me.TP_HEADER_INFO_2.Margin = New System.Windows.Forms.Padding(0) + Me.TP_HEADER_INFO_2.Name = "TP_HEADER_INFO_2" + Me.TP_HEADER_INFO_2.RowCount = 1 + Me.TP_HEADER_INFO_2.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_HEADER_INFO_2.Size = New System.Drawing.Size(469, 32) + Me.TP_HEADER_INFO_2.TabIndex = 1 + ' + 'ICON_CLOCK + ' + ICON_CLOCK.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom + ICON_CLOCK.Dock = System.Windows.Forms.DockStyle.Fill + ICON_CLOCK.Image = Global.SCrawler.My.Resources.Resources.ClockPic_16 + ICON_CLOCK.Location = New System.Drawing.Point(3, 3) + ICON_CLOCK.Name = "ICON_CLOCK" + ICON_CLOCK.Size = New System.Drawing.Size(19, 26) + ICON_CLOCK.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom + ICON_CLOCK.TabIndex = 0 + ICON_CLOCK.TabStop = False + ' + 'LBL_TIME + ' + Me.LBL_TIME.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_TIME.Font = New System.Drawing.Font("Arial", 9.0!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_TIME.ForeColor = System.Drawing.SystemColors.ControlDarkDark + Me.LBL_TIME.Location = New System.Drawing.Point(28, 0) + Me.LBL_TIME.Name = "LBL_TIME" + Me.LBL_TIME.Size = New System.Drawing.Size(56, 32) + Me.LBL_TIME.TabIndex = 0 + Me.LBL_TIME.Text = "00:00:00" + Me.LBL_TIME.TextAlign = System.Drawing.ContentAlignment.MiddleCenter + ' + 'ICON_LINK + ' + ICON_LINK.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom + ICON_LINK.Dock = System.Windows.Forms.DockStyle.Fill + ICON_LINK.Image = Global.SCrawler.My.Resources.Resources.LinkPic_32 + ICON_LINK.Location = New System.Drawing.Point(90, 3) + ICON_LINK.Name = "ICON_LINK" + ICON_LINK.Size = New System.Drawing.Size(19, 26) + ICON_LINK.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom + ICON_LINK.TabIndex = 2 + ICON_LINK.TabStop = False + ' + 'LBL_URL + ' + Me.LBL_URL.AutoSize = True + Me.LBL_URL.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_URL.Font = New System.Drawing.Font("Arial", 9.0!, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_URL.LinkColor = System.Drawing.Color.FromArgb(CType(CType(0, Byte), Integer), CType(CType(0, Byte), Integer), CType(CType(192, Byte), Integer)) + Me.LBL_URL.Location = New System.Drawing.Point(115, 0) + Me.LBL_URL.Name = "LBL_URL" + Me.LBL_URL.Size = New System.Drawing.Size(351, 32) + Me.LBL_URL.TabIndex = 1 + Me.LBL_URL.TabStop = True + Me.LBL_URL.Text = "https://www.youtube.com/watch?v=abcdefghijk" + Me.LBL_URL.TextAlign = System.Drawing.ContentAlignment.MiddleLeft + ' + 'TP_FOOTER + ' + TP_FOOTER.ColumnCount = 1 + TP_FOOTER.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_FOOTER.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_FOOTER.Controls.Add(TP_DESTINATION, 0, 0) + TP_FOOTER.Controls.Add(TP_OK_CANCEL, 0, 1) + TP_FOOTER.Dock = System.Windows.Forms.DockStyle.Fill + TP_FOOTER.Location = New System.Drawing.Point(6, 215) + TP_FOOTER.Margin = New System.Windows.Forms.Padding(6, 3, 6, 3) + TP_FOOTER.Name = "TP_FOOTER" + TP_FOOTER.RowCount = 2 + TP_FOOTER.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_FOOTER.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_FOOTER.Size = New System.Drawing.Size(589, 52) + TP_FOOTER.TabIndex = 5 + ' + 'TP_DESTINATION + ' + TP_DESTINATION.ColumnCount = 2 + TP_DESTINATION.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_DESTINATION.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + TP_DESTINATION.Controls.Add(Me.TXT_FILE, 0, 0) + TP_DESTINATION.Controls.Add(Me.BTT_BROWSE, 1, 0) + TP_DESTINATION.Dock = System.Windows.Forms.DockStyle.Fill + TP_DESTINATION.Location = New System.Drawing.Point(0, 0) + TP_DESTINATION.Margin = New System.Windows.Forms.Padding(0) + TP_DESTINATION.Name = "TP_DESTINATION" + TP_DESTINATION.RowCount = 1 + TP_DESTINATION.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_DESTINATION.Size = New System.Drawing.Size(589, 26) + TP_DESTINATION.TabIndex = 0 + ' + 'TXT_FILE + ' + Me.TXT_FILE.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_FILE.Location = New System.Drawing.Point(3, 3) + Me.TXT_FILE.Name = "TXT_FILE" + Me.TXT_FILE.Size = New System.Drawing.Size(503, 20) + Me.TXT_FILE.TabIndex = 0 + Me.TXT_FILE.WordWrap = False + ' + 'BTT_BROWSE + ' + Me.BTT_BROWSE.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_BROWSE.Location = New System.Drawing.Point(512, 2) + Me.BTT_BROWSE.Margin = New System.Windows.Forms.Padding(3, 2, 3, 2) + Me.BTT_BROWSE.Name = "BTT_BROWSE" + Me.BTT_BROWSE.Size = New System.Drawing.Size(74, 22) + Me.BTT_BROWSE.TabIndex = 1 + Me.BTT_BROWSE.Text = "Browse" + TT_MAIN.SetToolTip(Me.BTT_BROWSE, "Choose an output file") + Me.BTT_BROWSE.UseVisualStyleBackColor = True + ' + 'TP_OK_CANCEL + ' + TP_OK_CANCEL.ColumnCount = 3 + TP_OK_CANCEL.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_OK_CANCEL.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + TP_OK_CANCEL.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + TP_OK_CANCEL.Controls.Add(Me.BTT_DOWN, 1, 0) + TP_OK_CANCEL.Controls.Add(Me.BTT_CANCEL, 2, 0) + TP_OK_CANCEL.Dock = System.Windows.Forms.DockStyle.Fill + TP_OK_CANCEL.Location = New System.Drawing.Point(0, 26) + TP_OK_CANCEL.Margin = New System.Windows.Forms.Padding(0) + TP_OK_CANCEL.Name = "TP_OK_CANCEL" + TP_OK_CANCEL.RowCount = 1 + TP_OK_CANCEL.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_OK_CANCEL.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 26.0!)) + TP_OK_CANCEL.Size = New System.Drawing.Size(589, 26) + TP_OK_CANCEL.TabIndex = 1 + ' + 'BTT_DOWN + ' + Me.BTT_DOWN.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_DOWN.Location = New System.Drawing.Point(432, 2) + Me.BTT_DOWN.Margin = New System.Windows.Forms.Padding(3, 2, 3, 2) + Me.BTT_DOWN.Name = "BTT_DOWN" + Me.BTT_DOWN.Size = New System.Drawing.Size(74, 22) + Me.BTT_DOWN.TabIndex = 0 + Me.BTT_DOWN.Text = "Download" + Me.BTT_DOWN.UseVisualStyleBackColor = True + ' + 'BTT_CANCEL + ' + Me.BTT_CANCEL.DialogResult = System.Windows.Forms.DialogResult.Cancel + Me.BTT_CANCEL.Dock = System.Windows.Forms.DockStyle.Fill + Me.BTT_CANCEL.Location = New System.Drawing.Point(512, 2) + Me.BTT_CANCEL.Margin = New System.Windows.Forms.Padding(3, 2, 3, 2) + Me.BTT_CANCEL.Name = "BTT_CANCEL" + Me.BTT_CANCEL.Size = New System.Drawing.Size(74, 22) + Me.BTT_CANCEL.TabIndex = 1 + Me.BTT_CANCEL.Text = "Cancel" + Me.BTT_CANCEL.UseVisualStyleBackColor = True + ' + 'LB_SEP_1 + ' + LB_SEP_1.Anchor = CType((System.Windows.Forms.AnchorStyles.Left Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles) + LB_SEP_1.BackColor = System.Drawing.SystemColors.ControlDark + LB_SEP_1.Location = New System.Drawing.Point(6, 179) + LB_SEP_1.Margin = New System.Windows.Forms.Padding(6, 0, 6, 0) + LB_SEP_1.Name = "LB_SEP_1" + LB_SEP_1.Size = New System.Drawing.Size(589, 1) + LB_SEP_1.TabIndex = 3 + ' + 'LB_SEP_2 + ' + LB_SEP_2.Anchor = CType((System.Windows.Forms.AnchorStyles.Left Or System.Windows.Forms.AnchorStyles.Right), System.Windows.Forms.AnchorStyles) + LB_SEP_2.BackColor = System.Drawing.SystemColors.ControlDark + LB_SEP_2.Location = New System.Drawing.Point(6, 209) + LB_SEP_2.Margin = New System.Windows.Forms.Padding(6, 0, 6, 0) + LB_SEP_2.Name = "LB_SEP_2" + LB_SEP_2.Size = New System.Drawing.Size(589, 1) + LB_SEP_2.TabIndex = 5 + ' + 'TP_WHAT + ' + TP_WHAT.ColumnCount = 3 + TP_WHAT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 65.0!)) + TP_WHAT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_WHAT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + TP_WHAT.Controls.Add(LBL_WHAT, 0, 0) + TP_WHAT.Controls.Add(Me.OPT_VIDEO, 1, 0) + TP_WHAT.Controls.Add(Me.OPT_AUDIO, 2, 0) + TP_WHAT.Dock = System.Windows.Forms.DockStyle.Fill + TP_WHAT.Location = New System.Drawing.Point(0, 0) + TP_WHAT.Margin = New System.Windows.Forms.Padding(0) + TP_WHAT.Name = "TP_WHAT" + TP_WHAT.RowCount = 1 + TP_WHAT.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_WHAT.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_WHAT.Size = New System.Drawing.Size(189, 28) + TP_WHAT.TabIndex = 0 + ' + 'LBL_WHAT + ' + LBL_WHAT.AutoSize = True + LBL_WHAT.Dock = System.Windows.Forms.DockStyle.Fill + LBL_WHAT.Location = New System.Drawing.Point(3, 0) + LBL_WHAT.Name = "LBL_WHAT" + LBL_WHAT.Size = New System.Drawing.Size(59, 28) + LBL_WHAT.TabIndex = 4 + LBL_WHAT.Text = "Download" + LBL_WHAT.TextAlign = System.Drawing.ContentAlignment.MiddleRight + ' + 'OPT_VIDEO + ' + Me.OPT_VIDEO.AutoSize = True + Me.OPT_VIDEO.Dock = System.Windows.Forms.DockStyle.Fill + Me.OPT_VIDEO.Location = New System.Drawing.Point(68, 3) + Me.OPT_VIDEO.Name = "OPT_VIDEO" + Me.OPT_VIDEO.Size = New System.Drawing.Size(56, 22) + Me.OPT_VIDEO.TabIndex = 0 + Me.OPT_VIDEO.TabStop = True + Me.OPT_VIDEO.Text = "Video" + Me.OPT_VIDEO.UseVisualStyleBackColor = True + ' + 'OPT_AUDIO + ' + Me.OPT_AUDIO.AutoSize = True + Me.OPT_AUDIO.Dock = System.Windows.Forms.DockStyle.Fill + Me.OPT_AUDIO.Location = New System.Drawing.Point(130, 3) + Me.OPT_AUDIO.Name = "OPT_AUDIO" + Me.OPT_AUDIO.Size = New System.Drawing.Size(56, 22) + Me.OPT_AUDIO.TabIndex = 1 + Me.OPT_AUDIO.TabStop = True + Me.OPT_AUDIO.Text = "Audio" + Me.OPT_AUDIO.UseVisualStyleBackColor = True + ' + 'LBL_FORMAT + ' + LBL_FORMAT.Dock = System.Windows.Forms.DockStyle.Fill + LBL_FORMAT.Location = New System.Drawing.Point(192, 0) + LBL_FORMAT.Name = "LBL_FORMAT" + LBL_FORMAT.Size = New System.Drawing.Size(74, 28) + LBL_FORMAT.TabIndex = 4 + LBL_FORMAT.Text = "Format:" + LBL_FORMAT.TextAlign = System.Drawing.ContentAlignment.MiddleRight + TT_MAIN.SetToolTip(LBL_FORMAT, "Output Video Format") + ' + 'LBL_SUBS_FORMAT + ' + LBL_SUBS_FORMAT.AutoSize = True + LBL_SUBS_FORMAT.Dock = System.Windows.Forms.DockStyle.Fill + LBL_SUBS_FORMAT.Location = New System.Drawing.Point(432, 0) + LBL_SUBS_FORMAT.Name = "LBL_SUBS_FORMAT" + LBL_SUBS_FORMAT.Size = New System.Drawing.Size(74, 28) + LBL_SUBS_FORMAT.TabIndex = 2 + LBL_SUBS_FORMAT.Text = "Format:" + LBL_SUBS_FORMAT.TextAlign = System.Drawing.ContentAlignment.MiddleRight + TT_MAIN.SetToolTip(LBL_SUBS_FORMAT, "Output Subtitles Format") + ' + 'LBL_AUDIO_CODEC + ' + Me.LBL_AUDIO_CODEC.AutoSize = True + Me.LBL_AUDIO_CODEC.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_AUDIO_CODEC.Location = New System.Drawing.Point(432, 0) + Me.LBL_AUDIO_CODEC.Name = "LBL_AUDIO_CODEC" + Me.LBL_AUDIO_CODEC.Size = New System.Drawing.Size(74, 28) + Me.LBL_AUDIO_CODEC.TabIndex = 5 + Me.LBL_AUDIO_CODEC.Text = "Audio Codec" + Me.LBL_AUDIO_CODEC.TextAlign = System.Drawing.ContentAlignment.MiddleRight + TT_MAIN.SetToolTip(Me.LBL_AUDIO_CODEC, "Output Audio Codec") + ' + 'TP_HEADER_BASE + ' + Me.TP_HEADER_BASE.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + Me.TP_HEADER_BASE.ColumnCount = 1 + Me.TP_HEADER_BASE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_HEADER_BASE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_HEADER_BASE.Controls.Add(TP_HEADER, 0, 0) + Me.TP_HEADER_BASE.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_HEADER_BASE.Location = New System.Drawing.Point(0, 0) + Me.TP_HEADER_BASE.Margin = New System.Windows.Forms.Padding(0) + Me.TP_HEADER_BASE.Name = "TP_HEADER_BASE" + Me.TP_HEADER_BASE.RowCount = 1 + Me.TP_HEADER_BASE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_HEADER_BASE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 64.0!)) + Me.TP_HEADER_BASE.Size = New System.Drawing.Size(601, 65) + Me.TP_HEADER_BASE.TabIndex = 6 + ' + 'TP_SUBS + ' + Me.TP_SUBS.ColumnCount = 3 + Me.TP_SUBS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_SUBS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + Me.TP_SUBS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + Me.TP_SUBS.Controls.Add(Me.TXT_SUBS, 0, 0) + Me.TP_SUBS.Controls.Add(LBL_SUBS_FORMAT, 1, 0) + Me.TP_SUBS.Controls.Add(Me.CMB_SUBS_FORMAT, 2, 0) + Me.TP_SUBS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_SUBS.Location = New System.Drawing.Point(6, 93) + Me.TP_SUBS.Margin = New System.Windows.Forms.Padding(6, 0, 6, 0) + Me.TP_SUBS.Name = "TP_SUBS" + Me.TP_SUBS.RowCount = 1 + Me.TP_SUBS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_SUBS.Size = New System.Drawing.Size(589, 28) + Me.TP_SUBS.TabIndex = 2 + ' + 'TXT_SUBS + ' + ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image) + ActionButton1.Name = "Open" + ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open + ActionButton1.ToolTipText = "Choose subtitles" + ActionButton2.BackgroundImage = CType(resources.GetObject("ActionButton2.BackgroundImage"), System.Drawing.Image) + ActionButton2.Name = "Refresh" + ActionButton2.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Refresh + ActionButton2.ToolTipText = "Reset subtitles to initial selected" + ActionButton3.BackgroundImage = CType(resources.GetObject("ActionButton3.BackgroundImage"), System.Drawing.Image) + ActionButton3.Name = "Clear" + ActionButton3.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + ActionButton3.ToolTipText = "Clear subtitles selection (don't download subtitles)" + Me.TXT_SUBS.Buttons.Add(ActionButton1) + Me.TXT_SUBS.Buttons.Add(ActionButton2) + Me.TXT_SUBS.Buttons.Add(ActionButton3) + Me.TXT_SUBS.CaptionText = "Subtitles" + Me.TXT_SUBS.CaptionToolTipEnabled = True + Me.TXT_SUBS.CaptionToolTipText = "The selected subtitles will also be downloaded" + Me.TXT_SUBS.CaptionWidth = 60.0R + Me.TXT_SUBS.ClearTextByButtonClear = False + Me.TXT_SUBS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_SUBS.Location = New System.Drawing.Point(3, 3) + Me.TXT_SUBS.Name = "TXT_SUBS" + Me.TXT_SUBS.Size = New System.Drawing.Size(423, 22) + Me.TXT_SUBS.TabIndex = 0 + Me.TXT_SUBS.TextBoxReadOnly = True + ' + 'CMB_SUBS_FORMAT + ' + Me.CMB_SUBS_FORMAT.Dock = System.Windows.Forms.DockStyle.Fill + Me.CMB_SUBS_FORMAT.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList + Me.CMB_SUBS_FORMAT.FormattingEnabled = True + Me.CMB_SUBS_FORMAT.Location = New System.Drawing.Point(512, 3) + Me.CMB_SUBS_FORMAT.Name = "CMB_SUBS_FORMAT" + Me.CMB_SUBS_FORMAT.Size = New System.Drawing.Size(74, 21) + Me.CMB_SUBS_FORMAT.TabIndex = 1 + ' + 'TP_MAIN + ' + Me.TP_MAIN.ColumnCount = 1 + Me.TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_MAIN.Controls.Add(Me.TP_HEADER_BASE, 0, 0) + Me.TP_MAIN.Controls.Add(TP_FOOTER, 0, 8) + Me.TP_MAIN.Controls.Add(Me.TP_OPTIONS, 0, 1) + Me.TP_MAIN.Controls.Add(Me.TP_CONTROLS, 0, 6) + Me.TP_MAIN.Controls.Add(LB_SEP_1, 0, 5) + Me.TP_MAIN.Controls.Add(LB_SEP_2, 0, 7) + Me.TP_MAIN.Controls.Add(Me.TP_SUBS, 0, 2) + Me.TP_MAIN.Controls.Add(Me.TXT_SUBS_ADDIT, 0, 3) + Me.TP_MAIN.Controls.Add(Me.TXT_EXTRA_AUDIO_FORMATS, 0, 4) + Me.TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_MAIN.Location = New System.Drawing.Point(0, 0) + Me.TP_MAIN.Name = "TP_MAIN" + Me.TP_MAIN.RowCount = 10 + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 65.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 5.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 5.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 58.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle()) + Me.TP_MAIN.Size = New System.Drawing.Size(601, 271) + Me.TP_MAIN.TabIndex = 0 + ' + 'TP_OPTIONS + ' + Me.TP_OPTIONS.ColumnCount = 6 + Me.TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + Me.TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + Me.TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + Me.TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + Me.TP_OPTIONS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 80.0!)) + Me.TP_OPTIONS.Controls.Add(LBL_FORMAT, 1, 0) + Me.TP_OPTIONS.Controls.Add(TP_WHAT, 0, 0) + Me.TP_OPTIONS.Controls.Add(Me.CMB_FORMAT, 2, 0) + Me.TP_OPTIONS.Controls.Add(Me.LBL_AUDIO_CODEC, 4, 0) + Me.TP_OPTIONS.Controls.Add(Me.CMB_AUDIO_CODEC, 5, 0) + Me.TP_OPTIONS.Controls.Add(Me.NUM_RES, 3, 0) + Me.TP_OPTIONS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_OPTIONS.Location = New System.Drawing.Point(6, 65) + Me.TP_OPTIONS.Margin = New System.Windows.Forms.Padding(6, 0, 6, 0) + Me.TP_OPTIONS.Name = "TP_OPTIONS" + Me.TP_OPTIONS.RowCount = 1 + Me.TP_OPTIONS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_OPTIONS.Size = New System.Drawing.Size(589, 28) + Me.TP_OPTIONS.TabIndex = 1 + ' + 'CMB_FORMAT + ' + Me.CMB_FORMAT.Dock = System.Windows.Forms.DockStyle.Fill + Me.CMB_FORMAT.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList + Me.CMB_FORMAT.FormattingEnabled = True + Me.CMB_FORMAT.Location = New System.Drawing.Point(272, 3) + Me.CMB_FORMAT.Name = "CMB_FORMAT" + Me.CMB_FORMAT.Size = New System.Drawing.Size(74, 21) + Me.CMB_FORMAT.TabIndex = 1 + ' + 'CMB_AUDIO_CODEC + ' + Me.CMB_AUDIO_CODEC.Dock = System.Windows.Forms.DockStyle.Fill + Me.CMB_AUDIO_CODEC.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList + Me.CMB_AUDIO_CODEC.FormattingEnabled = True + Me.CMB_AUDIO_CODEC.Location = New System.Drawing.Point(512, 3) + Me.CMB_AUDIO_CODEC.Name = "CMB_AUDIO_CODEC" + Me.CMB_AUDIO_CODEC.Size = New System.Drawing.Size(74, 21) + Me.CMB_AUDIO_CODEC.TabIndex = 3 + ' + 'NUM_RES + ' + Me.NUM_RES.Dock = System.Windows.Forms.DockStyle.Fill + Me.NUM_RES.Location = New System.Drawing.Point(352, 3) + Me.NUM_RES.Maximum = New Decimal(New Integer() {10000, 0, 0, 0}) + Me.NUM_RES.Minimum = New Decimal(New Integer() {1, 0, 0, -2147483648}) + Me.NUM_RES.Name = "NUM_RES" + Me.NUM_RES.Size = New System.Drawing.Size(74, 20) + Me.NUM_RES.TabIndex = 2 + Me.NUM_RES.TextAlign = System.Windows.Forms.HorizontalAlignment.Center + Me.NUM_RES.Value = New Decimal(New Integer() {1080, 0, 0, 0}) + ' + 'TP_CONTROLS + ' + Me.TP_CONTROLS.ColumnCount = 1 + Me.TP_CONTROLS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_CONTROLS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_CONTROLS.Location = New System.Drawing.Point(3, 182) + Me.TP_CONTROLS.Margin = New System.Windows.Forms.Padding(3, 0, 3, 0) + Me.TP_CONTROLS.Name = "TP_CONTROLS" + Me.TP_CONTROLS.RowCount = 1 + Me.TP_CONTROLS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_CONTROLS.Size = New System.Drawing.Size(595, 25) + Me.TP_CONTROLS.TabIndex = 0 + ' + 'TXT_SUBS_ADDIT + ' + ActionButton4.BackgroundImage = CType(resources.GetObject("ActionButton4.BackgroundImage"), System.Drawing.Image) + ActionButton4.Enabled = False + ActionButton4.Name = "Open" + ActionButton4.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open + ActionButton4.ToolTipText = "Choose additional formats" + ActionButton5.BackgroundImage = CType(resources.GetObject("ActionButton5.BackgroundImage"), System.Drawing.Image) + ActionButton5.Enabled = False + ActionButton5.Name = "Refresh" + ActionButton5.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Refresh + ActionButton5.ToolTipText = "Fill in additional formats from the defaults" + ActionButton6.BackgroundImage = CType(resources.GetObject("ActionButton6.BackgroundImage"), System.Drawing.Image) + ActionButton6.Enabled = False + ActionButton6.Name = "Clear" + ActionButton6.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + ActionButton6.ToolTipText = "Remove all additional formats" + Me.TXT_SUBS_ADDIT.Buttons.Add(ActionButton4) + Me.TXT_SUBS_ADDIT.Buttons.Add(ActionButton5) + Me.TXT_SUBS_ADDIT.Buttons.Add(ActionButton6) + Me.TXT_SUBS_ADDIT.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.CheckBox + Me.TXT_SUBS_ADDIT.CaptionText = "Additional subtitle formats" + Me.TXT_SUBS_ADDIT.CaptionToolTipEnabled = True + Me.TXT_SUBS_ADDIT.CaptionToolTipText = "Subtitles will be downloaded in 'SRT' format and converted to additional formats" + Me.TXT_SUBS_ADDIT.CaptionWidth = 150.0R + Me.TXT_SUBS_ADDIT.ClearTextByButtonClear = False + Me.TXT_SUBS_ADDIT.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_SUBS_ADDIT.Location = New System.Drawing.Point(6, 124) + Me.TXT_SUBS_ADDIT.Margin = New System.Windows.Forms.Padding(6, 3, 6, 3) + Me.TXT_SUBS_ADDIT.Name = "TXT_SUBS_ADDIT" + Me.TXT_SUBS_ADDIT.Size = New System.Drawing.Size(589, 22) + Me.TXT_SUBS_ADDIT.TabIndex = 3 + Me.TXT_SUBS_ADDIT.Tag = "s" + Me.TXT_SUBS_ADDIT.TextBoxReadOnly = True + ' + 'TXT_EXTRA_AUDIO_FORMATS + ' + ActionButton7.BackgroundImage = CType(resources.GetObject("ActionButton7.BackgroundImage"), System.Drawing.Image) + ActionButton7.Enabled = False + ActionButton7.Name = "Open" + ActionButton7.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open + ActionButton7.ToolTipText = "Choose additional formats" + ActionButton8.BackgroundImage = CType(resources.GetObject("ActionButton8.BackgroundImage"), System.Drawing.Image) + ActionButton8.Enabled = False + ActionButton8.Name = "Refresh" + ActionButton8.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Refresh + ActionButton8.ToolTipText = "Fill in additional formats from the defaults" + ActionButton9.BackgroundImage = CType(resources.GetObject("ActionButton9.BackgroundImage"), System.Drawing.Image) + ActionButton9.Enabled = False + ActionButton9.Name = "Clear" + ActionButton9.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + ActionButton9.ToolTipText = "Choose additional formats" + Me.TXT_EXTRA_AUDIO_FORMATS.Buttons.Add(ActionButton7) + Me.TXT_EXTRA_AUDIO_FORMATS.Buttons.Add(ActionButton8) + Me.TXT_EXTRA_AUDIO_FORMATS.Buttons.Add(ActionButton9) + Me.TXT_EXTRA_AUDIO_FORMATS.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.CheckBox + Me.TXT_EXTRA_AUDIO_FORMATS.CaptionText = "Additional audio formats" + Me.TXT_EXTRA_AUDIO_FORMATS.CaptionToolTipEnabled = True + Me.TXT_EXTRA_AUDIO_FORMATS.CaptionWidth = 150.0R + Me.TXT_EXTRA_AUDIO_FORMATS.ClearTextByButtonClear = False + Me.TXT_EXTRA_AUDIO_FORMATS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_EXTRA_AUDIO_FORMATS.Location = New System.Drawing.Point(6, 152) + Me.TXT_EXTRA_AUDIO_FORMATS.Margin = New System.Windows.Forms.Padding(6, 3, 6, 3) + Me.TXT_EXTRA_AUDIO_FORMATS.Name = "TXT_EXTRA_AUDIO_FORMATS" + Me.TXT_EXTRA_AUDIO_FORMATS.Size = New System.Drawing.Size(589, 22) + Me.TXT_EXTRA_AUDIO_FORMATS.TabIndex = 4 + Me.TXT_EXTRA_AUDIO_FORMATS.Tag = "a" + Me.TXT_EXTRA_AUDIO_FORMATS.TextBoxReadOnly = True + ' + 'VideoOptionsForm + ' + Me.AcceptButton = Me.BTT_DOWN + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.CancelButton = Me.BTT_CANCEL + Me.ClientSize = New System.Drawing.Size(601, 271) + Me.Controls.Add(Me.TP_MAIN) + Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle + Me.Icon = Global.SCrawler.My.Resources.SiteYouTube.YouTubeIcon_32 + Me.KeyPreview = True + Me.MaximizeBox = False + Me.MinimizeBox = False + Me.Name = "VideoOptionsForm" + Me.ShowInTaskbar = False + Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide + Me.Text = "Download video" + TP_HEADER.ResumeLayout(False) + CType(Me.ICON_VIDEO, System.ComponentModel.ISupportInitialize).EndInit() + TP_HEADER_INFO.ResumeLayout(False) + Me.TP_HEADER_INFO_2.ResumeLayout(False) + Me.TP_HEADER_INFO_2.PerformLayout() + CType(ICON_CLOCK, System.ComponentModel.ISupportInitialize).EndInit() + CType(ICON_LINK, System.ComponentModel.ISupportInitialize).EndInit() + TP_FOOTER.ResumeLayout(False) + TP_DESTINATION.ResumeLayout(False) + TP_DESTINATION.PerformLayout() + TP_OK_CANCEL.ResumeLayout(False) + TP_WHAT.ResumeLayout(False) + TP_WHAT.PerformLayout() + Me.TP_HEADER_BASE.ResumeLayout(False) + Me.TP_SUBS.ResumeLayout(False) + Me.TP_SUBS.PerformLayout() + CType(Me.TXT_SUBS, System.ComponentModel.ISupportInitialize).EndInit() + Me.TP_MAIN.ResumeLayout(False) + Me.TP_OPTIONS.ResumeLayout(False) + Me.TP_OPTIONS.PerformLayout() + CType(Me.NUM_RES, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_SUBS_ADDIT, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_EXTRA_AUDIO_FORMATS, System.ComponentModel.ISupportInitialize).EndInit() + Me.ResumeLayout(False) + + End Sub + Private WithEvents ICON_VIDEO As PictureBox + Private WithEvents LBL_TITLE As Label + Private WithEvents LBL_TIME As Label + Private WithEvents LBL_URL As LinkLabel + Private WithEvents TP_OPTIONS As TableLayoutPanel + Private WithEvents LBL_AUDIO_CODEC As Label + Private WithEvents TP_SUBS As TableLayoutPanel + Private WithEvents NUM_RES As NumericUpDown + Private WithEvents TP_HEADER_BASE As TableLayoutPanel + Private WithEvents TP_CONTROLS As TableLayoutPanel + Private WithEvents TP_MAIN As TableLayoutPanel + Private WithEvents CMB_AUDIO_CODEC As ComboBox + Private WithEvents OPT_VIDEO As RadioButton + Private WithEvents OPT_AUDIO As RadioButton + Private WithEvents CMB_FORMAT As ComboBox + Private WithEvents TXT_SUBS As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents CMB_SUBS_FORMAT As ComboBox + Private WithEvents TXT_SUBS_ADDIT As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_EXTRA_AUDIO_FORMATS As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_FILE As TextBox + Private WithEvents BTT_BROWSE As Button + Private WithEvents BTT_DOWN As Button + Private WithEvents BTT_CANCEL As Button + Private WithEvents TP_HEADER_INFO_2 As TableLayoutPanel + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsForm.resx b/SCrawler.YouTube/Controls/VideoOptionsForm.resx new file mode 100644 index 0000000..f3ef8e9 --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOptionsForm.resx @@ -0,0 +1,271 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + 17, 17 + + + False + + + False + + + False + + + False + + + False + + + False + + + False + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP + WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP + aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+ + 5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8 + vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB + cMaRN0UdBBkAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6 + JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOElE + QVQ4T2P4//8/QczOJyyqHpzfiE0OQwAZC8iqszAzs7CJ69o4BR768V/W2jcGXQ0KB4aFNS3dDQtnrbCb + ePCK48wTN1wXXXzge/jXf/clV55zC4hIIatF0cjIyMikElzc57z0wX+XHd/+2+//99/ywP//xlu//tdb + +eK/4Zp3/1WTOhYzARViNUAluKjTdf37/0ZTTn9TbdhwXblhwwW1/qOP1Ja9+K8w+95/6cm3/6v2Xvkv + qKjniGGAoIqRpW3/4e8S9uGdzFz82gwMDFxAzCxm4ZegtuLDf+VJ1/8rZM25IqLvnM/CximCYYCic1QN + v7x2JIwPwyrJ3XNUylddE9G2TWNmZOBDl4czmJiZMSRBmFdSyYyJgUEQmxwIYxWEYXZBCUls4sgYq6CA + prWNbtG8nXKeaVPR5XiVjSxEzf0yYXy4BBMLO6eQjoOXZvrkbbazrv53Xf/2v4CSbjBMXkhBl1/CMyNZ + qWnvGy5pNQ+YONwAfjXzAOupl/47LLr333L50/96q9/8l23YdES6cO5KuYqVW+R7Tj6SnfP0v4hryjyY + HhQDmFjYeHVKFp7WX/Xuv9Kq9/+Vd/z7r7rv/3+l7f//y676DEwDN/9L+BVvYkKLCTgDhNkkVUyVlr74 + qbbz73/VOTc/qsy89kWx+9h7qbQpJwS1bbOAscGGrB6EUTggLOqf16C55ft/HlnNAFZOXgVWdi4FRgYG + VnR1MIwhwMTCyqEQ37qEmZVDFF0OE/9nAACtFF4Ey6OP+wAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP + WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP + aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+ + 5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8 + vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB + cMaRN0UdBBkAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6 + JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOElE + QVQ4T2P4//8/QczOJyyqHpzfiE0OQwAZC8iqszAzs7CJ69o4BR768V/W2jcGXQ0KB4aFNS3dDQtnrbCb + ePCK48wTN1wXXXzge/jXf/clV55zC4hIIatF0cjIyMikElzc57z0wX+XHd/+2+//99/ywP//xlu//tdb + +eK/4Zp3/1WTOhYzARViNUAluKjTdf37/0ZTTn9TbdhwXblhwwW1/qOP1Ja9+K8w+95/6cm3/6v2Xvkv + qKjniGGAoIqRpW3/4e8S9uGdzFz82gwMDFxAzCxm4ZegtuLDf+VJ1/8rZM25IqLvnM/CximCYYCic1QN + v7x2JIwPwyrJ3XNUylddE9G2TWNmZOBDl4czmJiZMSRBmFdSyYyJgUEQmxwIYxWEYXZBCUls4sgYq6CA + prWNbtG8nXKeaVPR5XiVjSxEzf0yYXy4BBMLO6eQjoOXZvrkbbazrv53Xf/2v4CSbjBMXkhBl1/CMyNZ + qWnvGy5pNQ+YONwAfjXzAOupl/47LLr333L50/96q9/8l23YdES6cO5KuYqVW+R7Tj6SnfP0v4hryjyY + HhQDmFjYeHVKFp7WX/Xuv9Kq9/+Vd/z7r7rv/3+l7f//y676DEwDN/9L+BVvYkKLCTgDhNkkVUyVlr74 + qbbz73/VOTc/qsy89kWx+9h7qbQpJwS1bbOAscGGrB6EUTggLOqf16C55ft/HlnNAFZOXgVWdi4FRgYG + VnR1MIwhwMTCyqEQ37qEmZVDFF0OE/9nAACtFF4Ey6OP+wAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP + WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP + aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+ + 5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8 + vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB + cMaRN0UdBBkAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6 + JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAACXBIWXMAAAsTAAALEwEAmpwYAAACOElE + QVQ4T2P4//8/QczOJyyqHpzfiE0OQwAZC8iqszAzs7CJ69o4BR768V/W2jcGXQ0KB4aFNS3dDQtnrbCb + ePCK48wTN1wXXXzge/jXf/clV55zC4hIIatF0cjIyMikElzc57z0wX+XHd/+2+//99/ywP//xlu//tdb + +eK/4Zp3/1WTOhYzARViNUAluKjTdf37/0ZTTn9TbdhwXblhwwW1/qOP1Ja9+K8w+95/6cm3/6v2Xvkv + qKjniGGAoIqRpW3/4e8S9uGdzFz82gwMDFxAzCxm4ZegtuLDf+VJ1/8rZM25IqLvnM/CximCYYCic1QN + v7x2JIwPwyrJ3XNUylddE9G2TWNmZOBDl4czmJiZMSRBmFdSyYyJgUEQmxwIYxWEYXZBCUls4sgYq6CA + prWNbtG8nXKeaVPR5XiVjSxEzf0yYXy4BBMLO6eQjoOXZvrkbbazrv53Xf/2v4CSbjBMXkhBl1/CMyNZ + qWnvGy5pNQ+YONwAfjXzAOupl/47LLr333L50/96q9/8l23YdES6cO5KuYqVW+R7Tj6SnfP0v4hryjyY + HhQDmFjYeHVKFp7WX/Xuv9Kq9/+Vd/z7r7rv/3+l7f//y676DEwDN/9L+BVvYkKLCTgDhNkkVUyVlr74 + qbbz73/VOTc/qsy89kWx+9h7qbQpJwS1bbOAscGGrB6EUTggLOqf16C55ft/HlnNAFZOXgVWdi4FRgYG + VnR1MIwhwMTCyqEQ37qEmZVDFF0OE/9nAACtFF4Ey6OP+wAAAABJRU5ErkJggg== + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + \ No newline at end of file diff --git a/SCrawler.YouTube/Controls/VideoOptionsForm.vb b/SCrawler.YouTube/Controls/VideoOptionsForm.vb new file mode 100644 index 0000000..534f5ed --- /dev/null +++ b/SCrawler.YouTube/Controls/VideoOptionsForm.vb @@ -0,0 +1,474 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.ComponentModel +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Controls +Imports PersonalUtilities.Forms.Controls.Base +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Imports PersonalUtilities.Tools +Imports SCrawler.API.YouTube.Base +Imports SCrawler.API.YouTube.Objects +Imports SCrawler.DownloadObjects.STDownloader +Imports UMTypes = SCrawler.Plugin.UserMediaTypes +Imports ADB = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons +Namespace API.YouTube.Controls + Friend Class VideoOptionsForm : Implements IDesignXMLContainer +#Region "Declarations" + Private MyView As FormView + Friend Property DesignXML As EContainer Implements IDesignXMLContainer.DesignXML + Private Property DesignXMLNodes As String() Implements IDesignXMLContainer.DesignXMLNodes + Private Property DesignXMLNodeName As String Implements IDesignXMLContainer.DesignXMLNodeName + Private Const ControlsRow As Integer = 6 + Private ReadOnly Property CNT_PROCESSOR As TableControlsProcessor + Friend Property MyContainer As YouTubeMediaContainerBase + Private Initialization As Boolean = True + Private ReadOnly IsSavedObject As Boolean +#End Region +#Region "Initializers" + Friend Sub New(ByVal Container As YouTubeMediaContainerBase, Optional ByVal IsSavedObject As Boolean = False) + InitializeComponent() + MyContainer = Container + CNT_PROCESSOR = New TableControlsProcessor(TP_CONTROLS) + Me.IsSavedObject = IsSavedObject + End Sub +#End Region +#Region "Form handlers" + Private Sub VideoOptionsForm_Load(sender As Object, e As EventArgs) Handles Me.Load + If Not DesignXML Is Nothing Then + MyView = New FormView(Me) With {.LocationOnly = True} + MyView.Import() + MyView.SetFormSize() + End If + + If Not MyContainer Is Nothing Then + With MyContainer + Dim i% + Dim arr$() = Nothing + Dim arrComparer As New FComparer(Of String)(Function(x, y) x.ToLower = y.ToLower) + Dim setDef As Action(Of ComboBox, String) = + Sub(ByVal cmb As ComboBox, ByVal compValue As String) + i = -1 + If Not compValue.IsEmptyString Then i = arr.ListIndexOf(compValue, arrComparer, EDP.ReturnValue) + If i >= 0 Then cmb.SelectedIndex = i Else cmb.SelectedIndex = 0 + End Sub + Dim __audioOnly As Boolean = False + Dim __optionValue$ + + If .HasElements Then + Text = "Playlist" + If Not .PlaylistTitle.IsEmptyString Or Not .Title.IsEmptyString Then Text &= $" - { .PlaylistTitle.IfNullOrEmpty(.Title)}" + TP_MAIN.Controls.Remove(TP_HEADER_BASE) + TP_MAIN.RowStyles(0).Height = 0 + Dim def% = If(IsSavedObject, .ArrayMaxResolution, MyYouTubeSettings.DefaultVideoDefinition.Value) + If IsSavedObject Then + __audioOnly = def = -2 + If def <= 0 Then def = MyYouTubeSettings.DefaultVideoDefinition + Else + If Not def.ValueBetween(-1, 10000) Then def = 1080 + End If + NUM_RES.Value = def + Else + TP_OPTIONS.Controls.Remove(NUM_RES) + TP_OPTIONS.ColumnStyles(3).Width = 0 + Dim img As Image = Nothing + Dim imgUrl$ = .ThumbnailUrlMedia + If Not imgUrl.IsEmptyString Then + img = ImageRenderer.GetImage(SFile.GetBytesFromNet(imgUrl, EDP.ReturnValue), EDP.ReturnValue) + If Not img Is Nothing Then ICON_VIDEO.Image = img : ICON_VIDEO.InitialImage = img + End If + LBL_TITLE.Text = .Title + LBL_TIME.Text = AConvert(Of String)(.Duration, TimeToStringProvider, String.Empty) + TP_HEADER_INFO_2.ColumnStyles(1).Width = MeasureTextDefault(LBL_TIME.Text, LBL_TIME.Font).Width + PaddingE.GetOf({LBL_TIME}).Horizontal + TP_HEADER_INFO_2.Refresh() + LBL_URL.Text = .URL + End If + + If .IsMusic Or __audioOnly Then + OPT_AUDIO.Checked = True + Else + OPT_VIDEO.Checked = True + End If + CMB_FORMAT.Enabled = OPT_VIDEO.Checked + + arr = AvailableVideoFormats + CMB_FORMAT.Items.AddRange(arr) + If IsSavedObject Then + __optionValue = .OutputVideoExtension.IfNullOrEmpty(MyYouTubeSettings.DefaultVideoFormat.Value) + Else + __optionValue = MyYouTubeSettings.DefaultVideoFormat.Value + End If + setDef(CMB_FORMAT, __optionValue) + + arr = AvailableAudioFormats + CMB_AUDIO_CODEC.Items.AddRange(arr) + If IsSavedObject Then + __optionValue = .OutputAudioCodec.IfNullOrEmpty(IIf(.IsMusic, MyYouTubeSettings.DefaultAudioCodecMusic.Value, MyYouTubeSettings.DefaultAudioCodec.Value)) + Else + __optionValue = IIf(.IsMusic, MyYouTubeSettings.DefaultAudioCodecMusic.Value, MyYouTubeSettings.DefaultAudioCodec.Value) + End If + setDef(CMB_AUDIO_CODEC, __optionValue) + + arr = AvailableSubtitlesFormats + CMB_SUBS_FORMAT.Items.AddRange(arr) + If IsSavedObject Then + __optionValue = .OutputSubtitlesFormat.IfNullOrEmpty(IIf(.IsMusic, "LRC", MyYouTubeSettings.DefaultSubtitlesFormat.Value)) + Else + __optionValue = IIf(.IsMusic, "LRC", MyYouTubeSettings.DefaultSubtitlesFormat.Value) + End If + setDef(CMB_SUBS_FORMAT, __optionValue) + + TP_SUBS.Enabled = .Subtitles.Count > 0 + TXT_SUBS_ADDIT.Enabled = .Subtitles.Count > 0 + RefillTextBoxes() + TXT_SUBS_ADDIT.Checked = .PostProcessing_OutputSubtitlesFormats.Count > 0 + TXT_EXTRA_AUDIO_FORMATS.Checked = .PostProcessing_OutputAudioFormats.Count > 0 + TXT_FILE.Text = .File + RefillList() + If OPT_VIDEO.Checked Then + ChangeFileExtension(CMB_FORMAT.Text) + Else + If .HasElements Then NUM_RES.Enabled = False + ChangeFileExtension(CMB_AUDIO_CODEC.Text) + End If + End With + End If + Initialization = False + End Sub + Private Sub VideoOptionsForm_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing + MyView.DisposeIfReady() + End Sub +#End Region +#Region "Refill" + Private Sub RefillList() + Dim i% + Dim h% = -1 + Dim rh% = IIf(MyContainer.HasElements, 60, 25) + Dim s As New Size(Width, h) + Dim CalculateSize As Action(Of Integer) = + Sub(ByVal InitHeight As Integer) + With TP_MAIN.RowStyles(ControlsRow) : .SizeType = SizeType.Absolute : .Height = InitHeight : End With + s.Height = InitHeight + For ii% = 0 To TP_MAIN.RowStyles.Count - 1 + If Not ii = ControlsRow And TP_MAIN.RowStyles(ii).SizeType = SizeType.Absolute Then s.Height += TP_MAIN.RowStyles(ii).Height + Next + s.Height += PaddingE.GetOf({TP_MAIN}).Vertical(TP_MAIN.RowStyles.Count - 3 - IIf(MyContainer.HasElements, 1, 0)) + End Sub + Dim __contentType As UMTypes = IIf(OPT_VIDEO.Checked, UMTypes.Video, UMTypes.Audio) + With TP_CONTROLS + If .Controls.Count > 0 Then + For Each cnt As Control In .Controls : cnt.Dispose() : Next + .Controls.Clear() + End If + .RowStyles.Clear() + .RowCount = 0 + End With + With MyContainer + Dim audio As MediaObject = Nothing + If __contentType = UMTypes.Video Then audio = .SelectedAudio + Dim data As IEnumerable(Of Control) + + If .HasElements Then + data = .Elements.Select(Function(ee) New MediaItem(ee) With {.Dock = DockStyle.Fill, .Checked = ee.Checked, .IgnoreDownloadState = True}) + Else + data = (From m As MediaObject In .Self.MediaObjects + Where m.Type = __contentType + Select New VideoOption(m, audio) With {.Dock = DockStyle.Fill, .Checked = m.Index = MyContainer.SelectedVideoIndex}) + End If + + If data.ListExists Then + With TP_CONTROLS + With .RowStyles + .Clear() + For i = 0 To data.Count - 1 : .Add(New RowStyle(SizeType.Absolute, rh)) : Next + .Add(New RowStyle(SizeType.AutoSize)) + End With + .RowCount = .RowStyles.Count + For i = 0 To data.Count - 1 : .Controls.Add(data(i), 0, i) : Next + .Controls.Cast(Of Control).ToList.ForEach(Sub(ByVal d As Control) + DirectCast(d, ISupportInitialize).EndInit() + If MyContainer.HasElements Then + With DirectCast(d, MediaItem) + AddHandler .CheckedChanged, AddressOf MediaItem_CheckedChanged + AddHandler .Click, AddressOf CNT_PROCESSOR.MediaItem_Click + AddHandler .KeyDown, AddressOf CNT_PROCESSOR.MediaItem_KeyDown + End With + End If + End Sub) + If MyContainer.HasElements Then + If .Controls.Count > 0 Then + Dim cIndx% = 0 + Dim c As Color + For Each cnt As MediaItem In .Controls + cIndx += 1 + If (cIndx Mod 2) = 0 Then c = SystemColors.ControlLight Else c = SystemColors.Window + cnt.BackColor = c + Next + End If + Else + If Not data.ListExists(Function(d As VideoOption) d.Checked) Then + If MyYouTubeSettings.DefaultVideoDefinition > 0 Then + For Each cnt As VideoOption In .Controls + If cnt.MyMedia.Height <= MyYouTubeSettings.DefaultVideoDefinition Then cnt.Checked = True : Exit For + Next + Else + DirectCast(.Controls(0), VideoOption).Checked = True + End If + End If + End If + End With + + h = data.Count * rh + PaddingE.GetOf({TP_CONTROLS}).Vertical(1.5) + CalculateSize(h) + Dim hh% = Screen.PrimaryScreen.WorkingArea.Height - 20 + If s.Height > hh Then + h = 5 * rh + PaddingE.GetOf({TP_CONTROLS}).Vertical(1.5) + CalculateSize(h) + With TP_CONTROLS + .AutoSizeMode = AutoSizeMode.GrowAndShrink + .AutoScroll = True + Dim p As Padding = .Padding + p.Right += 3 + .Padding = p + .VerticalScroll.Visible = True + .VerticalScroll.Enabled = True + .HorizontalScroll.Visible = False + .HorizontalScroll.Enabled = False + End With + End If + + With TP_CONTROLS + .PerformLayout() + .Select() + If .Controls.Count > 0 Then .Controls(0).Focus() + End With + End If + End With + + If s.Height = -1 Then s.Height = rh + MaximumSize = Nothing + MinimumSize = Nothing + Size = s + MinimumSize = Size + MaximumSize = Size + End Sub +#End Region +#Region "Media items' handlers" + Private Sub MediaItem_CheckedChanged(ByVal Sender As MediaItem, ByVal Container As IYouTubeMediaContainer) + ControlInvokeFast(TP_CONTROLS, Sub() Container.Checked = Sender.Checked, EDP.None) + End Sub +#End Region +#Region "OK, Cancel" + Private Sub BTT_DOWN_Click(sender As Object, e As EventArgs) Handles BTT_DOWN.Click + Try + Dim f As SFile + If MyContainer.HasElements Then + f = TXT_FILE.Text.CSFileP + Else + f = TXT_FILE.Text + End If + If f.IsEmptyString Then Throw New ArgumentNullException("File", "The output file cannot be null") + With MyContainer + .OutputVideoExtension = CMB_FORMAT.Text.StringToLower + .OutputAudioCodec = CMB_AUDIO_CODEC.Text.StringToLower + .OutputSubtitlesFormat = CMB_SUBS_FORMAT.Text.StringToLower + + If Not .HasElements Then + Dim cntIndex% = -1 + With TP_CONTROLS.Controls + If .Count > 0 Then + For Each cnt As VideoOption In .Self + If cnt.Checked Then cntIndex = cnt.MyMedia.Index : Exit For + Next + End If + End With + If cntIndex = -1 Then Throw New ArgumentOutOfRangeException("Download option", "What to download is not selected") + If OPT_VIDEO.Checked Then + .SelectedVideoIndex = cntIndex + Else + .SelectedVideoIndex = -1 + .SelectedAudioIndex = cntIndex + End If + .File = f + .FileSetManually = True + .UpdateInfoFields() + '#If DEBUG Then + ' Debug.WriteLine(.Command(False)) + '#End If + Else + If OPT_AUDIO.Checked Then + .SetMaxResolution(-2) + Else + .SetMaxResolution(NUM_RES.Value) + End If + .File = f + End If + End With + + If MyYouTubeSettings.OutputPathAutoChange Then MyYouTubeSettings.OutputPath.Value = f + + DialogResult = DialogResult.OK + Close() + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog + EDP.ShowMainMsg, ex, $"Download {IIf(MyContainer.HasElements, "playlist", "video")}") + End Try + End Sub + Private Sub BTT_CANCEL_Click(sender As Object, e As EventArgs) Handles BTT_CANCEL.Click + DialogResult = DialogResult.Cancel + Close() + End Sub +#End Region +#Region "Controls' handlers" +#Region "Header" + Private Sub LBL_URL_LinkClicked(sender As Object, e As LinkLabelLinkClickedEventArgs) Handles LBL_URL.LinkClicked + If Not LBL_URL.Text.IsEmptyString Then + Try : Process.Start(LBL_URL.Text) : Catch : End Try + End If + End Sub +#End Region +#Region "Settings" + Private Sub OPT_VIDEO_AUDIO_CheckedChanged(sender As Object, e As EventArgs) Handles OPT_VIDEO.CheckedChanged, OPT_AUDIO.CheckedChanged + If Not Initialization Then + CMB_FORMAT.Enabled = OPT_VIDEO.Checked + If MyContainer.HasElements Then + NUM_RES.Enabled = OPT_VIDEO.Checked + Else + RefillList() + If OPT_VIDEO.Checked Then + ChangeFileExtension(CMB_FORMAT.Text) + Else + ChangeFileExtension(CMB_AUDIO_CODEC.Text) + End If + End If + End If + End Sub + Private Sub CMB_FORMAT_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CMB_FORMAT.SelectedIndexChanged + If Not Initialization AndAlso OPT_VIDEO.Checked Then ChangeFileExtension(CMB_FORMAT.Text) + End Sub + Private Sub CMB_AUDIO_CODEC_SelectedIndexChanged(sender As Object, e As EventArgs) Handles CMB_AUDIO_CODEC.SelectedIndexChanged + If Not Initialization AndAlso OPT_AUDIO.Checked Then ChangeFileExtension(CMB_AUDIO_CODEC.Text) + End Sub + Private Sub ChangeFileExtension(ByVal NewExt As String) + If Not MyContainer.HasElements Then + Dim f As SFile = TXT_FILE.Text + f.Extension = NewExt.StringToLower + TXT_FILE.Text = f + End If + End Sub + Private Sub TXT_SUBS_ActionOnButtonClick(ByVal Sender As ActionButton, ByVal e As ActionButtonEventArgs) Handles TXT_SUBS.ActionOnButtonClick + Select Case Sender.DefaultButton + Case ADB.Open + If MyContainer.Subtitles.Count > 0 Then + Using f As New SimpleListForm(Of String)(MyContainer.Subtitles.Select(Function(s) s.Name)) With { + .DesignXML = DesignXML, + .DesignXMLNodeName = SimpleArraysFormNode, + .Mode = SimpleListFormModes.CheckedItems, + .FormText = "Subtitles" + } + With MyContainer + If .SubtitlesSelectedIndexes.Count > 0 Then f.DataSelectedIndexes.AddRange(.SubtitlesSelectedIndexes) + If f.ShowDialog() = DialogResult.OK Then + .SubtitlesSelectedIndexes.Clear() + If f.DataResultIndexes.Count > 0 Then .SubtitlesSelectedIndexes.AddRange(f.DataResultIndexes) + RefillTextBoxes() + End If + End With + End Using + End If + Case ADB.Refresh + MyContainer.SubtitlesSelectedIndexesReset() + RefillTextBoxes() + Case ADB.Clear + MyContainer.SubtitlesSelectedIndexes.Clear() + RefillTextBoxes() + End Select + End Sub + Private Sub CONTROLS_ADDIT_ActionOnButtonClick(ByVal Sender As ActionButton, ByVal e As ActionButtonEventArgs) Handles TXT_SUBS_ADDIT.ActionOnButtonClick, + TXT_EXTRA_AUDIO_FORMATS.ActionOnButtonClick + Dim isSubs As Boolean = CStr(DirectCast(e.AssociatedControl, TextBoxExtended).Tag) = "s" + Select Case Sender.DefaultButton + Case ADB.Open + Using f As New SimpleListForm(Of String)(If(isSubs, AvailableSubtitlesFormats, AvailableAudioFormats)) With { + .DesignXML = DesignXML, + .DesignXMLNodeName = SimpleArraysFormNode, + .Mode = SimpleListFormModes.CheckedItems, + .FormText = DirectCast(e.AssociatedControl, TextBoxExtended).CaptionText + } + With MyContainer + With If(isSubs, .PostProcessing_OutputSubtitlesFormats, .PostProcessing_OutputAudioFormats) + If .Self.Count > 0 Then f.DataSelected.AddRange(.Self) + If f.ShowDialog() = DialogResult.OK Then + .Self.Clear() + If f.DataResultIndexes.Count > 0 Then .Self.AddRange(f.DataResult) + DirectCast(e.AssociatedControl, TextBoxExtended).Text = .ListToString + RefillTextBoxes() + End If + End With + End With + End Using + Case ADB.Refresh + If isSubs Then + MyContainer.PostProcessing_OutputSubtitlesFormats_Reset() + Else + MyContainer.PostProcessing_OutputAudioFormats_Reset() + End If + RefillTextBoxes() + Case ADB.Clear + If isSubs Then + MyContainer.PostProcessing_OutputSubtitlesFormats.Clear() + Else + MyContainer.PostProcessing_OutputAudioFormats.Clear() + End If + RefillTextBoxes() + End Select + End Sub +#End Region +#Region "Footer" + Private Sub BTT_BROWSE_Click(sender As Object, e As EventArgs) Handles BTT_BROWSE.Click + Dim f As SFile +#Disable Warning BC40000 + If MyContainer.HasElements Then + f = TXT_FILE.Text.CSFileP + f = SFile.SelectPath(f, "Select the destination of the video files", EDP.ReturnValue) + Else + f = TXT_FILE.Text + Dim sPattern$ = $"Video|{AvailableVideoFormats.Select(Function(vf) $"*.{vf.ToLower}").ListToString(";")}" & + $"|Audio|{AvailableAudioFormats.Select(Function(af) $"*.{af.ToLower}").ListToString(";")}" & + "|All Files|*.*" + f = SFile.SaveAs(f, "Select the destination of the video file",,, sPattern, EDP.ReturnValue) + End If +#Enable Warning + If Not f.IsEmptyString Then TXT_FILE.Text = f + End Sub +#End Region +#End Region +#Region "Functions" + Private Sub RefillTextBoxes() + With MyContainer + If .SubtitlesSelectedIndexes.Count > 0 Then + TXT_SUBS.Text = ListAddList(Nothing, .Subtitles.Select(Function(s, i) If(.SubtitlesSelectedIndexes.Contains(i), s.ID, String.Empty)), + LAP.NotContainsOnly, EDP.ReturnValue).ListToString(",") + Else + TXT_SUBS.Clear() + End If + If .PostProcessing_OutputSubtitlesFormats.Count > 0 Then + TXT_SUBS_ADDIT.Text = .PostProcessing_OutputSubtitlesFormats.ListToString + Else + TXT_SUBS_ADDIT.Clear() + End If + If .PostProcessing_OutputAudioFormats.Count > 0 Then + TXT_EXTRA_AUDIO_FORMATS.Text = .PostProcessing_OutputAudioFormats.ListToString + Else + TXT_EXTRA_AUDIO_FORMATS.Clear() + End If + End With + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Declarations.vb b/SCrawler.YouTube/Declarations.vb new file mode 100644 index 0000000..171e981 --- /dev/null +++ b/SCrawler.YouTube/Declarations.vb @@ -0,0 +1,112 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Forms.Toolbars +Imports PersonalUtilities.Functions.RegularExpressions +Namespace API.YouTube + Public Module YTDeclarations + Public Const YouTubeSite As String = "YouTube" + Public Const YouTubeSiteKey As String = "AndyProgram_YouTube" + Public Const YouTubeSettingsFile As String = "Settings\SettingsYouTube.xml" + Public Const DownloaderDataFolderYouTube As String = DownloadObjects.STDownloader.DownloaderDataFolder & "YouTube\" + Friend Const YouTubeDownloadPathDefault As String = "YouTubeDownloads\" + Friend Const SimpleArraysFormNode As String = "SimpleFormatsChooserForm" + Public Property MyYouTubeSettings As Base.YouTubeSettings + Public Property MyCache As CacheKeeper + Friend ReadOnly Property MyCacheSettings As New CacheKeeper(DownloaderDataFolderYouTube) With {.DeleteCacheOnDispose = False, .DeleteRootOnDispose = False} + Public ReadOnly Property YouTubeCookieNetscapeFile As New SFile($"Settings\Responser_{YouTubeSite}_Cookies_Netscape.txt") + Friend ReadOnly Property AvailableSubtitlesFormats As String() + Get + Return {"ASS", "LRC", "SRT", "VTT"} + End Get + End Property + Friend ReadOnly Property AvailableVideoFormats As String() + Get + Return {"AVI", "FLV", "GIF", "MKV", "MOV", "MP4", "WEBM", "AAC", "AIFF", "ALAC", "FLAC", "M4A", "MKA", "MP3", "OGG", "OPUS", "VORBIS", "WAV"} + End Get + End Property + Friend ReadOnly Property AvailableAudioFormats As String() + Get + 'AC3 not supported + Return {"AC3", "AAC", "ALAC", "FLAC", "M4A", "MP3", "OPUS", "VORBIS", "WAV"} + End Get + End Property + Friend ReadOnly VideoSizeProvider As New ANumbers(ANumbers.Cultures.USA, ANumbers.Options.DecimalsTrim) With {.DeclaredError = EDP.ReturnValue} + Friend ReadOnly NumberProvider As New ANumbers(ANumbers.Cultures.Primitive) With {.DeclaredError = EDP.ReturnValue} + Friend ReadOnly DateBaseProvider As New ADateTime(ADateTime.Formats.BaseDateTime) + Friend ReadOnly DateAddedProvider As New ADateTime(ADateTime.Formats.yyyymmdd) With {.DateSeparator = String.Empty} + Friend ReadOnly TimeToStringProvider As IFormatProvider = New TimeToStringConverter + Friend ReadOnly TitleHtmlConverter As Func(Of String, String) = Function(Input) Input.StringRemoveWinForbiddenSymbols().StringTrim() + Friend ReadOnly ProgressProvider As IMyProgressNumberProvider = MyProgressNumberProvider.Percentage + Public ReadOnly TrueUrlRegEx As RParams = RParams.DM(Base.YouTubeFunctions.TrueUrlPattern, 0, EDP.ReturnValue) + Private Class TimeToStringConverter : Implements ICustomProvider + Private ReadOnly _Provider As New ADateTime("mm\:ss") With {.TimeParseMode = ADateTime.TimeModes.TimeSpan} + Private ReadOnly _ProviderWithHours As New ADateTime("h\:mm\:ss") With {.TimeParseMode = ADateTime.TimeModes.TimeSpan} + Private ReadOnly Property Provider(ByVal t As TimeSpan) As IFormatProvider + Get + Return If(t.Hours > 0, _ProviderWithHours, _Provider) + End Get + End Property + Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, + Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert + If Not IsNothing(Value) Then + If TypeOf Value Is Nullable(Of TimeSpan) Then + With DirectCast(Value, Nullable(Of TimeSpan)) + If .HasValue Then Return AConvert(Of String)(.Value, Me.Provider(.Value), String.Empty) + End With + ElseIf TypeOf Value Is TimeSpan Then + Dim t As TimeSpan = Value + Return AConvert(Of String)(t, Me.Provider(t), String.Empty) + End If + End If + Return String.Empty + End Function + Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat + Throw New NotImplementedException("'GetFormat' is not available in the 'TimeToStringConverter' context") + End Function + End Class + Friend Class DurationXmlConverter : Implements ICustomProvider + Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, + Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert + Try + If DestinationType Is GetType(String) Then + If IsNothing(Value) Then + Return 0 + ElseIf TypeOf Value Is TimeSpan Then + Return DirectCast(Value, TimeSpan).TotalSeconds + Else + Throw New Exception + End If + ElseIf DestinationType Is GetType(TimeSpan) Then + If IsNothing(Value) Then + Return New TimeSpan + ElseIf TypeOf Value Is String Then + If CStr(Value).IsEmptyString Then + Return New TimeSpan + Else + Return TimeSpan.FromSeconds(AConvert(Of Double)(Value, EDP.ThrowException)) + End If + ElseIf TypeOf Value Is Double Or IsNumeric(Value) Then + Return TimeSpan.FromSeconds(Value) + Else + Throw New Exception + End If + Else + Throw New Exception + End If + Catch ex As Exception + Throw New Exception($"Cannot convert {Value.GetType.Name} to {DestinationType.Name}", ex) + End Try + End Function + Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat + Throw New NotImplementedException("'GetFormat' is not available in the 'DurationXmlConverter' context") + End Function + End Class + End Module +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/IDownloaderSettings.vb b/SCrawler.YouTube/Downloader/IDownloaderSettings.vb new file mode 100644 index 0000000..14ae81f --- /dev/null +++ b/SCrawler.YouTube/Downloader/IDownloaderSettings.vb @@ -0,0 +1,20 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace DownloadObjects.STDownloader + Public Interface IDownloaderSettings + ReadOnly Property ShowNotifications As Boolean + ReadOnly Property ShowNotificationsEveryDownload As Boolean + ReadOnly Property MaxJobsCount As Integer + ReadOnly Property DownloadAutomatically As Boolean + ReadOnly Property RemoveDownloadedAutomatically As Boolean + ReadOnly Property OnItemDoubleClick As DoubleClickBehavior + ReadOnly Property OpenFolderInOtherProgram As Boolean + ReadOnly Property OpenFolderInOtherProgram_Command As String + End Interface +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/MediaItem.Designer.vb b/SCrawler.YouTube/Downloader/MediaItem.Designer.vb new file mode 100644 index 0000000..59365af --- /dev/null +++ b/SCrawler.YouTube/Downloader/MediaItem.Designer.vb @@ -0,0 +1,257 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace DownloadObjects.STDownloader + + Partial Public Class MediaItem : Inherits System.Windows.Forms.UserControl + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Me.components = New System.ComponentModel.Container() + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(MediaItem)) + Me.ICON_VIDEO = New System.Windows.Forms.PictureBox() + Me.CONTEXT_MAIN = New System.Windows.Forms.ContextMenuStrip(Me.components) + Me.BTT_DOWN = New System.Windows.Forms.ToolStripMenuItem() + Me.SEP_DOWN = New System.Windows.Forms.ToolStripSeparator() + Me.BTT_OPEN_FOLDER = New System.Windows.Forms.ToolStripMenuItem() + Me.SEP_FOLDER = New System.Windows.Forms.ToolStripSeparator() + Me.BTT_COPY_LINK = New System.Windows.Forms.ToolStripMenuItem() + Me.BTT_OPEN_IN_BROWSER = New System.Windows.Forms.ToolStripMenuItem() + Me.SEP_DOWN_AGAIN = New System.Windows.Forms.ToolStripSeparator() + Me.BTT_DOWN_AGAIN = New System.Windows.Forms.ToolStripMenuItem() + Me.SEP_DEL = New System.Windows.Forms.ToolStripSeparator() + Me.BTT_REMOVE_FROM_LIST = New System.Windows.Forms.ToolStripMenuItem() + Me.BTT_DELETE_FILE = New System.Windows.Forms.ToolStripMenuItem() + Me.TP_INFO = New System.Windows.Forms.TableLayoutPanel() + Me.TP_CHECKED_TITLE = New System.Windows.Forms.TableLayoutPanel() + Me.LBL_TITLE = New System.Windows.Forms.Label() + Me.CH_CHECKED = New System.Windows.Forms.CheckBox() + Me.BTT_VIEW_SETTINGS = New System.Windows.Forms.ToolStripMenuItem() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + TP_MAIN.SuspendLayout() + CType(Me.ICON_VIDEO, System.ComponentModel.ISupportInitialize).BeginInit() + Me.CONTEXT_MAIN.SuspendLayout() + Me.TP_INFO.SuspendLayout() + Me.TP_CHECKED_TITLE.SuspendLayout() + Me.SuspendLayout() + ' + 'TP_MAIN + ' + TP_MAIN.ColumnCount = 2 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 125.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.Controls.Add(Me.ICON_VIDEO, 0, 0) + TP_MAIN.Controls.Add(Me.TP_INFO, 1, 0) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Margin = New System.Windows.Forms.Padding(0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 1 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 65.0!)) + TP_MAIN.Size = New System.Drawing.Size(549, 65) + TP_MAIN.TabIndex = 0 + AddHandler TP_MAIN.Click, AddressOf Me.Controls_Click + ' + 'ICON_VIDEO + ' + Me.ICON_VIDEO.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom + Me.ICON_VIDEO.ContextMenuStrip = Me.CONTEXT_MAIN + Me.ICON_VIDEO.Dock = System.Windows.Forms.DockStyle.Fill + Me.ICON_VIDEO.Location = New System.Drawing.Point(3, 3) + Me.ICON_VIDEO.Name = "ICON_VIDEO" + Me.ICON_VIDEO.Size = New System.Drawing.Size(119, 59) + Me.ICON_VIDEO.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom + Me.ICON_VIDEO.TabIndex = 0 + Me.ICON_VIDEO.TabStop = False + ' + 'CONTEXT_MAIN + ' + Me.CONTEXT_MAIN.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_DOWN, Me.SEP_DOWN, Me.BTT_OPEN_FOLDER, Me.SEP_FOLDER, Me.BTT_COPY_LINK, Me.BTT_OPEN_IN_BROWSER, Me.SEP_DOWN_AGAIN, Me.BTT_DOWN_AGAIN, Me.BTT_VIEW_SETTINGS, Me.SEP_DEL, Me.BTT_REMOVE_FROM_LIST, Me.BTT_DELETE_FILE}) + Me.CONTEXT_MAIN.Name = "CONTEXT_MAIN" + Me.CONTEXT_MAIN.ShowItemToolTips = False + Me.CONTEXT_MAIN.Size = New System.Drawing.Size(185, 226) + ' + 'BTT_DOWN + ' + Me.BTT_DOWN.Image = Global.SCrawler.My.Resources.Resources.ArrowDownPic_Blue_24 + Me.BTT_DOWN.Name = "BTT_DOWN" + Me.BTT_DOWN.Size = New System.Drawing.Size(184, 22) + Me.BTT_DOWN.Text = "Download" + ' + 'SEP_DOWN + ' + Me.SEP_DOWN.Name = "SEP_DOWN" + Me.SEP_DOWN.Size = New System.Drawing.Size(181, 6) + ' + 'BTT_OPEN_FOLDER + ' + Me.BTT_OPEN_FOLDER.Image = CType(resources.GetObject("BTT_OPEN_FOLDER.Image"), System.Drawing.Image) + Me.BTT_OPEN_FOLDER.Name = "BTT_OPEN_FOLDER" + Me.BTT_OPEN_FOLDER.Size = New System.Drawing.Size(184, 22) + Me.BTT_OPEN_FOLDER.Text = "Open folder" + ' + 'SEP_FOLDER + ' + Me.SEP_FOLDER.Name = "SEP_FOLDER" + Me.SEP_FOLDER.Size = New System.Drawing.Size(181, 6) + ' + 'BTT_COPY_LINK + ' + Me.BTT_COPY_LINK.Image = Global.SCrawler.My.Resources.Resources.LinkPic_32 + Me.BTT_COPY_LINK.Name = "BTT_COPY_LINK" + Me.BTT_COPY_LINK.Size = New System.Drawing.Size(184, 22) + Me.BTT_COPY_LINK.Text = "Copy link address" + ' + 'BTT_OPEN_IN_BROWSER + ' + Me.BTT_OPEN_IN_BROWSER.Image = CType(resources.GetObject("BTT_OPEN_IN_BROWSER.Image"), System.Drawing.Image) + Me.BTT_OPEN_IN_BROWSER.Name = "BTT_OPEN_IN_BROWSER" + Me.BTT_OPEN_IN_BROWSER.Size = New System.Drawing.Size(184, 22) + Me.BTT_OPEN_IN_BROWSER.Text = "Open link in browser" + ' + 'SEP_DOWN_AGAIN + ' + Me.SEP_DOWN_AGAIN.Name = "SEP_DOWN_AGAIN" + Me.SEP_DOWN_AGAIN.Size = New System.Drawing.Size(181, 6) + ' + 'BTT_DOWN_AGAIN + ' + Me.BTT_DOWN_AGAIN.Image = CType(resources.GetObject("BTT_DOWN_AGAIN.Image"), System.Drawing.Image) + Me.BTT_DOWN_AGAIN.Name = "BTT_DOWN_AGAIN" + Me.BTT_DOWN_AGAIN.Size = New System.Drawing.Size(184, 22) + Me.BTT_DOWN_AGAIN.Text = "Download again" + ' + 'SEP_DEL + ' + Me.SEP_DEL.Name = "SEP_DEL" + Me.SEP_DEL.Size = New System.Drawing.Size(181, 6) + ' + 'BTT_REMOVE_FROM_LIST + ' + Me.BTT_REMOVE_FROM_LIST.Image = CType(resources.GetObject("BTT_REMOVE_FROM_LIST.Image"), System.Drawing.Image) + Me.BTT_REMOVE_FROM_LIST.Name = "BTT_REMOVE_FROM_LIST" + Me.BTT_REMOVE_FROM_LIST.Size = New System.Drawing.Size(184, 22) + Me.BTT_REMOVE_FROM_LIST.Text = "Remove from the list" + ' + 'BTT_DELETE_FILE + ' + Me.BTT_DELETE_FILE.Image = CType(resources.GetObject("BTT_DELETE_FILE.Image"), System.Drawing.Image) + Me.BTT_DELETE_FILE.Name = "BTT_DELETE_FILE" + Me.BTT_DELETE_FILE.Size = New System.Drawing.Size(184, 22) + Me.BTT_DELETE_FILE.Text = "Delete file" + ' + 'TP_INFO + ' + Me.TP_INFO.ColumnCount = 1 + Me.TP_INFO.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_INFO.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_INFO.Controls.Add(Me.TP_CHECKED_TITLE, 0, 0) + Me.TP_INFO.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_INFO.Location = New System.Drawing.Point(125, 0) + Me.TP_INFO.Margin = New System.Windows.Forms.Padding(0) + Me.TP_INFO.Name = "TP_INFO" + Me.TP_INFO.RowCount = 2 + Me.TP_INFO.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + Me.TP_INFO.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!)) + Me.TP_INFO.Size = New System.Drawing.Size(424, 65) + Me.TP_INFO.TabIndex = 1 + ' + 'TP_CHECKED_TITLE + ' + Me.TP_CHECKED_TITLE.ColumnCount = 2 + Me.TP_CHECKED_TITLE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_CHECKED_TITLE.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_CHECKED_TITLE.Controls.Add(Me.LBL_TITLE, 1, 0) + Me.TP_CHECKED_TITLE.Controls.Add(Me.CH_CHECKED, 0, 0) + Me.TP_CHECKED_TITLE.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_CHECKED_TITLE.Location = New System.Drawing.Point(0, 0) + Me.TP_CHECKED_TITLE.Margin = New System.Windows.Forms.Padding(0) + Me.TP_CHECKED_TITLE.Name = "TP_CHECKED_TITLE" + Me.TP_CHECKED_TITLE.RowCount = 1 + Me.TP_CHECKED_TITLE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_CHECKED_TITLE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 32.0!)) + Me.TP_CHECKED_TITLE.Size = New System.Drawing.Size(424, 32) + Me.TP_CHECKED_TITLE.TabIndex = 0 + ' + 'LBL_TITLE + ' + Me.LBL_TITLE.ContextMenuStrip = Me.CONTEXT_MAIN + Me.LBL_TITLE.Dock = System.Windows.Forms.DockStyle.Fill + Me.LBL_TITLE.Font = New System.Drawing.Font("Arial", 9.0!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(204, Byte)) + Me.LBL_TITLE.Location = New System.Drawing.Point(23, 0) + Me.LBL_TITLE.Name = "LBL_TITLE" + Me.LBL_TITLE.Size = New System.Drawing.Size(398, 32) + Me.LBL_TITLE.TabIndex = 1 + Me.LBL_TITLE.Text = "Video title" + Me.LBL_TITLE.TextAlign = System.Drawing.ContentAlignment.MiddleLeft + ' + 'CH_CHECKED + ' + Me.CH_CHECKED.AutoSize = True + Me.CH_CHECKED.Dock = System.Windows.Forms.DockStyle.Fill + Me.CH_CHECKED.Location = New System.Drawing.Point(3, 3) + Me.CH_CHECKED.Name = "CH_CHECKED" + Me.CH_CHECKED.Size = New System.Drawing.Size(14, 26) + Me.CH_CHECKED.TabIndex = 0 + Me.CH_CHECKED.UseVisualStyleBackColor = True + ' + 'BTT_VIEW_SETTINGS + ' + Me.BTT_VIEW_SETTINGS.Image = Global.SCrawler.My.Resources.Resources.SettingsPic_16 + Me.BTT_VIEW_SETTINGS.Name = "BTT_VIEW_SETTINGS" + Me.BTT_VIEW_SETTINGS.Size = New System.Drawing.Size(184, 22) + Me.BTT_VIEW_SETTINGS.Text = "View settings" + ' + 'MediaItem + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.Controls.Add(TP_MAIN) + Me.Name = "MediaItem" + Me.Size = New System.Drawing.Size(549, 65) + TP_MAIN.ResumeLayout(False) + CType(Me.ICON_VIDEO, System.ComponentModel.ISupportInitialize).EndInit() + Me.CONTEXT_MAIN.ResumeLayout(False) + Me.TP_INFO.ResumeLayout(False) + Me.TP_CHECKED_TITLE.ResumeLayout(False) + Me.TP_CHECKED_TITLE.PerformLayout() + Me.ResumeLayout(False) + + End Sub + Private WithEvents ICON_VIDEO As PictureBox + Private WithEvents LBL_TITLE As Label + Private WithEvents CONTEXT_MAIN As ContextMenuStrip + Private WithEvents BTT_OPEN_FOLDER As ToolStripMenuItem + Private WithEvents BTT_COPY_LINK As ToolStripMenuItem + Private WithEvents BTT_OPEN_IN_BROWSER As ToolStripMenuItem + Private WithEvents BTT_DOWN_AGAIN As ToolStripMenuItem + Private WithEvents BTT_REMOVE_FROM_LIST As ToolStripMenuItem + Private WithEvents BTT_DELETE_FILE As ToolStripMenuItem + Private WithEvents BTT_DOWN As ToolStripMenuItem + Private WithEvents SEP_DOWN As ToolStripSeparator + Private WithEvents TP_INFO As TableLayoutPanel + Friend WithEvents TP_CHECKED_TITLE As TableLayoutPanel + Private WithEvents CH_CHECKED As CheckBox + Private WithEvents SEP_FOLDER As ToolStripSeparator + Private WithEvents SEP_DOWN_AGAIN As ToolStripSeparator + Private WithEvents SEP_DEL As ToolStripSeparator + Private WithEvents BTT_VIEW_SETTINGS As ToolStripMenuItem + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/MediaItem.resx b/SCrawler.YouTube/Downloader/MediaItem.resx new file mode 100644 index 0000000..e269219 --- /dev/null +++ b/SCrawler.YouTube/Downloader/MediaItem.resx @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + 17, 17 + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + wwAADsMBx2+oZAAAAk9JREFUOE+Nk0tIVGEUgKcWZlZWEK1aZEmZUlmpEQUtrF1Q0WNfUUgtCqKZLMXQ + UPJRmuY4TYYKmbPJcVSCAkuFQislzCx7rSo3Oc5T7zzu17njdZosxMXH/bn/+b97zn/ONfBjAJoPQctR + sB2bH1rsw8OEvnVj0BbUbdNJ19HW2+fmbgZO4wIR2MRm3irIQcsO2RA0Qe3mf9EOajGRuEycuXEiaDky + vdlXTfjrc0Kfu1BfVkJjtqR68G80cfRDIrgSL4Km/fCinFC/GU9JEs7CTUy25hAafYIy8hhl2IEy2Iwy + 0IzacRaq1kNNClizdEHjPhiw4nFcRClKFLukWZvG1PWVuI0GPMVr8LYb8XZcRvX/gqEH0HZSskmPEbyu + w9N+CaV41bS9/Qx86oQPdhh1EOwuwesw4bGdY7LvPoHhNgIlq5nIS5gl0DLoyIF3LfgbDjBhFap348uL + Q5EDyrVEgvZT+Htv4ytYiit/WYzAfh6lIgl1bBB/xQYmi1YQqkomVJmMapbLswhN2ajfX+ErW8dU+Vpc + eUtiBRcIPDpBoKcU99V4uJf1p2Va+2q3QE8hSvcN3PkJUL9r5g6kXW8sUoKRqSE7vlupBCUTrJl6u3SB + PNWfb/GWbyQQ2Z/pgjaJ9XsI91sIjr1H+fgUVeYh/KVrFs8Itp6O1B2RR+fAdhzupEHDXnw3U3GVpuAq + +w/y3l2wnHCNxMrhGIGMcp2WpkyYWeqcC30Co5OYu0gvwZIRtc4b606cpoUYtF9y3BgvNkFSmhcSO25a + TGCkk99shOQwl9bXawAAAABJRU5ErkJggg== + + + + + Qk02AwAAAAAAADYAAAAoAAAAEAAAABAAAAABABgAAAAAAAAAAADDDgAAww4AAAAAAAAAAAAA/wD//wD/ + /wD//wD/9vb29vb29vb29vb29vb29vb29vb29vb2/wD//wD//wD//wD//wD//wD/9vb29vb27t654bNN + 15UA15UA15UA15UA4bNN7t659vb29vb2/wD//wD//wD/9vb29vb25b9s15UA15UA15UA15UA15UA15UA + 15UA15UA5b9s9vb29vb2/wD//wD/9vb247lc15UA15UA5MJ43KYt7+nh7+nh3KYt5MJ415UA15UA5b9s + 9vb2/wD/9vb27t6515UA15UA7N7D69m015UA8e/w8e/w15UA69m07N7D15UA15UA7t659vb29vb24bNN + 15UA5MJ48e/w15UA7uTS8e/w8e/w7uTS15UA8e/w5MJ415UA4bNN9vb29vb215UA15UA15UA15UA15UA + 15UA15UA15UA15UA15UA15UA15UA15UA15UA9vb29vb215UA15UA8e/w8e/w15UA8e/w8e/w8e/w8e/w + 15UA8e/w8e/w15UA15UA9vb29vb215UA15UA8e/w8e/w15UA8e/w8e/w8e/w8e/w15UA8e/w8e/w15UA + 15UA9vb29vb215UA15UA15UA15UA15UA15UA15UA15UA15UA15UA15UA15UA15UA15UA9vb29vb24bNN + 15UA5MJ48e/w15UA7uTS8e/w8e/w7uTS15UA8e/w5MJ415UA4bNN9vb29vb27t6515UA15UA7N7D69m0 + 15UA8e/w8e/w15UA69m07N7D15UA15UA7t659vb2/wD/9vb25b9s15UA15UA5MJ43KYt7+nh7+nh3KYt + 5MJ415UA15UA5b9s9vb2/wD//wD/9vb29vb247lc15UA15UA15UA15UA15UA15UA15UA15UA5b9s9vb2 + 9vb2/wD//wD//wD/9vb29vb27t654bNN15UA15UA15UA15UA4bNN7t659vb29vb2/wD//wD//wD//wD/ + /wD//wD/9vb29vb29vb29vb29vb29vb29vb29vb2/wD//wD//wD//wD/ + + + + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGOfPtRkwAAACBjSFJNAAB6 + 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 + + + \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/MediaItem.vb b/SCrawler.YouTube/Downloader/MediaItem.vb new file mode 100644 index 0000000..e79b510 --- /dev/null +++ b/SCrawler.YouTube/Downloader/MediaItem.vb @@ -0,0 +1,468 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.ComponentModel +Imports SCrawler.API.YouTube +Imports SCrawler.API.YouTube.Objects +Imports SCrawler.API.YouTube.Controls +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Forms.Toolbars +Namespace DownloadObjects.STDownloader + Public Delegate Sub MediaItemEventHandler(ByVal Sender As MediaItem, ByVal Container As IYouTubeMediaContainer) + + Public Class MediaItem : Implements ISupportInitialize +#Region "Events" + Public Event DownloadStarted As MediaItemEventHandler + Public Event FileDownloaded As MediaItemEventHandler + Public Event Removal As MediaItemEventHandler + Public Event DownloadAgain As MediaItemEventHandler + Public Event DownloadRequested As MediaItemEventHandler + Public Event CheckedChanged As MediaItemEventHandler +#End Region +#Region "Declarations" +#Region "Controls" + Private WithEvents TP_CONTROLS As TableLayoutPanel + Private WithEvents TP_PROGRESS As TableLayoutPanel + Private WithEvents ICON_SITE As PictureBox + Private WithEvents ICON_CLOCK As PictureBox + Private WithEvents ICON_WHAT As PictureBox + Private WithEvents LBL_TIME As Label '54 + Private WithEvents ICON_SIZE As PictureBox + Private WithEvents LBL_SIZE As Label '68 + Private WithEvents LBL_INFO As Label + Private WithEvents LBL_PROGRESS As Label + Private WithEvents PR_MAIN As ProgressBar +#End Region + Private ReadOnly BindedControls As List(Of MediaItem) + Public Property MyContainer As IYouTubeMediaContainer + Private ReadOnly Property MyProgress As MyProgress + Public Property UseCookies As Boolean + Public Property Pending As Boolean = False + Public Property Checked As Boolean + Get + Return ControlInvokeFast(CH_CHECKED, Function() CH_CHECKED.Checked, False, EDP.ReturnValue) + End Get + Set(ByVal _Checked As Boolean) + ControlInvokeFast(CH_CHECKED, Sub() CH_CHECKED.Checked = _Checked, EDP.None) + End Set + End Property + Public Property IgnoreDownloadState As Boolean = False + Private ReadOnly FileOption As SFO = SFO.File +#End Region +#Region "Initializers" + Public Sub New() + InitializeComponent() + BindedControls = New List(Of MediaItem) + + CreateLabel(LBL_PROGRESS) + PR_MAIN = New ProgressBar With {.Anchor = AnchorStyles.Left + AnchorStyles.Right, .Size = New Size(.Size.Width, 18), .ContextMenuStrip = CONTEXT_MAIN} + TP_CONTROLS = New TableLayoutPanel With {.Dock = DockStyle.Fill, .ContextMenuStrip = CONTEXT_MAIN} + TP_PROGRESS = New TableLayoutPanel With {.Dock = DockStyle.Fill, .ContextMenuStrip = CONTEXT_MAIN} + With TP_PROGRESS + With .ColumnStyles + .Add(New ColumnStyle(SizeType.Percent, 40)) + .Add(New ColumnStyle(SizeType.Percent, 60)) + End With + .ColumnCount = .ColumnStyles.Count + .RowStyles.Add(New RowStyle(SizeType.Percent, 100)) + .RowCount = .RowStyles.Count + .Controls.Add(PR_MAIN, 0, 0) + .Controls.Add(LBL_PROGRESS, 1, 0) + End With + With TP_CONTROLS + .RowStyles.Add(New RowStyle(SizeType.Percent, 100)) + .RowCount = .RowStyles.Count + End With + + CreateIcon(ICON_SITE) + CreateIcon(ICON_WHAT) + CreateIcon(ICON_CLOCK, My.Resources.ClockPic_16) + CreateLabel(LBL_TIME) + CreateIcon(ICON_SIZE, My.Resources.RulerPic_32) + CreateLabel(LBL_SIZE) + CreateLabel(LBL_INFO) + + MyProgress = New MyProgress(PR_MAIN, LBL_PROGRESS) + End Sub + Private Sub CreateLabel(ByRef LBL As Label) + LBL = New Label With { + .Text = String.Empty, + .Margin = New Padding(0), + .AutoSize = False, + .Dock = DockStyle.Fill, + .TextAlign = ContentAlignment.MiddleLeft, + .Font = New Font("Arial", 9, FontStyle.Bold, GraphicsUnit.Point, 204), + .ForeColor = ForeColorLabels, + .ContextMenuStrip = CONTEXT_MAIN + } + End Sub + Private Sub CreateIcon(ByRef Obj As PictureBox, Optional ByVal Image As Image = Nothing) + Obj = New PictureBox With { + .Margin = New Padding(3), + .BackgroundImageLayout = ImageLayout.Zoom, + .SizeMode = PictureBoxSizeMode.Zoom, + .Dock = DockStyle.Fill, + .Image = Image, + .ContextMenuStrip = CONTEXT_MAIN + } + End Sub + Public Sub New(ByVal Container As IYouTubeMediaContainer) + Me.New + Const d$ = " " & ChrW(183) & " " + MyContainer = Container + MyContainer.Progress = MyProgress + If MyContainer.HasElements Then FileOption = SFO.Path Else FileOption = SFO.File + If Not MyContainer.SiteKey = YouTubeSiteKey Then + BTT_DOWN_AGAIN.Visible = False + SEP_DOWN_AGAIN.Visible = False + End If + + ICON_SITE.Image = MyContainer.SiteIcon + LBL_TIME.Text = AConvert(Of String)(Container.Duration, TimeToStringProvider, String.Empty) + LBL_TITLE.Text = Container.ToString(True) + If Not Container.SiteKey = YouTubeSiteKey And Container.ContentType = Plugin.UserMediaTypes.Picture Then + LBL_INFO.Text = Container.File.Extension.StringToUpper + ElseIf Not Container.IsMusic Then + If Container.Height > 0 Then + LBL_INFO.Text = $"{Container.File.Extension.StringToUpper}{d}{Container.Height}p" + Else + LBL_INFO.Text = Container.File.Extension.StringToUpper + End If + Else + If Container.Bitrate > 0 Then + LBL_INFO.Text = $"{Container.File.Extension.StringToUpper}{d}{Container.Bitrate}k" + Else + LBL_INFO.Text = Container.File.Extension.StringToUpper + End If + End If + UpdateMediaIcon() + End Sub +#End Region +#Region "Control handlers" + Private Sub MediaItem_Load(sender As Object, e As EventArgs) Handles Me.Load + RefillControls() + End Sub + Private Sub MediaItem_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + BindedControls.Clear() + MyProgress.Dispose() + ICON_SITE.Dispose() + ICON_CLOCK.Dispose() + ICON_WHAT.Dispose() + LBL_TIME.Dispose() + ICON_SIZE.Dispose() + LBL_SIZE.Dispose() + LBL_INFO.Dispose() + LBL_PROGRESS.Dispose() + PR_MAIN.Dispose() + TP_CONTROLS.Controls.Clear() + TP_CONTROLS.Dispose() + TP_PROGRESS.Controls.Clear() + TP_PROGRESS.Dispose() + End Sub +#End Region +#Region "RefillControls" + Private Sub UpdateMediaIcon() + ControlInvokeFast(Me, Sub() + With MyContainer + If Not .SiteKey = YouTubeSiteKey And .ContentType = Plugin.UserMediaTypes.Picture Then + ICON_WHAT.Image = My.Resources.ImagePic_32 + ElseIf Not .IsMusic Then + ICON_WHAT.Image = My.Resources.VideoCamera_32 + Else + ICON_WHAT.Image = My.Resources.AudioMusic_32 + End If + End With + End Sub, EDP.None) + End Sub + Private Sub RefillControls() + ControlInvokeFast(Me, AddressOf RefillControlsImpl, EDP.None) + End Sub + Private Sub RefillControlsImpl() + With MyContainer + If ICON_VIDEO.Image Is Nothing Then + If .ThumbnailFile.Exists Then + ICON_VIDEO.Image = ImageRenderer.GetImage(SFile.GetBytes(.ThumbnailFile, EDP.ReturnValue), EDP.ReturnValue) + ElseIf Not .ThumbnailUrlMedia.IsEmptyString Then + ICON_VIDEO.Image = ImageRenderer.GetImage(SFile.GetBytesFromNet(.ThumbnailUrlMedia, EDP.ReturnValue), EDP.ReturnValue) + End If + End If + Dim s%, t% + Dim sv% = .Size / 1024 + If sv >= 1000 Then + LBL_SIZE.Text = AConvert(Of String)(sv / 1024, VideoSizeProvider) + LBL_SIZE.Text &= " GB" + Else + LBL_SIZE.Text = AConvert(Of String)(sv, VideoSizeProvider) + LBL_SIZE.Text &= " MB" + End If + If .Size > 0 Then + s = MeasureTextDefault(LBL_SIZE.Text, LBL_SIZE.Font).Width + Else + s = 0 + End If + If .Duration.TotalSeconds > 0 Then + t = MeasureTextDefault(LBL_TIME.Text, LBL_TIME.Font).Width + Else + t = 0 + End If + + LBL_TITLE.Text = MyContainer.ToString(True) + + If Not .SiteKey = YouTubeSiteKey Then BTT_VIEW_SETTINGS.Visible = False + + With TP_CONTROLS + .Controls.Clear() + .ColumnStyles.Clear() + .ColumnCount = 0 + If IgnoreDownloadState Or MyContainer.MediaState = Plugin.UserMediaStates.Downloaded Then + If Not MyContainer.SiteKey = YouTubeSiteKey Then UpdateMediaIcon() + If IgnoreDownloadState Then + BTT_OPEN_FOLDER.Visible = False + SEP_FOLDER.Visible = False + BTT_DOWN_AGAIN.Visible = False + SEP_DOWN_AGAIN.Visible = False + BTT_REMOVE_FROM_LIST.Visible = False + BTT_DELETE_FILE.Visible = False + SEP_DEL.Visible = False + End If + BTT_DOWN.Visible = False + SEP_DOWN.Visible = False + BTT_VIEW_SETTINGS.Visible = False + With .ColumnStyles + .Add(New ColumnStyle(SizeType.Absolute, 30)) + .Add(New ColumnStyle(SizeType.Absolute, 30)) + .Add(New ColumnStyle(SizeType.Absolute, IIf(t = 0, 0, 30))) + .Add(New ColumnStyle(SizeType.Absolute, t)) + .Add(New ColumnStyle(SizeType.Absolute, IIf(s = 0, 0, 30))) + .Add(New ColumnStyle(SizeType.Absolute, s)) + .Add(New ColumnStyle(SizeType.Percent, 100)) + End With + .ColumnCount = .ColumnStyles.Count + With .Controls + .Add(ICON_SITE, 0, 0) + .Add(ICON_WHAT, 1, 0) + If t > 0 Then + .Add(ICON_CLOCK, 2, 0) + .Add(LBL_TIME, 3, 0) + End If + If s > 0 Then + .Add(ICON_SIZE, 4, 0) + .Add(LBL_SIZE, 5, 0) + End If + .Add(LBL_INFO, 6, 0) + End With + Else + With .ColumnStyles + .Add(New ColumnStyle(SizeType.Absolute, 100)) + .Add(New ColumnStyle(SizeType.Percent, 100)) + End With + .ColumnCount = .ColumnStyles.Count + With .Controls + .Add(PR_MAIN, 0, 0) + .Add(LBL_PROGRESS, 1, 0) + End With + End If + End With + TP_INFO.Controls.Add(TP_CONTROLS, 0, 1) + BTT_OPEN_FOLDER.Enabled = .File.Exists(FileOption, False) + BTT_DELETE_FILE.Enabled = BTT_OPEN_FOLDER.Enabled + End With + End Sub +#End Region +#Region "Context buttons' handlers" + Public Sub AddToQueue() + ControlInvokeFast(Me, Sub() + BTT_DOWN.Visible = False + SEP_DOWN.Visible = False + End Sub, EDP.None) + End Sub + Private Sub BTT_DOWN_Click(sender As Object, e As EventArgs) Handles BTT_DOWN.Click + RaiseEvent DownloadRequested(Me, MyContainer) + End Sub + Public Sub Download(ByVal Token As Threading.CancellationToken) + Try + If Not MyContainer Is Nothing Then + RaiseEvent DownloadStarted(Me, MyContainer) + AddToQueue() + MyContainer.Download(UseCookies, Token) + MyContainer.Save() + Pending = False + RefillControls() + RaiseEvent FileDownloaded(Me, MyContainer) + End If + Catch dex As ObjectDisposedException When MyContainer.IsDisposed + Catch oex As OperationCanceledException When Token.IsCancellationRequested + Throw oex + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"MediaItem.Download:{vbCr}{MyContainer.ToString}{vbCr}{MyContainer.URL})") + End Try + End Sub +#End Region +#Region "Colors" + Private ReadOnly ForeColorLabels As Color = SystemColors.ControlDark + Private ForeColorDefault As Color + Public Overrides Property ForeColor As Color + Get + Return MyBase.ForeColor + End Get + Set(ByVal c As Color) + ForeColorDefault = c + MyBase.ForeColor = c + End Set + End Property + Private BackColorDefault As Color + Public Overrides Property BackColor As Color + Get + Return MyBase.BackColor + End Get + Set(ByVal c As Color) + BackColorDefault = c + MyBase.BackColor = c + End Set + End Property + Private IsActiveControl As Boolean = False + Private Sub DropColor() + IsActiveControl = False + MyBase.BackColor = BackColorDefault + MyBase.ForeColor = ForeColorDefault + ChangeLabelsColor(ForeColorLabels) + End Sub + Private Sub ChangeLabelsColor(ByVal ForeColor As Color) + ControlInvokeFast(Me, Sub() + LBL_TIME.ForeColor = ForeColor + LBL_SIZE.ForeColor = ForeColor + LBL_INFO.ForeColor = ForeColor + LBL_PROGRESS.ForeColor = ForeColor + End Sub, EDP.None) + End Sub +#End Region +#Region "Click handlers" + Public Sub PerformClick() + Controls_Click(Me, EventArgs.Empty) + End Sub + Private Sub Controls_Click(sender As Object, e As EventArgs) Handles ICON_VIDEO.MouseClick, CH_CHECKED.MouseClick, LBL_TITLE.MouseClick, TP_INFO.MouseClick, + TP_CONTROLS.MouseClick, TP_PROGRESS.MouseClick, ICON_SITE.MouseClick, ICON_CLOCK.MouseClick, + ICON_WHAT.MouseClick, LBL_TIME.MouseClick, ICON_SIZE.MouseClick, LBL_INFO.MouseClick, + LBL_PROGRESS.MouseClick, PR_MAIN.MouseClick, CONTEXT_MAIN.Opened + IsActiveControl = True + MyBase.BackColor = SystemColors.Highlight + MyBase.ForeColor = SystemColors.HighlightText + ChangeLabelsColor(SystemColors.HighlightText) + BindedControls.ForEach(Sub(c) c.DropColor()) + OnClick(e) + End Sub + Private Sub Controls_DoubleClick(sender As Object, e As EventArgs) Handles ICON_VIDEO.DoubleClick, LBL_TITLE.DoubleClick, TP_INFO.DoubleClick, + TP_CONTROLS.DoubleClick, TP_PROGRESS.DoubleClick, ICON_SITE.DoubleClick, ICON_CLOCK.DoubleClick, + ICON_WHAT.DoubleClick, LBL_TIME.DoubleClick, ICON_SIZE.DoubleClick, LBL_INFO.DoubleClick, + LBL_PROGRESS.DoubleClick, PR_MAIN.DoubleClick + Controls_Click(sender, e) + If Not IgnoreDownloadState AndAlso Not MyDownloaderSettings.OnItemDoubleClick = DoubleClickBehavior.None Then + Dim m As New MMessage("The specified path was not found.", "Open file/folder",, vbExclamation) + If MyDownloaderSettings.OnItemDoubleClick = DoubleClickBehavior.File Then + If FileOption = SFO.File And MyContainer.File.Exists(SFO.File, False) Then + MyContainer.File.Open(SFO.File,, EDP.ShowMainMsg) + ElseIf MyContainer.File.Exists(SFO.Path, False) Then + MyContainer.File.Open(SFO.Path,, EDP.ShowMainMsg) + Else + m.Show() + End If + Else + If MyContainer.File.Exists(SFO.Path, False) Then MyContainer.File.Open(SFO.Path,, EDP.ShowMainMsg) Else m.Show() + End If + End If + OnDoubleClick(e) + End Sub + Private Sub CH_CHECKED_CheckedChanged(sender As Object, e As EventArgs) Handles CH_CHECKED.CheckedChanged + RaiseEvent CheckedChanged(Me, MyContainer) + End Sub + Protected Overrides Function ProcessDialogKey(ByVal KeyData As Keys) As Boolean + If IsActiveControl Then + If KeyData = Keys.Down Or KeyData = Keys.Up Then + OnKeyDown(New KeyEventArgs(KeyData)) + Return True + Else + Return MyBase.ProcessDialogKey(KeyData) + End If + Else + Return False + End If + End Function +#End Region +#Region "Context buttons' handlers" + Private Sub BTT_OPEN_FOLDER_Click(sender As Object, e As EventArgs) Handles BTT_OPEN_FOLDER.Click + If MyContainer.File.Exists(FileOption, False) Then GlobalOpenPath(MyContainer.File) + End Sub + Private Sub BTT_COPY_LINK_Click(sender As Object, e As EventArgs) Handles BTT_COPY_LINK.Click + If Not MyContainer.URL.IsEmptyString Then + BufferText = MyContainer.URL + Else + MsgBoxE({"Media URL is not found", "Copy media URL"}, vbExclamation) + End If + End Sub + Private Sub BTT_OPEN_IN_BROWSER_Click(sender As Object, e As EventArgs) Handles BTT_OPEN_IN_BROWSER.Click + If Not MyContainer.URL_BASE.IsEmptyString Then + Try : Process.Start(MyContainer.URL_BASE) : Catch : End Try + Else + MsgBoxE({"Media URL is not found", "Open link in browser"}, vbExclamation) + End If + End Sub + Private Sub BTT_DOWN_AGAIN_Click(sender As Object, e As EventArgs) Handles BTT_DOWN_AGAIN.Click + RaiseEvent DownloadAgain(Me, MyContainer) + End Sub + Private Sub BTT_VIEW_SETTINGS_Click(sender As Object, e As EventArgs) Handles BTT_VIEW_SETTINGS.Click + If Not MyContainer Is Nothing Then + Dim f As Form = Nothing + Select Case MyContainer.ObjectType + Case Base.YouTubeMediaType.Single : f = New VideoOptionsForm(MyContainer, True) + Case Base.YouTubeMediaType.Channel, Base.YouTubeMediaType.PlayList + If MyContainer.IsMusic Then + f = New MusicPlaylistsForm(MyContainer) + Else + f = New VideoOptionsForm(MyContainer, True) + End If + End Select + If Not f Is Nothing Then + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then MyContainer.Save() + f.Dispose() + End If + End If + End Sub + Private Sub BTT_REMOVE_FROM_LIST_Click(sender As Object, e As EventArgs) Handles BTT_REMOVE_FROM_LIST.Click + RaiseEvent Removal(Me, MyContainer) + End Sub + Private Sub BTT_DELETE_FILE_Click(sender As Object, e As EventArgs) Handles BTT_DELETE_FILE.Click + If MsgBoxE({$"Are you sure you want to delete the following {FileOption.ToString.ToLower}:{vbCr}" & + If(FileOption = SFO.File, MyContainer.File.ToString, MyContainer.File.PathWithSeparator), + $"Deleting a {FileOption.ToString.ToLower}"}, vbExclamation,,, {"Process", "Cancel"}) = 0 Then + MyContainer.Delete(True) + RaiseEvent Removal(Me, MyContainer) + End If + End Sub +#End Region +#Region "ISupportInitialize Support" + Public Sub BeginInit() Implements ISupportInitialize.BeginInit + End Sub + Public Sub EndInit() Implements ISupportInitialize.EndInit + If Not Parent Is Nothing AndAlso TypeOf Parent Is TableLayoutPanel Then + With DirectCast(Parent, TableLayoutPanel) + If .Controls.Count > 0 Then + For Each cnt As Control In .Controls + If Not cnt Is Nothing AndAlso TypeOf cnt Is MediaItem AndAlso Not cnt Is Me Then + With DirectCast(cnt, MediaItem) + If Not BindedControls.Contains(cnt) Then BindedControls.Add(cnt) + End With + End If + Next + End If + End With + End If + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/Notificator.vb b/SCrawler.YouTube/Downloader/Notificator.vb new file mode 100644 index 0000000..4550236 --- /dev/null +++ b/SCrawler.YouTube/Downloader/Notificator.vb @@ -0,0 +1,32 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Tools.Notifications +Namespace DownloadObjects.STDownloader + Public Interface INotificator + Sub Clear() + Sub ShowNotification(ByVal Text As String, ByVal Image As SFile) + End Interface + Friend Class YTNotificator : Implements INotificator + Private WithEvents Notificator As NotificationsManager + Private ReadOnly Property SourceForm As Form + Friend Sub New(ByRef Source As Form) + Notificator = New NotificationsManager + SourceForm = Source + End Sub + Friend Sub Clear() Implements INotificator.Clear + Notificator.Clear() + End Sub + Friend Sub ShowNotification(ByVal Text As String, ByVal Image As SFile) Implements INotificator.ShowNotification + If MyDownloaderSettings.ShowNotifications Then Notification.ShowNotification(Text,,, Image) + End Sub + Private Sub Notificator_OnClicked(ByVal Key As String) Handles Notificator.OnClicked + SourceForm.FormShowS + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/STDownloaderDeclarations.vb b/SCrawler.YouTube/Downloader/STDownloaderDeclarations.vb new file mode 100644 index 0000000..f21cd65 --- /dev/null +++ b/SCrawler.YouTube/Downloader/STDownloaderDeclarations.vb @@ -0,0 +1,20 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace DownloadObjects.STDownloader + Public Module STDownloaderDeclarations + Public Const DownloaderDataFolder As String = "Settings\DownloaderData\" + Public Enum DoubleClickBehavior As Integer + None = SFO.None + Folder = SFO.Path + File = SFO.File + End Enum + Public Property MyNotificator As INotificator + Public Property MyDownloaderSettings As IDownloaderSettings + End Module +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/VideoListForm.Designer.vb b/SCrawler.YouTube/Downloader/VideoListForm.Designer.vb new file mode 100644 index 0000000..bd7c91f --- /dev/null +++ b/SCrawler.YouTube/Downloader/VideoListForm.Designer.vb @@ -0,0 +1,306 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace DownloadObjects.STDownloader + + Partial Public Class VideoListForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Dim SEP_2 As System.Windows.Forms.ToolStripSeparator + Dim SEP_3 As System.Windows.Forms.ToolStripSeparator + Dim MENU_ADD_SEP_1 As System.Windows.Forms.ToolStripSeparator + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(VideoListForm)) + Me.TOOLBAR_BOTTOM = New System.Windows.Forms.StatusStrip() + Me.PR_MAIN = New System.Windows.Forms.ToolStripProgressBar() + Me.LBL_INFO = New System.Windows.Forms.ToolStripStatusLabel() + Me.TP_CONTROLS = New System.Windows.Forms.TableLayoutPanel() + Me.TOOLBAR_TOP = New System.Windows.Forms.ToolStrip() + Me.BTT_SETTINGS = New System.Windows.Forms.ToolStripButton() + Me.SEP_1 = New System.Windows.Forms.ToolStripSeparator() + Me.MENU_ADD = New System.Windows.Forms.ToolStripDropDownButton() + Me.BTT_ADD = New PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick() + Me.BTT_ADD_PLS_ARR = New PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick() + Me.BTT_ADD_NO_SHORTS = New PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick() + Me.BTT_ADD_SHORTS_ONLY = New PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick() + Me.BTT_DOWN = New System.Windows.Forms.ToolStripButton() + Me.BTT_STOP = New System.Windows.Forms.ToolStripButton() + Me.BTT_DELETE = New System.Windows.Forms.ToolStripButton() + Me.BTT_CLEAR_DONE = New System.Windows.Forms.ToolStripButton() + Me.BTT_CLEAR_ALL = New System.Windows.Forms.ToolStripButton() + Me.SEP_LOG = New System.Windows.Forms.ToolStripSeparator() + Me.BTT_LOG = New System.Windows.Forms.ToolStripButton() + Me.BTT_INFO = New System.Windows.Forms.ToolStripButton() + Me.BTT_DONATE = New System.Windows.Forms.ToolStripButton() + SEP_2 = New System.Windows.Forms.ToolStripSeparator() + SEP_3 = New System.Windows.Forms.ToolStripSeparator() + MENU_ADD_SEP_1 = New System.Windows.Forms.ToolStripSeparator() + Me.TOOLBAR_BOTTOM.SuspendLayout() + Me.TOOLBAR_TOP.SuspendLayout() + Me.SuspendLayout() + ' + 'SEP_2 + ' + SEP_2.Name = "SEP_2" + SEP_2.Size = New System.Drawing.Size(6, 25) + ' + 'SEP_3 + ' + SEP_3.Name = "SEP_3" + SEP_3.Size = New System.Drawing.Size(6, 25) + ' + 'MENU_ADD_SEP_1 + ' + MENU_ADD_SEP_1.Name = "MENU_ADD_SEP_1" + MENU_ADD_SEP_1.Size = New System.Drawing.Size(181, 6) + ' + 'TOOLBAR_BOTTOM + ' + Me.TOOLBAR_BOTTOM.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.PR_MAIN, Me.LBL_INFO}) + Me.TOOLBAR_BOTTOM.Location = New System.Drawing.Point(0, 439) + Me.TOOLBAR_BOTTOM.Name = "TOOLBAR_BOTTOM" + Me.TOOLBAR_BOTTOM.Size = New System.Drawing.Size(584, 22) + Me.TOOLBAR_BOTTOM.TabIndex = 0 + ' + 'PR_MAIN + ' + Me.PR_MAIN.Name = "PR_MAIN" + Me.PR_MAIN.Size = New System.Drawing.Size(200, 16) + ' + 'LBL_INFO + ' + Me.LBL_INFO.Name = "LBL_INFO" + Me.LBL_INFO.Size = New System.Drawing.Size(0, 17) + ' + 'TP_CONTROLS + ' + Me.TP_CONTROLS.AutoScroll = True + Me.TP_CONTROLS.BackColor = System.Drawing.SystemColors.Window + Me.TP_CONTROLS.ColumnCount = 1 + Me.TP_CONTROLS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_CONTROLS.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_CONTROLS.Location = New System.Drawing.Point(0, 25) + Me.TP_CONTROLS.Name = "TP_CONTROLS" + Me.TP_CONTROLS.RowCount = 1 + Me.TP_CONTROLS.RowStyles.Add(New System.Windows.Forms.RowStyle()) + Me.TP_CONTROLS.Size = New System.Drawing.Size(584, 414) + Me.TP_CONTROLS.TabIndex = 0 + ' + 'TOOLBAR_TOP + ' + Me.TOOLBAR_TOP.GripStyle = System.Windows.Forms.ToolStripGripStyle.Hidden + Me.TOOLBAR_TOP.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_SETTINGS, Me.SEP_1, Me.MENU_ADD, SEP_2, Me.BTT_DOWN, Me.BTT_STOP, SEP_3, Me.BTT_DELETE, Me.BTT_CLEAR_DONE, Me.BTT_CLEAR_ALL, Me.SEP_LOG, Me.BTT_LOG, Me.BTT_INFO, Me.BTT_DONATE}) + Me.TOOLBAR_TOP.Location = New System.Drawing.Point(0, 0) + Me.TOOLBAR_TOP.Name = "TOOLBAR_TOP" + Me.TOOLBAR_TOP.Size = New System.Drawing.Size(584, 25) + Me.TOOLBAR_TOP.TabIndex = 2 + ' + 'BTT_SETTINGS + ' + Me.BTT_SETTINGS.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image + Me.BTT_SETTINGS.Image = Global.SCrawler.My.Resources.Resources.SettingsPic_16 + Me.BTT_SETTINGS.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_SETTINGS.Name = "BTT_SETTINGS" + Me.BTT_SETTINGS.Size = New System.Drawing.Size(23, 22) + Me.BTT_SETTINGS.Text = "Settings" + ' + 'SEP_1 + ' + Me.SEP_1.Name = "SEP_1" + Me.SEP_1.Size = New System.Drawing.Size(6, 25) + ' + 'MENU_ADD + ' + Me.MENU_ADD.AutoToolTip = False + Me.MENU_ADD.DropDownItems.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_ADD, Me.BTT_ADD_PLS_ARR, MENU_ADD_SEP_1, Me.BTT_ADD_NO_SHORTS, Me.BTT_ADD_SHORTS_ONLY}) + Me.MENU_ADD.Image = CType(resources.GetObject("MENU_ADD.Image"), System.Drawing.Image) + Me.MENU_ADD.ImageTransparentColor = System.Drawing.Color.Magenta + Me.MENU_ADD.Name = "MENU_ADD" + Me.MENU_ADD.Size = New System.Drawing.Size(84, 22) + Me.MENU_ADD.Text = "Add (Ins)" + ' + 'BTT_ADD + ' + Me.BTT_ADD.AutoToolTip = True + Me.BTT_ADD.Image = CType(resources.GetObject("BTT_ADD.Image"), System.Drawing.Image) + Me.BTT_ADD.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_ADD.Name = "BTT_ADD" + Me.BTT_ADD.Size = New System.Drawing.Size(184, 22) + Me.BTT_ADD.Tag = "a" + Me.BTT_ADD.Text = "Add (Ins)" + Me.BTT_ADD.ToolTipText = "Click to add." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Ctrl+click to use cookies for download (if supported)." + ' + 'BTT_ADD_PLS_ARR + ' + Me.BTT_ADD_PLS_ARR.AutoToolTip = True + Me.BTT_ADD_PLS_ARR.Image = CType(resources.GetObject("BTT_ADD_PLS_ARR.Image"), System.Drawing.Image) + Me.BTT_ADD_PLS_ARR.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_ADD_PLS_ARR.Name = "BTT_ADD_PLS_ARR" + Me.BTT_ADD_PLS_ARR.Size = New System.Drawing.Size(184, 22) + Me.BTT_ADD_PLS_ARR.Tag = "pls" + Me.BTT_ADD_PLS_ARR.Text = "Add playlist array" + Me.BTT_ADD_PLS_ARR.ToolTipText = "Click to add." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Ctrl+click to use cookies for download (if supported)." + ' + 'BTT_ADD_NO_SHORTS + ' + Me.BTT_ADD_NO_SHORTS.AutoToolTip = True + Me.BTT_ADD_NO_SHORTS.Image = CType(resources.GetObject("BTT_ADD_NO_SHORTS.Image"), System.Drawing.Image) + Me.BTT_ADD_NO_SHORTS.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_ADD_NO_SHORTS.Name = "BTT_ADD_NO_SHORTS" + Me.BTT_ADD_NO_SHORTS.Size = New System.Drawing.Size(184, 22) + Me.BTT_ADD_NO_SHORTS.Tag = "ans" + Me.BTT_ADD_NO_SHORTS.Text = "Add (without Shorts)" + Me.BTT_ADD_NO_SHORTS.ToolTipText = "Download all videos except 'Shorts'." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Click to add." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Ctrl+click to use cookies fo" & + "r download (if supported)." + ' + 'BTT_ADD_SHORTS_ONLY + ' + Me.BTT_ADD_SHORTS_ONLY.AutoToolTip = True + Me.BTT_ADD_SHORTS_ONLY.Image = CType(resources.GetObject("BTT_ADD_SHORTS_ONLY.Image"), System.Drawing.Image) + Me.BTT_ADD_SHORTS_ONLY.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_ADD_SHORTS_ONLY.Name = "BTT_ADD_SHORTS_ONLY" + Me.BTT_ADD_SHORTS_ONLY.Size = New System.Drawing.Size(184, 22) + Me.BTT_ADD_SHORTS_ONLY.Tag = "as" + Me.BTT_ADD_SHORTS_ONLY.Text = "Add (Shorts only)" + Me.BTT_ADD_SHORTS_ONLY.ToolTipText = "Download only 'Shorts' videos." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Click to add." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Ctrl+click to use cookies for down" & + "load (if supported)." + ' + 'BTT_DOWN + ' + Me.BTT_DOWN.Image = CType(resources.GetObject("BTT_DOWN.Image"), System.Drawing.Image) + Me.BTT_DOWN.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_DOWN.Name = "BTT_DOWN" + Me.BTT_DOWN.Size = New System.Drawing.Size(81, 22) + Me.BTT_DOWN.Text = "Download" + Me.BTT_DOWN.ToolTipText = "Download pending items" + ' + 'BTT_STOP + ' + Me.BTT_STOP.AutoToolTip = False + Me.BTT_STOP.Image = CType(resources.GetObject("BTT_STOP.Image"), System.Drawing.Image) + Me.BTT_STOP.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_STOP.Name = "BTT_STOP" + Me.BTT_STOP.Size = New System.Drawing.Size(51, 22) + Me.BTT_STOP.Text = "Stop" + ' + 'BTT_DELETE + ' + Me.BTT_DELETE.Image = CType(resources.GetObject("BTT_DELETE.Image"), System.Drawing.Image) + Me.BTT_DELETE.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_DELETE.Name = "BTT_DELETE" + Me.BTT_DELETE.Size = New System.Drawing.Size(60, 22) + Me.BTT_DELETE.Text = "Delete" + Me.BTT_DELETE.ToolTipText = "Delete selected items" + ' + 'BTT_CLEAR_DONE + ' + Me.BTT_CLEAR_DONE.Image = CType(resources.GetObject("BTT_CLEAR_DONE.Image"), System.Drawing.Image) + Me.BTT_CLEAR_DONE.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_CLEAR_DONE.Name = "BTT_CLEAR_DONE" + Me.BTT_CLEAR_DONE.Size = New System.Drawing.Size(54, 22) + Me.BTT_CLEAR_DONE.Text = "Clear" + Me.BTT_CLEAR_DONE.ToolTipText = "Remove all downloaded items" + ' + 'BTT_CLEAR_ALL + ' + Me.BTT_CLEAR_ALL.Image = CType(resources.GetObject("BTT_CLEAR_ALL.Image"), System.Drawing.Image) + Me.BTT_CLEAR_ALL.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_CLEAR_ALL.Name = "BTT_CLEAR_ALL" + Me.BTT_CLEAR_ALL.Size = New System.Drawing.Size(69, 22) + Me.BTT_CLEAR_ALL.Text = "Clear all" + Me.BTT_CLEAR_ALL.ToolTipText = "Remove all items (pending and downloaded)" + ' + 'SEP_LOG + ' + Me.SEP_LOG.Name = "SEP_LOG" + Me.SEP_LOG.Size = New System.Drawing.Size(6, 25) + ' + 'BTT_LOG + ' + Me.BTT_LOG.AutoToolTip = False + Me.BTT_LOG.Image = CType(resources.GetObject("BTT_LOG.Image"), System.Drawing.Image) + Me.BTT_LOG.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_LOG.Name = "BTT_LOG" + Me.BTT_LOG.Size = New System.Drawing.Size(50, 22) + Me.BTT_LOG.Text = "LOG" + ' + 'BTT_INFO + ' + Me.BTT_INFO.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right + Me.BTT_INFO.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image + Me.BTT_INFO.Image = Global.SCrawler.My.Resources.Resources.InfoPic_32 + Me.BTT_INFO.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_INFO.Name = "BTT_INFO" + Me.BTT_INFO.Size = New System.Drawing.Size(23, 22) + Me.BTT_INFO.ToolTipText = "Show program information and check for updates" + ' + 'BTT_DONATE + ' + Me.BTT_DONATE.Alignment = System.Windows.Forms.ToolStripItemAlignment.Right + Me.BTT_DONATE.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image + Me.BTT_DONATE.Image = Global.SCrawler.My.Resources.Resources.HeartPic_32 + Me.BTT_DONATE.ImageTransparentColor = System.Drawing.Color.Magenta + Me.BTT_DONATE.Name = "BTT_DONATE" + Me.BTT_DONATE.Size = New System.Drawing.Size(23, 22) + Me.BTT_DONATE.ToolTipText = "Support" + ' + 'VideoListForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(584, 461) + Me.Controls.Add(Me.TP_CONTROLS) + Me.Controls.Add(Me.TOOLBAR_TOP) + Me.Controls.Add(Me.TOOLBAR_BOTTOM) + Me.Icon = Global.SCrawler.My.Resources.SiteYouTube.YouTubeIcon_32 + Me.KeyPreview = True + Me.MinimumSize = New System.Drawing.Size(300, 200) + Me.Name = "VideoListForm" + Me.Text = "YouTube Downloader" + Me.TOOLBAR_BOTTOM.ResumeLayout(False) + Me.TOOLBAR_BOTTOM.PerformLayout() + Me.TOOLBAR_TOP.ResumeLayout(False) + Me.TOOLBAR_TOP.PerformLayout() + Me.ResumeLayout(False) + Me.PerformLayout() + + End Sub + + Private WithEvents TOOLBAR_BOTTOM As StatusStrip + Private WithEvents PR_MAIN As ToolStripProgressBar + Private WithEvents LBL_INFO As ToolStripStatusLabel + Protected WithEvents TP_CONTROLS As TableLayoutPanel + Private WithEvents TOOLBAR_TOP As ToolStrip + Private WithEvents BTT_DELETE As ToolStripButton + Private WithEvents BTT_CLEAR_DONE As ToolStripButton + Private WithEvents BTT_CLEAR_ALL As ToolStripButton + Private WithEvents BTT_SETTINGS As ToolStripButton + Private WithEvents SEP_1 As ToolStripSeparator + Private WithEvents SEP_LOG As ToolStripSeparator + Private WithEvents BTT_LOG As ToolStripButton + Private WithEvents BTT_STOP As ToolStripButton + Private WithEvents BTT_INFO As ToolStripButton + Private WithEvents BTT_DONATE As ToolStripButton + Protected WithEvents BTT_ADD As PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick + Protected WithEvents BTT_ADD_PLS_ARR As PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick + Protected WithEvents BTT_ADD_NO_SHORTS As PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick + Protected WithEvents BTT_ADD_SHORTS_ONLY As PersonalUtilities.Forms.Controls.KeyClick.ToolStripMenuItemKeyClick + Protected WithEvents MENU_ADD As ToolStripDropDownButton + Protected WithEvents BTT_DOWN As ToolStripButton + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/VideoListForm.resx b/SCrawler.YouTube/Downloader/VideoListForm.resx new file mode 100644 index 0000000..18d3f3c --- /dev/null +++ b/SCrawler.YouTube/Downloader/VideoListForm.resx @@ -0,0 +1,390 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + False + + + 17, 17 + + + 177, 17 + + + + + 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 + VAUAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3 + FeVFihZsqaBjRM2ouILiC0oUozGaaHxJluzD9mH7sGUmM26JLsuGwqiZE5jKQAcGAR3WUvpeuOwlWaRn + /1NanbFsPMkvt/ec5/yff59z7r08Gn1FRTHmpKRPri1aNHxVJvt4nVT6GoajApP/E0MqFfubTHZ9ZOHC + 3kvJyQYMxYCIwCSNfpNJeKuo6Kuh5mYyeu4cudnU5L/Ksh15ItFSTEfPZIWPQYZ527Z58yPu9GkydfYs + Ga6s9HyYmlovi42Nx/RMkY4lSz66v3cveXjsGBk9epRYkWw5cIC0sux1uUj0JlLCFhlRq9dA3MKdOUM4 + rOVaWsjUkSPkvlbr/UYq/QApYhDFuyaRWKn4w6Ym8ujQIWJF0vjJk6Svudl/VaX6KUskSkHic0XQlmJb + VZWVOqfiUxDn9u8nUzDmq6kh5oSEAaSxQMRrT0w8P1xbS0ZRwAKsBw8SGxY4Tpwgd9GudpbtyhQKU5Ec + KDKkUBSPV1ZaJ0+dIhzMTMEUh3VTe/aQiR07SKtK5doikVxEaikQ8/SLF7/eqlRe/3nbNv9jJI2Bcbhx + YE/caJlz3z5yDUVyhcK0XximaMxgsE0ePx5oCQfHHNrLNTaSSYibc3M9KQLB5xA2AjmgG86LZiSSVBTp + smzZQmw7dxJ7QwNx7N5NXCjmhdBAQ4O/W62+86C83ModPhxwzGGOQy5XX/9UPE0gOA+9WpARFI8EgeDn + isVpVxSKzt7qar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t1Op9MqFQipOnaeD+eDZUQ0GXyUW + p7dnZ3fZNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jMy/PliEQXsH47oM4F4AXxUPCXxcdn + tMvlXf1q9RPPpk3ErdEQN8MQl1RKXCIRcUVHE9e8eWRAKPS3JSV52bg4uqF1IBO8DGYVD0WgXbfk8r5x + CNoh5gBOQIUDREWR3uzsvwypqd8h/32QBeYkHghbTY3JplZb7ALBi+KAjvUnJz8xa7UP0hITy7EkFsxN + 3Gc07nMVFLjscD+b+Bh4BCwZGaRXpxt4Z/nyHCz9z9dKINxGY4tz7Vq3PSbmBXEnek/bRcVHwXBkJOkH + N3Jy/H0VFfcMLEvbNHsR7/bthxwlJZ5wzp0SCXlcUvL33ZQUPxUfCor3gE5gZpjpPvpPZiuCo9hiLyjw + 2oXCgPBzbVmwgAyWlv5+fOXK7p7168ctaWnkV4jeBjfAj7QAZdmy6ZsGw50CuZy+IJ+96j21tc1U3BFO + PCGBDK5a9Uc9w7QjtaEkM/PdgbKyB4PofUi8A7SBVlpEpZrurqjo+UyrpRvPBxE8z4YNVkdcXHjnEK9T + KNqQaAJ0I8W78/Pf6tXrR+5kZT0V/x58Cy6DvvT0J7f1+vvIXQz4PLtON+iRycI6NzHMD0iqD4qHvlT8 + PYWFbG9FxciVrCz/v8W/joggt1HArNGMIa8IxPO6q6s3j2s0bjdEQ877i4v/3BlePBT8xvz8PGzs8K3s + bHIpKG7Oy5v+UqN5XLp06afImSmAmN9WWXnYWlbmvceyfuq8UanswPhs4qHgG1esUNB24OvnN6vV0xfh + /BWB4CzmdGCmRYgIJikptkev/+JhWZnzQmEhTl9kI8YZIKTzNGmW4F+uqnrvrk43dkOrdZWkpNBvgR5I + AT2uT9fSH3FACdYFr3N9/F8C9GjSk7MevAHCPnDzAHVMP9b0Su/nEtQEff+/GoQWDBrj8f4B7pXZMs39 + OqoAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3 + FeVFihZsqaBjRM2ouILiC0oUozGaaHxJluzD9mH7sGUmM26JLsuGwqiZE5jKQAcGAR3WUvpeuOwlWaRn + /1NanbFsPMkvt/ec5/yff59z7r08Gn1FRTHmpKRPri1aNHxVJvt4nVT6GoajApP/E0MqFfubTHZ9ZOHC + 3kvJyQYMxYCIwCSNfpNJeKuo6Kuh5mYyeu4cudnU5L/Ksh15ItFSTEfPZIWPQYZ527Z58yPu9GkydfYs + Ga6s9HyYmlovi42Nx/RMkY4lSz66v3cveXjsGBk9epRYkWw5cIC0sux1uUj0JlLCFhlRq9dA3MKdOUM4 + rOVaWsjUkSPkvlbr/UYq/QApYhDFuyaRWKn4w6Ym8ujQIWJF0vjJk6Svudl/VaX6KUskSkHic0XQlmJb + VZWVOqfiUxDn9u8nUzDmq6kh5oSEAaSxQMRrT0w8P1xbS0ZRwAKsBw8SGxY4Tpwgd9GudpbtyhQKU5Ec + KDKkUBSPV1ZaJ0+dIhzMTMEUh3VTe/aQiR07SKtK5doikVxEaikQ8/SLF7/eqlRe/3nbNv9jJI2Bcbhx + YE/caJlz3z5yDUVyhcK0XximaMxgsE0ePx5oCQfHHNrLNTaSSYibc3M9KQLB5xA2AjmgG86LZiSSVBTp + smzZQmw7dxJ7QwNx7N5NXCjmhdBAQ4O/W62+86C83ModPhxwzGGOQy5XX/9UPE0gOA+9WpARFI8EgeDn + isVpVxSKzt7qar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t1Op9MqFQipOnaeD+eDZUQ0GXyUW + p7dnZ3fZNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jMy/PliEQXsH47oM4F4AXxUPCXxcdn + tMvlXf1q9RPPpk3ErdEQN8MQl1RKXCIRcUVHE9e8eWRAKPS3JSV52bg4uqF1IBO8DGYVD0WgXbfk8r5x + CNoh5gBOQIUDREWR3uzsvwypqd8h/32QBeYkHghbTY3JplZb7ALBi+KAjvUnJz8xa7UP0hITy7EkFsxN + 3Gc07nMVFLjscD+b+Bh4BCwZGaRXpxt4Z/nyHCz9z9dKINxGY4tz7Vq3PSbmBXEnek/bRcVHwXBkJOkH + N3Jy/H0VFfcMLEvbNHsR7/bthxwlJZ5wzp0SCXlcUvL33ZQUPxUfCor3gE5gZpjpPvpPZiuCo9hiLyjw + 2oXCgPBzbVmwgAyWlv5+fOXK7p7168ctaWnkV4jeBjfAj7QAZdmy6ZsGw50CuZy+IJ+96j21tc1U3BFO + PCGBDK5a9Uc9w7QjtaEkM/PdgbKyB4PofUi8A7SBVlpEpZrurqjo+UyrpRvPBxE8z4YNVkdcXHjnEK9T + KNqQaAJ0I8W78/Pf6tXrR+5kZT0V/x58Cy6DvvT0J7f1+vvIXQz4PLtON+iRycI6NzHMD0iqD4qHvlT8 + PYWFbG9FxciVrCz/v8W/joggt1HArNGMIa8IxPO6q6s3j2s0bjdEQ877i4v/3BlePBT8xvz8PGzs8K3s + bHIpKG7Oy5v+UqN5XLp06afImSmAmN9WWXnYWlbmvceyfuq8UanswPhs4qHgG1esUNB24OvnN6vV0xfh + /BWB4CzmdGCmRYgIJikptkev/+JhWZnzQmEhTl9kI8YZIKTzNGmW4F+uqnrvrk43dkOrdZWkpNBvgR5I + AT2uT9fSH3FACdYFr3N9/F8C9GjSk7MevAHCPnDzAHVMP9b0Su/nEtQEff+/GoQWDBrj8f4B7pXZMs39 + OqoAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3 + FeVFihZsqaBjRM2ouILiC0oUozGaaHxJluzD9mH7sGUmM26JLsuGwqiZE5jKQAcGAR3WUvpeuOwlWaRn + /1NanbFsPMkvt/ec5/yff59z7r08Gn1FRTHmpKRPri1aNHxVJvt4nVT6GoajApP/E0MqFfubTHZ9ZOHC + 3kvJyQYMxYCIwCSNfpNJeKuo6Kuh5mYyeu4cudnU5L/Ksh15ItFSTEfPZIWPQYZ527Z58yPu9GkydfYs + Ga6s9HyYmlovi42Nx/RMkY4lSz66v3cveXjsGBk9epRYkWw5cIC0sux1uUj0JlLCFhlRq9dA3MKdOUM4 + rOVaWsjUkSPkvlbr/UYq/QApYhDFuyaRWKn4w6Ym8ujQIWJF0vjJk6Svudl/VaX6KUskSkHic0XQlmJb + VZWVOqfiUxDn9u8nUzDmq6kh5oSEAaSxQMRrT0w8P1xbS0ZRwAKsBw8SGxY4Tpwgd9GudpbtyhQKU5Ec + KDKkUBSPV1ZaJ0+dIhzMTMEUh3VTe/aQiR07SKtK5doikVxEaikQ8/SLF7/eqlRe/3nbNv9jJI2Bcbhx + YE/caJlz3z5yDUVyhcK0XximaMxgsE0ePx5oCQfHHNrLNTaSSYibc3M9KQLB5xA2AjmgG86LZiSSVBTp + smzZQmw7dxJ7QwNx7N5NXCjmhdBAQ4O/W62+86C83ModPhxwzGGOQy5XX/9UPE0gOA+9WpARFI8EgeDn + isVpVxSKzt7qar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t1Op9MqFQipOnaeD+eDZUQ0GXyUW + p7dnZ3fZNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jMy/PliEQXsH47oM4F4AXxUPCXxcdn + tMvlXf1q9RPPpk3ErdEQN8MQl1RKXCIRcUVHE9e8eWRAKPS3JSV52bg4uqF1IBO8DGYVD0WgXbfk8r5x + CNoh5gBOQIUDREWR3uzsvwypqd8h/32QBeYkHghbTY3JplZb7ALBi+KAjvUnJz8xa7UP0hITy7EkFsxN + 3Gc07nMVFLjscD+b+Bh4BCwZGaRXpxt4Z/nyHCz9z9dKINxGY4tz7Vq3PSbmBXEnek/bRcVHwXBkJOkH + N3Jy/H0VFfcMLEvbNHsR7/bthxwlJZ5wzp0SCXlcUvL33ZQUPxUfCor3gE5gZpjpPvpPZiuCo9hiLyjw + 2oXCgPBzbVmwgAyWlv5+fOXK7p7168ctaWnkV4jeBjfAj7QAZdmy6ZsGw50CuZy+IJ+96j21tc1U3BFO + PCGBDK5a9Uc9w7QjtaEkM/PdgbKyB4PofUi8A7SBVlpEpZrurqjo+UyrpRvPBxE8z4YNVkdcXHjnEK9T + KNqQaAJ0I8W78/Pf6tXrR+5kZT0V/x58Cy6DvvT0J7f1+vvIXQz4PLtON+iRycI6NzHMD0iqD4qHvlT8 + PYWFbG9FxciVrCz/v8W/joggt1HArNGMIa8IxPO6q6s3j2s0bjdEQ877i4v/3BlePBT8xvz8PGzs8K3s + bHIpKG7Oy5v+UqN5XLp06afImSmAmN9WWXnYWlbmvceyfuq8UanswPhs4qHgG1esUNB24OvnN6vV0xfh + /BWB4CzmdGCmRYgIJikptkev/+JhWZnzQmEhTl9kI8YZIKTzNGmW4F+uqnrvrk43dkOrdZWkpNBvgR5I + AT2uT9fSH3FACdYFr3N9/F8C9GjSk7MevAHCPnDzAHVMP9b0Su/nEtQEff+/GoQWDBrj8f4B7pXZMs39 + OqoAAAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8 + YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFmSURBVFhH1dc/K4VhHMbxJ5EFEQbFiERKCotIrMJIiYEi + pbwCZcOqJC9AikUWiqRkJYtSRDbESMT3V07dna7zHHru+9T51me+Ts//E+V7LRjFFAZRiZzUhDVc4/vX + B47Rh6D14Aqp4XQ36ECQ2nALNezaQjG8Vo5DqMF0bxiA1+bwCTWoLMFbNTiDGsrkABXw0jDsKldDmdyj + HokrwCrUSBz7wXbRJs4eLkdQI9m0I3ENeIAaiGN3QjMSZ4fxv+ffnKIKibOnmhqI84V5eMleOHY41VAm + 9k7wdgtW4wRqSHlCP7y2AjWmbMB7Y7DzqgZdz2iF9zrxCDXq2oU9uLz31+tgAcHahhp1DSFY9pGhRl29 + CFYXxrMoQ7BmsZfFPkoRpHWow+56hX26BWkRatR1gRIEaQLvUMMpOyhCkBpxBzWcMoOgLUMNm0vUIWj2 + ebaJF7jj5+hGTiqE/f+bxDRGUIt8LIp+AC/GHt3tQnwvAAAAAElFTkSuQmCC + + + \ No newline at end of file diff --git a/SCrawler.YouTube/Downloader/VideoListForm.vb b/SCrawler.YouTube/Downloader/VideoListForm.vb new file mode 100644 index 0000000..94503a1 --- /dev/null +++ b/SCrawler.YouTube/Downloader/VideoListForm.vb @@ -0,0 +1,499 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.ComponentModel +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Toolbars +Imports PersonalUtilities.Forms.Controls.KeyClick +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Imports PersonalUtilities.Functions.Messaging +Imports SCrawler.API.YouTube +Imports SCrawler.API.YouTube.Base +Imports SCrawler.API.YouTube.Controls +Imports SCrawler.API.YouTube.Objects +Namespace DownloadObjects.STDownloader + Public Class VideoListForm : Implements IDesignXMLContainer +#Region "Declarations" + Private ReadOnly MyView As FormView + Private ReadOnly MyProgress As MyProgress + Protected WithEvents MyJob As JobThread(Of MediaItem) + Public Property DesignXML As EContainer Implements IDesignXMLContainer.DesignXML + Public Property DesignXMLNodes As String() Implements IDesignXMLContainer.DesignXMLNodes + Public Property DesignXMLNodeName As String Implements IDesignXMLContainer.DesignXMLNodeName + Private ReadOnly ControlsDownloaded As New FPredicate(Of MediaItem)(Function(i) i.MyContainer.MediaState = Plugin.UserMediaStates.Downloaded) + Private ReadOnly ControlsChecked As Predicate(Of MediaItem) = Function(i) i.Checked + Private ReadOnly CNT_PROCESSOR As TableControlsProcessor + Protected AppMode As Boolean = True +#End Region +#Region "Initializer" + Public Sub New() + InitializeComponent() + CNT_PROCESSOR = New TableControlsProcessor(TP_CONTROLS) + MyView = New FormView(Me) + MyProgress = New MyProgress(TOOLBAR_BOTTOM, PR_MAIN, LBL_INFO) + MyJob = New JobThread(Of MediaItem) + End Sub +#End Region +#Region "Form handlers" + Protected Overridable Sub VideoListForm_Load(sender As Object, e As EventArgs) Handles Me.Load + If Not LicenseManager.UsageMode = LicenseUsageMode.Designtime Then + If MyYouTubeSettings Is Nothing Then MyYouTubeSettings = New YouTubeSettings + DesignXML = MyYouTubeSettings.DesignXml + If MyCache Is Nothing Then MyCache = New CacheKeeper(YouTubeFunctions.YouTubeCachePathRoot) + End If + + If AppMode Then + If Now.Month.ValueBetween(6, 8) Then Text = "SCrawler: Happy LGBT Pride Month! :-)" + MyNotificator = New YTNotificator(Me) + MyDownloaderSettings = MyYouTubeSettings + End If + + With MyView : .Import() : .SetFormSize() : End With + BTT_DELETE.Enabled = False + If Not AppMode Then + BTT_SETTINGS.Visible = False + SEP_1.Visible = False + SEP_LOG.Visible = False + BTT_LOG.Visible = False + BTT_INFO.Visible = False + BTT_DONATE.Visible = False + End If + MyProgress.Visible = False + LoadData() + End Sub + Protected Overridable Sub VideoListForm_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing + If Not AppMode Then e.Cancel = True : Hide() + End Sub + Protected Overridable Sub VideoListForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + MyView.Dispose() + MyCache.DisposeIfReady() + If AppMode Then + MyNotificator.Clear() + If Not MyMainLOG.IsEmptyString Then SaveLogToFile() + End If + If Not MyYouTubeSettings Is Nothing Then MyYouTubeSettings.Close() + End Sub + Private Sub VideoListForm_KeyDown(sender As Object, e As KeyEventArgs) Handles Me.KeyDown + If e.KeyCode = Keys.Insert Then BTT_ADD.PerformClick() : e.Handled = True + End Sub +#End Region +#Region "Refill, save list" + Protected Sub LoadData() + Dim c As List(Of IYouTubeMediaContainer) = LoadData_GetFiles() + If c.ListExists Then + c.Sort(New ContainerDateComparer) + SuspendLayout() + For i% = c.Count - 1 To 0 Step -1 : ControlCreateAndAdd(c(i), True, i = 0) : Next + ResumeLayout(False) + PerformLayout() + End If + End Sub + Protected Overridable Function LoadData_GetFiles() As List(Of IYouTubeMediaContainer) + Try + Dim l As New List(Of IYouTubeMediaContainer) + Dim path As SFile = DownloaderDataFolderYouTube + If path.Exists(SFO.Path, False) Then + Dim files As List(Of SFile) = SFile.GetFiles(path, "*.xml",, EDP.ReturnValue) + If files.Count > 0 Then files.ForEach(Sub(f) l.Add(YouTubeFunctions.CreateContainer(f))) + End If + If l.Count > 0 Then l.RemoveAll(Function(c) c Is Nothing) + If l.Count > 0 Then l.ListDisposeRemoveAll(Function(c) Not c.Exists) + Return l + Catch ex As Exception + Dim e As EDP = EDP.LogMessageValue + If Not ex.HelpLink.IsEmptyString AndAlso ex.HelpLink = NameOf(YouTubeFunctions.CreateContainer) Then e = EDP.SendToLog + EDP.ReturnValue + Return ErrorsDescriber.Execute(e, ex, "VideoListForm.LoadData_GetFiles", New List(Of IYouTubeMediaContainer)) + End Try + End Function +#End Region +#Region "Controls" + Protected Sub ControlCreateAndAdd(ByVal Container As IYouTubeMediaContainer, Optional ByVal DisableDownload As Boolean = False, + Optional ByVal PerformClick As Boolean = True) + ControlInvokeFast(TP_CONTROLS, Sub() + With TP_CONTROLS + .SuspendLayout() + If DisableDownload Or Not MyDownloaderSettings.DownloadAutomatically Then Container.Save() + '.AutoScroll = True + '.HorizontalScroll.Visible = False + .RowStyles.Insert(0, New RowStyle(SizeType.Absolute, 60)) + .RowCount = .RowStyles.Count + OffsetControls(0, True) + Dim cnt As New MediaItem(Container) With {.Dock = DockStyle.Fill, .Margin = New Padding(0)} + AddHandler cnt.FileDownloaded, AddressOf MediaControl_FileDownloaded + AddHandler cnt.Removal, AddressOf MediaControl_Removal + AddHandler cnt.DownloadAgain, AddressOf MediaControl_DownloadAgain + AddHandler cnt.DownloadRequested, AddressOf MediaControl_DownloadRequested + AddHandler cnt.CheckedChanged, AddressOf MediaControl_CheckedChanged + AddHandler cnt.Click, AddressOf CNT_PROCESSOR.MediaItem_Click + AddHandler cnt.KeyDown, AddressOf CNT_PROCESSOR.MediaItem_KeyDown + .Controls.Add(cnt, 0, 0) + .Controls.Cast(Of ISupportInitialize).ToList.ForEach(Sub(_cnt) _cnt.EndInit()) + .ScrollControlIntoView(cnt) + cnt.Select() + RefillColors() + '.AutoScroll = False + '.AutoScroll = True + .ResumeLayout() + .PerformLayout() + UpdateScrolls(Me, Nothing) + If PerformClick Then cnt.PerformClick() + If Not DisableDownload And MyDownloaderSettings.DownloadAutomatically Then AddToDownload(cnt, True) + End With + End Sub, EDP.None) + End Sub +#Region "Controls rendering" + Private Overloads Sub OffsetControls() + Try + With TP_CONTROLS + If .Controls.Count > 0 Then + Dim i%, ri% + Dim cntIndx% = -1 + Dim cnt As Control + For i = .Controls.Count - 1 To 0 Step -1 + cnt = .Controls(i) + If Not cnt Is Nothing Then cntIndx += 1 : .SetCellPosition(cnt, New TableLayoutPanelCellPosition(0, cntIndx)) + Next + For i = .RowStyles.Count - 1 To 0 Step -1 + If Not .GetControlFromPosition(0, i) Is Nothing Then + If i + 1 < .RowStyles.Count - 1 Then + For ri = .RowStyles.Count - 1 To i + 1 Step -1 : .RowStyles.RemoveAt(i) : Next + .RowStyles.Add(New RowStyle(SizeType.AutoSize)) + .RowCount = .RowStyles.Count + End If + Exit For + End If + Next + Else + .RowStyles.Clear() + .RowCount = 0 + .RowStyles.Add(New RowStyle(SizeType.AutoSize)) + .RowCount = .RowStyles.Count + End If + End With + Catch + End Try + End Sub + Private Overloads Sub OffsetControls(ByVal ReflectedRow As Integer, ByVal Add As Boolean) + ControlInvokeFast(TP_CONTROLS, Sub() + Dim offset% = IIf(Add, 1, -1) + Dim cnt As Control + With TP_CONTROLS + If .RowStyles.Count > 1 Then + For i% = .RowStyles.Count - 1 To ReflectedRow Step -1 + cnt = .GetControlFromPosition(0, i) + If Not cnt Is Nothing Then .SetCellPosition(cnt, New TableLayoutPanelCellPosition(0, i + offset)) + Next + End If + End With + End Sub, EDP.None) + End Sub + Private Sub RefillColors() + ControlInvokeFast(TP_CONTROLS, Sub() + With TP_CONTROLS + If .Controls.Count > 0 Then + Dim i% = 0 + Dim c As Color + For Each cnt As MediaItem In .Controls + i += 1 + If (i Mod 2) = 0 Then c = SystemColors.ControlLight Else c = SystemColors.Window + cnt.BackColor = c + Next + End If + End With + End Sub, EDP.None) + End Sub + Private Sub UpdateScrolls(sender As Object, e As EventArgs) Handles TP_CONTROLS.StyleChanged, Me.ResizeEnd, Me.SizeChanged + ControlInvokeFast(TP_CONTROLS, Sub() + With TP_CONTROLS + .SuspendLayout() + .Padding = New Padding(0, 0, .VerticalScroll.Visible.BoolToInteger * 3, 0) + .HorizontalScroll.Visible = False + .HorizontalScroll.Enabled = False + .ResumeLayout() + .PerformLayout() + End With + End Sub, EDP.None) + End Sub +#End Region +#Region "Toolbar controls handlers" + Protected Overridable Sub BTT_SETTINGS_Click(sender As Object, e As EventArgs) Handles BTT_SETTINGS.Click + MyYouTubeSettings.ShowForm(AppMode) + End Sub + Protected Overridable Sub BTT_ADD_KeyClick(ByVal Sender As ToolStripMenuItemKeyClick, ByVal e As KeyClickEventArgs) Handles BTT_ADD.KeyClick, BTT_ADD_PLS_ARR.KeyClick, + BTT_ADD_NO_SHORTS.KeyClick, BTT_ADD_SHORTS_ONLY.KeyClick + Dim pForm As ParsingProgressForm = Nothing + Try + Dim canProcess As Boolean = True + If TP_CONTROLS.Controls.Count >= MyYouTubeSettings.ItemsListLimit Then canProcess = TP_CONTROLS.Controls.Cast(Of MediaItem).ListExists(ControlsDownloaded) + If canProcess Then + Dim useCookies As Boolean = MyYouTubeSettings.DefaultUseCookies + If e.Control Then useCookies = True + Dim useCookiesParse As Boolean? = Nothing + If useCookies Then useCookiesParse = True + + Dim c As IYouTubeMediaContainer = Nothing + Dim url$ = String.Empty + Dim GetDefault As Boolean = True + Dim GetShorts As Boolean = True + + If Sender.Tag = "pls" Then + Using pf As New PlaylistArrayForm With {.DesignXML = DesignXML} + pf.ShowDialog() + If pf.DialogResult = DialogResult.OK Then + With pf.URLs + If .Count > 0 Then + pForm = New ParsingProgressForm + pForm.Show() + pForm.SetInitialValues(.Count, "Parsing playlists...") + Dim containers As New List(Of IYouTubeMediaContainer) + For Each u$ In .Self : containers.Add(YouTubeFunctions.Parse(u, useCookiesParse, pForm.Token, pForm.MyProgress, True, False)) : pForm.MyProgress.Perform() : Next + pForm.Dispose() + If containers.Count > 0 Then containers.ListDisposeRemoveAll(Function(cc) cc.HasError Or Not cc.Exists) + If containers.Count > 0 Then + c = New Channel With {.UserTitle = IIf(pf.IsOneArtist, containers(0).UserTitle, "Playlists")} + c.Elements.AddRange(containers) + End If + End If + End With + End If + End Using + Else + Select Case CStr(Sender.Tag) + Case "ans" : GetShorts = False + Case "as" : GetDefault = False : GetShorts = True + End Select + url = BufferText + If url.IsEmptyString OrElse Not YouTubeFunctions.IsMyUrl(url) Then url = InputBoxE("Enter a valid URL to the YouTube video:", "YouTube link") + End If + + If Not c Is Nothing OrElse YouTubeFunctions.IsMyUrl(url) Then + If c Is Nothing Then + pForm = New ParsingProgressForm + pForm.Show() + pForm.SetInitialValues(1, "Parsing data...") + c = YouTubeFunctions.Parse(url, useCookiesParse, pForm.Token, pForm.MyProgress, GetDefault, GetShorts) + pForm.Dispose() + End If + If Not c Is Nothing Then + Dim f As Form + Select Case c.ObjectType + Case YouTubeMediaType.Single : f = New VideoOptionsForm(c) + Case YouTubeMediaType.Channel, YouTubeMediaType.PlayList + If c.IsMusic Then + f = New MusicPlaylistsForm(c) + Else + f = New VideoOptionsForm(c) + End If + Case Else : c.Dispose() : Throw New ArgumentException($"Object type {c.ObjectType} not implemented", "IYouTubeMediaContainer.ObjectType") + End Select + If Not f Is Nothing Then + If TypeOf f Is IDesignXMLContainer Then DirectCast(f, IDesignXMLContainer).DesignXML = DesignXML + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then + If TP_CONTROLS.Controls.Count >= MyYouTubeSettings.ItemsListLimit Then _ + RemoveControls(TP_CONTROLS.Controls.Cast(Of MediaItem).LastOrDefault(ControlsDownloaded)) + ControlCreateAndAdd(c) + End If + f.Dispose() + End If + End If + End If + Else + MsgBoxE({$"Number of items to download exceeded!{vbCr}Reduce the number of items or increase the limit.", "New download"}, vbCritical) + End If + Catch oex As OperationCanceledException + Catch dex As ObjectDisposedException + Catch ex As Exception + ErrorsDescriber.Execute(EDP.LogMessageValue, ex, "VideoListForm.Add") + UpdateLogButton() + Finally + If Not pForm Is Nothing Then pForm.Dispose() + End Try + End Sub + Private Sub BTT_DOWN_Click(sender As Object, e As EventArgs) Handles BTT_DOWN.Click + With TP_CONTROLS + If .Controls.Count > 0 Then + For Each cnt As MediaItem In .Controls + If Not cnt.MyContainer.MediaState = Plugin.UserMediaStates.Downloaded And Not cnt.Pending Then AddToDownload(cnt, False) + Next + End If + End With + StartDownloading() + End Sub + Private Sub BTT_STOP_Click(sender As Object, e As EventArgs) Handles BTT_STOP.Click + ControlInvoke(TOOLBAR_TOP, BTT_STOP, Sub() BTT_STOP.Enabled = False, EDP.SendToLog) + MyJob.Cancel() + End Sub + Private Sub BTT_DELETE_Click(sender As Object, e As EventArgs) Handles BTT_DELETE.Click + RemoveControls(ControlsChecked) + End Sub + Protected Overridable Sub BTT_CLEAR_DONE_Click(sender As Object, e As EventArgs) Handles BTT_CLEAR_DONE.Click + RemoveControls(ControlsDownloaded) + End Sub + Protected Overridable Sub BTT_CLEAR_ALL_Click(sender As Object, e As EventArgs) Handles BTT_CLEAR_ALL.Click + RemoveControls() + End Sub + Private Sub BTT_LOG_Click(sender As Object, e As EventArgs) Handles BTT_LOG.Click + MyMainLOG_ShowForm(DesignXML,,,, AddressOf UpdateLogButton) + End Sub + Friend Sub UpdateLogButton() + If AppMode Then MyMainLOG_UpdateLogButton(BTT_LOG, TOOLBAR_TOP) + End Sub + Private Sub BTT_DONATE_Click(sender As Object, e As EventArgs) Handles BTT_DONATE.Click + Try : Process.Start("https://github.com/AAndyProgram/SCrawler/blob/main/HowToSupport.md") : Catch : End Try + End Sub + Private Sub BTT_INFO_Click(sender As Object, e As EventArgs) Handles BTT_INFO.Click + Try + MsgBoxE({$"YouTube Downloader v{My.Application.Info.Version}" & vbCr & + $"Address: https://github.com/AAndyProgram/SCrawler" & vbCr & + "Created by Greek LGBT person Andy (Gay)", + "Program information"},,,, + {"OK", New MsgBoxButton("Go to site") With {.CallBack = Sub(r, n, b) Process.Start("https://github.com/AAndyProgram/SCrawler/releases")}}) + Catch + End Try + End Sub + Protected Overloads Sub RemoveControls(Optional ByVal Predicate As Predicate(Of MediaItem) = Nothing) + ControlInvokeFast(TP_CONTROLS, Sub() + With TP_CONTROLS + If .Controls.Count > 0 Then + Dim i% + Dim rCnt As New List(Of Integer) + Dim predicateExists As Boolean = Not Predicate Is Nothing + For i = 0 To .Controls.Count - 1 + If Not predicateExists OrElse Predicate.Invoke(.Controls(i)) Then rCnt.Add(i) + Next + If rCnt.Count > 0 Then + Dim cnt As MediaItem + For i = rCnt.Count - 1 To 0 Step -1 + cnt = .Controls(rCnt(i)) + .Controls.RemoveAt(rCnt(i)) + If Not cnt.MyContainer Is Nothing Then cnt.MyContainer.Delete(False) + cnt.Dispose() + Next + End If + End If + If .Controls.Count > 0 Then + OffsetControls() + Else + .RowStyles.Clear() + .RowStyles.Add(New RowStyle(SizeType.AutoSize)) + .RowCount = 1 + End If + End With + UpdateScrolls(Nothing, Nothing) + End Sub, EDP.None) + End Sub + Private Overloads Sub RemoveControls(ByVal CNT As MediaItem) + ControlInvokeFast(TP_CONTROLS, Sub() + If Not CNT Is Nothing Then TP_CONTROLS.Controls.Remove(CNT) : OffsetControls() + End Sub, EDP.None) + End Sub +#End Region +#Region "Media controls' handlers" + Private Sub MediaControl_FileDownloaded(ByVal Sender As MediaItem, ByVal Container As IYouTubeMediaContainer) + If MyDownloaderSettings.ShowNotifications Then MyNotificator.ShowNotification(Container.ToString(), Container.ThumbnailFile) + If MyDownloaderSettings.RemoveDownloadedAutomatically Then RemoveControls(Sender) + End Sub + Private Sub MediaControl_Removal(ByVal Sender As MediaItem, ByVal Container As IYouTubeMediaContainer) + RemoveControls(Sender) + End Sub + Private Sub MediaControl_DownloadAgain(ByVal Sender As MediaItem, ByVal Container As IYouTubeMediaContainer) + If Not Container.URL.IsEmptyString Then BufferText = Container.URL : BTT_ADD.PerformClick() + End Sub + Private Sub MediaControl_DownloadRequested(ByVal Sender As MediaItem, ByVal Container As IYouTubeMediaContainer) + AddToDownload(Sender, True) + End Sub + Private Sub MediaControl_CheckedChanged(ByVal Sender As MediaItem, ByVal Container As IYouTubeMediaContainer) + With TP_CONTROLS.Controls + ControlInvokeFast(TOOLBAR_TOP, BTT_DELETE, + Sub() BTT_DELETE.Enabled = .Count > 0 AndAlso .Cast(Of MediaItem).ListExists(Function(cnt) cnt.Checked), EDP.None) + End With + End Sub +#End Region +#End Region +#Region "Downloading" + Protected Overridable Sub MyJob_Started(ByVal Sender As Object, ByVal e As EventArgs) Handles MyJob.Started + End Sub + Protected Overridable Sub MyJob_Finished(ByVal Sender As Object, ByVal e As EventArgs) Handles MyJob.Finished + UpdateLogButton() + End Sub + Protected Sub AddToDownload(ByRef Item As MediaItem, ByVal RunThread As Boolean) + If MyJob.Count = 0 OrElse Not MyJob.Items.Exists(Function(i) i.MyContainer.GetHashCode) Then + Item.Pending = True + MyJob.Add(Item) + Item.AddToQueue() + If RunThread Then StartDownloading() + End If + End Sub + Private Sub StartDownloading() + If Not MyJob.Working And MyJob.Count > 0 Then + EnableDownloadButtons(True) + MyJob.StartThread(AddressOf DownloadData) + End If + End Sub + Private Sub EnableDownloadButtons(ByVal Downloading As Boolean) + ControlInvoke(TOOLBAR_TOP, BTT_DOWN, Sub() + BTT_DOWN.Enabled = Not Downloading + BTT_STOP.Enabled = Downloading + End Sub, EDP.SendToLog) + End Sub + Private ReadOnly PNumProv As New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral} + Private Sub DownloadData() + Try + MyJob.Start() + Const nf As ANumbers.Formats = ANumbers.Formats.Number + Dim t As New List(Of Task) + Dim i% + Dim __item As MediaItem + Dim Indexes As New List(Of Integer) + Dim maxJobCount% = MyDownloaderSettings.MaxJobsCount + If maxJobCount <= 0 Then maxJobCount = 1 + MyProgress.Visible = True + MyProgress.Maximum = MyJob.Count + Do While MyJob.Count > 0 And Not MyJob.IsCancellationRequested + i = -1 + Indexes.Clear() + For Each __item In MyJob.Items + i += 1 + If i <= maxJobCount - 1 Then + Indexes.Add(i) + t.Add(Task.Run(Sub() __item.Download(MyJob.Token))) + Else + Exit For + End If + Next + If t.Count > 0 Then + MyProgress.Information = $"Downloading {t.Count.NumToString(nf, PNumProv)}/{MyJob.Count.NumToString(nf, PNumProv)}" + MyProgress.InformationTemporary = MyProgress.Information + Task.WaitAll(t.ToArray) + MyProgress.Perform(t.Count) + If Indexes.Count > 0 Then + For i = Indexes.Count - 1 To 0 Step -1 : MyJob.Items.RemoveAt(Indexes(i)) : Next + End If + t.Clear() + End If + Loop + Indexes.Clear() + MyProgress.Done() + MyProgress.InformationTemporary = "Download completed" + Catch aoex As ArgumentOutOfRangeException + Catch oex As OperationCanceledException + MyProgress.InformationTemporary = "Download canceled" + Catch ex As Exception + MyProgress.InformationTemporary = "Download error" + ErrorsDescriber.Execute(EDP.SendToLog, ex, "[VideoListForm.DownloadData]") + Finally + MyJob.Finish() + EnableDownloadButtons(False) + End Try + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/MainModShared.vb b/SCrawler.YouTube/MainModShared.vb new file mode 100644 index 0000000..8c12a36 --- /dev/null +++ b/SCrawler.YouTube/MainModShared.vb @@ -0,0 +1,39 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Tools +Imports SCrawler.DownloadObjects.STDownloader +Public Module MainModShared + Public Property BATCH As BatchExecutor + Private _BatchLogSent As Boolean = False + ''' + Public Sub GlobalOpenPath(ByVal f As SFile, Optional ByVal e As ErrorsDescriber = Nothing) + Dim b As Boolean = False + If Not e.Exists Then e = EDP.None + Try + If f.Exists(SFO.Path, False) Then + If MyDownloaderSettings.OpenFolderInOtherProgram AndAlso Not MyDownloaderSettings.OpenFolderInOtherProgram_Command.IsEmptyString Then + If BATCH Is Nothing Then BATCH = New BatchExecutor With {.RedirectStandardError = True} + b = True + With BATCH + .Reset() + .Execute({String.Format(MyDownloaderSettings.OpenFolderInOtherProgram_Command, f.PathWithSeparator)}, EDP.SendToLog + EDP.ThrowException) + If .HasError Or Not .ErrorOutput.IsEmptyString Then Throw New Exception(.ErrorOutput, .ErrorException) + End With + Else + f.Open(SFO.Path,, e) + End If + End If + Catch ex As Exception + If b Then + If Not _BatchLogSent Then ErrorsDescriber.Execute(EDP.SendToLog, ex, $"GlobalOpenPath({f.Path})") : _BatchLogSent = True + f.Open(SFO.Path,, e) + End If + End Try + End Sub +End Module \ No newline at end of file diff --git a/SCrawler.YouTube/My Project/Application.Designer.vb b/SCrawler.YouTube/My Project/Application.Designer.vb new file mode 100644 index 0000000..88dd01c --- /dev/null +++ b/SCrawler.YouTube/My Project/Application.Designer.vb @@ -0,0 +1,13 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + diff --git a/SCrawler.YouTube/My Project/Application.myapp b/SCrawler.YouTube/My Project/Application.myapp new file mode 100644 index 0000000..1243847 --- /dev/null +++ b/SCrawler.YouTube/My Project/Application.myapp @@ -0,0 +1,11 @@ + + + true + Form1 + false + 0 + true + 0 + 0 + true + diff --git a/SCrawler.YouTube/My Project/AssemblyInfo.vb b/SCrawler.YouTube/My Project/AssemblyInfo.vb new file mode 100644 index 0000000..cf910bd --- /dev/null +++ b/SCrawler.YouTube/My Project/AssemblyInfo.vb @@ -0,0 +1,37 @@ +Imports System.Resources +Imports System +Imports System.Reflection +Imports System.Runtime.InteropServices + +' General Information about an assembly is controlled through the following +' set of attributes. Change these attribute values to modify the information +' associated with an assembly. + +' Review the values of the assembly attributes + + + + + + + + + + +'The following GUID is for the ID of the typelib if this project is exposed to COM + + +' Version information for an assembly consists of the following four values: +' +' Major Version +' Minor Version +' Build Number +' Revision +' +' You can specify all the values or you can default the Build and Revision Numbers +' by using the '*' as shown below: +' + + + + diff --git a/SCrawler.YouTube/My Project/Resources.Designer.vb b/SCrawler.YouTube/My Project/Resources.Designer.vb new file mode 100644 index 0000000..c8826fb --- /dev/null +++ b/SCrawler.YouTube/My Project/Resources.Designer.vb @@ -0,0 +1,163 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System + +Namespace My.Resources + + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. + ''' + ''' A strongly-typed resource class, for looking up localized strings, etc. + ''' + _ + Public Module Resources + + Private resourceMan As Global.System.Resources.ResourceManager + + Private resourceCulture As Global.System.Globalization.CultureInfo + + ''' + ''' Returns the cached ResourceManager instance used by this class. + ''' + _ + Public ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("SCrawler.Resources", GetType(Resources).Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. + ''' + _ + Public Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = value + End Set + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property ArrowDownPic_Blue_24() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("ArrowDownPic_Blue_24", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property AudioMusic_32() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("AudioMusic_32", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property ClockPic_16() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("ClockPic_16", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property HeartPic_32() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("HeartPic_32", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property ImagePic_32() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("ImagePic_32", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property InfoPic_32() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("InfoPic_32", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property LinkPic_32() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("LinkPic_32", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property RulerPic_32() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("RulerPic_32", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property SettingsPic_16() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("SettingsPic_16", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public ReadOnly Property VideoCamera_32() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("VideoCamera_32", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + End Module +End Namespace diff --git a/SCrawler.YouTube/My Project/Resources.resx b/SCrawler.YouTube/My Project/Resources.resx new file mode 100644 index 0000000..7d3d570 --- /dev/null +++ b/SCrawler.YouTube/My Project/Resources.resx @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Content\Pictures\ArrowDownPic_Blue_24.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\AudioMusic_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\ClockPic_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\HeartPic_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\ImagePic_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\InfoPic_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\LinkPic_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\RulerPic_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\SettingsPic_16.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Pictures\VideoCamera_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/SCrawler.YouTube/My Project/Settings.Designer.vb b/SCrawler.YouTube/My Project/Settings.Designer.vb new file mode 100644 index 0000000..fcfd812 --- /dev/null +++ b/SCrawler.YouTube/My Project/Settings.Designer.vb @@ -0,0 +1,73 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + + +Namespace My + + _ + Partial Friend NotInheritable Class MySettings + Inherits Global.System.Configuration.ApplicationSettingsBase + + Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings) + +#Region "My.Settings Auto-Save Functionality" +#If _MyType = "WindowsForms" Then + Private Shared addedHandler As Boolean + + Private Shared addedHandlerLockObject As New Object + + _ + Private Shared Sub AutoSaveSettings(sender As Global.System.Object, e As Global.System.EventArgs) + If My.Application.SaveMySettingsOnExit Then + My.Settings.Save() + End If + End Sub +#End If +#End Region + + Public Shared ReadOnly Property [Default]() As MySettings + Get + +#If _MyType = "WindowsForms" Then + If Not addedHandler Then + SyncLock addedHandlerLockObject + If Not addedHandler Then + AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings + addedHandler = True + End If + End SyncLock + End If +#End If + Return defaultInstance + End Get + End Property + End Class +End Namespace + +Namespace My + + _ + Friend Module MySettingsProperty + + _ + Friend ReadOnly Property Settings() As Global.SCrawler.My.MySettings + Get + Return Global.SCrawler.My.MySettings.Default + End Get + End Property + End Module +End Namespace diff --git a/SCrawler.YouTube/My Project/Settings.settings b/SCrawler.YouTube/My Project/Settings.settings new file mode 100644 index 0000000..85b890b --- /dev/null +++ b/SCrawler.YouTube/My Project/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SCrawler.YouTube/Objects/Channel.vb b/SCrawler.YouTube/Objects/Channel.vb new file mode 100644 index 0000000..95298d2 --- /dev/null +++ b/SCrawler.YouTube/Objects/Channel.vb @@ -0,0 +1,98 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Forms.Toolbars +Namespace API.YouTube.Objects + Public Class Channel : Inherits YouTubeMediaContainerBase + Public Sub New() + ObjectType = Base.YouTubeMediaType.Channel + End Sub + Public Overrides Function ToString(ByVal ForMediaItem As Boolean) As String + Return UserTitle + End Function + Public Overrides Function Parse(ByVal Container As EContainer, ByVal Path As SFile, ByVal IsMusic As Boolean, + Optional ByVal Token As Threading.CancellationToken = Nothing, Optional ByVal Progress As IMyProgress = Nothing) As Boolean + _MediaType = IIf(IsMusic, Plugin.UserMediaTypes.Audio, Plugin.UserMediaTypes.Video) + _ObjectType = Base.YouTubeMediaType.Channel + Me.IsMusic = IsMusic + If ParseFiles(Path, IsMusic, Token, Progress) Then + PlaylistID = String.Empty + PlaylistIndex = 0 + PlaylistTitle = String.Empty + ThrowAny(Token) + + 'Reconfiguration + If IsMusic AndAlso HasElements AndAlso Elements.Exists(Function(e) Not e.PlaylistID.IsEmptyString) Then + Dim elems As New List(Of IYouTubeMediaContainer)(Elements) + Dim elemsNew As New List(Of IYouTubeMediaContainer) + Dim playlistDic As New Dictionary(Of String, List(Of IYouTubeMediaContainer)) + Elements.Clear() + For Each elem In elems + If Not elem.PlaylistTitle.IsEmptyString Then + If Not playlistDic.ContainsKey(elem.PlaylistTitle) Then playlistDic.Add(elem.PlaylistTitle, New List(Of IYouTubeMediaContainer)) + playlistDic(elem.PlaylistTitle).Add(elem) + ElseIf elem.PlaylistID = elem.UserID Then + elem.PlaylistID = String.Empty + elem.PlaylistIndex = -1 + elem.PlaylistTitle = String.Empty + elemsNew.Add(elem) + Else + elemsNew.Add(elem) + End If + Next + If playlistDic.Count > 0 Then + Dim i%, ii% + Dim v As YouTubeMediaContainerBase + For Each kv In playlistDic + i = -1 + If elemsNew.Count > 0 Then i = elemsNew.FindIndex(Function(e) e.PlaylistID = kv.Key) + If i = -1 Then + elemsNew.Add(New PlayList) + v = kv.Value.First + With DirectCast(elemsNew.Last, YouTubeMediaContainerBase) + .ObjectType = Base.YouTubeMediaType.PlayList + .MediaType = v.MediaType + .IsMusic = v.IsMusic + .ID = v.PlaylistID + .Title = v.PlaylistTitle + .PlaylistID = .ID + .PlaylistTitle = .Title + .PlaylistIndex = -1 + .UserID = v.UserID + .UserTitle = v.UserTitle + End With + i = elemsNew.Count - 1 + End If + With elemsNew(i).Elements + .AddRange(kv.Value) + If .Count > 0 Then + For ii = 0 To .Count - 1 + With DirectCast(.Item(ii), YouTubeMediaContainerBase) + .PlaylistIndex = ii + 1 + .PlaylistCount = kv.Value.Count + End With + Next + End If + End With + Next + playlistDic.Clear() + End If + If elemsNew.Count > 0 Then Elements.AddRange(elemsNew) + elems.Clear() + elemsNew.Clear() + File = MyYouTubeSettings.OutputPath + End If + + Return True + Else + Return False + End If + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Objects/IYouTubeMediaContainer.vb b/SCrawler.YouTube/Objects/IYouTubeMediaContainer.vb new file mode 100644 index 0000000..2d2c7da --- /dev/null +++ b/SCrawler.YouTube/Objects/IYouTubeMediaContainer.vb @@ -0,0 +1,88 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports SCrawler.Plugin +Imports SCrawler.API.YouTube.Base +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Imports PersonalUtilities.Forms.Toolbars +Imports UMTypes = SCrawler.Plugin.UserMediaTypes +Imports UMStates = SCrawler.Plugin.UserMediaStates +Namespace API.YouTube.Objects + Public Interface IYouTubeMediaContainer : Inherits IDownloadableMedia, IEContainerProvider, IComparable(Of IYouTubeMediaContainer) +#Region "Events" + Event FileDownloaded As EventHandler + Event FileDownloadStarted As EventHandler + Event DataDownloaded As EventHandler +#End Region +#Region "Base data" + ReadOnly Property ObjectType As YouTubeMediaType + ReadOnly Property MediaType As UMTypes + ReadOnly Property MediaState As UMStates + Property IsMusic As Boolean + Property ID As String + Property Description As String + Property PlaylistID As String + Property PlaylistTitle As String + Property UserID As String + Property UserTitle As String +#End Region +#Region "Playlist support" + ReadOnly Property Elements As List(Of IYouTubeMediaContainer) + ReadOnly Property HasElements As Boolean + ReadOnly Property Count As Integer + Property PlaylistIndex As Integer +#End Region +#Region "Data info" +#Region "Thumbnails" + ReadOnly Property Thumbnails As List(Of Thumbnail) + ReadOnly Property ThumbnailUrlMedia As String + Overloads ReadOnly Property ThumbnailFile As SFile +#End Region +#Region "Subtitles" + ReadOnly Property Subtitles As List(Of Subtitles) + ReadOnly Property SubtitlesSelectedIndexes As List(Of Integer) +#End Region +#Region "MediaObjects" + ReadOnly Property MediaObjects As List(Of MediaObject) + Property SelectedAudioIndex As Integer + Property SelectedVideoIndex As Integer +#End Region + ReadOnly Property SizeStr As String + Property Height As Integer + Property Bitrate As Integer + Property DateCreated As Date + Property DateAdded As Date + Property DateDownloaded As Date + Property OutputVideoExtension As String + Property OutputAudioCodec As String + Property OutputSubtitlesFormat As String +#End Region +#Region "HasError, Exists, Checked" + ReadOnly Property CheckState As CheckState +#End Region +#Region "URL, File, Command" + Overloads Property File As SFile + ReadOnly Property Files As List(Of SFile) + Property UseCookies As Boolean + ReadOnly Property Command(ByVal WithCookies As Boolean) As String + Sub UpdateInfoFields() +#End Region +#Region "Download" + Overloads Property Progress As MyProgress +#End Region +#Region "Parse, Load" + Overloads Sub Load(ByVal f As SFile) + Overloads Function Parse(ByVal Container As EContainer, ByVal Path As SFile, ByVal IsMusic As Boolean, + Optional ByVal Token As Threading.CancellationToken = Nothing, Optional ByVal Progress As IMyProgress = Nothing) As Boolean +#End Region +#Region "IDisposable" + ReadOnly Property IsDisposed As Boolean +#End Region + End Interface +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Objects/PlayList.vb b/SCrawler.YouTube/Objects/PlayList.vb new file mode 100644 index 0000000..e418238 --- /dev/null +++ b/SCrawler.YouTube/Objects/PlayList.vb @@ -0,0 +1,43 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Forms.Toolbars +Namespace API.YouTube.Objects + Public Class PlayList : Inherits YouTubeMediaContainerBase + Public Sub New() + _ObjectType = Base.YouTubeMediaType.PlayList + End Sub + Public Overrides Function ToString(ByVal ForMediaItem As Boolean) As String + Dim t$ = String.Empty + Dim s$ = SizeStr + Dim __title$ = $" - {Title}" + If Not s.IsEmptyString Then s = $" [{s}]" + If Not PlaylistTitle.IsEmptyString And Not ForMediaItem Then t = $"{PlaylistTitle} - " + If IsMusic Then + If Count <= 1 Then t &= "Single" Else t &= "Album" + Else + t &= "Playlist" + End If + If Not PlaylistTitle.IsEmptyString And Not ForMediaItem Then t &= $" - {PlaylistTitle}" + If PlaylistTitle = Title Then __title = String.Empty + If ForMediaItem Then + Return $"{t} ({Count}){__title}" + Else + Return $"{t} ({Count}){__title} ({AConvert(Of String)(Duration, TimeToStringProvider)}){s}" + End If + End Function + Public Overrides Function Parse(ByVal Container As EContainer, ByVal Path As SFile, ByVal IsMusic As Boolean, + Optional ByVal Token As Threading.CancellationToken = Nothing, Optional ByVal Progress As IMyProgress = Nothing) As Boolean + _MediaType = IIf(IsMusic, Plugin.UserMediaTypes.Audio, Plugin.UserMediaTypes.Video) + _ObjectType = Base.YouTubeMediaType.PlayList + Me.IsMusic = IsMusic + Return ParseFiles(Path, IsMusic, Token, Progress) + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Objects/Track.vb b/SCrawler.YouTube/Objects/Track.vb new file mode 100644 index 0000000..1cee2bb --- /dev/null +++ b/SCrawler.YouTube/Objects/Track.vb @@ -0,0 +1,61 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Forms.Toolbars +Namespace API.YouTube.Objects + Public Class Track : Inherits YouTubeMediaContainerBase + Public Sub New() + IsMusic = True + ObjectType = Base.YouTubeMediaType.Single + End Sub + Protected Overrides Sub GenerateFileName() + If Not FileSetManually Or _File.IsEmptyString Then + Dim indx$ = String.Empty + If PlaylistIndex > 0 Then indx = PlaylistIndex.NumToString(ANumbers.Formats.NumberGroup, PlaylistCount.ToString.Length) + If Not indx.IsEmptyString Then indx &= ". " + _File.Name = $"{indx}{Title}" + If Not OutputAudioCodec.IsEmptyString Then + _File.Extension = OutputAudioCodec.StringToLower + ElseIf Not MyYouTubeSettings.DefaultAudioCodecMusic.IsEmptyString Then + _File.Extension = MyYouTubeSettings.DefaultAudioCodecMusic.Value.StringToLower + Else + _File.Extension = mp3 + End If + End If + End Sub + Public Overrides Function ToString(ByVal ForMediaItem As Boolean) As String + Dim s$ = SizeStr + If Not s.IsEmptyString Then s = $" [{s}]" + Dim pls$ = String.Empty + If PlaylistIndex > 0 Then pls = $"{PlaylistIndex.NumToString(ANumbers.Formats.NumberGroup, PlaylistCount.ToString.Length)}. " + If ForMediaItem Then + Return Title + Else + Return $"{pls}{Title} ({AConvert(Of String)(Duration, TimeToStringProvider)}){s}" + End If + End Function + Public Overrides Function Parse(ByVal Container As EContainer, ByVal Path As SFile, ByVal IsMusic As Boolean, + Optional ByVal Token As Threading.CancellationToken = Nothing, Optional ByVal Progress As IMyProgress = Nothing) As Boolean + _MediaType = Plugin.UserMediaTypes.Audio + _ObjectType = Base.YouTubeMediaType.Single + Me.IsMusic = IsMusic + If MyBase.Parse(Container, Path, IsMusic, Token, Progress) Then + Dim f As SFile = MyYouTubeSettings.OutputPath + If f.IsEmptyString Then f = "YouTubeDownloads\OutputFile.mp3" + Dim ext$ = MyYouTubeSettings.DefaultAudioCodec.Value.StringToLower + If ext.IsEmptyString Then ext = "mp3" + f.Extension = ext + File = f + Return True + Else + Return False + End If + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Objects/Video.vb b/SCrawler.YouTube/Objects/Video.vb new file mode 100644 index 0000000..d99ef8c --- /dev/null +++ b/SCrawler.YouTube/Objects/Video.vb @@ -0,0 +1,29 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports SCrawler.API.YouTube.Base +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Forms.Toolbars +Namespace API.YouTube.Objects + Public Class Video : Inherits YouTubeMediaContainerBase + Public Sub New() + _ObjectType = YouTubeMediaType.Single + _MediaType = Plugin.UserMediaTypes.Video + End Sub + Public Overrides Function ToString(ByVal ForMediaItem As Boolean) As String + Return Title + End Function + Public Overrides Function Parse(ByVal Container As EContainer, ByVal Path As SFile, ByVal IsMusic As Boolean, + Optional ByVal Token As Threading.CancellationToken = Nothing, Optional ByVal Progress As IMyProgress = Nothing) As Boolean + _MediaType = Plugin.UserMediaTypes.Video + _ObjectType = YouTubeMediaType.Single + Me.IsMusic = IsMusic + Return MyBase.Parse(Container, Path, IsMusic, Token, Progress) + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb b/SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb new file mode 100644 index 0000000..8460109 --- /dev/null +++ b/SCrawler.YouTube/Objects/YouTubeMediaContainerBase.vb @@ -0,0 +1,1457 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Threading +Imports SCrawler.Plugin +Imports SCrawler.API.YouTube.Base +Imports PersonalUtilities.Forms.Toolbars +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.XML.Base +Imports PersonalUtilities.Functions.XML.Attributes +Imports PersonalUtilities.Functions.RegularExpressions +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Tools.Web.Clients +Imports PersonalUtilities.Tools.Web.Documents.JSON +Imports UMTypes = SCrawler.Plugin.UserMediaTypes +Imports UMStates = SCrawler.Plugin.UserMediaStates +Imports CollectionModes = PersonalUtilities.Functions.XML.Objects.IXMLValuesCollection.Modes +Namespace API.YouTube.Objects + Public Class ContainerDateComparer : Implements IComparer(Of IYouTubeMediaContainer) + Private ReadOnly NullDateValue As New Date + Public Function Compare(ByVal x As IYouTubeMediaContainer, ByVal y As IYouTubeMediaContainer) As Integer Implements IComparer(Of IYouTubeMediaContainer).Compare + If x.DateDownloaded = NullDateValue And y.DateDownloaded = NullDateValue Then + Return x.DateCreated.CompareTo(y.DateCreated) * -1 + ElseIf x.DateDownloaded = NullDateValue Then + Return -1 + ElseIf y.DateDownloaded = NullDateValue Then + Return 1 + Else + Return x.DateDownloaded.CompareTo(y.DateDownloaded) * -1 + End If + End Function + End Class + Public MustInherit Class YouTubeMediaContainerBase : Implements IYouTubeMediaContainer +#Region "Events" + Public Event CheckedChange As EventHandler Implements IDownloadableMedia.CheckedChange + Public Event FileDownloaded As EventHandler Implements IYouTubeMediaContainer.FileDownloaded + Public Event FileDownloadStarted As EventHandler Implements IYouTubeMediaContainer.FileDownloadStarted + Public Event DataDownloaded As EventHandler Implements IYouTubeMediaContainer.DataDownloaded + Public Event ThumbnailChanged As EventHandler Implements IDownloadableMedia.ThumbnailChanged + Public Event StateChanged As EventHandler Implements IDownloadableMedia.StateChanged +#End Region +#Region "XML names" + Protected Friend Const Name_ObjectType As String = "ObjectType" + Protected Friend Const Name_MediaType As String = "MediaType" + Protected Friend Const Name_SiteKey As String = "SiteKey" + Protected Friend Const Name_IsMusic As String = "IsMusic" + Protected Friend Const Name_CachePath As String = "CachePath" + + Private Const Name_CheckedElements As String = "CheckedElements" + Private Const Name_CheckedAttribute As String = "Checked" +#End Region +#Region "Base data" + Protected _ObjectType As YouTubeMediaType = YouTubeMediaType.Undefined + Public Property ObjectType As YouTubeMediaType Implements IYouTubeMediaContainer.ObjectType + Get + Return _ObjectType + End Get + Set(ByVal t As YouTubeMediaType) + _ObjectType = t + End Set + End Property + Protected _MediaType As UMTypes = UMTypes.Undefined + Public Property MediaType As UMTypes Implements IYouTubeMediaContainer.MediaType, IUserMedia.ContentType + Get + Return _MediaType + End Get + Set(ByVal t As UMTypes) + _MediaType = t + End Set + End Property + Protected _MediaState As UMStates = UMStates.Unknown + Public Property MediaState As UMStates Implements IYouTubeMediaContainer.MediaState, IUserMedia.DownloadState + Get + If _MediaState = UMStates.Unknown And HasElements Then + Return If(Elements.Exists(Function(e) e.MediaState = UMStates.Downloaded), UMStates.Downloaded, _MediaState) + Else + Return _MediaState + End If + End Get + Set(ByVal s As UMStates) + _MediaState = s + End Set + End Property + Protected _SiteIcon As Image = Nothing + Protected _SiteIconSetManually As Boolean = False + Public Property SiteIcon As Image Implements IDownloadableMedia.SiteIcon + Get + If _SiteIconSetManually Then + Return _SiteIcon + Else + Return If(IsMusic, My.Resources.SiteYouTube.YouTubeMusicPic_96, My.Resources.SiteYouTube.YouTubePic_96) + End If + End Get + Set(ByVal Img As Image) + _SiteIcon = Img + _SiteIconSetManually = True + End Set + End Property + Protected _Site As String = YouTubeSite + Public Property Site As String Implements IDownloadableMedia.Site + Get + Return _Site + End Get + Set(ByVal s As String) + _Site = s + End Set + End Property + Protected _SiteKey As String = YouTubeSiteKey + Public Property SiteKey As String Implements IDownloadableMedia.SiteKey + Get + Return _SiteKey + End Get + Set(ByVal Key As String) + _SiteKey = Key + End Set + End Property + Public Property IsMusic As Boolean = False Implements IYouTubeMediaContainer.IsMusic + Public Property IsShorts As Boolean = False + Public Property ID As String Implements IYouTubeMediaContainer.ID, IUserMedia.PostID + Public Property Title As String Implements IDownloadableMedia.Title + Public Property Description As String Implements IYouTubeMediaContainer.Description + Public Property PlaylistID As String Implements IYouTubeMediaContainer.PlaylistID + Public Property PlaylistTitle As String Implements IYouTubeMediaContainer.PlaylistTitle + Public Property UserID As String Implements IYouTubeMediaContainer.UserID + Public Property UserTitle As String Implements IYouTubeMediaContainer.UserTitle +#End Region +#Region "Playlist support" + Friend ReadOnly Property Elements As List(Of IYouTubeMediaContainer) Implements IYouTubeMediaContainer.Elements + Friend ReadOnly Property HasElements As Boolean Implements IYouTubeMediaContainer.HasElements + Get + Return Count > 0 + End Get + End Property + Friend ReadOnly Property Count As Integer Implements IYouTubeMediaContainer.Count + Get + Return Elements.Count + End Get + End Property + Public Property PlaylistIndex As Integer = -1 Implements IYouTubeMediaContainer.PlaylistIndex + Protected Friend PlaylistCount As Integer = 0 +#End Region +#Region "Data info" + Friend ReadOnly Property MediaObjects As List(Of MediaObject) Implements IYouTubeMediaContainer.MediaObjects +#Region "Array" + ''' [-10] = disabled; [-1] = max; [-2] = audio only + Friend Property ArrayMaxResolution As Integer = -10 + ''' [-1] = max; [-2] = audio only + Friend Sub SetMaxResolution(ByVal Value As Integer) + ArrayMaxResolution = Value + SelectedVideoIndex = -1 + If MediaObjects.Count > 0 And Value <> -2 Then + If Value = -1 Then + SelectedVideoIndex = MediaObjects.FindIndex(Function(mo) mo.Type = UMTypes.Video) + Else + SelectedVideoIndex = MediaObjects.FindIndex(Function(mo) mo.Type = UMTypes.Video And mo.Height <= Value) + If SelectedVideoIndex = -1 Then SelectedVideoIndex = MediaObjects.FindIndex(Function(mo) mo.Type = UMTypes.Video) + End If + End If + If HasElements Then Elements.ForEach(Sub(e As YouTubeMediaContainerBase) e.SetMaxResolution(Value)) + End Sub +#End Region +#Region "Thumbnails" + Public ReadOnly Property Thumbnails As List(Of Thumbnail) Implements IYouTubeMediaContainer.Thumbnails + Protected _ThumbnailUrl As String = String.Empty + Public Overridable Property ThumbnailUrl As String Implements IDownloadableMedia.ThumbnailUrl + Get + If _ThumbnailUrl.IsEmptyString And Thumbnails.Count > 0 Then + Return Thumbnails.FirstOrDefault.URL + Else + Return _ThumbnailUrl + End If + End Get + Set(ByVal url As String) + _ThumbnailUrl = url + End Set + End Property + Public ReadOnly Property ThumbnailUrlMedia As String Implements IYouTubeMediaContainer.ThumbnailUrlMedia + Get + If _ThumbnailUrl.IsEmptyString And Thumbnails.Count > 0 Then + Dim u$ = Thumbnails.FirstOrDefault(Function(t) Not t.URL.Contains(".webp")).URL + If u.IsEmptyString Then u = Thumbnails.First.URL + If u.IsEmptyString Then Return ThumbnailUrl Else Return u + ElseIf HasElements Then + Return If(Elements.FirstOrDefault(Function(e) Not e.ThumbnailUrlMedia.IsEmptyString)?.ThumbnailUrlMedia, String.Empty).IfNullOrEmpty(_ThumbnailUrl) + Else + Return _ThumbnailUrl + End If + End Get + End Property + Protected _ThumbnailFile As SFile = Nothing + Public ReadOnly Property ThumbnailFile As SFile Implements IYouTubeMediaContainer.ThumbnailFile + Get + Return _ThumbnailFile + End Get + End Property + Private Property IDownloadableMedia_ThumbnailFile As String Implements IDownloadableMedia.ThumbnailFile + Get + Return ThumbnailFile + End Get + Set(ByVal f As String) + _ThumbnailFile = f + End Set + End Property +#End Region +#Region "Video" + Friend Property SelectedVideoIndex As Integer = -1 Implements IYouTubeMediaContainer.SelectedVideoIndex + Friend ReadOnly Property SelectedVideo As MediaObject + Get + If SelectedVideoIndex >= 0 Then Return MediaObjects(SelectedVideoIndex) Else Return Nothing + End Get + End Property + Protected _OutputVideoExtension As String + Friend Property OutputVideoExtension As String Implements IYouTubeMediaContainer.OutputVideoExtension + Get + Return _OutputVideoExtension + End Get + Set(ByVal _OutputVideoExtension As String) + Me._OutputVideoExtension = _OutputVideoExtension + If HasElements Then Elements.ForEach(Sub(e) e.OutputVideoExtension = _OutputVideoExtension) + End Set + End Property +#End Region +#Region "Audio" + Friend Property SelectedAudioIndex As Integer = -1 Implements IYouTubeMediaContainer.SelectedAudioIndex + Friend ReadOnly Property SelectedAudio As MediaObject + Get + If SelectedAudioIndex >= 0 Then Return MediaObjects(SelectedAudioIndex) Else Return Nothing + End Get + End Property + Protected _OutputAudioCodec As String + Friend Property OutputAudioCodec As String Implements IYouTubeMediaContainer.OutputAudioCodec + Get + Return _OutputAudioCodec + End Get + Set(ByVal _OutputAudioCodec As String) + Me._OutputAudioCodec = _OutputAudioCodec + If HasElements Then Elements.ForEach(Sub(e) e.OutputAudioCodec = _OutputAudioCodec) + End Set + End Property + + Friend ReadOnly Property PostProcessing_OutputAudioFormats As List(Of String) + Friend Sub PostProcessing_OutputAudioFormats_Reset() + PostProcessing_OutputAudioFormats.Clear() + PostProcessing_OutputAudioFormats.ListAddList(MyYouTubeSettings.DefaultAudioCodecAddit) + If PostProcessing_OutputAudioFormats.Count > 0 Then + PostProcessing_OutputAudioFormats.Sort() + PostProcessing_OutputAudioFormats.RemoveAll(Function(s) s = -1) + End If + End Sub +#End Region +#Region "Subtitles" + Protected ReadOnly _Subtitles As List(Of Subtitles) + Private ReadOnly _SubtitlesDelegated As List(Of Subtitles) + Friend ReadOnly Property Subtitles As List(Of Subtitles) Implements IYouTubeMediaContainer.Subtitles + Get + If HasElements Then + If _SubtitlesDelegated.Count > 0 Then + Return _SubtitlesDelegated + Else + Return _Subtitles.Concat(Elements.SelectMany(Function(e) e.Subtitles)).Distinct.ListIfNothing.ListSort + End If + ElseIf _SubtitlesDelegated.Count > 0 Then + Return _SubtitlesDelegated + Else + Return _Subtitles + End If + End Get + End Property + + Friend ReadOnly Property SubtitlesSelectedIndexes As List(Of Integer) Implements IYouTubeMediaContainer.SubtitlesSelectedIndexes + Protected _OutputSubtitlesFormat As String + Friend Property OutputSubtitlesFormat As String Implements IYouTubeMediaContainer.OutputSubtitlesFormat + Get + Return _OutputSubtitlesFormat + End Get + Set(ByVal _OutputSubtitlesFormat As String) + Me._OutputSubtitlesFormat = _OutputSubtitlesFormat + If HasElements Then Elements.ForEach(Sub(e) e.OutputSubtitlesFormat = _OutputSubtitlesFormat) + End Set + End Property + + Friend ReadOnly Property PostProcessing_OutputSubtitlesFormats As List(Of String) + Friend Sub PostProcessing_OutputSubtitlesFormats_Reset() + PostProcessing_OutputSubtitlesFormats.Clear() + PostProcessing_OutputSubtitlesFormats.ListAddList(MyYouTubeSettings.DefaultSubtitlesFormatAddit) + If PostProcessing_OutputSubtitlesFormats.Count > 0 Then + PostProcessing_OutputSubtitlesFormats.Sort() + PostProcessing_OutputSubtitlesFormats.RemoveAll(Function(s) s = -1) + End If + End Sub + Friend Sub SubtitlesSelectedIndexesReset() + SubtitlesSelectedIndexes.Clear() + Dim subs As List(Of Subtitles) = Subtitles + SubtitlesSelectedIndexes.ListAddList(MyYouTubeSettings.DefaultSubtitles.Select(Function(s) subs.FindIndex(Function(ss) ss.ID = s))) + If SubtitlesSelectedIndexes.Count > 0 Then + SubtitlesSelectedIndexes.Sort() + SubtitlesSelectedIndexes.RemoveAll(Function(s) s = -1) + End If + End Sub + Private Sub SetElementsSubtitles(ByVal Source As YouTubeMediaContainerBase) + If Not Source Is Nothing And HasElements Then + Dim subs As List(Of Subtitles) = Source.Subtitles + For Each elem As YouTubeMediaContainerBase In Elements + With elem + ._SubtitlesDelegated.Clear() + If subs.Count > 0 Then ._SubtitlesDelegated.AddRange(subs) + .SubtitlesSelectedIndexes.Clear() + If Source.SubtitlesSelectedIndexes.Count > 0 Then .SubtitlesSelectedIndexes.AddRange(Source.SubtitlesSelectedIndexes) + .OutputSubtitlesFormat = Source.OutputSubtitlesFormat + .PostProcessing_OutputSubtitlesFormats.Clear() + If Source.PostProcessing_OutputSubtitlesFormats.Count > 0 Then .PostProcessing_OutputSubtitlesFormats.AddRange(Source.PostProcessing_OutputSubtitlesFormats) + End With + Next + End If + End Sub +#End Region +#Region "IUserMedia Support" + Private Property Attempts As Integer Implements IUserMedia.Attempts + Private _Object As Object = Nothing + Private Property [Object] As Object Implements IUserMedia.Object + Get + Return If(_Object, Me) + End Get + Set(ByVal Obj As Object) + _Object = Obj + End Set + End Property + Private Property IUserMedia_MD5 As String Implements IUserMedia.MD5 + Public Shared Sub Update(ByVal Source As IUserMedia, ByVal Destination As IYouTubeMediaContainer) + If Not Source Is Nothing And Not Destination Is Nothing Then + Destination.ContentType = Source.ContentType + Destination.URL = Source.URL + Destination.URL_BASE = Source.URL_BASE + Destination.MD5 = Source.MD5 + Destination.File = Source.File + Destination.DownloadState = Source.DownloadState + Destination.ID = Source.PostID + Destination.PostDate = Source.PostDate + Destination.SpecialFolder = Source.SpecialFolder + Destination.Attempts = Source.Attempts + End If + End Sub +#End Region + Protected _Duration As TimeSpan = Nothing + + Public Overridable Property Duration As TimeSpan Implements IDownloadableMedia.Duration + Get + If HasElements Then + Return TimeSpan.FromSeconds(Elements.Sum(Function(e) If(e.Checked, e.Duration.TotalSeconds, 0))) + Else + Return _Duration + End If + End Get + Set(ByVal d As TimeSpan) + _Duration = d + End Set + End Property + Protected _Size As Integer = 0 + Public Overridable Property Size As Integer Implements IDownloadableMedia.Size + Get + If HasElements Then + Return Elements.Sum(Function(e) If(e.Checked, e.Size, 0)) + Else + If Checked Then + If IsMusic And SelectedAudioIndex.ValueBetween(0, MediaObjects.Count - 1) Then + Return MediaObjects(SelectedAudioIndex).Size + ElseIf Not IsMusic And SelectedVideoIndex.ValueBetween(0, MediaObjects.Count - 1) Then + Return MediaObjects(SelectedVideoIndex).Size + + If(SelectedAudioIndex.ValueBetween(0, MediaObjects.Count - 1), MediaObjects(SelectedAudioIndex).Size, 0) + Else + Return _Size + End If + Else + Return 0 + End If + End If + End Get + Set(ByVal s As Integer) + _Size = s + End Set + End Property + Public ReadOnly Property SizeStr As String Implements IYouTubeMediaContainer.SizeStr + Get + If Size > 0 Then + Dim sv% = Size / 1024 + Dim value$ + If sv >= 1000 Then + value = AConvert(Of String)(sv / 1024, VideoSizeProvider) + value &= " GB" + Else + value = AConvert(Of String)(sv, VideoSizeProvider) + value &= " MB" + End If + Return value + Else + Return String.Empty + End If + End Get + End Property + Public Property Height As Integer Implements IYouTubeMediaContainer.Height + Protected _Bitrate As Integer = 0 + Public Overridable Property Bitrate As Integer Implements IYouTubeMediaContainer.Bitrate + Get + If HasElements Then + Try + Return Elements.Average(Function(e) e.Bitrate) + Catch + Return _Bitrate + End Try + Else + Return _Bitrate + End If + End Get + Set(ByVal _Bitrate As Integer) + Me._Bitrate = _Bitrate + End Set + End Property + Public Property DateCreated As Date = Now Implements IYouTubeMediaContainer.DateCreated + Public Property DateAdded As Date Implements IYouTubeMediaContainer.DateAdded + Private Property IUserMedia_PostDate As Date? Implements IUserMedia.PostDate + Get + Return DateAdded + End Get + Set(ByVal d As Date?) + If d.HasValue Then DateAdded = d.Value Else DateAdded = New Date + End Set + End Property + Public Property DateDownloaded As Date Implements IYouTubeMediaContainer.DateDownloaded +#End Region +#Region "HasError, Exists" + Protected _HasError As Boolean = False + Public ReadOnly Property HasError As Boolean Implements IDownloadableMedia.HasError + Get + Return _HasError + End Get + End Property + Protected _Exists As Boolean = True + Public ReadOnly Property Exists As Boolean Implements IDownloadableMedia.Exists + Get + If Not _Exists Then + Return False + ElseIf Me.MediaState = UMStates.Downloaded Then + Return _Exists + ElseIf ObjectType = YouTubeMediaType.PlayList Or ObjectType = YouTubeMediaType.Channel Then + Return HasElements + Else + Return MediaObjects.Count > 0 + End If + End Get + End Property + Protected Overridable Property IDownloadableMedia_Instance As IPluginContentProvider Implements IDownloadableMedia.Instance +#End Region +#Region "Checked" + Protected _Checked As Boolean = True + Public Property Checked As Boolean Implements IDownloadableMedia.Checked + Get + If HasElements Then + Return Elements.Exists(Function(e) e.Checked) + Else + Return _Checked + End If + End Get + Set(ByVal _Checked As Boolean) + Dim b As Boolean = Not Me._Checked = _Checked + Me._Checked = _Checked + If HasElements Then Elements.ForEach(Sub(e) e.Checked = _Checked) + If b Then RaiseEvent CheckedChange(Me, Nothing) + End Set + End Property + Public ReadOnly Property CheckState As CheckState Implements IYouTubeMediaContainer.CheckState + Get + If HasElements Then + Dim ecs As IEnumerable(Of CheckState) = Elements.Select(Function(e) e.CheckState) + If ecs.All(Function(c) c = CheckState.Checked) Then + Return CheckState.Checked + ElseIf ecs.All(Function(c) c = CheckState.Unchecked) Then + Return CheckState.Unchecked + Else + Return CheckState.Indeterminate + End If + ElseIf Checked Then + Return CheckState.Checked + Else + Return CheckState.Unchecked + End If + End Get + End Property +#End Region +#Region "URL, File, Files, CachePath, SpecialPath, FileSettings" + Private CachePath As SFile + Public Property URL As String Implements IUserMedia.URL + Private _IUserMedia_URL_BASE As String = String.Empty + Private Property IUserMedia_URL_BASE As String Implements IUserMedia.URL_BASE + Get + Return _IUserMedia_URL_BASE.IfNullOrEmpty(URL) + End Get + Set(ByVal u As String) + _IUserMedia_URL_BASE = u + End Set + End Property + Protected Overridable Sub GenerateFileName() + End Sub + Protected Function GetPlayListTitle() As String + Dim plsTitle$ = String.Empty + If IsMusic And Not DateAdded = New Date Then plsTitle = $"{DateAdded.Year} - " + plsTitle &= PlaylistTitle + If IsShorts Then plsTitle &= " - Shorts" + Return plsTitle + End Function + Public Property SpecialPathDisabled As Boolean = False + Protected _SpecialPath As String = String.Empty + Public Property SpecialPath As String Implements IUserMedia.SpecialFolder + Get + If SpecialPathDisabled Then + Return String.Empty + ElseIf Not _SpecialPath.IsEmptyString Then + Return _SpecialPath + ElseIf IsShorts Then + Return "Shorts" + ElseIf HasElements Or PlaylistCount > 0 Then + Return PlaylistTitle.IfNullOrEmpty(Title).IfNullOrEmpty(UserTitle) + Else + Return String.Empty + End If + End Get + Set(ByVal p As String) + _SpecialPath = p + End Set + End Property + Public Sub SpecialPathSetForPlaylist(ByVal Path As String) + _SpecialPath = Path + _FileIsPlaylistObject = True + If ObjectType = YouTubeMediaType.Single AndAlso Not GetPlayListTitle.IsEmptyString Then _SpecialPath.StringAppend(GetPlayListTitle(), "\") + If Elements.Count > 0 Then Elements.ForEach(Sub(e) e.SpecialFolder = Path) + End Sub + Friend ReadOnly Property Files As List(Of SFile) Implements IYouTubeMediaContainer.Files + Protected _File As SFile + Protected Friend Property FileSetManually As Boolean = False + Public Property FileIgnorePlaylist As Boolean = False + Private _FileIsPlaylistObject As Boolean = False + ''' Compatible property for IUserMedia. Default: . + ''' DON'T USE IN STD! + Public ReadOnly Property FileIsPlaylistObject As Boolean + Get + Return _FileIsPlaylistObject + End Get + End Property + Public Overridable Property File As SFile Implements IYouTubeMediaContainer.File + Get + Return _File + End Get + Set(ByVal f As SFile) + Select Case ObjectType + Case YouTubeMediaType.Channel : _File = f.Path + Case YouTubeMediaType.PlayList : _File.Path = $"{f.PathWithSeparator}{GetPlayListTitle()}" + Case YouTubeMediaType.Single + If PlaylistCount > 0 And Not FileIgnorePlaylist Then + _File.Path = f.Path + Dim pls$ = GetPlayListTitle() + If Not _File.Path.Contains(pls) Then _File.Path = $"{_File.PathWithSeparator(Not pls.IsEmptyString)}{pls}" + ElseIf Not f.Name.IsEmptyString Then + _File = f + Else + _File.Path = f.Path + End If + Case Else : _File = f + End Select + GenerateFileName() + If HasElements Then Elements.ForEach(Sub(e) e.File = _File) + End Set + End Property + Public Property FileSettings As SFile + Private Property IUserMedia_File As String Implements IUserMedia.File + Get + Return File + End Get + Set(ByVal f As String) + File = f + End Set + End Property +#End Region +#Region "Command" + Public Property UseCookies As Boolean = MyYouTubeSettings.DefaultUseCookies Implements IYouTubeMediaContainer.UseCookies + Protected Const mp3 As String = "mp3" + Private Const aac As String = "aac" + Private Const ac3 As String = "ac3" + Protected PostProcessing_AudioAC3 As Boolean = False + Public Overridable ReadOnly Property Command(ByVal WithCookies As Boolean) As String Implements IYouTubeMediaContainer.Command + Get + If Not File.IsEmptyString Then + If File.Exists Then File = SFile.IndexReindex(File) + Dim cmd$ = String.Empty, formats$ = String.Empty, subs$ = String.Empty, remux$ = String.Empty + _Size = 0 + Height = 0 + Bitrate = 0 + _MediaType = UMTypes.Undefined + If SelectedVideoIndex >= 0 Then + cmd.StringAppend($"bv*[format_id={SelectedVideo.ID}]") + _Size = SelectedVideo.Size + _MediaType = UMTypes.Video + Height = SelectedVideo.Height + _File.Extension = OutputVideoExtension + Else + formats.StringAppend("--extract-audio", " ") + _MediaType = UMTypes.Audio + End If + If SelectedAudioIndex >= 0 Then + Dim atCodec$ + cmd.StringAppend($"ba*[format_id={SelectedAudio.ID}]", "+") + If OutputAudioCodec.StringToLower = ac3 Then + PostProcessing_AudioAC3 = True + formats.StringAppend($"--audio-format {aac}", " ") + atCodec = aac + Else + formats.StringAppend($"--audio-format {OutputAudioCodec.StringToLower}", " ") + atCodec = OutputAudioCodec.StringToLower + End If + If SelectedVideoIndex = -1 Then formats.StringAppend("--add-metadata", " ") + _Size += SelectedAudio.Size + If _MediaType = UMTypes.Undefined Then _MediaType = UMTypes.Audio + Bitrate = SelectedAudio.Bitrate + Dim aCodec$ = SelectedAudio.Codec.StringToLower + If Not aCodec.IsEmptyString AndAlso Not aCodec.StringToLower = atCodec Then + remux.StringAppend($"{aCodec}>{atCodec}") + If SelectedVideoIndex = -1 Then + remux &= $"/{atCodec}" + _File.Extension = atCodec + End If + End If + End If + If SelectedVideoIndex >= 0 And Not SelectedVideo.Extension.StringToLower = OutputVideoExtension.StringToLower Then _ + remux.StringAppend($"{SelectedVideo.Extension.StringToLower}>{OutputVideoExtension.StringToLower}/{OutputVideoExtension.StringToLower}", "/") + If Not remux.IsEmptyString Then formats.StringAppend($"--remux-video ""{remux}""", " ") + If SubtitlesSelectedIndexes.Count > 0 Then + subs = ListAddList(Nothing, Subtitles.Select(Function(s, i) If(SubtitlesSelectedIndexes.Contains(i), s.FullID, String.Empty)), + LAP.NotContainsOnly, EDP.ReturnValue).ListToString(",") + subs = $"--write-subs --write-auto-subs --sub-format {OutputSubtitlesFormat.StringToLower} --sub-langs ""{subs}"" --convert-subs {OutputSubtitlesFormat.StringToLower}" + End If + If Not cmd.IsEmptyString Then + cmd = $"yt-dlp -f ""{cmd}""" + cmd.StringAppend(formats, " ") + cmd.StringAppend(subs, " ") + cmd.StringAppend(YouTubeFunctions.GetCookiesCommand(WithCookies, YouTubeCookieNetscapeFile), " ") + cmd &= $" {URL} -o ""{File.PathWithSeparator}{File.Name}""" + File.Exists(SFO.Path, True) + Return cmd + End If + End If + Return String.Empty + End Get + End Property +#End Region +#Region "Initializer" + Protected Sub New() + Elements = New List(Of IYouTubeMediaContainer) + Thumbnails = New List(Of Thumbnail) + _Subtitles = New List(Of Subtitles) + _SubtitlesDelegated = New List(Of Subtitles) + SubtitlesSelectedIndexes = New List(Of Integer) + MediaObjects = New List(Of MediaObject) + Files = New List(Of SFile) + + PostProcessing_OutputSubtitlesFormats = New List(Of String) + PostProcessing_OutputSubtitlesFormats.ListAddList(MyYouTubeSettings.DefaultSubtitlesFormatAddit) + PostProcessing_OutputAudioFormats = New List(Of String) + PostProcessing_OutputAudioFormats.ListAddList(MyYouTubeSettings.DefaultAudioCodecAddit) + End Sub +#End Region +#Region "ToString, GetHashCode, Equals" + Public NotOverridable Overloads Overrides Function ToString() As String Implements IDownloadableMedia.ToString + Return ToString(False) + End Function + Public Overridable Overloads Function ToString(ByVal ForMediaItem As Boolean) As String Implements IDownloadableMedia.ToString + Return Title + End Function + Public Overrides Function GetHashCode() As Integer + Return $"{ID}.{PlaylistID}.{UserID}.{Title}".GetHashCode + End Function + Public Overrides Function Equals(ByVal Obj As Object) As Boolean + If Not Obj Is Nothing AndAlso Obj.GetType Is Me.GetType Then Return GetHashCode() = Obj.GetHashCode Else Return False + End Function +#End Region +#Region "Delete, UpdateInfoFields, ThrowAny" + Public Overridable Sub Delete(ByVal RemoveFiles As Boolean) Implements IDownloadableMedia.Delete + If CachePath.Exists(SFO.Path, False) Then CachePath.Delete(SFO.Path, SFODelete.DeletePermanently, EDP.None) + If FileSettings.Exists Then FileSettings.Delete(SFO.File, SFODelete.DeletePermanently, EDP.None) + If RemoveFiles Then + Dim fErr As New ErrorsDescriber(EDP.None) + Dim dMode As SFODelete = SFODelete.DeleteToRecycleBin + File.Delete(SFO.File, dMode, fErr) + ThumbnailFile.Delete(SFO.File, dMode, fErr) + If Files.Count > 0 Then Files.ForEach(Sub(f) f.Delete(SFO.File, dMode, fErr)) + End If + If HasElements Then Elements.ForEach(Sub(e) e.Delete(RemoveFiles)) + End Sub + Friend Sub UpdateInfoFields() Implements IYouTubeMediaContainer.UpdateInfoFields + _Size = 0 + If SelectedVideoIndex >= 0 Then _Size += SelectedVideo.Size + If SelectedAudioIndex >= 0 Then _Size += SelectedAudio.Size + If HasElements Then Elements.ForEach(Sub(e) e.UpdateInfoFields()) + End Sub + Protected Sub ThrowAny(ByVal Token As CancellationToken) + Token.ThrowIfCancellationRequested() + If disposedValue Then Throw New ObjectDisposedException(ToString(), "Object disposed") + End Sub +#End Region +#Region "Download" + Private ReadOnly DownloadProgressPattern As RParams = RParams.DMS("\[download\]\s*([\d\.,]+)", 1, EDP.ReturnValue) + Public Property Progress As MyProgress Implements IYouTubeMediaContainer.Progress + Private Property IDownloadableMedia_Progress As Object Implements IDownloadableMedia.Progress + Get + Return Progress + End Get + Set(ByVal p As Object) + If Not p Is Nothing Then + If TypeOf p Is MyProgress Then Progress = p + Else + Progress = Nothing + End If + End Set + End Property + Private Sub DownloadElementsApply() + If HasElements Then + SetElementsSubtitles(Me) + For Each elem As YouTubeMediaContainerBase In Elements + With elem + .OutputAudioCodec = OutputAudioCodec + .OutputSubtitlesFormat = OutputSubtitlesFormat + .OutputVideoExtension = OutputVideoExtension + .PostProcessing_OutputAudioFormats.Clear() + If PostProcessing_OutputAudioFormats.Count > 0 Then .PostProcessing_OutputAudioFormats.AddRange(PostProcessing_OutputAudioFormats) + End With + Next + End If + End Sub + Public Overridable Sub Download(ByVal UseCookies As Boolean, ByVal Token As CancellationToken) Implements IDownloadableMedia.Download + DownloadElementsApply() + If ObjectType = YouTubeMediaType.Single Then + DownloadCommand(UseCookies, Token) + Else + DownloadCommandArray(UseCookies, Token) + End If + RaiseEvent DataDownloaded(Me, Nothing) + End Sub + Private Function DownloadGetElemCountSingle() As Integer + If ObjectType = YouTubeMediaType.Single Then + Return 1 + Else + Return Elements.Sum(Function(e) DirectCast(e, YouTubeMediaContainerBase).DownloadGetElemCountSingle()) + End If + End Function + Protected Sub DownloadCommandArray(ByVal UseCookies As Boolean, ByVal Token As CancellationToken) + Try + If HasElements Then + Dim prExists As Boolean = Not Progress Is Nothing + Dim fDown As EventHandler = Sub(ByVal Sender As Object, ByVal e As EventArgs) + RaiseEvent FileDownloadStarted(Sender, e) + If prExists Then Progress.Perform() + End Sub + If prExists Then + With Progress + .Visible = True + .Value = 0 + .Maximum = DownloadGetElemCountSingle() + .Information = $"Download {ObjectType}" + End With + End If + + Dim cDown As Boolean = False + For Each elem In Elements + With DirectCast(elem, YouTubeMediaContainerBase) + If Not .CoverDownloaded Then .CoverDownloaded = cDown + AddHandler .FileDownloadStarted, fDown + .Download(UseCookies, Token) + cDown = .CoverDownloaded + RemoveHandler .FileDownloadStarted, fDown + End With + If Token.IsCancellationRequested Or disposedValue Then Exit For + Next + + If prExists Then + With Progress + .Value = .Maximum + .Perform(0) + .InformationTemporary = "Download completed" + End With + End If + End If + Catch oex As OperationCanceledException When Token.IsCancellationRequested + Throw oex + Catch dex As ObjectDisposedException When disposedValue + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"YTContainer.DownloadArrayError{ToString()}") + Finally + If Not Token.IsCancellationRequested And Not disposedValue Then + DateDownloaded = Now + MediaState = UMStates.Downloaded + End If + End Try + End Sub + Protected CoverDownloaded As Boolean = False + Private Sub DownloadPlaylistCover(ByVal PlsId As String, ByVal f As SFile, ByVal UseCookies As Boolean) + Try + Dim url$ = $"https://{IIf(IsMusic, "music", "www")}.youtube.com/playlist?list={PlsId}" + Dim r$ + Using resp As New Responser + If UseCookies And MyYouTubeSettings.Cookies.Count > 0 Then resp.Cookies.AddRange(MyYouTubeSettings.Cookies,, EDP.SendToLog) + r = resp.GetResponse(url,, EDP.ReturnValue) + If Not r.IsEmptyString Then + Dim p As RParams = RParams.DM("(?<=https:[\\/]{2,4})[^\.]*[\.]?googleusercontent.com[^\,]+?w(\d+).h(\d+)[^\,]+?(?=\\x22)", 0, RegexReturn.List, EDP.ReturnValue) + Dim l As List(Of String) = RegexReplace(r, p) + If l.ListExists Then l.RemoveAll(Function(uu) uu.IsEmptyString) + If l.ListExists Then + Dim u$ = l.Last + u = u.Replace("\/", "/").TrimStart("/") + Dim position% + Dim ch$ + Do + position = InStr(u, "\") + If position > 0 Then + ch = $"%{Mid(u, position + 2, 2)}" + ch = SymbolsConverter.ASCII.Decode(ch, New ErrorsDescriber(False, False, False, String.Empty)) + u = u.Replace(Mid(u, position, 4), ch) + End If + Loop While position > 0 + url = LinkFormatterSecure(u) + f.Name = "cover" + f.Extension = "jpg" + If resp.DownloadFile(url, f, EDP.ReturnValue) And f.Exists Then CoverDownloaded = True + End If + End If + End Using + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"DownloadPlaylistCover({PlsId}, {f})") + End Try + End Sub + Protected Sub DownloadCommand(ByVal UseCookies As Boolean, ByVal Token As CancellationToken) + Dim dCommand$ = String.Empty + Try + ThrowAny(Token) + If MediaState = UMStates.Downloaded Or Not Checked Then Exit Sub + Dim h As DataReceivedEventHandler = Sub(ByVal Sender As Object, ByVal e As DataReceivedEventArgs) + If Not e.Data.IsEmptyString Then + Dim v# = AConvert(Of Double)(RegexReplace(e.Data, DownloadProgressPattern), NumberProvider, -1) + If v >= 0 Then Progress.Value = v : Progress.Perform(0) + End If + End Sub + RaiseEvent FileDownloadStarted(Me, Nothing) + Using batch As New BatchExecutor(True) With {.Encoding = 65001} + With batch + Dim prExists As Boolean = Not Progress Is Nothing + If prExists Then + AddHandler .OutputDataReceived, h + With Progress + .Visible = True + .Value = 0 + .Maximum = 100 + .Provider = ProgressProvider + .Information = $"Download {MediaType}" + End With + End If + .FileExchanger = MyCache.NewInstance(Of BatchFileExchanger)(CachePath, EDP.ReturnValue) + .FileExchanger.DeleteCacheOnDispose = True + .AddCommand("chcp 65001") + .ChangeDirectory(MyYouTubeSettings.YTDLP.Value) + dCommand = Command(UseCookies) +#If DEBUG Then + Debug.WriteLine(dCommand) +#End If + Task.WaitAll({Task.Run(Sub() .Execute(dCommand))}, Token) + If Token.IsCancellationRequested Then .Kill(EDP.None) + ThrowAny(Token) + If prExists Then + RemoveHandler .OutputDataReceived, h + Progress.Value = 100 + Progress.Perform(0) + End If + If Not File.Exists Then _File.Name = File.File + If File.Exists Then + If PlaylistCount > 0 And Not CoverDownloaded And Not PlaylistID.IsEmptyString Then DownloadPlaylistCover(PlaylistID, File, UseCookies) + If prExists Then Progress.InformationTemporary = $"Download {MediaType}: post processing" + _ThumbnailFile = File + _ThumbnailFile.Name &= "_thumb" + _ThumbnailFile.Extension = "jpg" + If Not ThumbnailUrl.IsEmptyString Then GetWebFile(ThumbnailUrl, _ThumbnailFile, EDP.None) + + ThrowAny(Token) + If MyYouTubeSettings.FFMPEG.Value.Exists Then + .Reset() + .CommandsPermanent.Clear() + .CommandsPermanent.AddRange({"chcp 65001", BatchExecutor.GetDirectoryCommand(MyYouTubeSettings.FFMPEG.Value)}) + .AutoReset = True + Dim files As IEnumerable(Of SFile) + Dim f As SFile + Dim commandFile As SFile + Dim format$ + Dim fPattern$ = $"{File.PathWithSeparator}{File.Name}." & "{0}" + Dim fPatternFiles$ = $"{File.Name}*." & "{0}" + Dim fAacAudio As New SFile(String.Format(fPattern, aac)) + Dim fAc3Audio As New SFile(String.Format(fPattern, ac3)) + Dim aacRequested As Boolean = PostProcessing_OutputAudioFormats.Count > 0 AndAlso + PostProcessing_OutputAudioFormats.Exists(Function(af) af.StringToLower = aac) + Dim ac3Requested As Boolean = PostProcessing_OutputAudioFormats.Count > 0 AndAlso + PostProcessing_OutputAudioFormats.Exists(Function(af) af.StringToLower = ac3) + + ThrowAny(Token) + If PostProcessing_OutputSubtitlesFormats.Count > 0 Then + files = SFile.GetFiles(File, String.Format(fPatternFiles, OutputSubtitlesFormat.StringToLower),, EDP.ReturnValue) + If files.ListExists Then + For Each f In files + For Each format In PostProcessing_OutputSubtitlesFormats + format = format.StringToLower + commandFile = $"{f.PathWithSeparator}{f.Name}.{format}" + Me.Files.Add(commandFile) + ThrowAny(Token) + .Execute($"ffmpeg -i ""{f}"" ""{commandFile}""") + Next + Next + End If + End If + + ThrowAny(Token) + If PostProcessing_OutputAudioFormats.Count > 0 Or PostProcessing_AudioAC3 Then + If Not fAacAudio.Exists Then .Execute($"ffmpeg -i ""{File}"" -vn -acodec {aac} ""{fAacAudio}""") + If PostProcessing_AudioAC3 And Not fAc3Audio.Exists Then + ThrowAny(Token) + .Execute($"ffmpeg -i ""{File}"" -vn -acodec {ac3} ""{fAc3Audio}""") + If Not fAc3Audio.Exists And fAacAudio.Exists Then ThrowAny(Token) : .Execute($"ffmpeg -i ""{fAacAudio}"" -f {ac3} ""{fAc3Audio}""") + End If + If PostProcessing_OutputAudioFormats.Count > 0 Then + For Each format In PostProcessing_OutputAudioFormats + format = format.StringToLower + f = String.Format(fPattern, format) + Me.Files.Add(f) + If Not format = ac3 Or Not f.Exists Then ThrowAny(Token) : .Execute($"ffmpeg -i ""{fAacAudio}"" -f {format} ""{f}""") + Next + End If + End If + + ThrowAny(Token) + If PostProcessing_AudioAC3 Then + f = File + If SelectedVideoIndex >= 0 Then + f.Name &= "tmp00" + Else + f.Extension = ac3 + End If + If Not f.Exists Then ThrowAny(Token) : .Execute($"ffmpeg -i ""{File}"" -i ""{fAc3Audio}"" -c:v copy -c copy -map 0:v:0 -map 1:a:0 ""{f}""") + If f.Exists Then + File.Delete() + If SelectedVideoIndex >= 0 Then SFile.Rename(f, File,, EDP.LogMessageValue) + End If + If fAacAudio.Exists And Not aacRequested Then fAacAudio.Delete() + If fAc3Audio.Exists And Not ac3Requested And SelectedVideoIndex >= 0 Then fAc3Audio.Delete() + End If + End If + End If + End With + End Using + _MediaState = UMStates.Downloaded + Catch oex As OperationCanceledException When Token.IsCancellationRequested + Throw oex + Catch dex As ObjectDisposedException When disposedValue + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"YTContainer.DownloadError: {ToString()}".StringAppendLine(dCommand)) + Finally + If Not Token.IsCancellationRequested And Not disposedValue Then + DateDownloaded = Now + MediaState = UMStates.Downloaded + If Not Progress Is Nothing Then Progress.InformationTemporary = "Download completed" + RaiseEvent FileDownloaded(Me, Nothing) + End If + End Try + End Sub +#End Region +#Region "Load" + Private Sub ApplyElementCheckedValue(ByVal e As EContainer) + If HasElements And e.Count > 0 Then + Dim obj As YouTubeMediaContainerBase + For Each elem As EContainer In e + If Not elem.Value.IsEmptyString Then + obj = GetElementByID(elem.Value, True) + If Not obj Is Nothing Then + If obj.HasElements Then + obj.ApplyElementCheckedValue(elem) + Else + obj.Checked = elem.Attribute(Name_CheckedAttribute).Value.FromXML(Of Boolean)(True) + End If + End If + End If + Next + End If + End Sub + Private Function GetElementByID(ByVal ID As String, Optional ByVal IgnoreCurrentInstance As Boolean = False) As YouTubeMediaContainerBase + If HasElements Then + Dim obj As YouTubeMediaContainerBase + For Each elem As YouTubeMediaContainerBase In Elements + If elem.ID = ID Then + Return elem + Else + obj = elem.GetElementByID(ID) + If Not obj Is Nothing Then Return obj + End If + Next + ElseIf Not IgnoreCurrentInstance And Me.ID = ID Then + Return Me + End If + Return Nothing + End Function + Private Sub IDownloadableMedia_Load(ByVal File As String) Implements IDownloadableMedia.Load + Load(File) + End Sub + Public Overridable Sub Load(ByVal f As SFile) Implements IYouTubeMediaContainer.Load + Try + FileSettings = f + If f.Exists Then + Using x As New XmlFile(f, Protector.Modes.All, False) With {.AllowSameNames = True, .XmlReadOnly = True} + x.LoadData() + Dim fc As SFile = x.Value(Name_CachePath).CSFileP + If fc.Exists(SFO.Path, False) AndAlso SFile.GetFiles(fc, "*.json",, EDP.ReturnValue).Count > 0 Then Parse(Nothing, fc, IsMusic) + XMLPopulateData(Me, x) + _Exists = True + If If(x(Name_CheckedElements)?.Count, 0) > 0 Then ApplyElementCheckedValue(x(Name_CheckedElements)) + If ArrayMaxResolution <> -10 Then SetMaxResolution(ArrayMaxResolution) + End Using + Else + _Exists = False + End If + Catch ex As Exception + _HasError = True + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"YouTubeMediaContainerBase.Load({f})") + End Try + End Sub +#End Region +#Region "Save" + Public Overridable Sub Save() Implements IDownloadableMedia.Save + Try + Dim fSettings As SFile = FileSettings + If fSettings.IsEmptyString Then fSettings = MyCacheSettings.NewFile + Dim f As SFile = fSettings + + If Not MediaState = UMStates.Downloaded Then + If CachePath.Exists(SFO.Path, False) AndAlso Not CachePath.Path.Contains(MyCacheSettings.RootDirectory.Path) Then + f = $"{f.PathWithSeparator}{f.Name}\" + If f.Exists(SFO.Path) Then + Dim files As List(Of SFile) = SFile.GetFiles(CachePath, "*.json", IO.SearchOption.AllDirectories, EDP.ReturnValue) + If files.ListExists Then + CachePath = f + Dim fd As SFile = f + fd.Extension = "json" + For Each f In files + fd.Name = f.Name + SFile.Move(f, fd) + Next + Else + If CachePath.Exists(SFO.Path, False) Then CachePath.Delete(SFO.Path, SFODelete.DeletePermanently, EDP.None) + CachePath = Nothing + End If + End If + End If + Else + If CachePath.Exists(SFO.Path, False) Then CachePath.Delete(SFO.Path, SFODelete.DeletePermanently, EDP.None) + CachePath = Nothing + End If + + Using x As New XmlFile With {.AllowSameNames = True} + fSettings.Extension = "xml" + FileSettings = fSettings + x.AddRange(ToEContainer.Elements) + x.Name = "MediaContainer" + x.Save(fSettings) + End Using + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"YouTubeMediaContainerBase.Save({FileSettings})") + End Try + End Sub +#End Region +#Region "Parse" + Public Overridable Function Parse(ByVal Container As EContainer, ByVal Path As SFile, ByVal IsMusic As Boolean, + Optional ByVal Token As CancellationToken = Nothing, Optional ByVal Progress As IMyProgress = Nothing) As Boolean Implements IYouTubeMediaContainer.Parse + Try + Me.IsMusic = IsMusic + CachePath = Path + If Not Path.IsEmptyString And Not Container Is Nothing Then Throw New InvalidOperationException("Both arguments (Container, Path) are not null") + If Path.IsEmptyString And Container Is Nothing Then Throw New InvalidOperationException("Both arguments (Container, Path) are null") + If Not Path.IsEmptyString AndAlso Not Path.Exists(SFO.File, False) AndAlso Path.Exists(SFO.Path, False) Then + Dim files As List(Of SFile) = SFile.GetFiles(Path,, IO.SearchOption.AllDirectories, EDP.ReturnValue) + If files.Count > 0 Then + If files.Count = 1 Then + Path = files(0) + Else + If ParseFiles(Path, IsMusic, Token, Progress) Then + File = MyYouTubeSettings.OutputPath + Return True + Else + Return False + End If + End If + End If + End If + ThrowAny(Token) + If Not Path.IsEmptyString AndAlso Not Path.File.IsEmptyString AndAlso Path.Exists(SFO.File, False) Then + Dim t$ = Path.GetText(EDP.ReturnValue) + If Not t.IsEmptyString Then Container = JsonDocument.Parse(t, EDP.ReturnValue) + End If + If Not Container Is Nothing Then + With Container + ID = .Value("id") + Title = TitleHtmlConverter.Invoke(.Value("title")) + Description = .Value("description") + URL = .Value("webpage_url") + + PlaylistID = .Value("playlist_id") + PlaylistCount = .Value("n_entries").IfNullOrEmpty(.Value("playlist_count")).FromXML(Of Integer)(0) + PlaylistIndex = .Value("playlist_index").FromXML(Of Integer)(-1) + PlaylistTitle = TitleHtmlConverter.Invoke(.Value("album").IfNullOrEmpty(.Value("playlist_title")).IfNullOrEmpty(.Value("playlist"))) + If Not PlaylistTitle.IsEmptyString And .Value("album").IsEmptyString Then + Dim tmpPls$ = PlaylistTitle.Replace("Album", String.Empty).StringTrimStart(" ", "-") + IsShorts = Not tmpPls.IsEmptyString AndAlso PlaylistTitle.Contains(" - Shorts") + tmpPls = tmpPls.Replace("Shorts", String.Empty).StringTrimStart(" ", "-") + If Not tmpPls.IsEmptyString Then PlaylistTitle = tmpPls + End If + + UserID = .Value("uploader_id") + UserTitle = TitleHtmlConverter.Invoke(.Value("uploader")) + If Not UserTitle.IsEmptyString Then + Dim tmpTitle$ = UserTitle.Replace("Topic", String.Empty).StringTrimEnd(" ", "-") + If Not tmpTitle.IsEmptyString Then UserTitle = tmpTitle + End If + + Dim ext$ = IIf(IsMusic, + MyYouTubeSettings.DefaultAudioCodecMusic.Value.StringToLower, + MyYouTubeSettings.DefaultVideoFormat.Value.StringToLower) + If ext.IsEmptyString Then ext = IIf(IsMusic, "mp3", "mp4") + If Not Title.IsEmptyString Then + _File = $"{Title}.{ext}" + Else + _File.Name = $"{ID}.{ext}" + End If + If Not MyYouTubeSettings.OutputPath.IsEmptyString Then _File.Path = MyYouTubeSettings.OutputPath.Value.Path + File = _File + + If .Contains("duration") Then + Dim tValue%? = AConvert(Of Integer)(.Value("duration"), AModes.Var, Nothing) + If tValue.HasValue Then Duration = TimeSpan.FromSeconds(tValue.Value) + End If + DateAdded = AConvert(Of Date)(.Value("release_date").IfNullOrEmpty(.Value("upload_date")), DateAddedProvider, New Date) + + ParseFormats(.Self) + + ParseThumbnails(.Self) + + ParseSubtitles(.Self) + End With + Return True + End If + If Not Progress Is Nothing Then Progress.Perform() + Return False + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.YouTube.Objects.YouTubeMediaContainerBase.Parse]") + _HasError = True + Return False + End Try + End Function + Protected Function ParseFiles(ByVal Path As SFile, ByVal IsMusic As Boolean, Optional ByVal Token As CancellationToken = Nothing, + Optional ByVal Progress As IMyProgress = Nothing) As Boolean + Me.IsMusic = IsMusic + CachePath = Path + If Path.Exists(SFO.Path, False) Then + Dim files As List(Of SFile) = SFile.GetFiles(Path,, IO.SearchOption.AllDirectories, EDP.ReturnValue) + If files.Count > 0 Then + Dim progressExists As Boolean = Not Progress Is Nothing + Dim pErr As New ErrorsDescriber(EDP.ReturnValue) + Dim e As EContainer + Dim obj As IYouTubeMediaContainer + Dim t$ + Dim playListDataObtained As Boolean = False + If progressExists Then Progress.Maximum += files.Count + For Each f As SFile In files + t = f.GetText(pErr) + If Not t.IsEmptyString Then + ThrowAny(Token) + e = JsonDocument.Parse(t, pErr) + If Not e Is Nothing Then + If e.Count > 0 Then + If IsMusic Then obj = New Track Else obj = New Video + ThrowAny(Token) + obj.Parse(e, Nothing, IsMusic) + If progressExists Then Progress.Perform() + ThrowAny(Token) + If obj.Exists And Not obj.HasError Then + Duration += obj.Duration + DirectCast(obj, YouTubeMediaContainerBase).CachePath = Path + 'Size += obj.Size + If Not playListDataObtained Then + playListDataObtained = True + With obj + ID = .PlaylistID + Title = .PlaylistTitle + Bitrate = .Bitrate + UserID = .UserID + UserTitle = .UserTitle + End With + End If + Elements.Add(obj) + End If + End If + e.Dispose() + End If + End If + Next + File = MyYouTubeSettings.OutputPath + End If + End If + _Exists = HasElements + Return _Exists + End Function + Protected Sub ParseFormats(ByVal e As EContainer) + Const av As UMTypes = UMTypes.Audio + UMTypes.Video + If If(e({"formats"})?.Count, 0) > 0 Then + Dim obj As MediaObject + Dim nValue# + Dim sValue$ + Dim validCodecValue As Func(Of String, Boolean) = Function(codec) Not codec.IsEmptyString AndAlso Not codec = "none" + + For Each ee In e({"formats"}) + obj = New MediaObject With { + .ID = ee.Value("format_id"), + .URL = ee.Value("url"), + .Extension = ee.Value("ext") + } + obj.Width = AConvert(Of Integer)(ee.Value("width"), NumberProvider, -1) + obj.Height = AConvert(Of Integer)(ee.Value("height"), NumberProvider, -1) + obj.FPS = AConvert(Of Double)(ee.Value("fps"), NumberProvider, -1) + obj.Bitrate = AConvert(Of Double)(ee.Value("tbr"), NumberProvider, -1) + nValue = AConvert(Of Double)(ee.Value("filesize"), NumberProvider, -1) + If nValue > 0 Then obj.Size = (nValue / 1024).RoundVal(2) + sValue = ee.Value("vcodec") + If validCodecValue(sValue) Then + obj.Type = UMTypes.Video + obj.Codec = sValue.Split(".").First + If validCodecValue(ee.Value("acodec")) Then + obj.Type = av + If obj.Size <= 0 Then + nValue = AConvert(Of Double)(ee.Value("filesize_approx"), NumberProvider, -1) + If nValue > 0 Then obj.Size = (nValue / 1024).RoundVal(2) + End If + End If + Else + sValue = ee.Value("acodec") + If validCodecValue(sValue) Then + obj.Type = UMTypes.Audio + obj.Codec = sValue.Split(".").First + obj.Bitrate = AConvert(Of Double)(ee.Value("tbr"), NumberProvider, -1) + Else + Continue For + End If + End If + MediaObjects.Add(obj) + Next + MediaObjects.RemoveAll(Function(m) (m.Type = UMTypes.Video And (m.Width <= 0 Or m.Height <= 0)) Or m.URL.IsEmptyString) + Dim DupRemover As Action(Of UMTypes) = + Sub(ByVal t As UMTypes) + Const webm$ = "webm" + Const avc$ = "avc" + Dim data As New List(Of MediaObject)(MediaObjects.Where(Function(mo) mo.Type = t And mo.Extension = webm)) + If data.Count > 0 Then + Dim d As MediaObject = Nothing + Dim expWebm As Predicate(Of MediaObject) = Function(mo) mo.Extension = webm + Dim expAVC As Predicate(Of MediaObject) = Function(mo) mo.Codec.IfNullOrEmpty("/").ToLower.StartsWith(avc) + Dim comp As Func(Of MediaObject, Predicate(Of MediaObject), Boolean, Boolean) = + Function(mo, exp, isTrue) mo.Type = t And exp.Invoke(mo) = isTrue And mo.Width = d.Width + Dim CountWebm As Func(Of MediaObject, Boolean) = Function(mo) comp.Invoke(mo, expWebm, False) + Dim RemoveWebm As Predicate(Of MediaObject) = Function(mo) comp.Invoke(mo, expWebm, True) + Dim CountAVC As Func(Of MediaObject, Boolean) = Function(mo) comp.Invoke(mo, expAVC, True) + Dim RemoveAVC As Predicate(Of MediaObject) = Function(mo) comp.Invoke(mo, expAVC, False) + For Each d In data + If MediaObjects.Count = 0 Then Exit For + If MediaObjects.LongCount(CountWebm) > 0 Then MediaObjects.RemoveAll(RemoveWebm) + If MediaObjects.Count > 0 AndAlso MediaObjects.LongCount(CountAVC) > 0 Then MediaObjects.RemoveAll(RemoveAVC) + Next + End If + End Sub + If MediaObjects.Count > 0 Then DupRemover.Invoke(UMTypes.Audio) + If MediaObjects.Count > 0 Then DupRemover.Invoke(UMTypes.Video) + If MediaObjects.Count > 0 Then + MediaObjects.Sort() + SelectedAudioIndex = MediaObjects.FindIndex(Function(mo) mo.Type = UMTypes.Audio) + If SelectedAudioIndex >= 0 Then + Dim aSize# = MediaObjects(SelectedAudioIndex).Size + If aSize > 0 Then + For i% = 0 To MediaObjects.Count - 1 + obj = MediaObjects(i) + If obj.Type = UMTypes.Video Then obj.Size += aSize : MediaObjects(i) = obj + Next + End If + End If + + With MyYouTubeSettings + If Not .DefaultVideoFormat.IsEmptyString Then OutputVideoExtension = .DefaultVideoFormat + PostProcessing_OutputAudioFormats_Reset() + If Not IsMusic Then + If .DefaultVideoDefinition > 0 Then _ + SelectedVideoIndex = MediaObjects.FindIndex(Function(mo) mo.Type = UMTypes.Video And mo.Height <= .DefaultVideoDefinition) + If SelectedVideoIndex = -1 Then MediaObjects.FindIndex(Function(mo) mo.Type = UMTypes.Video) + If Not .DefaultAudioCodec.IsEmptyString Then OutputAudioCodec = .DefaultAudioCodec + Else + If Not .DefaultAudioCodecMusic.IsEmptyString Then OutputAudioCodec = .DefaultAudioCodecMusic + SelectedVideoIndex = -1 + End If + End With + MediaObjects.ListReindex + End If + End If + End Sub + Protected Sub ParseThumbnails(ByVal e As EContainer) + If If(e({"thumbnails"})?.Count, 0) > 0 Then + Dim thumb As Thumbnail + For Each ee In e({"thumbnails"}) + thumb = New Thumbnail With {.ID = ee.Value("id"), .URL = ee.Value("url")} + thumb.Width = AConvert(Of Integer)(ee.Value("width"), NumberProvider, -1) + thumb.Height = AConvert(Of Integer)(ee.Value("height"), NumberProvider, -1) + If thumb.Width > 0 And thumb.Height > 0 And Not thumb.URL.IsEmptyString Then Thumbnails.Add(thumb) + Next + If Thumbnails.Count > 0 Then + Thumbnails.Sort() + Thumbnails.ListReindex + _ThumbnailUrl = Thumbnails.FirstOrDefault(Function(t) Not t.URL.Contains(".webp")).URL + If _ThumbnailUrl.IsEmptyString Then _ThumbnailUrl = Thumbnails.First.URL + End If + End If + End Sub + Protected Sub ParseSubtitles(ByVal e As EContainer) + Dim subt As Subtitles + Dim ee As EContainer + Dim se As EContainer = e({"subtitles"}) + If If(se?.Count, 0) = 0 OrElse (se.Count = 1 And se(0).Name = "live_chat") Then se = e({"automatic_captions"}) + If If(se?.Count, 0) > 0 Then + If se.Count > 1 OrElse Not se(0).Name = "live_chat" Then + For Each ee In se + subt = New Subtitles With {.ID = ee.Name} + If ee.Count > 0 Then + subt.Name = ee(0).Value("name") + subt.Formats = ee.Select(Function(f) f.Value("ext")).ListToString(",") + End If + If Not subt.ID.IsEmptyString Then _Subtitles.Add(subt) + Next + With MyYouTubeSettings + If Not .DefaultSubtitlesFormat.IsEmptyString Then OutputSubtitlesFormat = .DefaultSubtitlesFormat + If _Subtitles.Count > 0 And .DefaultSubtitles.Count > 0 Then + _Subtitles.Sort() + _Subtitles.ListReindex + SubtitlesSelectedIndexesReset() + PostProcessing_OutputSubtitlesFormats_Reset() + End If + End With + End If + End If + End Sub +#End Region +#Region "IEContainerProvider Support" + Private Function GetElementsChecked() As IEnumerable(Of EContainer) + If HasElements Then + Return Elements.SelectMany(Function(elem As YouTubeMediaContainerBase) elem.GetElementsChecked()) + Else + Return {New EContainer("Element", ID, {New EAttribute(Name_CheckedAttribute, Checked.BoolToInteger)}) With {.AllowSameNames = True}} + End If + End Function + Public Function ToEContainer(Optional ByVal e As ErrorsDescriber = Nothing) As EContainer Implements IEContainerProvider.ToEContainer + Dim c As New EContainer("DataItems") With {.AllowSameNames = True} + c.AddRange(XMLGenerateContainers(Me)) + If HasElements AndAlso Not Elements.All(Function(cc) cc.CheckState = CheckState.Checked) Then + c.Add(New EContainer(Name_CheckedElements) With {.AllowSameNames = True}) + c.Last.AddRange(Of EContainer)(GetElementsChecked()) + End If + Return ToEContainer_Addit(c) + End Function + Protected Overridable Function ToEContainer_Addit(ByRef Container As EContainer) As EContainer + Return Container + End Function +#End Region +#Region "IComparable Support" + Protected Overridable Function CompareTo(ByVal Other As IYouTubeMediaContainer) As Integer Implements IComparable(Of IYouTubeMediaContainer).CompareTo + If CInt(ObjectType).CompareTo(CInt(Other.ObjectType)) = 0 Then + Select Case ObjectType + Case YouTubeMediaType.PlayList + If DateAdded.CompareTo(Other.DateAdded) = 0 Then + If Not PlaylistTitle.IsEmptyString AndAlso Not Other.PlaylistTitle.IsEmptyString Then + Return PlaylistTitle.CompareTo(Other.PlaylistTitle) + Else + Return 0 + End If + Else + Return DateAdded.CompareTo(Other.DateAdded) + End If + Case YouTubeMediaType.Single + If PlaylistIndex.CompareTo(Other.PlaylistIndex) = 0 Then + If Not Title.IsEmptyString And Not Other.Title.IsEmptyString Then + Return Title.CompareTo(Other.Title) + Else + Return 0 + End If + Else + Return PlaylistIndex.CompareTo(Other.PlaylistIndex) + End If + Case YouTubeMediaType.Channel + If Not Title.IsEmptyString And Not Other.Title.IsEmptyString Then + Return Title.CompareTo(Other.Title) + Else + Return 0 + End If + Case Else : Return 0 + End Select + Else + Return CInt(ObjectType).CompareTo(CInt(Other.ObjectType)) + End If + End Function +#End Region +#Region "IDisposable Support" + Protected disposedValue As Boolean = False + Public ReadOnly Property IsDisposed As Boolean Implements IYouTubeMediaContainer.IsDisposed + Get + Return disposedValue + End Get + End Property + Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean) + If Not disposedValue Then + If disposing Then + Elements.ListClearDispose + Thumbnails.Clear() + _Subtitles.Clear() + _SubtitlesDelegated.Clear() + SubtitlesSelectedIndexes.Clear() + MediaObjects.Clear() + Files.Clear() + PostProcessing_OutputAudioFormats.Clear() + PostProcessing_OutputSubtitlesFormats.Clear() + End If + disposedValue = True + End If + End Sub + Protected NotOverridable Overrides Sub Finalize() + Dispose(False) + MyBase.Finalize() + End Sub + Public Overloads Sub Dispose() Implements IDisposable.Dispose + Dispose(True) + GC.SuppressFinalize(Me) + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler.YouTube/SCrawler.YouTube.vbproj b/SCrawler.YouTube/SCrawler.YouTube.vbproj new file mode 100644 index 0000000..8cc5d22 --- /dev/null +++ b/SCrawler.YouTube/SCrawler.YouTube.vbproj @@ -0,0 +1,318 @@ + + + + + Debug + AnyCPU + {7C764707-7FD1-469C-A365-94605C193607} + Library + + + SCrawler + SCrawler.YouTube + 512 + Windows + v4.6.1 + true + true + + + AnyCPU + true + full + true + true + bin\Debug\ + + + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + + + AnyCPU + pdbonly + false + true + true + bin\Release\ + + + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + + + On + + + Binary + + + Off + + + On + + + true + true + true + bin\x64\Debug\ + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + full + x64 + true + + + true + bin\x64\Release\ + true + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + pdbonly + x64 + + + true + true + true + bin\x86\Debug\ + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + full + x86 + true + + + true + bin\x86\Release\ + true + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + pdbonly + x86 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PlayListParserForm.vb + + + Form + + + + + MediaItem.vb + + + UserControl + + + MusicPlaylistsForm.vb + + + Form + + + ParsingProgressForm.vb + + + Form + + + PlaylistArrayForm.vb + + + Form + + + VideoOption.vb + + + UserControl + + + + + + + + + True + Application.myapp + True + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + True + True + SiteYouTube.resx + + + + + VideoListForm.vb + + + Form + + + VideoOptionsForm.vb + + + Form + + + + + + + PlayListParserForm.vb + + + MediaItem.vb + + + MusicPlaylistsForm.vb + + + ParsingProgressForm.vb + + + PlaylistArrayForm.vb + + + VideoOption.vb + + + PublicVbMyResourcesResXFileCodeGenerator + Resources.Designer.vb + My.Resources + Designer + + + My.Resources + PublicResXFileCodeGenerator + SiteYouTube.Designer.vb + + + VideoListForm.vb + + + VideoOptionsForm.vb + + + + + + MyApplicationCodeGenerator + Application.Designer.vb + + + SettingsSingleFileGenerator + My + Settings.Designer.vb + + + + + + {fc532253-1ab3-4def-a28a-dfdd9a481eb2} + PersonalUtilities.Notifications + + + {8405896b-2685-4916-bc93-1fb514c323a9} + PersonalUtilities + + + {d4650f6b-5a54-44b6-999b-6c675b7116b1} + SCrawler.PluginProvider + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SCrawler.YouTube/SiteYouTube.Designer.vb b/SCrawler.YouTube/SiteYouTube.Designer.vb new file mode 100644 index 0000000..94c0ae9 --- /dev/null +++ b/SCrawler.YouTube/SiteYouTube.Designer.vb @@ -0,0 +1,107 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System + +Namespace My.Resources + + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. + ''' + ''' A strongly-typed resource class, for looking up localized strings, etc. + ''' + _ + Public Class SiteYouTube + + Private Shared resourceMan As Global.System.Resources.ResourceManager + + Private Shared resourceCulture As Global.System.Globalization.CultureInfo + + _ + Friend Sub New() + MyBase.New + End Sub + + ''' + ''' Returns the cached ResourceManager instance used by this class. + ''' + _ + Public Shared ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("SCrawler.SiteYouTube", GetType(SiteYouTube).Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. + ''' + _ + Public Shared Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = value + End Set + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + ''' + Public Shared ReadOnly Property YouTubeIcon_32() As System.Drawing.Icon + Get + Dim obj As Object = ResourceManager.GetObject("YouTubeIcon_32", resourceCulture) + Return CType(obj,System.Drawing.Icon) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + ''' + Public Shared ReadOnly Property YouTubeMusicIcon_32() As System.Drawing.Icon + Get + Dim obj As Object = ResourceManager.GetObject("YouTubeMusicIcon_32", resourceCulture) + Return CType(obj,System.Drawing.Icon) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public Shared ReadOnly Property YouTubeMusicPic_96() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("YouTubeMusicPic_96", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Bitmap. + ''' + Public Shared ReadOnly Property YouTubePic_96() As System.Drawing.Bitmap + Get + Dim obj As Object = ResourceManager.GetObject("YouTubePic_96", resourceCulture) + Return CType(obj,System.Drawing.Bitmap) + End Get + End Property + End Class +End Namespace diff --git a/SCrawler.YouTube/SiteYouTube.resx b/SCrawler.YouTube/SiteYouTube.resx new file mode 100644 index 0000000..ebeb740 --- /dev/null +++ b/SCrawler.YouTube/SiteYouTube.resx @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + Content\Icons\YouTubeIcon_32.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Content\Icons\YouTubeMusicIcon_32.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Content\Pictures\YouTubeMusicPic_96.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Content\Pictures\YouTubePic_96.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/.editorconfig b/SCrawler.YouTubeDownloader/.editorconfig new file mode 100644 index 0000000..18ddd08 --- /dev/null +++ b/SCrawler.YouTubeDownloader/.editorconfig @@ -0,0 +1,3 @@ +[*.vb] +# Modifier preferences +file_header_template = Copyright (C) 2023 Andy https://github.com/AAndyProgram\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/App.config b/SCrawler.YouTubeDownloader/App.config new file mode 100644 index 0000000..5534e28 --- /dev/null +++ b/SCrawler.YouTubeDownloader/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/Content/Icons/ArrowDownIcon_Orange_24.ico b/SCrawler.YouTubeDownloader/Content/Icons/ArrowDownIcon_Orange_24.ico new file mode 100644 index 0000000000000000000000000000000000000000..81b7259eea64ceed02b6f5a56cca7fd8ad087e20 GIT binary patch literal 29926 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 Xgylv$>b~3g4iQV&t^VyE>-YZ$b%u1D literal 0 HcmV?d00001 diff --git a/SCrawler.YouTubeDownloader/Content/Icons/RainbowIcon_48.ico b/SCrawler.YouTubeDownloader/Content/Icons/RainbowIcon_48.ico new file mode 100644 index 0000000000000000000000000000000000000000..47e29f85d181d3a8c672d95c989b1540b687c101 GIT binary patch literal 9662 zcmchddrVVT9LH=}~E()^P1U&`Wazr4Hja>t=pOeH7LF~J(|1t!zBOk<$d=NYG zEeF3$B-#M+XKvi^2;$#)HNv3NVge|kr9JH)@EF{2&i z@0}Tf_+xThLlD0(q06xx{8{3)MG*fVKYZ2#@o&46-vaXY_&Y)VY^NFGr+t0P!7mew zrXc=g_+t+Aw!{2gKRpk>JYE|C9X+K=U_8x1xy#Gk02YJ=EA)qbp0w>{GQ*UPi^-&S5)RHvs?a;fB*^IGO+sUE5uo=)&A=eHsIOt=d z^@c)WVe>+56hmt6UhtfN;@MfBI!4TW?=*P53f9tzfo6z3R;r(8@0$%W+18-Zyr@QQ z<>l!hPRMtsupm%>Pi@J$Ur2p@J@uLU6jV2)vjWO}{MC_muEZB5`g6R{^5r}ZE^y8k*p&;hXreVl!V4=Z<$rnE%e&Qq8mpnF z(EWIk%gpWmhx$yT=LbTWK>G2TRrTQW+GrLdXBl%IS)b>9x7Q1HxY)+_M z?3weIKAdu2y5plxUGq|44EuX^WcIG958^xCVDoa@Bu=is(s0dJV{Us?IymWrSh+rx zd(KmEZ+sT9W8=mU^PA;9i5>ZclCI5MUT&Yu$?jJf8oV_Yy|H|7)Xn7!)vs{PT9g{! zBIai6(-=vO@i8W1|gOO<$`Cv7(x;a(Zlh^URH-()!rU=7rj_2Crk? zCiof~WS8pmAXdo7egA0t6@#+_h4;q&5HH(TrWvy+i=2z6PFtyUDbIeVOrOuRZzXG2 zL9CE3T3-CR@}W5gUZZSIs9kZ>+k5Fx2ZfCr1NuIbeKv-8xjxJhsg}BDy{c}%6XN9h z3N*feO6_rAZnokW=xfx}hf>{pT)uGqNVrSgbK_NxfKjEJa&Z|+{bH@pTU<3b!e-{S zPi0sZ`0O=MEaCXeJ+rQ={uyIQ`@{%|k>oFH_E`%`oB=K`yPubDf1Gy0vISz}GP zS146A`)nxBeAXlo%VMU?dhujt_bYsOBAtWS*hc5pj9AO-hs`C`LvDcQ>Gd<**!etr z+sTZ6;=M$4AG2VxM1& zwZ=BIXXNA#DC9ZTR8-3^YTx6_7j7O!wFIY7)N!5~oF<@{|6HN|W%^kEq5CTY0&0=b zX+~X)w$Qqg9;XEuUv)|HH|Vstg5YZy&i(iPN_($j*kr-y?|3sU4pnzp3>0WG+y+yDRo literal 0 HcmV?d00001 diff --git a/SCrawler.YouTubeDownloader/MainFrame.Designer.vb b/SCrawler.YouTubeDownloader/MainFrame.Designer.vb new file mode 100644 index 0000000..bc7fecb --- /dev/null +++ b/SCrawler.YouTubeDownloader/MainFrame.Designer.vb @@ -0,0 +1,64 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY + +Partial Public Class MainFrame : Inherits SCrawler.DownloadObjects.STDownloader.VideoListForm + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + MyBase.Dispose(disposing) + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Me.components = New System.ComponentModel.Container() + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(MainFrame)) + Me.TRAY_ICON = New System.Windows.Forms.NotifyIcon(Me.components) + Me.TRAY_CONTEXT = New System.Windows.Forms.ContextMenuStrip(Me.components) + Me.BTT_TRAY_CLOSE = New System.Windows.Forms.ToolStripMenuItem() + Me.TRAY_CONTEXT.SuspendLayout() + Me.SuspendLayout() + ' + 'TRAY_ICON + ' + Me.TRAY_ICON.BalloonTipIcon = System.Windows.Forms.ToolTipIcon.Info + Me.TRAY_ICON.BalloonTipTitle = "YouTube Downloader" + Me.TRAY_ICON.ContextMenuStrip = Me.TRAY_CONTEXT + Me.TRAY_ICON.Icon = CType(resources.GetObject("TRAY_ICON.Icon"), System.Drawing.Icon) + Me.TRAY_ICON.Text = "YouTube Downloader" + ' + 'TRAY_CONTEXT + ' + Me.TRAY_CONTEXT.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_TRAY_CLOSE}) + Me.TRAY_CONTEXT.Name = "ContextMenuStrip1" + Me.TRAY_CONTEXT.Size = New System.Drawing.Size(181, 48) + ' + 'BTT_TRAY_CLOSE + ' + Me.BTT_TRAY_CLOSE.Image = CType(resources.GetObject("BTT_TRAY_CLOSE.Image"), System.Drawing.Image) + Me.BTT_TRAY_CLOSE.Name = "BTT_TRAY_CLOSE" + Me.BTT_TRAY_CLOSE.Size = New System.Drawing.Size(180, 22) + Me.BTT_TRAY_CLOSE.Text = "Close" + ' + 'MainFrame + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.ClientSize = New System.Drawing.Size(1008, 729) + Me.Name = "MainFrame" + Me.TRAY_CONTEXT.ResumeLayout(False) + Me.ResumeLayout(False) + Me.PerformLayout() + + End Sub + + Private WithEvents TRAY_ICON As NotifyIcon + Private WithEvents TRAY_CONTEXT As ContextMenuStrip + Private WithEvents BTT_TRAY_CLOSE As ToolStripMenuItem +End Class \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/MainFrame.resx b/SCrawler.YouTubeDownloader/MainFrame.resx new file mode 100644 index 0000000..cbd1fc6 --- /dev/null +++ b/SCrawler.YouTubeDownloader/MainFrame.resx @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 310, 17 + + + 425, 17 + + + + + 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 + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFQAAAAABAABUBQAA + rAwAALISAgLLFxER/RsjI/8gLi7/JDQ0/yc0NP8nNDT/JzQ0/yc0NP8nNDT/JzQ0/yc0NP8nKyv/IyEh + /yAPD/0bAgLLFgAAshIAAKwNAABTBgAAAgEAABcAAAAAAAAAAAAAAAAAAAAAAAAA8QAAANUUAADpWgAA + 8ZEAAPSvAAD0vwAA8csAAPTTAQH/2QMD/94DA//iBAT/5QQE/+UEBP/lBAT/5QQE/+UEBP/lBAT/5QQE + /+UDA//hAgL/3gEB/9gAAPTTAADxywAA88AAAPOwAAD0lgAA7GQAAOUbAAD/AAAAUQAAAP4AAADxKQAA + +bYAAP76AAD+/wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD+/AAA+scAAOk5AAD/AAAA + xxEAAPivAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + +cQAAN4cAADlSAAA/PAAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD++AAA6mAAAOl2AAD+/QAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP7/AADrkgAA6JYAAP7/AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wMD//8GBv//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAPW0AAD0rAAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//HR3//4uL//81Nf//AgL//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA88sAAPS+AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8hIf//39///+Tk//94eP//Fxf//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD12QAA7s0AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//yEh///e3v////////v7///Bwf//TEz//wYG + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAPLkAADt0gAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//ISH//97e//////////////// + ///v7///l5f//ygo//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + 8OsAAO/SAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8hIf//3t7///// + ////////////////////////2tr//11d//8CAv//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AADy7AAA7dIAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//yEh + ///e3v////////////////////////v7///Bwf//SEj//wIC//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAPHsAADt0gAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//ISH//97e///////////////////g4P//dnb//xUV//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA8ekAAO/LAAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8hIf//3t7////////09P//pKT//zEx//8BAf//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AADz4QAA9bwAAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//yEh///e3v//zs7//1lZ//8KCv//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAPTVAADxqAAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//GRn//2xs//8fH///AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA88UAAOiRAAD+/wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8BAf//AgL//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AADzqwAA + 6XAAAP78AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + /v8AAOuHAADiPwAA++sAAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD88wAA5FIAALoKAAD1nAAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAPi0AADJEwAA7gAAAN8aAAD0mgAA/O4AAP7/AAD+/wAA//8AAP//AAD//wAA + //8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA + //8AAP//AAD+/wAA/v8AAPz1AAD5tAAA7isAAP8AAAAAAAAA5AAAAMMIAADmOgAA6GwAAOeJAQH4nAQE + +KsGBva1CAj+vAoK/8ILC//GDAz/ywwM/8sMDP/LDAz/ywwM/8sMDP/LDAz/ywsL/8kKCv/ECQn/wAcH + /LkFBfayAwP5qAEB9ZoAAOaIAADnbwAA5UYAANARAADqAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAExM + aABNTWYBQ0N2BEBAnQdAQPcLQED/DkBA/xBAQP8SQED/EkBA/xJAQP8SQED/EkBA/xJAQP8SQED/EUBA + /w9AQP8NQEDiCkBAhAZGRnQDVlZVAVNTWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAA//////////////////////AAAA/AAAADgAAAAQAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAA + AAHAAAAD/AAAP/////////////////////8= + + + \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/MainFrame.vb b/SCrawler.YouTubeDownloader/MainFrame.vb new file mode 100644 index 0000000..edd400e --- /dev/null +++ b/SCrawler.YouTubeDownloader/MainFrame.vb @@ -0,0 +1,87 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.ComponentModel +Imports SCrawler.API.YouTube +Imports PersonalUtilities.Forms +Public Class MainFrame + Private WithEvents MyActivator As FormActivator + Public Sub New() + InitializeComponent() + AppMode = True + MyActivator = New FormActivator(Me, TRAY_ICON) + End Sub + Protected Overrides Sub VideoListForm_Load(sender As Object, e As EventArgs) + MyBase.VideoListForm_Load(sender, e) + TRAY_ICON.Visible = MyYouTubeSettings.CloseToTray + End Sub + Private _CloseInvoked As Boolean = False + Private _IgnoreTrayOptions As Boolean = False + Private _IgnoreCloseConfirm As Boolean = False + Protected Overrides Async Sub VideoListForm_Closing(sender As Object, e As CancelEventArgs) + If MyYouTubeSettings.CloseToTray And Not _IgnoreTrayOptions Then + e.Cancel = True + Hide() + Else + If CheckForClose(_IgnoreCloseConfirm) Then + If _CloseInvoked Then GoTo CloseContinue + If MyJob.Working Then + If MsgBoxE({"The program is still downloading something..." & vbNewLine & + "Are you sure you want to stop downloading and exit the program?", + "Downloading in progress"}, + MsgBoxStyle.Exclamation,,, + {"Stop downloading and close", "Cancel"}) = 0 Then + _CloseInvoked = True + e.Cancel = True + MyJob.Cancel() + Await Task.Run(Sub() + While MyJob.Working : Threading.Thread.Sleep(500) : End While + End Sub) + End If + End If + Else + GoTo DropCloseParams + End If + GoTo CloseContinue +DropCloseParams: + e.Cancel = True + _IgnoreTrayOptions = False + _IgnoreCloseConfirm = False + _CloseInvoked = False + Exit Sub +CloseContinue: + If _CloseInvoked Then Close() +CloseResume: + End If + End Sub + Protected Overrides Sub VideoListForm_Disposed(sender As Object, e As EventArgs) + MyActivator.Dispose() + MyBase.VideoListForm_Disposed(sender, e) + End Sub + Private Sub BTT_TRAY_CLOSE_Click(sender As Object, e As EventArgs) Handles BTT_TRAY_CLOSE.Click + If CheckForClose(False) Then _IgnoreCloseConfirm = True : _IgnoreTrayOptions = True : Close() + End Sub + Private Function CheckForClose(ByVal _Ignore As Boolean) As Boolean + If MyYouTubeSettings.ExitConfirm And Not _Ignore Then + Return MsgBoxE({"Do you want to close the program?", "Closing the program"}, MsgBoxStyle.YesNo) = MsgBoxResult.Yes + Else + Return True + End If + End Function + Protected Overrides Sub BTT_SETTINGS_Click(sender As Object, e As EventArgs) + MyBase.BTT_SETTINGS_Click(sender, e) + TRAY_ICON.Visible = MyYouTubeSettings.CloseToTray + End Sub + Protected Overrides Sub MyJob_Started(ByVal Sender As Object, ByVal e As EventArgs) + TRAY_ICON.Icon = My.Resources.ArrowDownIcon_Orange_24 + End Sub + Protected Overrides Sub MyJob_Finished(ByVal Sender As Object, ByVal e As EventArgs) + TRAY_ICON.Icon = My.Resources.SiteYouTube.YouTubeIcon_32 + MyBase.MyJob_Finished(Sender, e) + End Sub +End Class \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/My Project/Application.Designer.vb b/SCrawler.YouTubeDownloader/My Project/Application.Designer.vb new file mode 100644 index 0000000..39d65f1 --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/Application.Designer.vb @@ -0,0 +1,38 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + + +Namespace My + + 'NOTE: This file is auto-generated; do not modify it directly. To make changes, + ' or if you encounter build errors in this file, go to the Project Designer + ' (go to Project Properties or double-click the My Project node in + ' Solution Explorer), and make changes on the Application tab. + ' + Partial Friend Class MyApplication + + _ + Public Sub New() + MyBase.New(Global.Microsoft.VisualBasic.ApplicationServices.AuthenticationMode.Windows) + Me.IsSingleInstance = false + Me.EnableVisualStyles = true + Me.SaveMySettingsOnExit = true + Me.ShutDownStyle = Global.Microsoft.VisualBasic.ApplicationServices.ShutdownMode.AfterMainFormCloses + End Sub + + _ + Protected Overrides Sub OnCreateMainForm() + Me.MainForm = Global.SCrawler.MainFrame + End Sub + End Class +End Namespace diff --git a/SCrawler.YouTubeDownloader/My Project/Application.myapp b/SCrawler.YouTubeDownloader/My Project/Application.myapp new file mode 100644 index 0000000..bd96f73 --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/Application.myapp @@ -0,0 +1,10 @@ + + + true + MainFrame + false + 0 + true + 0 + true + \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb b/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb new file mode 100644 index 0000000..b9a296a --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/AssemblyInfo.vb @@ -0,0 +1,37 @@ +Imports System.Resources +Imports System +Imports System.Reflection +Imports System.Runtime.InteropServices + +' General Information about an assembly is controlled through the following +' set of attributes. Change these attribute values to modify the information +' associated with an assembly. + +' Review the values of the assembly attributes + + + + + + + + + + +'The following GUID is for the ID of the typelib if this project is exposed to COM + + +' Version information for an assembly consists of the following four values: +' +' Major Version +' Minor Version +' Build Number +' Revision +' +' You can specify all the values or you can default the Build and Revision Numbers +' by using the '*' as shown below: +' + + + + diff --git a/SCrawler.YouTubeDownloader/My Project/Resources.Designer.vb b/SCrawler.YouTubeDownloader/My Project/Resources.Designer.vb new file mode 100644 index 0000000..941fb36 --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/Resources.Designer.vb @@ -0,0 +1,83 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + +Imports System + +Namespace My.Resources + + 'This class was auto-generated by the StronglyTypedResourceBuilder + 'class via a tool like ResGen or Visual Studio. + 'To add or remove a member, edit your .ResX file then rerun ResGen + 'with the /str option, or rebuild your VS project. + ''' + ''' A strongly-typed resource class, for looking up localized strings, etc. + ''' + _ + Friend Module Resources + + Private resourceMan As Global.System.Resources.ResourceManager + + Private resourceCulture As Global.System.Globalization.CultureInfo + + ''' + ''' Returns the cached ResourceManager instance used by this class. + ''' + _ + Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager + Get + If Object.ReferenceEquals(resourceMan, Nothing) Then + Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("SCrawler.Resources", GetType(Resources).Assembly) + resourceMan = temp + End If + Return resourceMan + End Get + End Property + + ''' + ''' Overrides the current thread's CurrentUICulture property for all + ''' resource lookups using this strongly typed resource class. + ''' + _ + Friend Property Culture() As Global.System.Globalization.CultureInfo + Get + Return resourceCulture + End Get + Set + resourceCulture = value + End Set + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + ''' + Friend ReadOnly Property ArrowDownIcon_Orange_24() As System.Drawing.Icon + Get + Dim obj As Object = ResourceManager.GetObject("ArrowDownIcon_Orange_24", resourceCulture) + Return CType(obj,System.Drawing.Icon) + End Get + End Property + + ''' + ''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon). + ''' + Friend ReadOnly Property RainbowIcon_48() As System.Drawing.Icon + Get + Dim obj As Object = ResourceManager.GetObject("RainbowIcon_48", resourceCulture) + Return CType(obj,System.Drawing.Icon) + End Get + End Property + End Module +End Namespace diff --git a/SCrawler.YouTubeDownloader/My Project/Resources.resx b/SCrawler.YouTubeDownloader/My Project/Resources.resx new file mode 100644 index 0000000..3a89741 --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/Resources.resx @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + ..\Content\Icons\ArrowDownIcon_Orange_24.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + ..\Content\Icons\RainbowIcon_48.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + \ No newline at end of file diff --git a/SCrawler.YouTubeDownloader/My Project/Settings.Designer.vb b/SCrawler.YouTubeDownloader/My Project/Settings.Designer.vb new file mode 100644 index 0000000..fcfd812 --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/Settings.Designer.vb @@ -0,0 +1,73 @@ +'------------------------------------------------------------------------------ +' +' This code was generated by a tool. +' Runtime Version:4.0.30319.42000 +' +' Changes to this file may cause incorrect behavior and will be lost if +' the code is regenerated. +' +'------------------------------------------------------------------------------ + +Option Strict On +Option Explicit On + + +Namespace My + + _ + Partial Friend NotInheritable Class MySettings + Inherits Global.System.Configuration.ApplicationSettingsBase + + Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings) + +#Region "My.Settings Auto-Save Functionality" +#If _MyType = "WindowsForms" Then + Private Shared addedHandler As Boolean + + Private Shared addedHandlerLockObject As New Object + + _ + Private Shared Sub AutoSaveSettings(sender As Global.System.Object, e As Global.System.EventArgs) + If My.Application.SaveMySettingsOnExit Then + My.Settings.Save() + End If + End Sub +#End If +#End Region + + Public Shared ReadOnly Property [Default]() As MySettings + Get + +#If _MyType = "WindowsForms" Then + If Not addedHandler Then + SyncLock addedHandlerLockObject + If Not addedHandler Then + AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings + addedHandler = True + End If + End SyncLock + End If +#End If + Return defaultInstance + End Get + End Property + End Class +End Namespace + +Namespace My + + _ + Friend Module MySettingsProperty + + _ + Friend ReadOnly Property Settings() As Global.SCrawler.My.MySettings + Get + Return Global.SCrawler.My.MySettings.Default + End Get + End Property + End Module +End Namespace diff --git a/SCrawler.YouTubeDownloader/My Project/Settings.settings b/SCrawler.YouTubeDownloader/My Project/Settings.settings new file mode 100644 index 0000000..85b890b --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SCrawler.YouTubeDownloader/My Project/app.manifest b/SCrawler.YouTubeDownloader/My Project/app.manifest new file mode 100644 index 0000000..9ce67d2 --- /dev/null +++ b/SCrawler.YouTubeDownloader/My Project/app.manifest @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SCrawler.YouTubeDownloader/SCrawler.YouTubeDownloader.vbproj b/SCrawler.YouTubeDownloader/SCrawler.YouTubeDownloader.vbproj new file mode 100644 index 0000000..d15d2d8 --- /dev/null +++ b/SCrawler.YouTubeDownloader/SCrawler.YouTubeDownloader.vbproj @@ -0,0 +1,198 @@ + + + + + Debug + AnyCPU + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF} + WinExe + SCrawler.My.MyApplication + SCrawler + YouTubeDownloader + 512 + WindowsForms + v4.6.1 + true + true + + + AnyCPU + true + full + true + true + bin\Debug\ + + + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + false + + + AnyCPU + pdbonly + false + true + true + bin\Release\ + + + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + false + + + On + + + Binary + + + Off + + + On + + + Content\Icons\RainbowIcon_48.ico + + + true + true + true + bin\x64\Debug\ + + + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + full + x64 + true + + + true + bin\x64\Release\ + + + true + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + pdbonly + x64 + true + + + true + true + true + bin\x86\Debug\ + + + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + full + x86 + true + + + true + bin\x86\Release\ + + + true + 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 + pdbonly + x86 + true + + + My Project\app.manifest + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MainFrame.vb + + + Form + + + + True + Application.myapp + True + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + MainFrame.vb + + + VbMyResourcesResXFileCodeGenerator + Resources.Designer.vb + My.Resources + Designer + + + + + + + MyApplicationCodeGenerator + Application.Designer.vb + + + SettingsSingleFileGenerator + My + Settings.Designer.vb + + + + + + {8405896b-2685-4916-bc93-1fb514c323a9} + PersonalUtilities + + + {7c764707-7fd1-469c-a365-94605c193607} + SCrawler.YouTube + + + + + + + + + + \ No newline at end of file diff --git a/SCrawler.sln b/SCrawler.sln index cee7b98..5f6a042 100644 --- a/SCrawler.sln +++ b/SCrawler.sln @@ -12,13 +12,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .gitignore = .gitignore Changelog.md = Changelog.md README.md = README.md - ToDo.txt = ToDo.txt EndProjectSection EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "SCrawler.PluginProvider", "SCrawler.PluginProvider\SCrawler.PluginProvider.vbproj", "{D4650F6B-5A54-44B6-999B-6C675B7116B1}" EndProject Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "PersonalUtilities.Notifications", "..\..\MyUtilities\PersonalUtilities.Notifications\PersonalUtilities.Notifications.vbproj", "{FC532253-1AB3-4DEF-A28A-DFDD9A481EB2}" EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "SCrawler.YouTube", "SCrawler.YouTube\SCrawler.YouTube.vbproj", "{7C764707-7FD1-469C-A365-94605C193607}" +EndProject +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "SCrawler.YouTubeDownloader", "SCrawler.YouTubeDownloader\SCrawler.YouTubeDownloader.vbproj", "{3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -77,6 +80,30 @@ Global {FC532253-1AB3-4DEF-A28A-DFDD9A481EB2}.Release|x64.Build.0 = Release|x64 {FC532253-1AB3-4DEF-A28A-DFDD9A481EB2}.Release|x86.ActiveCfg = Release|x86 {FC532253-1AB3-4DEF-A28A-DFDD9A481EB2}.Release|x86.Build.0 = Release|x86 + {7C764707-7FD1-469C-A365-94605C193607}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C764707-7FD1-469C-A365-94605C193607}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C764707-7FD1-469C-A365-94605C193607}.Debug|x64.ActiveCfg = Debug|x64 + {7C764707-7FD1-469C-A365-94605C193607}.Debug|x64.Build.0 = Debug|x64 + {7C764707-7FD1-469C-A365-94605C193607}.Debug|x86.ActiveCfg = Debug|x86 + {7C764707-7FD1-469C-A365-94605C193607}.Debug|x86.Build.0 = Debug|x86 + {7C764707-7FD1-469C-A365-94605C193607}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C764707-7FD1-469C-A365-94605C193607}.Release|Any CPU.Build.0 = Release|Any CPU + {7C764707-7FD1-469C-A365-94605C193607}.Release|x64.ActiveCfg = Release|x64 + {7C764707-7FD1-469C-A365-94605C193607}.Release|x64.Build.0 = Release|x64 + {7C764707-7FD1-469C-A365-94605C193607}.Release|x86.ActiveCfg = Release|x86 + {7C764707-7FD1-469C-A365-94605C193607}.Release|x86.Build.0 = Release|x86 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Debug|x64.ActiveCfg = Debug|x64 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Debug|x64.Build.0 = Debug|x64 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Debug|x86.ActiveCfg = Debug|x86 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Debug|x86.Build.0 = Debug|x86 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Release|Any CPU.Build.0 = Release|Any CPU + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Release|x64.ActiveCfg = Release|x64 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Release|x64.Build.0 = Release|x64 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Release|x86.ActiveCfg = Release|x86 + {3F2F2C29-4ADB-48B5-A66E-EE0F51D0DCEF}.Release|x86.Build.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/SCrawler/API/Base/Declarations.vb b/SCrawler/API/Base/Declarations.vb index 714cbad..5a181c6 100644 --- a/SCrawler/API/Base/Declarations.vb +++ b/SCrawler/API/Base/Declarations.vb @@ -8,7 +8,11 @@ ' but WITHOUT ANY WARRANTY Namespace API.Base Friend Module Declarations + Friend Const UserLabelName As String = "User" Friend ReadOnly LNC As New ListAddParams(LAP.NotContainsOnly) + Friend ReadOnly UnixDate32Provider As New ADateTime(ADateTime.Formats.Unix32) + Friend ReadOnly UnixDate64Provider As New ADateTime(ADateTime.Formats.Unix64) + Friend ReadOnly HtmlConverter As Func(Of String, String) = Function(Input) SymbolsConverter.HTML.Decode(Input, EDP.ReturnValue) Friend ReadOnly TitleHtmlConverter As Func(Of String, String) = Function(Input) SymbolsConverter.HTML.Decode(SymbolsConverter.Convert(Input, EDP.ReturnValue), EDP.ReturnValue). StringRemoveWinForbiddenSymbols().StringTrim() diff --git a/SCrawler/API/Base/DownDetector.vb b/SCrawler/API/Base/DownDetector.vb index 72f8f14..e766a28 100644 --- a/SCrawler/API/Base/DownDetector.vb +++ b/SCrawler/API/Base/DownDetector.vb @@ -60,7 +60,7 @@ Namespace API.Base End Using Return l2 Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog + EDP.ReturnValue, ex, $"[DownDetector.GetData({Site})]") + Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, $"[DownDetector.GetData({Site})]") End Try End Function End Class diff --git a/SCrawler/API/Base/GDLBatch.vb b/SCrawler/API/Base/GDLBatch.vb new file mode 100644 index 0000000..7715b3c --- /dev/null +++ b/SCrawler/API/Base/GDLBatch.vb @@ -0,0 +1,86 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Tools +Imports PersonalUtilities.Functions.RegularExpressions +Namespace API.Base.GDL + Friend Module Declarations + Private Structure GDLURL : Implements IRegExCreator + Private _URL As String + Friend ReadOnly Property URL As String + Get + Return _URL + End Get + End Property + Public Shared Widening Operator CType(ByVal u As String) As GDLURL + Return New GDLURL With {._URL = u} + End Operator + Public Shared Widening Operator CType(ByVal u As GDLURL) As String + Return u.URL + End Operator + Private Function CreateFromArray(ByVal ParamsArray() As String) As Object Implements IRegExCreator.CreateFromArray + If ParamsArray.ListExists(2) Then + Dim u$ = ParamsArray(0).StringTrim.StringTrimEnd("/"), u2$ + If Not u.IsEmptyString Then + u2 = ParamsArray(1).StringTrim + If Not u2.IsEmptyString AndAlso u2.StartsWith("GET", StringComparison.OrdinalIgnoreCase) Then + u2 = u2.Remove(0, 3).StringTrim.StringTrimStart("/") + If Not u2.IsEmptyString Then _URL = $"{u}/{u2}" + End If + End If + End If + Return Me + End Function + Public Shared Operator =(ByVal x As GDLURL, ByVal y As GDLURL) As Boolean + Return x.URL = y.URL + End Operator + Public Shared Operator <>(ByVal x As GDLURL, ByVal y As GDLURL) As Boolean + Return Not x.URL = y.URL + End Operator + Public Overrides Function ToString() As String + Return URL + End Function + Public Overrides Function Equals(ByVal Obj As Object) As Boolean + Return URL = CType(Obj, String) + End Function + End Structure + Private ReadOnly Property GdlUrlPattern As RParams = RParams.DM(GDLBatch.UrlLibStart.Replace("[", "\[").Replace("]", "\]") & + "([^""]+?)""(GET [^""]+)""", 0, EDP.ReturnValue) + Friend Function GetUrlsFromGalleryDl(ByVal Batch As BatchExecutor, ByVal Command As String) As List(Of String) + Dim urls As New List(Of String) + Dim u As GDLURL + With Batch + .Execute(Command) + If .ErrorOutputData.Count > 0 Then + For Each eValue$ In .ErrorOutputData + u = RegexFields(Of GDLURL)(eValue, {GdlUrlPattern}, {1, 2}, EDP.ReturnValue).ListIfNothing.FirstOrDefault + If Not u.URL.IsEmptyString Then urls.ListAddValue(u, LNC) + Next + End If + End With + Return urls + End Function + End Module + Friend Class GDLBatch : Inherits BatchExecutor + Friend Property TempPostsList As List(Of String) + Friend Const UrlLibStart As String = "[urllib3.connectionpool][debug]" + Friend Const UrlTextStart As String = UrlLibStart & " https" + Friend Sub New() + MyBase.New(True) + ChangeDirectory(Settings.GalleryDLFile.File) + End Sub + Protected Overrides Async Sub OutputDataReceiver(ByVal Sender As Object, ByVal e As DataReceivedEventArgs) + MyBase.OutputDataReceiver(Sender, e) + Await Validate(e.Data) + End Sub + Protected Overridable Async Function Validate(ByVal Value As String) As Task + If Await Task.Run(Of Boolean)(Function() Not Value.IsEmptyString AndAlso + TempPostsList.Exists(Function(v) Value.Contains(v))) Then Kill(EDP.None) + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Base/M3U8Base.vb b/SCrawler/API/Base/M3U8Base.vb index f8b48f0..a712f4a 100644 --- a/SCrawler/API/Base/M3U8Base.vb +++ b/SCrawler/API/Base/M3U8Base.vb @@ -6,9 +6,12 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY -Imports PersonalUtilities.Functions.RegularExpressions +Imports System.Threading +Imports PersonalUtilities.Tools Imports PersonalUtilities.Tools.Web Imports PersonalUtilities.Tools.Web.Clients +Imports PersonalUtilities.Forms.Toolbars +Imports PersonalUtilities.Functions.RegularExpressions Namespace API.Base Namespace M3U8Declarations Friend Module M3U8Defaults @@ -16,6 +19,7 @@ Namespace API.Base End Module End Namespace Friend NotInheritable Class M3U8Base + Friend Const TempCacheFolderName As String = "tmpCache" Private Sub New() End Sub Friend Shared Function CreateUrl(ByVal Appender As String, ByVal File As String) As String @@ -28,36 +32,40 @@ Namespace API.Base Return $"{Appender.StringTrimEnd("/")}/{File}" End If End Function - Friend Shared Function Download(ByVal URLs As List(Of String), ByVal DestinationFile As SFile, Optional ByVal Responser As Responser = Nothing) As SFile - Dim CachePath As SFile = Nothing + Friend Shared Function Download(ByVal URLs As List(Of String), ByVal DestinationFile As SFile, Optional ByVal Responser As Responser = Nothing, + Optional ByVal Token As CancellationToken = Nothing, Optional ByVal Progress As MyProgress = Nothing) As SFile + Dim Cache As CacheKeeper = Nothing Try If URLs.ListExists Then Dim ConcatFile As SFile = DestinationFile If ConcatFile.Name.IsEmptyString Then ConcatFile.Name = "PlayListFile" ConcatFile.Extension = "mp4" - CachePath = $"{DestinationFile.PathWithSeparator}_Cache\{SFile.GetDirectories($"{DestinationFile.PathWithSeparator}_Cache\",,, EDP.ReturnValue).ListIfNothing.Count + 1}\" - If CachePath.Exists(SFO.Path) Then - Dim p As New SFileNumbers(ConcatFile.Name,,, New ANumbers With {.Format = ANumbers.Formats.General}) - ConcatFile = SFile.Indexed_IndexFile(ConcatFile,, p, EDP.ReturnValue) + Cache = New CacheKeeper($"{DestinationFile.PathWithSeparator}_{TempCacheFolderName}\") + Dim cache2 As CacheKeeper = Cache.NewInstance + If cache2.RootDirectory.Exists(SFO.Path) Then + Dim progressExists As Boolean = Not Progress Is Nothing + If progressExists Then Progress.Maximum += URLs.Count + Dim p As SFileNumbers = SFileNumbers.Default(ConcatFile.Name) + ConcatFile = SFile.IndexReindex(ConcatFile,,, p, EDP.ReturnValue) Dim i% - Dim eFiles As New List(Of SFile) - Dim dFile As SFile = CachePath + Dim dFile As SFile = cache2.RootDirectory dFile.Extension = "ts" Using w As New DownloadObjects.WebClient2(Responser) For i = 0 To URLs.Count - 1 + If progressExists Then Progress.Perform() + Token.ThrowIfCancellationRequested() dFile.Name = $"ConPart_{i}" w.DownloadFile(URLs(i), dFile) - eFiles.Add(dFile) + cache2.AddFile(dFile, True) Next End Using - DestinationFile = FFMPEG.ConcatenateFiles(eFiles, Settings.FfmpegFile, ConcatFile, p, EDP.ThrowException) - eFiles.Clear() + DestinationFile = FFMPEG.ConcatenateFiles(cache2, Settings.FfmpegFile.File, ConcatFile, Settings.CMDEncoding, p, EDP.ThrowException) Return DestinationFile End If End If Return Nothing Finally - CachePath.Delete(SFO.Path, SFODelete.None, EDP.None) + Cache.DisposeIfReady End Try End Function End Class diff --git a/SCrawler/API/Base/ProfileSaved.vb b/SCrawler/API/Base/ProfileSaved.vb index 71e49b0..079a579 100644 --- a/SCrawler/API/Base/ProfileSaved.vb +++ b/SCrawler/API/Base/ProfileSaved.vb @@ -18,20 +18,17 @@ Namespace API.Base HOST = h Progress = Bar End Sub - Friend Sub Download(ByVal Token As CancellationToken) + Friend Sub Download(ByVal Token As CancellationToken, ByVal Multiple As Boolean) Try If HOST.Source.ReadyToDownload(PDownload.SavedPosts) Then - If HOST.Available(PDownload.SavedPosts, False) Then + If HOST.Available(PDownload.SavedPosts, Multiple) Then HOST.DownloadStarted(PDownload.SavedPosts) Dim u As New UserInfo With {.Plugin = HOST.Key, .Site = HOST.Name, .SpecialPath = HOST.SavedPostsPath} Using user As IUserData = HOST.GetInstance(PDownload.SavedPosts, Nothing, False, False) - If Not user Is Nothing AndAlso Not user.Name.IsEmptyString Then - u.Name = user.Name + If Not user Is Nothing Then With DirectCast(user, UserDataBase) - With .User : u.IsChannel = .IsChannel : u.UpdateUserFile() : End With - .User = u - .LoadUserInformation() .IsSavedPosts = True + .LoadUserInformation() .Progress = Progress If Not .FileExists Then .UpdateUserInformation() End With @@ -49,7 +46,7 @@ Namespace API.Base End If Catch ex As Exception Progress.InformationTemporary = $"{HOST.Name} downloading error" - ErrorsDescriber.Execute(EDP.SendInLog, ex, $"[API.Base.ProfileSaved.Download({HOST.Key})]") + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"[API.Base.ProfileSaved.Download({HOST.Key})]") Finally HOST.DownloadDone(PDownload.SavedPosts) MainFrameObj.UpdateLogButton() diff --git a/SCrawler/API/Base/SiteSettingsBase.vb b/SCrawler/API/Base/SiteSettingsBase.vb index 6504616..d20b2a7 100644 --- a/SCrawler/API/Base/SiteSettingsBase.vb +++ b/SCrawler/API/Base/SiteSettingsBase.vb @@ -6,10 +6,9 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY -Imports PersonalUtilities.Functions.RegularExpressions -Imports PersonalUtilities.Tools.Web.Clients -Imports PersonalUtilities.Tools.Web.Cookies Imports SCrawler.Plugin +Imports PersonalUtilities.Tools.Web.Clients +Imports PersonalUtilities.Functions.RegularExpressions Imports Download = SCrawler.Plugin.ISiteSettings.Download Namespace API.Base Friend MustInherit Class SiteSettingsBase : Implements ISiteSettings, IResponserContainer @@ -18,6 +17,23 @@ Namespace API.Base Friend Overridable ReadOnly Property Image As Image Implements ISiteSettings.Image Private Property Logger As ILogProvider = LogConnector Implements ISiteSettings.Logger Friend Overridable ReadOnly Property Responser As Responser + Friend ReadOnly Property CookiesNetscapeFile As SFile + Protected CheckNetscapeCookiesOnEndInit As Boolean = False + Private _UseNetscapeCookies As Boolean = False + Protected Property UseNetscapeCookies As Boolean + Get + Return _UseNetscapeCookies + End Get + Set(ByVal use As Boolean) + Dim b As Boolean = Not _UseNetscapeCookies = use + _UseNetscapeCookies = use + If Not Responser Is Nothing Then + Responser.Cookies.ChangedAllowInternalDrop = Not _UseNetscapeCookies + Responser.Cookies.Changed = False + End If + If b And _UseNetscapeCookies Then Update_SaveCookiesNetscape() + End Set + End Property Private Property IResponserContainer_Responser As Responser Implements IResponserContainer.Responser Get Return Responser @@ -27,20 +43,15 @@ Namespace API.Base Friend MustOverride Function GetInstance(ByVal What As Download) As IPluginContentProvider Implements ISiteSettings.GetInstance Friend Sub New(ByVal SiteName As String) Site = SiteName + CookiesNetscapeFile = $"{SettingsFolderName}\Responser_{Site}_Cookies_Netscape.txt" End Sub Friend Sub New(ByVal SiteName As String, ByVal CookiesDomain As String) - Site = SiteName - Responser = New Responser($"{SettingsFolderName}\Responser_{Site}.xml") + Me.New(SiteName) + Responser = New Responser($"{SettingsFolderName}\Responser_{Site}.xml") With {.DeclaredError = EDP.ThrowException} With Responser - If .File.Exists Then - If EncryptCookies.CookiesEncrypted Then .CookiesEncryptKey = SettingsCLS.CookieEncryptKey - .LoadSettings() - Else - .CookiesDomain = CookiesDomain - .CookiesEncryptKey = SettingsCLS.CookieEncryptKey - .SaveSettings() - End If - If .CookiesDomain.IsEmptyString Then .CookiesDomain = CookiesDomain + .CookiesDomain = CookiesDomain + .CookiesEncryptKey = SettingsCLS.CookieEncryptKey + If .File.Exists Then .LoadSettings() Else .SaveSettings() End With End Sub #Region "XML" @@ -51,17 +62,47 @@ Namespace API.Base Friend Overridable Sub BeginInit() Implements ISiteSettings.BeginInit End Sub Friend Overridable Sub EndInit() Implements ISiteSettings.EndInit - EncryptCookies.ValidateCookiesEncrypt(Responser) If Not DefaultUserAgent.IsEmptyString And Not Responser Is Nothing Then Responser.UserAgent = DefaultUserAgent + If CheckNetscapeCookiesOnEndInit Then Update_SaveCookiesNetscape(, True) End Sub +#End Region +#Region "Update, Edit" Friend Overridable Sub BeginUpdate() Implements ISiteSettings.BeginUpdate End Sub Friend Overridable Sub EndUpdate() Implements ISiteSettings.EndUpdate End Sub + Protected _SiteEditorFormOpened As Boolean = False Friend Overridable Sub BeginEdit() Implements ISiteSettings.BeginEdit + _SiteEditorFormOpened = True End Sub Friend Overridable Sub EndEdit() Implements ISiteSettings.EndEdit + If _SiteEditorFormOpened Then DomainsReset() + _SiteEditorFormOpened = False End Sub + Friend Overridable Sub Update() Implements ISiteSettings.Update + If _SiteEditorFormOpened Then + If UseNetscapeCookies Then Update_SaveCookiesNetscape() + DomainsApply() + End If + If Not Responser Is Nothing Then Responser.SaveSettings() + End Sub + Protected Sub Update_SaveCookiesNetscape(Optional ByVal Force As Boolean = False, Optional ByVal IsInit As Boolean = False) + If Not Responser Is Nothing Then + With Responser + If .Cookies.Changed Or Force Or IsInit Then + If IsInit And CookiesNetscapeFile.Exists Then Exit Sub + If .CookiesExists Then .Cookies.SaveNetscapeFile(CookiesNetscapeFile) Else CookiesNetscapeFile.Delete() + .Cookies.Changed = False + End If + End With + End If + End Sub +#Region "Specialized" + Protected Overridable Sub DomainsApply() + End Sub + Protected Overridable Sub DomainsReset() + End Sub +#End Region #End Region #Region "Before and After Download" Friend Overridable Sub DownloadStarted(ByVal What As Download) Implements ISiteSettings.DownloadStarted @@ -75,20 +116,15 @@ Namespace API.Base #End Region #Region "User info" Protected UrlPatternUser As String = String.Empty - Protected UrlPatternChannel As String = String.Empty - Friend Overridable Function GetUserUrl(ByVal User As IPluginContentProvider, ByVal Channel As Boolean) As String Implements ISiteSettings.GetUserUrl - If Channel Then - If Not UrlPatternChannel.IsEmptyString Then Return String.Format(UrlPatternChannel, User.Name) - Else - If Not UrlPatternUser.IsEmptyString Then Return String.Format(UrlPatternUser, User.Name) - End If + Friend Overridable Function GetUserUrl(ByVal User As IPluginContentProvider) As String Implements ISiteSettings.GetUserUrl + If Not UrlPatternUser.IsEmptyString Then Return String.Format(UrlPatternUser, User.Name) Return String.Empty End Function Private Function ISiteSettings_GetUserPostUrl(ByVal User As IPluginContentProvider, ByVal Media As IUserMedia) As String Implements ISiteSettings.GetUserPostUrl Return GetUserPostUrl(User, Media) End Function Friend Overridable Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String - Return String.Empty + Return Media.URL_BASE.IfNullOrEmpty(Media.URL) End Function Protected UserRegex As RParams = Nothing Friend Overridable Function IsMyUser(ByVal UserURL As String) As ExchangeOptions Implements ISiteSettings.IsMyUser @@ -99,43 +135,40 @@ Namespace API.Base End If Return Nothing Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog + EDP.ReturnValue, ex, $"[API.Base.SiteSettingsBase.IsMyUser({UserURL})]", New ExchangeOptions) + Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, $"[API.Base.SiteSettingsBase.IsMyUser({UserURL})]", New ExchangeOptions) End Try End Function Protected ImageVideoContains As String = String.Empty Friend Overridable Function IsMyImageVideo(ByVal URL As String) As ExchangeOptions Implements ISiteSettings.IsMyImageVideo If Not ImageVideoContains.IsEmptyString AndAlso URL.Contains(ImageVideoContains) Then - Return New ExchangeOptions With {.Exists = True} + Return New ExchangeOptions(Site, String.Empty) With {.Exists = True} Else Return Nothing End If End Function - Friend Overridable Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable Implements ISiteSettings.GetSpecialData - Return Nothing + Private Function ISiteSettings_GetSingleMediaInstance(ByVal URL As String, ByVal OutputFile As String) As IDownloadableMedia Implements ISiteSettings.GetSingleMediaInstance + Return GetSingleMediaInstance(URL, OutputFile) End Function - Friend Shared Function GetSpecialDataFile(ByVal Path As String, ByVal AskForPath As Boolean, ByRef SpecFolderObj As String) As SFile - Dim f As SFile = Path.CSFileP - If f.Name.IsEmptyString Then f.Name = "OutputFile" -#Disable Warning BC40000 - If Path.CSFileP.IsEmptyString Or AskForPath Then f = SFile.SaveAs(f, "File destination",,,, EDP.ReturnValue) : SpecFolderObj = f.Path -#Enable Warning - Return f + Friend Overridable Function GetSingleMediaInstance(ByVal URL As String, ByVal OutputFile As SFile) As IDownloadableMedia + Return New Hosts.DownloadableMediaHost(URL, OutputFile) End Function #End Region #Region "Ready, Available" + ''' True Friend Overridable Function BaseAuthExists() As Boolean Return True End Function + ''' JOB: leave or remove + ''' Return BaseAuthExists() Friend Overridable Function Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean Implements ISiteSettings.Available Return BaseAuthExists() End Function + ''' 'DownloadData': before processing + ''' True Friend Overridable Function ReadyToDownload(ByVal What As Download) As Boolean Implements ISiteSettings.ReadyToDownload Return True End Function #End Region - Friend Overridable Sub Update() Implements ISiteSettings.Update - If Not Responser Is Nothing Then Responser.SaveSettings() - End Sub Friend Overridable Sub Reset() Implements ISiteSettings.Reset End Sub Friend Overridable Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) Implements ISiteSettings.UserOptions diff --git a/SCrawler/API/Base/Structures.vb b/SCrawler/API/Base/Structures.vb index df56a67..eaf82a4 100644 --- a/SCrawler/API/Base/Structures.vb +++ b/SCrawler/API/Base/Structures.vb @@ -27,10 +27,12 @@ Namespace API.Base #End Region Friend Enum Types As Integer Undefined = 0 - [Picture] = 1 - [Video] = 2 - [Text] = 3 + Picture = 1 + Video = 2 + Audio = 200 + Text = 4 VideoPre = 10 + AudioPre = 215 GIF = 50 m3u8 = 100 End Enum @@ -51,12 +53,12 @@ Namespace API.Base Friend SpecialFolder As String Friend [Object] As Object #Region "Interface Support" - Private Property IUserMedia_Type As Integer Implements IUserMedia.ContentType + Private Property IUserMedia_Type As UserMediaTypes Implements IUserMedia.ContentType Get - Return Type + Return CInt(Type) End Get - Set(ByVal Type As Integer) - Me.Type = Type + Set(ByVal Type As UserMediaTypes) + Me.Type = CInt(Type) End Set End Property Private Property IUserMedia_URL_BASE As String Implements IUserMedia.URL_BASE @@ -91,12 +93,12 @@ Namespace API.Base Me.File = File End Set End Property - Private Property IUserMedia_State As Integer Implements IUserMedia.DownloadState + Private Property IUserMedia_State As UserMediaStates Implements IUserMedia.DownloadState Get - Return State + Return CInt(State) End Get - Set(ByVal State As Integer) - Me.State = State + Set(ByVal State As UserMediaStates) + Me.State = CInt(State) End Set End Property Private Property IUserMedia_PostID As String Implements IUserMedia.PostID diff --git a/SCrawler/API/Base/UserDataBase.vb b/SCrawler/API/Base/UserDataBase.vb index 2358142..e6e3c85 100644 --- a/SCrawler/API/Base/UserDataBase.vb +++ b/SCrawler/API/Base/UserDataBase.vb @@ -9,6 +9,7 @@ Imports System.IO Imports System.Net Imports System.Threading +Imports System.ComponentModel Imports System.Runtime.CompilerServices Imports SCrawler.Plugin Imports SCrawler.Plugin.Hosts @@ -18,6 +19,7 @@ Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Forms.Toolbars Imports PersonalUtilities.Tools Imports PersonalUtilities.Tools.Web.Clients +Imports PersonalUtilities.Tools.ImageRenderer Imports UStates = SCrawler.API.Base.UserMedia.States Imports UTypes = SCrawler.API.Base.UserMedia.Types Namespace API.Base @@ -98,7 +100,7 @@ Namespace API.Base #Region "XML Declarations" Private Const Name_Site As String = UserInfo.Name_Site Private Const Name_Plugin As String = UserInfo.Name_Plugin - Private Const Name_IsChannel As String = UserInfo.Name_IsChannel + Protected Const Name_IsChannel As String = "IsChannel" Friend Const Name_UserName As String = "UserName" Private Const Name_Model_User As String = UserInfo.Name_Model_User Private Const Name_Model_Collection As String = UserInfo.Name_Model_Collection @@ -108,9 +110,9 @@ Namespace API.Base Private Const Name_UserExists As String = "UserExists" Private Const Name_UserSuspended As String = "UserSuspended" - Private Const Name_FriendlyName As String = "FriendlyName" + Protected Const Name_FriendlyName As String = "FriendlyName" Private Const Name_UserSiteName As String = "UserSiteName" - Private Const Name_UserID As String = "UserID" + Protected Const Name_UserID As String = "UserID" Private Const Name_Description As String = "Description" Private Const Name_ParseUserMediaOnly As String = "ParseUserMediaOnly" Private Const Name_Temporary As String = "Temporary" @@ -132,10 +134,12 @@ Namespace API.Base Private Const Name_ScriptUse As String = "ScriptUse" Private Const Name_ScriptData As String = "ScriptData" - Friend Const Name_DataMerging As String = "DataMerging" + Protected Const Name_UseMD5Comparison As String = "UseMD5Comparison" + Protected Const Name_RemoveExistingDuplicates As String = "RemoveExistingDuplicates" + Protected Const Name_StartMD5Checked As String = "StartMD5Checked" #End Region #Region "Declarations" -#Region "Host, Site, Progress, Self" +#Region "Host, Site, Progress" Friend Property HOST As SettingsHost Implements IUserData.HOST Friend ReadOnly Property Site As String Implements IContentProvider.Site Get @@ -167,15 +171,17 @@ Namespace API.Base Me._UserSuspended = _UserSuspended End Set End Property - Friend Overridable Property Name As String Implements IContentProvider.Name, IPluginContentProvider.Name + Private Property IPluginContentProvider_Name As String Implements IPluginContentProvider.Name + Get + Return Name + End Get + Set(ByVal NewName As String) + End Set + End Property + Friend Overridable ReadOnly Property Name As String Implements IContentProvider.Name Get Return User.Name End Get - Set(ByVal NewName As String) - User.Name = NewName - User.UpdateUserFile() - Settings.UpdateUsersList(User) - End Set End Property Friend Overridable Property ID As String = String.Empty Implements IContentProvider.ID, IPluginContentProvider.ID Protected _FriendlyName As String = String.Empty @@ -275,11 +281,6 @@ Namespace API.Base End Property #End Region #Region "Channel" - Friend Overridable ReadOnly Property IsChannel As Boolean Implements IUserData.IsChannel - Get - Return User.IsChannel - End Get - End Property Friend Property CreatedByChannel As Boolean = False #End Region #Region "Images" @@ -564,7 +565,7 @@ BlockNullPicture: #End Region #Region "Plugins Support" Protected Event ProgressChanged As IPluginContentProvider.ProgressChangedEventHandler Implements IPluginContentProvider.ProgressChanged - Protected Event TotalCountChanged As IPluginContentProvider.TotalCountChangedEventHandler Implements IPluginContentProvider.TotalCountChanged + Protected Event ProgressMaximumChanged As IPluginContentProvider.ProgressMaximumChangedEventHandler Implements IPluginContentProvider.ProgressMaximumChanged Private Property IPluginContentProvider_Settings As ISiteSettings Implements IPluginContentProvider.Settings Get Return HOST.Source @@ -585,9 +586,11 @@ BlockNullPicture: Private Function IPluginContentProvider_XmlFieldsGet() As List(Of KeyValuePair(Of String, String)) Implements IPluginContentProvider.XmlFieldsGet Return Nothing End Function - Private Sub IPluginContentProvider_GetMedia() Implements IPluginContentProvider.GetMedia + Private Sub IPluginContentProvider_GetMedia(ByVal Token As CancellationToken) Implements IPluginContentProvider.GetMedia End Sub - Private Sub IPluginContentProvider_Download() Implements IPluginContentProvider.Download + Private Sub IPluginContentProvider_Download(ByVal Token As CancellationToken) Implements IPluginContentProvider.Download + End Sub + Private Sub IPluginContentProvider_DownloadSingleObject(ByVal Data As IDownloadableMedia, ByVal Token As CancellationToken) Implements IPluginContentProvider.DownloadSingleObject End Sub Friend Overridable Function ExchangeOptionsGet() As Object Implements IPluginContentProvider.ExchangeOptionsGet Return Nothing @@ -598,8 +601,8 @@ BlockNullPicture: #End Region #Region "IIndexable Support" Friend Property Index As Integer = 0 Implements IIndexable.Index - Private Function SetIndex(ByVal Obj As Object, ByVal _Index As Integer) As Object Implements IIndexable.SetIndex - DirectCast(Obj, UserDataBase).Index = _Index + Private Function SetIndex(ByVal Obj As Object, ByVal Index As Integer) As Object Implements IIndexable.SetIndex + DirectCast(Obj, UserDataBase).Index = Index Return Obj End Function #End Region @@ -607,7 +610,7 @@ BlockNullPicture: Friend ReadOnly Property LVIKey As String Implements IUserData.Key Get If Not _IsCollection Then - Return $"{IIf(IsChannel, "C", String.Empty)}{Site.ToString.ToUpper}_{Name}" + Return $"{Site.ToString.ToUpper}_{Name}" Else Return $"CCCC_{CollectionName}" End If @@ -658,7 +661,7 @@ BlockNullPicture: Next End If ElseIf Settings.ShowGroups Then - Return Destination.Groups.Item(GetLviGroupName(HOST, Temporary, Favorite, IsCollection, IsChannel)) + Return Destination.Groups.Item(GetLviGroupName(HOST, Temporary, Favorite, IsCollection)) End If Return Destination.Groups.Item(LabelsKeeper.NoLabeledName) Catch ex As Exception @@ -689,7 +692,7 @@ BlockNullPicture: ''' Friend Shared Function GetInstance(ByVal u As UserInfo, Optional ByVal _LoadUserInformation As Boolean = True) As IUserData If Not u.Plugin.IsEmptyString Then - Return Settings(u.Plugin).GetInstance(u.DownloadOption, u, _LoadUserInformation) + Return Settings(u.Plugin).GetInstance(ISiteSettings.Download.Main, u, _LoadUserInformation) Else Throw New ArgumentOutOfRangeException("Plugin", $"Plugin [{u.Plugin}] information does not recognized by loader") End If @@ -707,7 +710,7 @@ BlockNullPicture: End If Return String.Empty Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, $"GetPostUrl({uName}, {PostData.Post.ID})", String.Empty) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, $"GetPostUrl({uName}, {PostData.Post.ID})", String.Empty) End Try End Function #End Region @@ -716,7 +719,7 @@ BlockNullPicture: Private _UserInformationLoaded As Boolean = False Friend Overridable Sub LoadUserInformation() Implements IUserData.LoadUserInformation Try - UpdateDataFiles(, True) + UpdateDataFiles() If MyFileSettings.Exists Then FileExists = True Using x As New XmlFile(MyFileSettings) With {.XmlReadOnly = True} @@ -740,14 +743,7 @@ BlockNullPicture: LastUpdated = AConvert(Of Date)(x.Value(Name_LastUpdated), ADateTime.Formats.BaseDateTime, Nothing) ScriptUse = x.Value(Name_ScriptUse).FromXML(Of Boolean)(False) ScriptData = x.Value(Name_ScriptData) - 'TODELETE: UserDataBase remove old 'merge' constant -#Disable Warning BC40000 - If x.Contains(Name_DataMerging) Then - DataMerging = x.Value(Name_DataMerging).FromXML(Of Boolean)(False) - Else - DataMerging = x.Value(Name_Merged).FromXML(Of Boolean)(False) - End If -#Enable Warning + DataMerging = x.Value(Name_Merged).FromXML(Of Boolean)(False) ChangeCollectionName(x.Value(Name_CollectionName), False) Labels.ListAddList(x.Value(Name_LabelsName).StringToList(Of String, List(Of String))("|", EDP.ReturnValue), LAP.NotContainsOnly, LAP.ClearBeforeAdd) LoadUserInformation_OptionalFields(x, True) @@ -762,13 +758,12 @@ BlockNullPicture: End Sub Friend Overridable Sub UpdateUserInformation() Implements IUserData.UpdateUserInformation Try - UpdateDataFiles(True) + UpdateDataFiles() MyFileSettings.Exists(SFO.Path) Using x As New XmlFile With {.Name = "User"} x.Add(Name_Site, Site) x.Add(Name_Plugin, HOST.Key) x.Add(Name_UserName, User.Name) - x.Add(Name_IsChannel, IsChannel.BoolToInteger) x.Add(Name_Model_User, CInt(UserModel)) x.Add(Name_Model_Collection, CInt(CollectionModel)) x.Add(Name_SpecialPath, User.SpecialPath) @@ -815,7 +810,7 @@ BlockNullPicture: #Region "User data" Friend Overridable Overloads Sub LoadContentInformation(Optional ByVal Force As Boolean = False) Try - UpdateDataFiles(, True) + UpdateDataFiles() If Not MyFileData.Exists Or (_DataLoaded And Not Force) Then Exit Sub Using x As New XmlFile(MyFileData, Protector.Modes.All, False) With {.XmlReadOnly = True, .AllowSameNames = True} x.LoadData() @@ -830,7 +825,7 @@ BlockNullPicture: End Sub Friend Sub UpdateContentInformation() Try - UpdateDataFiles(True, True) + UpdateDataFiles() If MyFileData.IsEmptyString Then Exit Sub MyFileData.Exists(SFO.Path) Using x As New XmlFile With {.AllowSameNames = True, .Name = "Data"} @@ -846,7 +841,7 @@ BlockNullPicture: #Region "Open site, folder" Friend Overridable Sub OpenSite(Optional ByVal e As ErrorsDescriber = Nothing) Implements IContentProvider.OpenSite Try - Dim URL$ = HOST.Source.GetUserUrl(Me, IsChannel) + Dim URL$ = HOST.Source.GetUserUrl(Me) If Not URL.IsEmptyString Then Process.Start(URL) Catch ex As Exception If Not e.Exists Then e = New ErrorsDescriber(EDP.ShowAllMsg) @@ -886,7 +881,7 @@ BlockNullPicture: Protected Function CheckDatesLimit(ByVal DateObj As Object, ByVal DateProvider As IFormatProvider) As DateResult Try If (DownloadDateFrom.HasValue Or DownloadDateTo.HasValue) AndAlso ACheck(DateObj) Then - Dim td As Date? = AConvert(Of Date)(DateObj, DateProvider, Nothing) + Dim td As Date? = AConvert(DateObj, AModes.Var, GetType(Date),, True, Nothing, DateProvider) If td.HasValue Then If td.Value.ValueBetween(_DownloadDateFromF, _DownloadDateToF) Then Return DateResult.Continue @@ -899,13 +894,14 @@ BlockNullPicture: End If Return DateResult.Continue Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, $"[UserDataBase.CheckDatesLimit({If(TypeOf DateObj Is String, CStr(DateObj), "?")})]", DateResult.Continue) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, $"[UserDataBase.CheckDatesLimit({If(TypeOf DateObj Is String, CStr(DateObj), "?")})]", DateResult.Continue) End Try End Function #End Region #Region "Download functions and options" Protected Responser As Responser Protected UseResponserClient As Boolean = False + Protected UseClientTokens As Boolean = False Protected _ForceSaveUserData As Boolean = False Protected _ForceSaveUserInfo As Boolean = False Private _DownloadInProgress As Boolean = False @@ -915,7 +911,7 @@ BlockNullPicture: Private _PictureExists As Boolean Private _EnvirInvokeUserUpdated As Boolean = False Protected Sub EnvirDownloadSet() - UpdateDataFiles(, True) + UpdateDataFiles() _DownloadInProgress = True _DescriptionChecked = False _DescriptionEveryTime = Settings.UpdateUserDescriptionEveryTime @@ -948,8 +944,8 @@ BlockNullPicture: If Not Responser Is Nothing Then Responser.Dispose() Responser = New Responser If Not HOST.Responser Is Nothing Then Responser.Copy(HOST.Responser) - 'TODO: UserDataBase remove [Responser.DecodersError] - Responser.DecodersError = New ErrorsDescriber(EDP.SendInLog + EDP.ReturnValue) With { + + Responser.DecodersError = New ErrorsDescriber(EDP.SendToLog + EDP.ReturnValue) With { .DeclaredMessage = New MMessage($"SymbolsConverter error: [{ToStringForLog()}]", ToStringForLog())} Dim _downContent As Func(Of UserMedia, Boolean) = Function(c) c.State = UStates.Downloaded @@ -981,7 +977,9 @@ BlockNullPicture: ReparseVideo(Token) ThrowAny(Token) - If IsSavedPosts Then UpdateDataFiles(True) + + If UseMD5Comparison Then ValidateMD5(Token) : ThrowAny(Token) + If _TempPostsList.Count > 0 And Not DownloadMissingOnly And __SaveData Then _ TextSaver.SaveTextToFile(_TempPostsList.ListToString(Environment.NewLine), MyFilePosts, True,, EDP.None) _ContentNew.ListAddList(_TempMediaList, LAP.ClearBeforeAdd) @@ -1035,19 +1033,14 @@ BlockNullPicture: _ForceSaveUserInfo = False End Try End Sub - Protected Sub UpdateDataFiles(Optional ByVal ForceSaved As Boolean = False, Optional ByVal ValidateContetnt As Boolean = False) - 'TODELETE: saved posts name compatibility 2023.2.5.0 - Dim __validateSaved As Func(Of Boolean) = Function() MyFileData.Exists Or MyFilePosts.Exists - If Not User.File.IsEmptyString Then + Protected Sub UpdateDataFiles() + If Not User.File.IsEmptyString OrElse IsSavedPosts Then MyFileSettings = Nothing If IsSavedPosts Then - Dim u As UserInfo = User - u.Name = "SavedPosts" - u.UpdateUserFile() - Dim mfp As SFile = u.File - mfp.Name &= "_Posts" - mfp.Extension = "txt" - If (ValidateContetnt AndAlso mfp.Exists) Or (Not ValidateContetnt AndAlso u.File.Exists) Or ForceSaved Then MyFileSettings = u.File + User = New UserInfo(SettingsHost.SavedPostsFolderName, HOST) + User.File.Path = $"{HOST.SavedPostsPath.PathWithSeparator}{SettingsFolderName}" + MyFileSettings = User.File + MyFileSettings.Name = MyFileSettings.Name.Replace(SettingsHost.SavedPostsFolderName, "SavedPosts") End If If MyFileSettings.IsEmptyString Then MyFileSettings = User.File MyFileData = MyFileSettings @@ -1060,6 +1053,71 @@ BlockNullPicture: End If End Sub Protected MustOverride Sub DownloadDataF(ByVal Token As CancellationToken) +#Region "DownloadSingleObject" + Protected IsSingleObjectDownload As Boolean = False + Friend Overridable Sub DownloadSingleObject(ByVal Data As YouTube.Objects.IYouTubeMediaContainer, ByVal Token As CancellationToken) Implements IUserData.DownloadSingleObject + Try + Data.DownloadState = UserMediaStates.Tried + Progress = Data.Progress + If Not Responser Is Nothing Then Responser.Dispose() + Responser = New Responser + If Not HOST Is Nothing AndAlso Not HOST.Responser Is Nothing Then Responser.Copy(HOST.Responser) + SeparateVideoFolder = False + IsSingleObjectDownload = True + UseInternalDownloadFileFunction_UseProgress = True + UseInternalM3U8Function_UseProgress = True + DownloadSingleObject_GetPosts(Data, Token) + DownloadSingleObject_CreateMedia(Data, Token) + DownloadSingleObject_Download(Data, Token) + DownloadSingleObject_PostProcessing(Data) + Catch ex As Exception + Data.DownloadState = UserMediaStates.Missing + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"{Site} single data downloader error: {Data.URL}") + End Try + End Sub + Protected Overridable Sub DownloadSingleObject_CreateMedia(ByVal Data As YouTube.Objects.IYouTubeMediaContainer, ByVal Token As CancellationToken) + If _TempMediaList.Count > 0 Then + For Each m As UserMedia In _TempMediaList + m.File = DownloadSingleObject_CreateFile(Data, m.File) + _ContentNew.Add(m) + Next + End If + End Sub + Protected Overridable Sub DownloadSingleObject_PostProcessing(ByVal Data As YouTube.Objects.IYouTubeMediaContainer, Optional ByVal ResetTitle As Boolean = True) + If _ContentNew.Count > 0 Then + If _ContentNew.Any(Function(mm) mm.State = UStates.Downloaded) Then + Data.DownloadState = UserMediaStates.Downloaded + If _ContentNew(0).Type = UTypes.Picture Or _ContentNew(0).Type = UTypes.GIF Then + DirectCast(Data, IDownloadableMedia).ThumbnailFile = _ContentNew(0).File + ElseIf Settings.STDownloader_TakeSnapshot And Settings.FfmpegFile.Exists And Not Settings.STDownloader_RemoveDownloadedAutomatically Then + Dim f As SFile = _ContentNew(0).File + Dim ff As SFile = f + ff.Name &= "_thumb" + ff.Extension = "jpg" + f = Web.FFMPEG.TakeSnapshot(f, ff, Settings.FfmpegFile, TimeSpan.FromSeconds(1),,, EDP.LogMessageValue) + If f.Exists Then DirectCast(Data, IDownloadableMedia).ThumbnailFile = f + End If + Else + Data.DownloadState = UserMediaStates.Missing + End If + YouTube.Objects.YouTubeMediaContainerBase.Update(_ContentNew(0), Data) + If ResetTitle And Not _ContentNew(0).File.Name.IsEmptyString Then Data.Title = _ContentNew(0).File.Name + Else + Data.DownloadState = UserMediaStates.Missing + End If + End Sub + Protected Function DownloadSingleObject_CreateFile(ByVal Data As YouTube.Objects.IYouTubeMediaContainer, ByVal DFile As SFile) As SFile + If Not Data.File.Path.IsEmptyString Then DFile.Path = Data.File.Path + If DFile.Name.IsEmptyString Then DFile.Name = "OutputFile" + Return DFile + End Function + Protected Overridable Sub DownloadSingleObject_Download(ByVal Data As YouTube.Objects.IYouTubeMediaContainer, ByVal Token As CancellationToken) + DownloadContent(Token) + End Sub + Protected Overridable Sub DownloadSingleObject_GetPosts(ByVal Data As YouTube.Objects.IYouTubeMediaContainer, ByVal Token As CancellationToken) + End Sub +#End Region +#Region "ReparseVideo, ReparseMissing" Protected Overridable Sub ReparseVideo(ByVal Token As CancellationToken) End Sub ''' @@ -1069,15 +1127,170 @@ BlockNullPicture: ''' Protected Overridable Sub ReparseMissing(ByVal Token As CancellationToken) End Sub +#End Region +#Region "MD5 support" + Protected Const VALIDATE_MD5_ERROR As String = "VALIDATE_MD5_ERROR" + Friend Property UseMD5Comparison As Boolean = False + Protected Property StartMD5Checked As Boolean = True + Friend Property RemoveExistingDuplicates As Boolean = False + Protected Overridable Sub ValidateMD5(ByVal Token As CancellationToken) + Try + Dim missingMD5 As Predicate(Of UserMedia) = Function(d) (d.Type = UTypes.GIF Or d.Type = UTypes.Picture) And d.MD5.IsEmptyString + If UseMD5Comparison And _TempMediaList.Exists(missingMD5) Then + Dim i% + Dim itemsCount% = 0 + Dim limit% = If(DownloadTopCount, 0) + Dim data As UserMedia = Nothing + Dim hashList As New Dictionary(Of String, SFile) + Dim f As SFile + Dim ErrMD5 As New ErrorsDescriber(EDP.ReturnValue) + Dim __getMD5 As Func(Of UserMedia, Boolean, String) = + Function(ByVal __data As UserMedia, ByVal IsUrl As Boolean) As String + Try + Dim ImgFormat As Imaging.ImageFormat = Nothing + Dim hash$ = String.Empty + Dim __isGif As Boolean = False + If __data.Type = UTypes.GIF Then + ImgFormat = Imaging.ImageFormat.Gif + __isGif = True + ElseIf Not __data.File.IsEmptyString Then + ImgFormat = GetImageFormat(__data.File) + End If + If ImgFormat Is Nothing Then ImgFormat = Imaging.ImageFormat.Jpeg + If IsUrl Then + hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL_BASE.IfNullOrEmpty(__data.URL), ErrMD5), ImgFormat, ErrMD5)) + Else + hash = ByteArrayToString(GetMD5(SFile.GetBytes(__data.File, ErrMD5), ImgFormat, ErrMD5)) + End If + If hash.IsEmptyString And Not __isGif Then + If ImgFormat Is Imaging.ImageFormat.Jpeg Then ImgFormat = Imaging.ImageFormat.Png Else ImgFormat = Imaging.ImageFormat.Jpeg + If IsUrl Then + hash = ByteArrayToString(GetMD5(SFile.GetBytesFromNet(__data.URL_BASE.IfNullOrEmpty(__data.URL), ErrMD5), ImgFormat, ErrMD5)) + Else + hash = ByteArrayToString(GetMD5(SFile.GetBytes(__data.File, ErrMD5), ImgFormat, ErrMD5)) + End If + End If + Return hash + Catch + Return String.Empty + End Try + End Function + If Not StartMD5Checked Then + StartMD5Checked = True + If _ContentList.Exists(missingMD5) Then + Dim existingFiles As List(Of SFile) = SFile.GetFiles(MyFileSettings.CutPath, "*.jpg|*.jpeg|*.png|*.gif",, EDP.ReturnValue).ListIfNothing + Dim eIndx% + Dim eFinder As Predicate(Of SFile) = Function(ff) ff.File = data.File.File + If RemoveExistingDuplicates Then + RemoveExistingDuplicates = False + _ForceSaveUserInfo = True + If existingFiles.Count > 0 Then + Dim h$ + For i = existingFiles.Count - 1 To 0 Step -1 + h = __getMD5(New UserMedia With {.File = existingFiles(i)}, False) + If Not h.IsEmptyString Then + If hashList.ContainsKey(h) Then + MyMainLOG = $"{ToStringForLog()}: Removed image [{existingFiles(i).File}] (duplicate of [{hashList(h).File}])" + existingFiles(i).Delete(SFO.File, SFODelete.DeleteToRecycleBin, ErrMD5) + existingFiles.RemoveAt(i) + Else + hashList.Add(h, existingFiles(i)) + End If + End If + Next + End If + End If + For i = 0 To _ContentList.Count - 1 + data = _ContentList(i) + If (data.Type = UTypes.GIF Or data.Type = UTypes.Picture) Then + If data.MD5.IsEmptyString Then + ThrowAny(Token) + eIndx = existingFiles.FindIndex(eFinder) + If eIndx >= 0 Then + data.MD5 = __getMD5(New UserMedia With {.File = existingFiles(eIndx)}, False) + If Not data.MD5.IsEmptyString Then _ContentList(i) = data : _ForceSaveUserData = True + End If + End If + existingFiles.RemoveAll(eFinder) + End If + Next + If existingFiles.Count > 0 Then + For i = 0 To existingFiles.Count - 1 + f = existingFiles(i) + data = New UserMedia(f.File) With { + .State = UStates.Downloaded, + .Type = IIf(f.Extension = "gif", UTypes.GIF, UTypes.Picture), + .File = f + } + ThrowAny(Token) + data.MD5 = __getMD5(data, False) + If Not data.MD5.IsEmptyString Then _ContentList.Add(data) : _ForceSaveUserData = True + Next + existingFiles.Clear() + End If + End If + End If + + If _ContentList.Count > 0 Then + With _ContentList.Select(Function(d) d.MD5) + If .ListExists Then .ToList.ForEach(Sub(md5value) _ + If Not md5value.IsEmptyString AndAlso Not hashList.ContainsKey(md5value) Then hashList.Add(md5value, New SFile)) + End With + End If + + For i = _TempMediaList.Count - 1 To 0 Step -1 + If limit > 0 And itemsCount >= limit Then + _TempMediaList.RemoveAt(i) + Else + data = _TempMediaList(i) + If missingMD5(data) Then + ThrowAny(Token) + data.MD5 = __getMD5(data, True) + If Not data.MD5.IsEmptyString Then + If hashList.ContainsKey(data.MD5) Then + _TempMediaList.RemoveAt(i) + Else + hashList.Add(data.MD5, New SFile) + _TempMediaList(i) = data + itemsCount += 1 + End If + End If + End If + End If + Next + End If + Catch iex As ArgumentOutOfRangeException When Disposed + Catch ex As Exception + ProcessException(ex, Token, "ValidateMD5",, VALIDATE_MD5_ERROR) + End Try + End Sub +#End Region +#Region "DownloadContent" Protected MustOverride Sub DownloadContent(ByVal Token As CancellationToken) Private NotInheritable Class OptionalWebClient : Inherits DownloadObjects.WebClient2 + Private ReadOnly Source As UserDataBase Friend Sub New(ByRef Source As UserDataBase) + Me.Source = Source UseResponserClient = Source.UseResponserClient If UseResponserClient Then - RC = Source.Responser + Client = Source.Responser Else - WC = New WebClient + Client = New RWebClient With {.UseNativeClient = Not Source.IsSingleObjectDownload} End If + If Source.IsSingleObjectDownload Then DelegateEvents = True + End Sub + Private _LastProgressValue As Integer = 0 + Protected Overrides Sub Client_DownloadProgressChanged(ByVal Sender As Object, ByVal e As DownloadProgressChangedEventArgs) + Dim v% = e.ProgressPercentage + If v > _LastProgressValue Then + If v > 100 Then v = 100 + Source.Progress.Value = v + Source.Progress.Perform(0) + End If + _LastProgressValue = e.ProgressPercentage + End Sub + Protected Overrides Sub Client_DownloadFileCompleted(ByVal Sender As Object, ByVal e As AsyncCompletedEventArgs) + Source.Progress.Done() End Sub End Class Protected Sub DownloadContentDefault(ByVal Token As CancellationToken) @@ -1090,37 +1303,51 @@ BlockNullPicture: If _ContentNew.Count > 0 Then MyFile.Exists(SFO.Path) Dim MissingErrorsAdd As Boolean = Settings.AddMissingErrorsToLog - Dim MyDir$ = MyFile.CutPath.PathNoSeparator + Dim MyDir$ = DownloadContentDefault_GetRootDir() Dim vsf As Boolean = SeparateVideoFolderF Dim __isVideo As Boolean + Dim __interrupt As Boolean Dim f As SFile Dim v As UserMedia + Dim fileNumProvider As SFileNumbers = SFileNumbers.Default Using w As New OptionalWebClient(Me) If vsf Then CSFileP($"{MyDir}\Video\").Exists(SFO.Path) Progress.Maximum += _ContentNew.Count + If IsSingleObjectDownload Then + If _ContentNew.Count = 1 And _ContentNew(0).Type = UTypes.Video Then + Progress.Value = 0 + Progress.Maximum = 100 + Progress.Provider = MyProgressNumberProvider.Percentage + ElseIf _ContentNew(0).Type = UTypes.m3u8 Then + Progress.Provider = MyProgressNumberProvider.Percentage + Else + w.DelegateEvents = False + End If + End If + For i = 0 To _ContentNew.Count - 1 ThrowAny(Token) v = _ContentNew(i) v.State = UStates.Tried If v.File.IsEmptyString Then - f = v.URL + f = CreateFileFromUrl(v.URL) Else f = v.File End If f.Separator = "\" - f.Path = MyDir + If Not IsSingleObjectDownload Then f.Path = MyDir If v.URL_BASE.IsEmptyString Then v.URL_BASE = v.URL - If Not v.File.IsEmptyString And Not v.URL.IsEmptyString Then + If Not f.IsEmptyString And Not v.URL.IsEmptyString Then Try - __isVideo = v.Type = UTypes.Video Or f.Extension = "mp4" + __isVideo = v.Type = UTypes.Video Or f.Extension = "mp4" Or v.Type = UTypes.m3u8 If f.Extension.IsEmptyString Then Select Case v.Type Case UTypes.Picture : f.Extension = "jpg" - Case UTypes.Video : f.Extension = "mp4" + Case UTypes.Video, UTypes.m3u8 : f.Extension = "mp4" Case UTypes.GIF : f.Extension = "gif" End Select ElseIf f.Extension = "webp" And Settings.DownloadNativeImageFormat Then @@ -1138,16 +1365,29 @@ BlockNullPicture: End If End If + If __isVideo Then fileNumProvider.FileName = f.Name : f = SFile.IndexReindex(f,,, fileNumProvider) + + __interrupt = False If v.Type = UTypes.m3u8 And UseInternalM3U8Function Then - f = DownloadM3U8(v.URL, v, f) + f = DownloadM3U8(v.URL, v, f, Token) If f.IsEmptyString Then Throw New Exception("M3U8 download failed") + ElseIf UseInternalDownloadFileFunction AndAlso ValidateDownloadFile(v.URL, v, __interrupt) Then + f = DownloadFile(v.URL, v, f, Token) + If f.IsEmptyString Then Throw New Exception("InternalFunc download failed") Else - w.DownloadFile(v.URL, f.ToString) + If UseInternalDownloadFileFunction And __interrupt Then Throw New Exception("InternalFunc download interrupted") + If UseClientTokens Then + w.DownloadFile(v.URL, f, Token) + Else + w.DownloadFile(v.URL, f) + End If End If If __isVideo Then v.Type = UTypes.Video DownloadedVideos(False) += 1 + ElseIf v.Type = UTypes.GIF Then + DownloadedPictures(False) += 1 Else v.Type = UTypes.Picture DownloadedPictures(False) += 1 @@ -1155,11 +1395,20 @@ BlockNullPicture: v.File = ChangeFileNameByProvider(f, v) v.State = UStates.Downloaded + DownloadContentDefault_PostProcessing(v, f, Token) dCount += 1 - Catch wex As Exception - v.Attempts += 1 + Catch woex As OperationCanceledException When Token.IsCancellationRequested + If f.Exists Then f.Delete() v.State = UStates.Missing - If MissingErrorsAdd Then ErrorDownloading(f, v.URL) + v.Attempts += 1 + _ContentNew(i) = v + Throw woex + Catch wex As Exception + If DownloadContentDefault_ProcessDownloadException() Then + v.Attempts += 1 + v.State = UStates.Missing + If MissingErrorsAdd Then ErrorDownloading(f, v.URL) + End If End Try Else v.State = UStates.Skipped @@ -1185,9 +1434,30 @@ BlockNullPicture: End Try End Sub Protected UseInternalM3U8Function As Boolean = False - Protected Overridable Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile) As SFile + Protected UseInternalM3U8Function_UseProgress As Boolean = False + Protected Overridable Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, + ByVal Token As CancellationToken) As SFile Return Nothing End Function + Protected UseInternalDownloadFileFunction As Boolean = False + Protected UseInternalDownloadFileFunction_UseProgress As Boolean = False + Protected Overridable Function DownloadFile(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, + ByVal Token As CancellationToken) As SFile + Return Nothing + End Function + Protected Overridable Function ValidateDownloadFile(ByVal URL As String, ByVal Media As UserMedia, ByRef Interrupt As Boolean) As Boolean + Return True + End Function + Protected Overridable Function DownloadContentDefault_GetRootDir() As String + Return MyFile.CutPath(IIf(IsSingleObjectDownload, 0, 1)).PathNoSeparator + End Function + Protected Overridable Sub DownloadContentDefault_PostProcessing(ByRef m As UserMedia, ByVal File As SFile, ByVal Token As CancellationToken) + End Sub + Protected Overridable Function DownloadContentDefault_ProcessDownloadException() As Boolean + Return True + End Function +#End Region +#Region "ProcessException" Protected Const EXCEPTION_OPERATION_CANCELED As Integer = -1 ''' Request DownloadingException ''' 0 - exit @@ -1208,14 +1478,19 @@ BlockNullPicture: End Function ''' 0 - Execute LogError and set HasError Protected MustOverride Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, Optional ByVal EObj As Object = Nothing) As Integer - Protected Function ChangeFileNameByProvider(ByVal f As SFile, ByVal m As UserMedia) As SFile +#End Region +#Region "ChangeFileNameByProvider, RunScript" + Protected Overridable Function CreateFileFromUrl(ByVal URL As String) As SFile + Return New SFile(URL) + End Function + Protected Overridable Function ChangeFileNameByProvider(ByVal f As SFile, ByVal m As UserMedia) As SFile Dim ff As SFile = Nothing Try If f.Exists Then If Not Settings.FileReplaceNameByDate.Value = FileNameReplaceMode.None Then ff = f ff.Name = String.Format(FileDateAppenderPattern, f.Name, CStr(AConvert(Of String)(If(m.Post.Date, Now), FileDateAppenderProvider, String.Empty))) - ff = SFile.Indexed_IndexFile(ff,, New NumberedFile(ff)) + ff = SFile.IndexReindex(ff,,, New NumberedFile(ff)) End If If Not ff.Name.IsEmptyString Then My.Computer.FileSystem.RenameFile(f, ff.File) : Return ff End If @@ -1238,7 +1513,7 @@ BlockNullPicture: If Not ScriptPattern.IsEmptyString Then If Not ScriptPattern.Contains(spa) Then ScriptPattern &= $" ""{spa}""" Using b As New BatchExecutor With {.RedirectStandardError = True} - b.Execute({String.Format(ScriptPattern, MyFile.CutPath(1).PathNoSeparator)}, EDP.SendInLog + EDP.ThrowException) + b.Execute({String.Format(ScriptPattern, MyFile.CutPath(1).PathNoSeparator)}, EDP.SendToLog + EDP.ThrowException) If b.HasError Or Not b.ErrorOutput.IsEmptyString Then Throw New Exception(b.ErrorOutput, b.ErrorException) End Using End If @@ -1248,6 +1523,7 @@ BlockNullPicture: End Try End Sub #End Region +#End Region #Region "Delete, Move, Merge, Copy" Friend Overridable Function Delete(Optional ByVal Multiple As Boolean = False, Optional ByVal CollectionValue As Integer = -1) As Integer Implements IUserData.Delete Dim f As SFile = SFile.GetPath(MyFile.CutPath.Path) @@ -1359,7 +1635,7 @@ BlockNullPicture: FilesMover.Invoke If SFile.GetFiles(UserBefore.File.CutPath,, SearchOption.AllDirectories, New ErrorsDescriber(False, False, False, New List(Of SFile))).Count = 0 Then - UserBefore.File.CutPath.Delete(SFO.Path, Settings.DeleteMode, EDP.SendInLog) + UserBefore.File.CutPath.Delete(SFO.Path, Settings.DeleteMode, EDP.SendToLog) End If If Not ScriptData.IsEmptyString AndAlso ScriptData.Contains(UserBefore.File.PathNoSeparator) Then _ ScriptData = ScriptData.Replace(UserBefore.File.PathNoSeparator, MyFile.PathNoSeparator) @@ -1424,7 +1700,7 @@ BlockNullPicture: #End Region #Region "Errors functions" Protected Sub LogError(ByVal ex As Exception, ByVal Message As String) - ErrorsDescriber.Execute(EDP.SendInLog, ex, $"{ToStringForLog()}: {Message}") + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"{ToStringForLog()}: {Message}") End Sub Protected Sub ErrorDownloading(ByVal f As SFile, ByVal URL As String) If Not f.Exists Then MyMainLOG = $"Error downloading from [{URL}] to [{f}]" @@ -1546,7 +1822,7 @@ BlockNullPicture: #Region "Base interfaces" Friend Interface IContentProvider ReadOnly Property Site As String - Property Name As String + ReadOnly Property Name As String Property ID As String Property FriendlyName As String Property Description As String @@ -1554,6 +1830,7 @@ BlockNullPicture: Property Temporary As Boolean Sub OpenSite(Optional ByVal e As ErrorsDescriber = Nothing) Sub DownloadData(ByVal Token As CancellationToken) + Sub DownloadSingleObject(ByVal Data As YouTube.Objects.IYouTubeMediaContainer, ByVal Token As CancellationToken) End Interface Friend Interface IUserData : Inherits IContentProvider, IComparable(Of UserDataBase), IComparable, IEquatable(Of UserDataBase), IIndexable, IDisposable Event UserUpdated(ByVal User As IUserData) @@ -1571,7 +1848,6 @@ BlockNullPicture: ReadOnly Property IsVirtual As Boolean ReadOnly Property Labels As List(Of String) #End Region - ReadOnly Property IsChannel As Boolean Property Exists As Boolean Property Suspended As Boolean Property ReadyForDownload As Boolean diff --git a/SCrawler/API/BaseObjects/DomainEnvir.vb b/SCrawler/API/BaseObjects/DomainEnvir.vb deleted file mode 100644 index 8df1ee5..0000000 --- a/SCrawler/API/BaseObjects/DomainEnvir.vb +++ /dev/null @@ -1,86 +0,0 @@ -' Copyright (C) 2023 Andy https://github.com/AAndyProgram -' This program is free software: you can redistribute it and/or modify -' it under the terms of the GNU General Public License as published by -' the Free Software Foundation, either version 3 of the License, or -' (at your option) any later version. -' -' This program is distributed in the hope that it will be useful, -' but WITHOUT ANY WARRANTY -Imports PersonalUtilities.Forms -Imports ADB = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons -Namespace API.BaseObjects - Friend Interface IDomainContainer - ReadOnly Property Icon As Icon - ReadOnly Property Site As String - ReadOnly Property Domains As List(Of String) - ReadOnly Property DomainsTemp As List(Of String) - ReadOnly Property DomainsDefault As String - ReadOnly Property DomainsSettingProp As Plugin.PropertyValue - Property DomainsChanged As Boolean - Property Initialized As Boolean - Property DomainsUpdateInProgress As Boolean - Property DomainsUpdatedBySite As Boolean - Sub UpdateDomains() - End Interface - Friend NotInheritable Class DomainContainer - Private Sub New() - End Sub - Friend Shared Sub EndInit(ByVal s As IDomainContainer) - If ACheck(s.DomainsSettingProp.Value) Then s.Domains.ListAddList(CStr(s.DomainsSettingProp.Value).Split("|"), LAP.NotContainsOnly) - End Sub - Friend Overloads Shared Sub UpdateDomains(ByVal s As IDomainContainer) - UpdateDomains(s, Nothing, True) - End Sub - Friend Overloads Shared Sub UpdateDomains(ByVal s As IDomainContainer, ByVal NewDomains As IEnumerable(Of String), ByVal Internal As Boolean) - With s - If Not .Initialized Or (.DomainsUpdatedBySite And Not Internal) Then Exit Sub - If Not .DomainsUpdateInProgress Then - .DomainsUpdateInProgress = True - .Domains.ListAddList(.DomainsDefault.Split("|"), LAP.NotContainsOnly) - .Domains.ListAddList(NewDomains, LAP.NotContainsOnly) - .DomainsSettingProp.Value = .Domains.ListToString("|") - If Not Internal Then .DomainsUpdatedBySite = True - .DomainsUpdateInProgress = False - End If - End With - End Sub - Friend Shared Sub Update(ByVal s As IDomainContainer) - With s - If .DomainsChanged Then - .Domains.Clear() - .Domains.ListAddList(.DomainsTemp, LAP.NotContainsOnly) - .UpdateDomains() - End If - End With - End Sub - Friend Shared Sub EndEdit(ByVal s As IDomainContainer) - s.DomainsTemp.ListAddList(s.Domains, LAP.ClearBeforeAdd, LAP.NotContainsOnly) - s.DomainsChanged = False - End Sub - Friend Shared Sub OpenSettingsForm(ByVal s As IDomainContainer) - Dim __add As EventHandler(Of SimpleListFormEventArgs) = Sub(sender, e) e.Item = InputBoxE($"Enter a new domain using the pattern [{s.Site}.com]:", "New domain").IfNullOrEmptyE(Nothing) - Dim __delete As EventHandler(Of SimpleListFormEventArgs) = Sub(sender, e) - Dim n$ = AConvert(Of String)(e.Item, AModes.Var, String.Empty) - e.Result = MsgBoxE({$"Are you sure you want to delete the [{n}] domain?", - "Removing domains"}, vbYesNo) = vbYes - End Sub - Using f As New SimpleListForm(Of String)(If(s.DomainsChanged, s.DomainsTemp, s.Domains), Settings.Design) With { - .Buttons = {ADB.Add, ADB.Delete}, - .Mode = SimpleListFormModes.Remaining, - .FormText = s.Site, - .Icon = s.Icon, - .LocationOnly = True, - .Size = New Size(400, 330), - .DesignXMLNodeName = s.Site - } - AddHandler f.AddClick, __add - AddHandler f.DeleteClick, __delete - f.ShowDialog() - If f.DialogResult = DialogResult.OK Then - s.DomainsChanged = True - s.DomainsTemp.ListAddList(f.DataResult, LAP.ClearBeforeAdd, LAP.NotContainsOnly) - End If - End Using - End Sub - End Class -End Namespace \ No newline at end of file diff --git a/SCrawler/API/BaseObjects/DomainsContainer.vb b/SCrawler/API/BaseObjects/DomainsContainer.vb new file mode 100644 index 0000000..023f132 --- /dev/null +++ b/SCrawler/API/BaseObjects/DomainsContainer.vb @@ -0,0 +1,108 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports SCrawler.Plugin +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Tools +Imports ADB = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons +Namespace API.Base + Friend Class DomainsContainer : Implements IEnumerable(Of String), IMyEnumerator(Of String) + Friend Event DomainsUpdated(ByVal Sender As DomainsContainer) + Friend ReadOnly Property Domains As List(Of String) + Friend ReadOnly Property DomainsTemp As List(Of String) + Friend ReadOnly Property DomainsDefault As String + Friend Property Changed As Boolean + Private DomainsUpdateInProgress As Boolean = False + Friend Property UpdatedBySite As Boolean + Protected ReadOnly Property Instance As ISiteSettings + Friend Property DestinationProp As PropertyValue + Default Friend ReadOnly Property Item(ByVal Index As Integer) As String Implements IMyEnumerator(Of String).MyEnumeratorObject + Get + Return Domains(Index) + End Get + End Property + Friend ReadOnly Property Count As Integer Implements IMyEnumerator(Of String).MyEnumeratorCount + Get + Return Domains.Count + End Get + End Property + Friend Sub New(ByVal _Instance As ISiteSettings, ByVal DefaultValue As String) + Domains = New List(Of String) + DomainsTemp = New List(Of String) + Instance = _Instance + DomainsDefault = DefaultValue + If Not DomainsDefault.IsEmptyString Then Domains.ListAddList(CStr(DomainsDefault).Split("|"), LAP.NotContainsOnly) + End Sub + Friend Sub PopulateInitialDomains(ByVal InitialValue As String) + If Not InitialValue.IsEmptyString Then Domains.ListAddList(CStr(InitialValue).Split("|"), LAP.NotContainsOnly) + End Sub + Public Overrides Function ToString() As String + Return Domains.ListToString("|") + End Function + Friend Sub Add(ByVal NewDomains As IEnumerable(Of String), ByVal UpdateBySite As Boolean) + If Not DomainsUpdateInProgress Then + DomainsUpdateInProgress = True + Domains.ListAddList(NewDomains, LAP.NotContainsOnly) + If UpdateBySite Then Me.UpdatedBySite = True + Save() + DomainsUpdateInProgress = False + RaiseEvent DomainsUpdated(Me) + End If + End Sub + Friend Overridable Function Apply() As Boolean + If Changed Then + Domains.Clear() + Domains.ListAddList(DomainsTemp, LAP.NotContainsOnly) + Save() + RaiseEvent DomainsUpdated(Me) + Return True + Else + Return False + End If + End Function + Friend Overridable Sub Save() + If Not DestinationProp Is Nothing Then DestinationProp.Value = ToString() + End Sub + Friend Overridable Sub Reset() + Changed = False + DomainsTemp.Clear() + End Sub + Friend Overridable Sub OpenSettingsForm() + Dim __add As EventHandler(Of SimpleListFormEventArgs) = Sub(sender, e) e.Item = InputBoxE($"Enter a new domain using the pattern [{Instance.Site}.com]:", "New domain").IfNullOrEmptyE(Nothing) + Dim __delete As EventHandler(Of SimpleListFormEventArgs) = Sub(sender, e) + Dim n$ = AConvert(Of String)(e.Item, AModes.Var, String.Empty) + e.Result = MsgBoxE({$"Are you sure you want to delete the [{n}] domain?", + "Removing domains"}, vbYesNo) = vbYes + End Sub + Using f As New SimpleListForm(Of String)(If(Changed, DomainsTemp, Domains), Settings.Design) With { + .Buttons = {ADB.Add, ADB.Delete}, + .Mode = SimpleListFormModes.Remaining, + .FormText = Instance.Site, + .Icon = Instance.Icon, + .LocationOnly = True, + .Size = New Size(400, 330), + .DesignXMLNodeName = $"{Instance.Site}_DomainsForm" + } + AddHandler f.AddClick, __add + AddHandler f.DeleteClick, __delete + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then + Changed = True + DomainsTemp.Clear() + DomainsTemp.ListAddList(f.DataResult, LAP.NotContainsOnly) + End If + End Using + End Sub + Private Function GetEnumerator() As IEnumerator(Of String) Implements IEnumerable(Of String).GetEnumerator + Return New MyEnumerator(Of String)(Me) + End Function + Private Function IEnumerable_GetEnumerator() As IEnumerator Implements IEnumerable.GetEnumerator + Return GetEnumerator() + End Function + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/BaseObjects/InternalSettingsForm.Designer.vb b/SCrawler/API/BaseObjects/InternalSettingsForm.Designer.vb new file mode 100644 index 0000000..1097ec8 --- /dev/null +++ b/SCrawler/API/BaseObjects/InternalSettingsForm.Designer.vb @@ -0,0 +1,89 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.Base + + Partial Friend Class InternalSettingsForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Me.components = New System.ComponentModel.Container() + Me.CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + Me.TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + Me.TT_MAIN = New System.Windows.Forms.ToolTip(Me.components) + Me.CONTAINER_MAIN.ContentPanel.SuspendLayout() + Me.CONTAINER_MAIN.SuspendLayout() + Me.SuspendLayout() + ' + 'CONTAINER_MAIN + ' + ' + 'CONTAINER_MAIN.ContentPanel + ' + Me.CONTAINER_MAIN.ContentPanel.Controls.Add(Me.TP_MAIN) + Me.CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(184, 0) + Me.CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + Me.CONTAINER_MAIN.LeftToolStripPanelVisible = False + Me.CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) + Me.CONTAINER_MAIN.Name = "CONTAINER_MAIN" + Me.CONTAINER_MAIN.RightToolStripPanelVisible = False + Me.CONTAINER_MAIN.Size = New System.Drawing.Size(184, 25) + Me.CONTAINER_MAIN.TabIndex = 0 + Me.CONTAINER_MAIN.TopToolStripPanelVisible = False + ' + 'TP_MAIN + ' + Me.TP_MAIN.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + Me.TP_MAIN.ColumnCount = 1 + Me.TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + Me.TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + Me.TP_MAIN.Location = New System.Drawing.Point(0, 0) + Me.TP_MAIN.Name = "TP_MAIN" + Me.TP_MAIN.RowCount = 1 + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 160.0!)) + Me.TP_MAIN.Size = New System.Drawing.Size(184, 0) + Me.TP_MAIN.TabIndex = 0 + ' + 'InternalSettingsForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(184, 25) + Me.Controls.Add(Me.CONTAINER_MAIN) + Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle + Me.KeyPreview = True + Me.MaximizeBox = False + Me.MinimizeBox = False + Me.Name = "InternalSettingsForm" + Me.ShowInTaskbar = False + Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide + Me.Text = "Settings" + Me.CONTAINER_MAIN.ContentPanel.ResumeLayout(False) + Me.CONTAINER_MAIN.ResumeLayout(False) + Me.CONTAINER_MAIN.PerformLayout() + Me.ResumeLayout(False) + + End Sub + + Private WithEvents TP_MAIN As TableLayoutPanel + Private WithEvents TT_MAIN As ToolTip + Private WithEvents CONTAINER_MAIN As ToolStripContainer + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/Download/VideosDownloaderForm.resx b/SCrawler/API/BaseObjects/InternalSettingsForm.resx similarity index 88% rename from SCrawler/Download/VideosDownloaderForm.resx rename to SCrawler/API/BaseObjects/InternalSettingsForm.resx index d0894be..0a1e786 100644 --- a/SCrawler/Download/VideosDownloaderForm.resx +++ b/SCrawler/API/BaseObjects/InternalSettingsForm.resx @@ -117,16 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - False - - - False - - + 17, 17 - - 124, 17 - \ No newline at end of file diff --git a/SCrawler/API/BaseObjects/InternalSettingsForm.vb b/SCrawler/API/BaseObjects/InternalSettingsForm.vb new file mode 100644 index 0000000..0f84bee --- /dev/null +++ b/SCrawler/API/BaseObjects/InternalSettingsForm.vb @@ -0,0 +1,256 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Reflection +Imports System.ComponentModel +Imports PersonalUtilities.Forms +Imports SCrawler.Plugin +Imports SCrawler.Plugin.Attributes +Namespace API.Base + Friend Class InternalSettingsForm + Private WithEvents MyDefs As DefaultFormOptions + Private ReadOnly Property MySettingsInstance As ISiteSettings + Private ReadOnly Property MyObject As Object + Private ReadOnly IsSettingsForm As Boolean = True + Private ReadOnly Property MyMembers As List(Of MemberOption) + ''' Default: 200 + Friend Property MinimumWidth As Integer = 200 + Private Class MemberOption : Inherits Hosts.PropertyValueHost : Implements IDisposable + Friend ToolTip As String + Friend Caption As String + Friend ThreeState As Boolean = False + Friend AllowNull As Boolean = True + Friend Provider As Type + Friend Overrides Property Type As Type + Get + Return _Type + End Get + Set(ByVal t As Type) + MyBase.Type = t + End Set + End Property + Friend Overrides ReadOnly Property Name As String + Get + Return Member.Name + End Get + End Property + Friend Overrides Property LeftOffset As Integer + Get + Return If(_LeftOffset, 0) + End Get + Set(ByVal NewOffset As Integer) + MyBase.LeftOffset = NewOffset + End Set + End Property + Private ReadOnly _MinimumWidth As Integer? = Nothing + Friend ReadOnly Property Width As Integer + Get + Return LeftOffset + If(_MinimumWidth, 0) + If(TypeOf Control Is CheckBox, 0, 200) + + PaddingE.GetOf({Control}).Horizontal(2) + MeasureText(Caption, Control.Font).Width + End Get + End Property + Friend OptName As String = String.Empty + Friend Sub New(ByRef PropertySource As Object, ByVal m As MemberInfo, ByVal ps As PSettingAttribute, ByVal po As PropertyOption) + Source = PropertySource + Member = m + _Type = Member.GetMemberType + _Value = Member.GetMemberValue(PropertySource) + With ps + ToolTip = .ToolTip + Caption = .Caption + ThreeState = .ThreeState + AllowNull = .AllowNull + Provider = .Provider + _LeftOffset = .LeftOffsetGet + ControlNumber = .Number + _MinimumWidth = .MinimumWidth + End With + If Not po Is Nothing Then + With po + OptName = po.Name + If ToolTip.IsEmptyString Then ToolTip = .ControlToolTip + If Caption.IsEmptyString Then Caption = .ControlText + End With + End If + End Sub + Protected Overrides ReadOnly Property Control_IsInformationLabel As Boolean + Get + Return False + End Get + End Property + Protected Overrides ReadOnly Property Control_ThreeStates As Boolean + Get + Return ThreeState + End Get + End Property + Protected Overrides ReadOnly Property Control_Caption As String + Get + Return Caption + End Get + End Property + Protected Overrides ReadOnly Property Control_ToolTip As String + Get + Return ToolTip + End Get + End Property + Friend Overloads Sub CreateControl(ByVal f As FieldsChecker, ByVal TT As ToolTip) + CreateControl(TT) + If Not Provider Is Nothing Then f.AddControl(Control, Caption, Type, AllowNull, Activator.CreateInstance(Provider)) + End Sub +#Region "IDisposable Support" + Private disposedValue As Boolean = False + Protected Overridable Overloads Sub Dispose(ByVal disposing As Boolean) + If Not disposedValue Then + If disposing Then Control.Dispose() + Control = Nothing + disposedValue = True + End If + End Sub + Protected Overrides Sub Finalize() + Dispose(False) + MyBase.Finalize() + End Sub + Friend Overloads Sub Dispose() Implements IDisposable.Dispose + Dispose(True) + GC.SuppressFinalize(Me) + End Sub +#End Region + End Class + Friend Sub New(ByVal Obj As Object, ByVal s As ISiteSettings, ByVal _IsSettingsForm As Boolean) + InitializeComponent() + MyDefs = New DefaultFormOptions(Me, Settings.Design) + MyMembers = New List(Of MemberOption) + + MyObject = Obj + MySettingsInstance = s + IsSettingsForm = _IsSettingsForm + If _IsSettingsForm Then + Text = "Settings" + Else + Text = "Options" + End If + Icon = s.Icon + End Sub + Private Sub InternalSettingsForm_Load(sender As Object, e As EventArgs) Handles Me.Load + Try + With MyDefs + .MyViewInitialize(True) + .AddOkCancelToolbar() + + Dim members As IEnumerable(Of MemberInfo) + Dim member As MemberInfo + Dim attr As PSettingAttribute + Dim opt As PropertyOption + Dim providersMembersSettings As IEnumerable(Of MemberInfo) + Dim providersMembersObj As IEnumerable(Of MemberInfo) + Dim providersPredicate As Func(Of MemberInfo, Boolean) = Function(m) m.MemberType = MemberTypes.Property AndAlso + m.GetMemberCustomAttributes(Of Provider).ListExists + Dim m1 As MemberInfo, m2 As MemberInfo + Dim tmpObj As Object + + members = GetObjectMembers(MyObject, Function(m) (m.MemberType = MemberTypes.Field Or m.MemberType = MemberTypes.Property) AndAlso + Not m.GetCustomAttribute(Of PSettingAttribute) Is Nothing) + providersMembersSettings = GetObjectMembers(MySettingsInstance, providersPredicate) + providersMembersObj = GetObjectMembers(MyObject, providersPredicate) + + If members.ListExists Then + For Each member In members + attr = member.GetMemberCustomAttribute(Of PSettingAttribute) + If Not attr Is Nothing AndAlso + (attr.Address = SettingAddress.Both OrElse + ( + (IsSettingsForm And attr.Address = SettingAddress.Settings) Or + (Not IsSettingsForm And attr.Address = SettingAddress.User) + ) + ) Then + opt = Nothing + + If Not attr.NameAssoc.IsEmptyString Then + m1 = GetObjectMembers(MyObject, Function(m) m.Name = attr.NameAssocInstance).FirstOrDefault + If Not m1 Is Nothing AndAlso (m1.MemberType = MemberTypes.Property Or + m1.MemberType = MemberTypes.Field Or + m1.MemberType = MemberTypes.Method) Then + tmpObj = m1.GetMemberValue(MyObject) + If Not tmpObj Is Nothing Then + m2 = GetObjectMembers(tmpObj, Function(m) m.Name = attr.NameAssoc).FirstOrDefault + If Not m2 Is Nothing Then opt = m2.GetMemberCustomAttribute(Of PropertyOption) + End If + End If + End If + + MyMembers.Add(New MemberOption(MyObject, member, attr, opt)) + End If + Next + End If + + .MyFieldsCheckerE = New FieldsChecker + + If MyMembers.Count > 0 Then + + Dim prov As IEnumerable(Of Provider) + Dim _prov As Provider + Dim si% = -1 + Dim i% + For Each provEnum In {providersMembersObj, providersMembersSettings} + si += 1 + If provEnum.ListExists Then + For Each member In provEnum + prov = member.GetMemberCustomAttributes(Of Provider) + If prov.ListExists Then + For Each _prov In prov + i = MyMembers.FindIndex(Function(m) If(si = 0, m.Name, m.OptName) = _prov.Name) + If i >= 0 Then MyMembers(i).SetProvider(member.GetMemberValue(If(si = 0, MyObject, CObj(MySettingsInstance))), _prov) + Next + End If + Next + End If + Next + + TP_MAIN.RowStyles.Clear() + TP_MAIN.RowCount = 0 + For i% = 0 To MyMembers.Count - 1 + With MyMembers(i) + .CreateControl(MyDefs.MyFieldsCheckerE, TT_MAIN) + TP_MAIN.RowStyles.Add(New RowStyle(SizeType.Absolute, .ControlHeight)) + TP_MAIN.RowCount += 1 + TP_MAIN.Controls.Add(.Control, 0, TP_MAIN.RowStyles.Count - 1) + End With + Next + Else + Throw New ArgumentOutOfRangeException("Members", "Settings instance does not contain settings members") + End If + + .MyFieldsChecker.EndLoaderOperations() + + Dim s As Size = Size + s.Height += (MyMembers.Sum(Function(m) m.ControlHeight) + + (PaddingE.GetOf({TP_MAIN},,,,,, 0).Vertical(MyMembers.Count - 1) / 2).RoundDown + MyMembers.Count - 1) + s.Width = MyMembers.Max(Function(m) m.Width) + PaddingE.GetOf({TP_MAIN, CONTAINER_MAIN, CONTAINER_MAIN.ContentPanel, Me}, False).Horizontal(2) + If MinimumWidth > 0 And s.Width < MinimumWidth Then s.Width = MinimumWidth + Size = s + MinimumSize = s + MaximumSize = s + + .EndLoaderOperations() + End With + Catch ex As Exception + MyDefs.InvokeLoaderError(ex) + End Try + End Sub + Private Sub InternalSettingsForm_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing + TP_MAIN.Controls.Clear() + MyMembers.ListClearDispose + End Sub + Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick + If MyDefs.MyFieldsChecker.AllParamsOK Then + MyMembers.ForEach(Sub(m) m.UpdateValueByControl()) + MyDefs.CloseForm() + End If + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Gfycat/Envir.vb b/SCrawler/API/Gfycat/Envir.vb index 482a883..88a5164 100644 --- a/SCrawler/API/Gfycat/Envir.vb +++ b/SCrawler/API/Gfycat/Envir.vb @@ -7,12 +7,70 @@ ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY Imports System.Net +Imports System.Threading Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects +Imports SCrawler.Plugin.Hosts Imports PersonalUtilities.Functions.RegularExpressions Namespace API.Gfycat - Friend NotInheritable Class Envir - Private Sub New() + Friend NotInheritable Class Envir : Inherits UserDataBase + Friend Const SiteKey As String = "AndyProgram_Gfycat" + Friend Const SiteName As String = "Gfycat" + Friend Sub New() End Sub +#Region "UserDataBase Support" + Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As PersonalUtilities.Functions.XML.XmlFile, ByVal Loading As Boolean) + End Sub + Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) + End Sub + Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) + SeparateVideoFolder = False + DownloadContentDefault(Token) + End Sub + Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, + Optional ByVal EObj As Object = Nothing) As Integer + Return 0 + End Function +#End Region +#Region "DownloadSingleObject" + Private _IsRedGifs As Boolean = False + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim urlVideo$ = GetVideo(Data.URL) + If Not urlVideo.IsEmptyString Then + If urlVideo.Contains("redgifs.com") Then + _IsRedGifs = True + DirectCast(Settings(RedGifs.RedGifsSiteKey).Source, RedGifs.SiteSettings).UpdateTokenIfRequired() + Dim newData As IYouTubeMediaContainer = Settings(RedGifs.RedGifsSiteKey).GetSingleMediaInstance(urlVideo, Data.File) + If Not newData Is Nothing Then + newData.Progress = Data.Progress + newData.Download(Data.UseCookies, Token) + YouTubeMediaContainerBase.Update(newData, Data) + DirectCast(Data, DownloadableMediaHost).ExchangeData(newData, Data) + With DirectCast(Data, YouTubeMediaContainerBase) + .Site = RedGifs.RedGifsSite + .SiteKey = RedGifs.RedGifsSiteKey + .SiteIcon = Settings(RedGifs.RedGifsSiteKey).Source.Image + End With + Else + Throw New Exception($"Unable to get RedGifs instance{vbCr}{Data.URL}{vbCr}{urlVideo}") + End If + Else + Dim m As New UserMedia(urlVideo, UserMedia.Types.Video) With {.URL_BASE = Data.URL} + m.File.Path = Data.File.Path + _TempMediaList.Add(m) + End If + End If + End Sub + Protected Overrides Sub DownloadSingleObject_CreateMedia(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + If Not _IsRedGifs Then MyBase.DownloadSingleObject_CreateMedia(Data, Token) + End Sub + Protected Overrides Sub DownloadSingleObject_PostProcessing(ByVal Data As IYouTubeMediaContainer, Optional ByVal ResetTitle As Boolean = True) + If Not _IsRedGifs Then MyBase.DownloadSingleObject_PostProcessing(Data, ResetTitle) + End Sub + Protected Overrides Sub DownloadSingleObject_Download(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + If Not _IsRedGifs Then MyBase.DownloadSingleObject_Download(Data, Token) + End Sub +#End Region Friend Shared Function GetVideo(ByVal URL As String) As String Try Dim r$ @@ -33,14 +91,22 @@ Namespace API.Gfycat Dim e As EDP = EDP.ReturnValue If TypeOf ex Is WebException Then Dim obj As HttpWebResponse = TryCast(DirectCast(ex, WebException).Response, HttpWebResponse) - If Not If(obj?.StatusCode, HttpStatusCode.OK) = HttpStatusCode.NotFound Then e += EDP.SendInLog + If Not If(obj?.StatusCode, HttpStatusCode.OK) = HttpStatusCode.NotFound Then e += EDP.SendToLog End If Return ErrorsDescriber.Execute(e, ex, $"[API.Gfycat.Envir.GetVideo({URL})]", String.Empty) End Try End Function - Friend Shared Function GetVideoInfo(ByVal URL As String) As IEnumerable(Of UserMedia) - Dim u$ = GetVideo(URL) - Return If(u.IsEmptyString, Nothing, {New UserMedia(u, UserMedia.Types.Video)}) + Friend Shared Function GetSingleMediaInstance(ByVal URL As String, ByVal OutputFile As SFile) As IYouTubeMediaContainer + If Not URL.IsEmptyString AndAlso URL.Contains("gfycat") Then + Return New DownloadableMediaHost(URL, OutputFile) With { + .Instance = New Envir, + .Site = SiteName, + .SiteKey = SiteKey, + .SiteIcon = Nothing + } + Else + Return Nothing + End If End Function End Class End Namespace \ No newline at end of file diff --git a/SCrawler/API/Imgur/Envir.vb b/SCrawler/API/Imgur/Envir.vb index b9b7d09..9d47c2b 100644 --- a/SCrawler/API/Imgur/Envir.vb +++ b/SCrawler/API/Imgur/Envir.vb @@ -7,8 +7,11 @@ ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY Imports System.Net +Imports System.Threading Imports SCrawler.API.Base Imports SCrawler.API.Imgur.Declarations +Imports SCrawler.API.YouTube.Objects +Imports SCrawler.Plugin.Hosts Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Documents.JSON @@ -18,8 +21,28 @@ Namespace API.Imgur Friend ReadOnly PostRegex As RParams = RParams.DMS("/([^/]+?)(|#.*?|\.[\w]{0,4})(|\?.*?)\Z", 1) End Module End Namespace - Friend NotInheritable Class Envir - Private Sub New() + Friend NotInheritable Class Envir : Inherits UserDataBase + Friend Const SiteKey As String = "AndyProgram_Imgur" + Friend Const SiteName As String = "Imgur" + Friend Sub New() + End Sub +#Region "UserDataBase Support" + Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean) + End Sub + Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) + End Sub + Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) + SeparateVideoFolder = False + DownloadContentDefault(Token) + End Sub + Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, + Optional ByVal EObj As Object = Nothing) As Integer + Return 0 + End Function +#End Region + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim videos As IEnumerable(Of UserMedia) = GetVideoInfo(Data.URL, EDP.SendToLog) + If videos.ListExists Then _TempMediaList.AddRange(videos) End Sub Friend Shared Function GetGallery(ByVal URL As String, Optional ByVal e As ErrorsDescriber = Nothing) As List(Of String) Try @@ -47,7 +70,7 @@ Namespace API.Imgur End If Return Nothing Catch ex As Exception - Return DownloadingException(ex, $"[API.Imgur.Envir.GetGallery({URL})]", Nothing, e) + Return DownloadingException_Internal(ex, $"[API.Imgur.Envir.GetGallery({URL})]", Nothing, e) End Try End Function Friend Shared Function GetImage(ByVal URL As String, Optional ByVal e As ErrorsDescriber = Nothing) As String @@ -64,7 +87,7 @@ Namespace API.Imgur End If Return String.Empty Catch ex As Exception - Return DownloadingException(ex, $"[API.Imgur.Envir.GetImage({URL})]", String.Empty, e) + Return DownloadingException_Internal(ex, $"[API.Imgur.Envir.GetImage({URL})]", String.Empty, e) End Try End Function Friend Shared Function GetVideoInfo(ByVal URL As String, Optional ByVal e As ErrorsDescriber = Nothing) As IEnumerable(Of UserMedia) @@ -81,11 +104,23 @@ Namespace API.Imgur Return Nothing Catch ex As Exception If Not e.Exists Then e = EDP.LogMessageValue - Return ErrorsDescriber.Execute(e, ex, "Imgur standalone downloader: fetch media error") + Return ErrorsDescriber.Execute(e, ex, $"[API.Imgur.Envir.GetVideoInfo({URL})]: fetch media error") End Try End Function - Private Shared Function DownloadingException(ByVal ex As Exception, ByVal Message As String, - ByVal NullArg As Object, ByVal e As ErrorsDescriber) As Object + Friend Shared Function GetSingleMediaInstance(ByVal URL As String, ByVal OutputFile As SFile) As IYouTubeMediaContainer + If Not URL.IsEmptyString AndAlso URL.Contains("imgur.com") Then + Return New DownloadableMediaHost(URL, OutputFile) With { + .Instance = New Envir, + .Site = SiteName, + .SiteKey = SiteKey, + .SiteIcon = Nothing + } + Else + Return Nothing + End If + End Function + Private Shared Function DownloadingException_Internal(ByVal ex As Exception, ByVal Message As String, + ByVal NullArg As Object, ByVal e As ErrorsDescriber) As Object If TypeOf ex Is WebException Then Dim obj As HttpWebResponse = TryCast(DirectCast(ex, WebException).Response, HttpWebResponse) If Not obj Is Nothing Then @@ -97,7 +132,7 @@ Namespace API.Imgur End If End If End If - If Not e.Exists Then e = New ErrorsDescriber(EDP.ReturnValue + EDP.SendInLog) + If Not e.Exists Then e = New ErrorsDescriber(EDP.ReturnValue + EDP.SendToLog) Return ErrorsDescriber.Execute(e, ex, Message, NullArg) End Function End Class diff --git a/SCrawler/API/Instagram/Declarations.vb b/SCrawler/API/Instagram/Declarations.vb index a8853b3..93b46d5 100644 --- a/SCrawler/API/Instagram/Declarations.vb +++ b/SCrawler/API/Instagram/Declarations.vb @@ -6,16 +6,15 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY -Imports PersonalUtilities.Functions.RegularExpressions +Imports PersonalUtilities.Tools.Web.Cookies Imports PersonalUtilities.Tools.Web.Clients Imports PersonalUtilities.Tools.Web.Clients.EventArguments -Imports PersonalUtilities.Tools.Web.Cookies +Imports PersonalUtilities.Functions.RegularExpressions Namespace API.Instagram Friend Module Declarations Friend Const InstagramSite As String = "Instagram" Friend Const InstagramSiteKey As String = "AndyProgram_Instagram" Friend ReadOnly FilesPattern As RParams = RParams.DMS(".+?([^/\?]+?\.[\w\d]{3,4})(?=(\?|\Z))", 1, EDP.ReturnValue) - Friend ReadOnly Property DateProvider As New CustomProvider(Function(v, d, p, n, e) ADateTime.ParseUnicode(v)) Friend Sub UpdateResponser(ByVal Source As IResponse, ByRef Destination As Responser) Const r_wwwClaimName$ = "x-ig-set-www-claim" Const r_tokenName$ = "csrftoken" @@ -46,7 +45,7 @@ Namespace API.Instagram If Not wwwClaim.IsEmptyString Then Destination.Headers.Add(SiteSettings.Header_IG_WWW_CLAIM, wwwClaim) If Not token.IsEmptyString Then Destination.Headers.Add(SiteSettings.Header_CSRF_TOKEN, token) If Not isInternal Then - Destination.Cookies.Update(Source.Cookies, CookieKeeper.UpdateModes.ReplaceByNameAll, False, EDP.SendInLog) + Destination.Cookies.Update(Source.Cookies, CookieKeeper.UpdateModes.ReplaceByNameAll, False, EDP.SendToLog) Destination.SaveSettings() End If End If diff --git a/SCrawler/API/Instagram/EditorExchangeOptions.vb b/SCrawler/API/Instagram/EditorExchangeOptions.vb index a41a067..b84ed27 100644 --- a/SCrawler/API/Instagram/EditorExchangeOptions.vb +++ b/SCrawler/API/Instagram/EditorExchangeOptions.vb @@ -6,14 +6,24 @@ ' ' This program is distributed in the hope that it will be useful, ' but WITHOUT ANY WARRANTY -Imports SCrawler.Plugin +Imports SCrawler.Plugin.Attributes Namespace API.Instagram Friend Class EditorExchangeOptions + Friend Property GetTimeline As Boolean + Friend Property GetStories As Boolean + Friend Property GetTagged As Boolean - Friend Sub New(ByVal h As ISiteSettings) - With DirectCast(h, SiteSettings) + Friend Sub New(ByVal u As UserData) + With u + GetTimeline = .GetTimeline + GetStories = .GetStories + GetTagged = .GetTaggedData + End With + End Sub + Friend Sub New(ByVal s As SiteSettings) + With s GetTimeline = CBool(.GetTimeline.Value) GetStories = CBool(.GetStories.Value) GetTagged = CBool(.GetTagged.Value) diff --git a/SCrawler/API/Instagram/OptionsForm.Designer.vb b/SCrawler/API/Instagram/OptionsForm.Designer.vb deleted file mode 100644 index 6c59cb1..0000000 --- a/SCrawler/API/Instagram/OptionsForm.Designer.vb +++ /dev/null @@ -1,135 +0,0 @@ -' Copyright (C) 2023 Andy https://github.com/AAndyProgram -' This program is free software: you can redistribute it and/or modify -' it under the terms of the GNU General Public License as published by -' the Free Software Foundation, either version 3 of the License, or -' (at your option) any later version. -' -' This program is distributed in the hope that it will be useful, -' but WITHOUT ANY WARRANTY -Namespace API.Instagram - - Partial Friend Class OptionsForm : Inherits System.Windows.Forms.Form - - Protected Overrides Sub Dispose(ByVal disposing As Boolean) - Try - If disposing AndAlso components IsNot Nothing Then - components.Dispose() - End If - Finally - MyBase.Dispose(disposing) - End Try - End Sub - Private components As System.ComponentModel.IContainer - - Private Sub InitializeComponent() - Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer - Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel - Me.CH_GET_STORIES = New System.Windows.Forms.CheckBox() - Me.CH_GET_TAGGED = New System.Windows.Forms.CheckBox() - Me.CH_GET_TIMELINE = New System.Windows.Forms.CheckBox() - CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() - TP_MAIN = New System.Windows.Forms.TableLayoutPanel() - CONTAINER_MAIN.ContentPanel.SuspendLayout() - CONTAINER_MAIN.SuspendLayout() - TP_MAIN.SuspendLayout() - Me.SuspendLayout() - ' - 'CONTAINER_MAIN - ' - ' - 'CONTAINER_MAIN.ContentPanel - ' - CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) - CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(260, 79) - CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill - CONTAINER_MAIN.LeftToolStripPanelVisible = False - CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) - CONTAINER_MAIN.Name = "CONTAINER_MAIN" - CONTAINER_MAIN.RightToolStripPanelVisible = False - CONTAINER_MAIN.Size = New System.Drawing.Size(260, 104) - CONTAINER_MAIN.TabIndex = 0 - CONTAINER_MAIN.TopToolStripPanelVisible = False - ' - 'TP_MAIN - ' - TP_MAIN.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] - TP_MAIN.ColumnCount = 1 - TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_MAIN.Controls.Add(Me.CH_GET_STORIES, 0, 1) - TP_MAIN.Controls.Add(Me.CH_GET_TAGGED, 0, 2) - TP_MAIN.Controls.Add(Me.CH_GET_TIMELINE, 0, 0) - TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill - TP_MAIN.Location = New System.Drawing.Point(0, 0) - TP_MAIN.Name = "TP_MAIN" - TP_MAIN.RowCount = 4 - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!)) - TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) - TP_MAIN.Size = New System.Drawing.Size(260, 79) - TP_MAIN.TabIndex = 0 - ' - 'CH_GET_STORIES - ' - Me.CH_GET_STORIES.AutoSize = True - Me.CH_GET_STORIES.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_GET_STORIES.Location = New System.Drawing.Point(4, 30) - Me.CH_GET_STORIES.Name = "CH_GET_STORIES" - Me.CH_GET_STORIES.Size = New System.Drawing.Size(252, 19) - Me.CH_GET_STORIES.TabIndex = 1 - Me.CH_GET_STORIES.Text = "Get stories" - Me.CH_GET_STORIES.UseVisualStyleBackColor = True - ' - 'CH_GET_TAGGED - ' - Me.CH_GET_TAGGED.AutoSize = True - Me.CH_GET_TAGGED.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_GET_TAGGED.Location = New System.Drawing.Point(4, 56) - Me.CH_GET_TAGGED.Name = "CH_GET_TAGGED" - Me.CH_GET_TAGGED.Size = New System.Drawing.Size(252, 19) - Me.CH_GET_TAGGED.TabIndex = 2 - Me.CH_GET_TAGGED.Text = "Get tagged data" - Me.CH_GET_TAGGED.UseVisualStyleBackColor = True - ' - 'CH_GET_TIMELINE - ' - Me.CH_GET_TIMELINE.AutoSize = True - Me.CH_GET_TIMELINE.Dock = System.Windows.Forms.DockStyle.Fill - Me.CH_GET_TIMELINE.Location = New System.Drawing.Point(4, 4) - Me.CH_GET_TIMELINE.Name = "CH_GET_TIMELINE" - Me.CH_GET_TIMELINE.Size = New System.Drawing.Size(252, 19) - Me.CH_GET_TIMELINE.TabIndex = 0 - Me.CH_GET_TIMELINE.Text = "Get Timeline" - Me.CH_GET_TIMELINE.UseVisualStyleBackColor = True - ' - 'OptionsForm - ' - Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) - Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font - Me.ClientSize = New System.Drawing.Size(260, 104) - Me.Controls.Add(CONTAINER_MAIN) - Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle - Me.Icon = Global.SCrawler.My.Resources.SiteResources.InstagramIcon_32 - Me.KeyPreview = True - Me.MaximizeBox = False - Me.MaximumSize = New System.Drawing.Size(276, 143) - Me.MinimizeBox = False - Me.MinimumSize = New System.Drawing.Size(276, 143) - Me.Name = "OptionsForm" - Me.ShowInTaskbar = False - Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide - Me.Text = "Options" - CONTAINER_MAIN.ContentPanel.ResumeLayout(False) - CONTAINER_MAIN.ResumeLayout(False) - CONTAINER_MAIN.PerformLayout() - TP_MAIN.ResumeLayout(False) - TP_MAIN.PerformLayout() - Me.ResumeLayout(False) - - End Sub - - Private WithEvents CH_GET_STORIES As CheckBox - Private WithEvents CH_GET_TAGGED As CheckBox - Private WithEvents CH_GET_TIMELINE As CheckBox - End Class -End Namespace \ No newline at end of file diff --git a/SCrawler/API/Instagram/OptionsForm.vb b/SCrawler/API/Instagram/OptionsForm.vb deleted file mode 100644 index b220363..0000000 --- a/SCrawler/API/Instagram/OptionsForm.vb +++ /dev/null @@ -1,40 +0,0 @@ -' Copyright (C) 2023 Andy https://github.com/AAndyProgram -' This program is free software: you can redistribute it and/or modify -' it under the terms of the GNU General Public License as published by -' the Free Software Foundation, either version 3 of the License, or -' (at your option) any later version. -' -' This program is distributed in the hope that it will be useful, -' but WITHOUT ANY WARRANTY -Imports PersonalUtilities.Forms -Namespace API.Instagram - Friend Class OptionsForm - Private WithEvents MyDefs As DefaultFormOptions - Private ReadOnly Property MyExchangeOptions As EditorExchangeOptions - Friend Sub New(ByRef ExchangeOptions As EditorExchangeOptions) - InitializeComponent() - MyExchangeOptions = ExchangeOptions - MyDefs = New DefaultFormOptions(Me, Settings.Design) - End Sub - Private Sub OptionsForm_Load(sender As Object, e As EventArgs) Handles Me.Load - With MyDefs - .MyViewInitialize(True) - .AddOkCancelToolbar() - With MyExchangeOptions - CH_GET_TIMELINE.Checked = .GetTimeline - CH_GET_STORIES.Checked = .GetStories - CH_GET_TAGGED.Checked = .GetTagged - End With - .EndLoaderOperations() - End With - End Sub - Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick - With MyExchangeOptions - .GetTimeline = CH_GET_TIMELINE.Checked - .GetStories = CH_GET_STORIES.Checked - .GetTagged = CH_GET_TAGGED.Checked - End With - MyDefs.CloseForm() - End Sub - End Class -End Namespace \ No newline at end of file diff --git a/SCrawler/API/Instagram/SiteSettings.vb b/SCrawler/API/Instagram/SiteSettings.vb index 78046d0..78a8df7 100644 --- a/SCrawler/API/Instagram/SiteSettings.vb +++ b/SCrawler/API/Instagram/SiteSettings.vb @@ -34,49 +34,39 @@ Namespace API.Instagram End Property #End Region #Region "Providers" - Private Class TimersChecker : Implements IFieldsCheckerProvider - Private Property ErrorMessage As String Implements IFieldsCheckerProvider.ErrorMessage - Private Property Name As String Implements IFieldsCheckerProvider.Name - Private Property TypeError As Boolean Implements IFieldsCheckerProvider.TypeError + Private Class TimersChecker : Inherits FieldsCheckerProviderBase Private ReadOnly LVProvider As New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral} Private ReadOnly _LowestValue As Integer Friend Sub New(ByVal LowestValue As Integer) _LowestValue = LowestValue End Sub - Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, - Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert + Public Overrides Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, + Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object TypeError = False ErrorMessage = String.Empty If Not ACheck(Of Integer)(Value) Then TypeError = True ElseIf CInt(Value) < _LowestValue Then ErrorMessage = $"The value of [{Name}] field must be greater than or equal to {_LowestValue.NumToString(LVProvider)}" + HasError = True Else Return Value End If Return Nothing End Function - Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat - Throw New NotImplementedException("[GetFormat] is not available in the context of [TimersChecker]") - End Function End Class - Private Class TaggedNotifyLimitChecker : Implements IFieldsCheckerProvider - Private Property ErrorMessage As String Implements IFieldsCheckerProvider.ErrorMessage - Private Property Name As String Implements IFieldsCheckerProvider.Name - Private Property TypeError As Boolean Implements IFieldsCheckerProvider.TypeError - Private Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, - Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Implements ICustomProvider.Convert + Private Class TaggedNotifyLimitChecker : Inherits FieldsCheckerProviderBase + Public Overrides Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, + Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object Dim v% = AConvert(Of Integer)(Value, -10) If v > 0 Or v = -1 Then Return Value Else ErrorMessage = $"The value of [{Name}] field must be greater than 0 or equal to -1" + HasError = True Return Nothing End If End Function - Private Function GetFormat(ByVal FormatType As Type) As Object Implements IFormatProvider.GetFormat - Throw New NotImplementedException("[GetFormat] is not available in the context of [TaggedNotifyLimitChecker]") - End Function End Class #End Region #Region "Authorization properties" @@ -270,17 +260,11 @@ Namespace API.Instagram Return False End Function #End Region -#Region "Plugin functions" +#Region "GetInstance" Friend Overrides Function GetInstance(ByVal What As Download) As IPluginContentProvider - Select Case What - Case Download.Main : Return New UserData - Case Download.SavedPosts - Dim u As New UserData - DirectCast(u, UserDataBase).User = New UserInfo With {.Name = Site} - Return u - End Select - Return Nothing + Return New UserData End Function +#End Region #Region "Downloading" Friend Property SkipUntilNextSession As Boolean = False Friend Overrides Function ReadyToDownload(ByVal What As Download) As Boolean @@ -328,13 +312,11 @@ Namespace API.Instagram SkipUntilNextSession = False End Sub #End Region - Friend Overrides Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable - Return UserData.GetVideoInfo(URL, Responser) - End Function +#Region "UserOptions, GetUserPostUrl" Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) If Options Is Nothing OrElse Not TypeOf Options Is EditorExchangeOptions Then Options = New EditorExchangeOptions(Me) If OpenForm Then - Using f As New OptionsForm(Options) : f.ShowDialog() : End Using + Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using End If End Sub Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String @@ -342,7 +324,7 @@ Namespace API.Instagram Dim code$ = DirectCast(User, UserData).GetPostCodeById(Media.Post.ID) If Not code.IsEmptyString Then Return $"https://instagram.com/p/{code}/" Else Return String.Empty Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, "Can't open user's post", String.Empty) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "Can't open user's post", String.Empty) End Try End Function #End Region diff --git a/SCrawler/API/Instagram/UserData.vb b/SCrawler/API/Instagram/UserData.vb index 3eb9bf6..ad78fdd 100644 --- a/SCrawler/API/Instagram/UserData.vb +++ b/SCrawler/API/Instagram/UserData.vb @@ -8,13 +8,14 @@ ' but WITHOUT ANY WARRANTY Imports System.Net Imports System.Threading +Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.XML.Base Imports PersonalUtilities.Functions.Messaging Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Clients Imports PersonalUtilities.Tools.Web.Documents.JSON -Imports SCrawler.API.Base Imports UTypes = SCrawler.API.Base.UserMedia.Types Namespace API.Instagram Friend Class UserData : Inherits UserDataBase @@ -77,7 +78,7 @@ Namespace API.Instagram #End Region #Region "Exchange options" Friend Overrides Function ExchangeOptionsGet() As Object - Return New EditorExchangeOptions(HOST.Source) With {.GetTimeline = GetTimeline, .GetStories = GetStories, .GetTagged = GetTaggedData} + Return New EditorExchangeOptions(Me) End Function Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object) If Not Obj Is Nothing AndAlso TypeOf Obj Is EditorExchangeOptions Then @@ -139,7 +140,7 @@ Namespace API.Instagram x = New XmlFile With {.AllowSameNames = True} x.AddRange(PostsKVIDs) x.Name = "Posts" - x.Save(f, EDP.SendInLog) + x.Save(f, EDP.SendToLog) x.Dispose() End If End If @@ -182,7 +183,7 @@ Namespace API.Instagram End If Return String.Empty Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, $"{ToStringForLog()}: Cannot find post code by ID ({PostID})", String.Empty) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, $"{ToStringForLog()}: Cannot find post code by ID ({PostID})", String.Empty) End Try End Function Private Function GetPostIdBySection(ByVal ID As String, ByVal Section As Sections) As String @@ -621,7 +622,7 @@ Namespace API.Instagram PostsKVIDs.ListAddValue(PostIDKV, LNC) PostDate = .Value("taken_at") If Not IsSavedPosts Then - Select Case CheckDatesLimit(PostDate, DateProvider) + Select Case CheckDatesLimit(PostDate, UnixDate32Provider) Case DateResult.Skip : Continue For Case DateResult.Exit : If Not Pinned Then Return False End Select @@ -637,7 +638,7 @@ Namespace API.Instagram End Function #End Region #Region "Code ID converters" - Private Shared Function CodeToID(ByVal Code As String) As String + Private Function CodeToID(ByVal Code As String) As String Const CodeSymbols$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_" Try If Not Code.IsEmptyString Then @@ -652,13 +653,13 @@ Namespace API.Instagram Return String.Empty End If Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog, ex, $"[API.Instagram.UserData.CodeToID({Code})", String.Empty) + Return ErrorsDescriber.Execute(EDP.SendToLog, ex, $"[API.Instagram.UserData.CodeToID({Code})", String.Empty) End Try End Function #End Region #Region "Obtain Media" Private Sub ObtainMedia(ByVal n As EContainer, ByVal PostID As String, Optional ByVal SpecialFolder As String = Nothing, - Optional ByVal DateObj As String = Nothing) + Optional ByVal DateObj As String = Nothing) Try Dim img As Predicate(Of EContainer) = Function(_img) Not _img.Name.IsEmptyString AndAlso _img.Name.StartsWith("image_versions") AndAlso _img.Count > 0 Dim vid As Predicate(Of EContainer) = Function(_vid) Not _vid.Name.IsEmptyString AndAlso _vid.Name.StartsWith("video_versions") AndAlso _vid.Count > 0 @@ -737,7 +738,7 @@ Namespace API.Instagram l.Clear() End If Catch ex As Exception - ErrorsDescriber.Execute(EDP.SendInLog, ex, "API.Instagram.ObtainMedia2") + ErrorsDescriber.Execute(EDP.SendToLog, ex, "API.Instagram.ObtainMedia2") End Try End Sub #End Region @@ -898,38 +899,25 @@ Namespace API.Instagram End Sub #End Region #Region "Create media" - Private Shared Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, - Optional ByVal SpecialFolder As String = Nothing) As UserMedia + Private Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String, + Optional ByVal SpecialFolder As String = Nothing) As UserMedia _URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern)) Dim m As New UserMedia(_URL, t) With {.Post = New UserPost With {.ID = PostID}} If Not m.URL.IsEmptyString Then m.File = CStr(RegexReplace(m.URL, FilesPattern)) - If Not PostDate.IsEmptyString Then m.Post.Date = AConvert(Of Date)(PostDate, DateProvider, Nothing) Else m.Post.Date = Nothing + If Not PostDate.IsEmptyString Then m.Post.Date = AConvert(Of Date)(PostDate, UnixDate32Provider, Nothing) Else m.Post.Date = Nothing m.SpecialFolder = SpecialFolder Return m End Function #End Region #Region "Standalone downloader" - Friend Shared Function GetVideoInfo(ByVal URL As String, ByVal r As Responser) As IEnumerable(Of UserMedia) - Try - If Not URL.IsEmptyString AndAlso URL.Contains("instagram.com") Then - Dim PID$ = RegexReplace(URL, RParams.DMS(".*?instagram.com/p/([_\w\d]+)", 1)) - If Not PID.IsEmptyString AndAlso Not ACheck(Of Long)(PID) Then PID = CodeToID(PID) - If Not PID.IsEmptyString Then - Using t As New UserData - t.SetEnvironment(Settings(InstagramSiteKey), Nothing, False, False) - t.Responser = New Responser - t.Responser.Copy(r) - t.PostsToReparse.Add(New PostKV With {.ID = PID}) - t.DownloadPosts(Nothing) - Return ListAddList(Nothing, t._TempMediaList) - End Using - End If - End If - Return Nothing - Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.ShowMainMsg + EDP.SendInLog, ex, $"Instagram standalone downloader: fetch media error ({URL})") - End Try - End Function + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim PID$ = RegexReplace(Data.URL, RParams.DMS(".*?instagram.com/p/([_\w\d]+)", 1)) + If Not PID.IsEmptyString AndAlso Not ACheck(Of Long)(PID) Then PID = CodeToID(PID) + If Not PID.IsEmptyString Then + PostsToReparse.Add(New PostKV With {.ID = PID}) + DownloadPosts(Token) + End If + End Sub #End Region #Region "IDisposable Support" Protected Overrides Sub Dispose(ByVal disposing As Boolean) diff --git a/SCrawler/API/LPSG/Declarations.vb b/SCrawler/API/LPSG/Declarations.vb index d7bc31f..1c5a36c 100644 --- a/SCrawler/API/LPSG/Declarations.vb +++ b/SCrawler/API/LPSG/Declarations.vb @@ -10,28 +10,41 @@ Imports SCrawler.API.Base Imports PersonalUtilities.Functions.RegularExpressions Namespace API.LPSG Friend Module Declarations - Friend ReadOnly Property PhotoRegEx As RParams = RParams.DM("(https://www.lpsg.com/attachments)(.+?)(?="")", 0, RegexReturn.List) + Friend ReadOnly Property PhotoRegEx As RParams = + RParams.DM("(?<=(https://www.lpsg.com|)/attachments/)([^/]+?[-\.]{1}(jpg|jpeg|gif|png|webm)\.?\d*)(?=/?"")", 0, RegexReturn.List, + CType(Function(Input$) If(Input.IsEmptyString, String.Empty, $"https://www.lpsg.com/attachments/{Input.StringTrimStart("/")}"), + Func(Of String, String))) Friend ReadOnly Property PhotoRegExExt As New RParams("img.data.src=""(/proxy[^""]+?)""", Nothing, 1, RegexReturn.List) With { - .Converter = Function(Input) $"https://www.lpsg.com/{SymbolsConverter.HTML.Decode(Input)}"} + .Converter = Function(Input) $"https://www.lpsg.com/{SymbolsConverter.HTML.Decode(Input)}"} Friend ReadOnly Property NextPageRegex As RParams = RParams.DMS(" 0 Then Credentials.ListAddList(x, LAP.IgnoreICopier) + End Using + End If + End Sub + Friend Overrides Function Apply() As Boolean + If Changed Then + Credentials.Clear() + If CredentialsTemp.Count > 0 Then Credentials.AddRange(CredentialsTemp) + CredentialsTemp.Clear() + End If + Return MyBase.Apply() + End Function + Friend Overrides Sub Save() + If Credentials.Count > 0 Then + Using x As New XmlFile With {.AllowSameNames = True} + x.AddRange(Credentials) + x.Name = "DomainsCredentials" + x.Save(CredentialsFile) + End Using + Else + CredentialsFile.Delete(,, EDP.None) + End If + MyBase.Save() + End Sub + Friend Overrides Sub Reset() + CredentialsTemp.Clear() + MyBase.Reset() + End Sub + Friend Overrides Sub OpenSettingsForm() + Using f As New SettingsForm(Instance) + f.ShowDialog() + If f.DialogResult = DialogResult.OK Then + Changed = True + CredentialsTemp.Clear() + If f.MyCredentials.Count > 0 Then CredentialsTemp.AddRange(f.MyCredentials) + DomainsTemp.Clear() + If f.MyDomains.Count > 0 Then DomainsTemp.ListAddList(f.MyDomains, LAP.NotContainsOnly) + End If + End Using + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Mastodon/SettingsForm.Designer.vb b/SCrawler/API/Mastodon/SettingsForm.Designer.vb new file mode 100644 index 0000000..d1d6ad2 --- /dev/null +++ b/SCrawler/API/Mastodon/SettingsForm.Designer.vb @@ -0,0 +1,165 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Namespace API.Mastodon + + Partial Friend Class SettingsForm : Inherits System.Windows.Forms.Form + + Protected Overrides Sub Dispose(ByVal disposing As Boolean) + Try + If disposing AndAlso components IsNot Nothing Then + components.Dispose() + End If + Finally + MyBase.Dispose(disposing) + End Try + End Sub + Private components As System.ComponentModel.IContainer + + Private Sub InitializeComponent() + Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer + Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel + Dim ActionButton1 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(SettingsForm)) + Dim ActionButton2 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton3 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton4 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton5 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Dim ActionButton6 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton() + Me.CMB_DOMAINS = New PersonalUtilities.Forms.Controls.ComboBoxExtended() + Me.TXT_AUTH = New PersonalUtilities.Forms.Controls.TextBoxExtended() + Me.TXT_TOKEN = New PersonalUtilities.Forms.Controls.TextBoxExtended() + CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer() + TP_MAIN = New System.Windows.Forms.TableLayoutPanel() + CONTAINER_MAIN.ContentPanel.SuspendLayout() + CONTAINER_MAIN.SuspendLayout() + TP_MAIN.SuspendLayout() + CType(Me.CMB_DOMAINS, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_AUTH, System.ComponentModel.ISupportInitialize).BeginInit() + CType(Me.TXT_TOKEN, System.ComponentModel.ISupportInitialize).BeginInit() + Me.SuspendLayout() + ' + 'CONTAINER_MAIN + ' + ' + 'CONTAINER_MAIN.ContentPanel + ' + CONTAINER_MAIN.ContentPanel.Controls.Add(TP_MAIN) + CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(384, 361) + CONTAINER_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + CONTAINER_MAIN.LeftToolStripPanelVisible = False + CONTAINER_MAIN.Location = New System.Drawing.Point(0, 0) + CONTAINER_MAIN.Name = "CONTAINER_MAIN" + CONTAINER_MAIN.RightToolStripPanelVisible = False + CONTAINER_MAIN.Size = New System.Drawing.Size(384, 361) + CONTAINER_MAIN.TabIndex = 0 + CONTAINER_MAIN.TopToolStripPanelVisible = False + ' + 'TP_MAIN + ' + TP_MAIN.CellBorderStyle = System.Windows.Forms.TableLayoutPanelCellBorderStyle.[Single] + TP_MAIN.ColumnCount = 1 + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!)) + TP_MAIN.Controls.Add(Me.CMB_DOMAINS, 0, 0) + TP_MAIN.Controls.Add(Me.TXT_AUTH, 0, 1) + TP_MAIN.Controls.Add(Me.TXT_TOKEN, 0, 2) + TP_MAIN.Dock = System.Windows.Forms.DockStyle.Fill + TP_MAIN.Location = New System.Drawing.Point(0, 0) + TP_MAIN.Name = "TP_MAIN" + TP_MAIN.RowCount = 3 + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!)) + TP_MAIN.Size = New System.Drawing.Size(384, 361) + TP_MAIN.TabIndex = 0 + ' + 'CMB_DOMAINS + ' + ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image) + ActionButton1.Name = "Add" + ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Add + ActionButton2.BackgroundImage = CType(resources.GetObject("ActionButton2.BackgroundImage"), System.Drawing.Image) + ActionButton2.Name = "Delete" + ActionButton2.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Delete + ActionButton3.BackgroundImage = CType(resources.GetObject("ActionButton3.BackgroundImage"), System.Drawing.Image) + ActionButton3.Name = "Clear" + ActionButton3.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + ActionButton4.BackgroundImage = CType(resources.GetObject("ActionButton4.BackgroundImage"), System.Drawing.Image) + ActionButton4.Name = "ArrowDown" + ActionButton4.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.ArrowDown + ActionButton4.Visible = False + Me.CMB_DOMAINS.Buttons.Add(ActionButton1) + Me.CMB_DOMAINS.Buttons.Add(ActionButton2) + Me.CMB_DOMAINS.Buttons.Add(ActionButton3) + Me.CMB_DOMAINS.Buttons.Add(ActionButton4) + Me.CMB_DOMAINS.Dock = System.Windows.Forms.DockStyle.Fill + Me.CMB_DOMAINS.ListDropDownStyle = PersonalUtilities.Forms.Controls.ComboBoxExtended.ListMode.Simple + Me.CMB_DOMAINS.Location = New System.Drawing.Point(4, 4) + Me.CMB_DOMAINS.Name = "CMB_DOMAINS" + Me.CMB_DOMAINS.Size = New System.Drawing.Size(378, 296) + Me.CMB_DOMAINS.TabIndex = 0 + ' + 'TXT_AUTH + ' + ActionButton5.BackgroundImage = CType(resources.GetObject("ActionButton5.BackgroundImage"), System.Drawing.Image) + ActionButton5.Name = "Clear" + ActionButton5.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + Me.TXT_AUTH.Buttons.Add(ActionButton5) + Me.TXT_AUTH.CaptionText = "Auth" + Me.TXT_AUTH.CaptionToolTipEnabled = True + Me.TXT_AUTH.CaptionToolTipText = "Bearer token" + Me.TXT_AUTH.CaptionWidth = 50.0R + Me.TXT_AUTH.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_AUTH.Location = New System.Drawing.Point(4, 306) + Me.TXT_AUTH.Name = "TXT_AUTH" + Me.TXT_AUTH.Size = New System.Drawing.Size(376, 22) + Me.TXT_AUTH.TabIndex = 1 + ' + 'TXT_TOKEN + ' + ActionButton6.BackgroundImage = CType(resources.GetObject("ActionButton6.BackgroundImage"), System.Drawing.Image) + ActionButton6.Name = "Clear" + ActionButton6.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear + Me.TXT_TOKEN.Buttons.Add(ActionButton6) + Me.TXT_TOKEN.CaptionText = "Token" + Me.TXT_TOKEN.CaptionToolTipEnabled = True + Me.TXT_TOKEN.CaptionToolTipText = "csrf token" + Me.TXT_TOKEN.CaptionWidth = 50.0R + Me.TXT_TOKEN.Dock = System.Windows.Forms.DockStyle.Fill + Me.TXT_TOKEN.Location = New System.Drawing.Point(4, 335) + Me.TXT_TOKEN.Name = "TXT_TOKEN" + Me.TXT_TOKEN.Size = New System.Drawing.Size(376, 22) + Me.TXT_TOKEN.TabIndex = 2 + ' + 'SettingsForm + ' + Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!) + Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font + Me.ClientSize = New System.Drawing.Size(384, 361) + Me.Controls.Add(CONTAINER_MAIN) + Me.Icon = Global.SCrawler.My.Resources.SiteResources.MastodonIcon_48 + Me.MinimumSize = New System.Drawing.Size(400, 400) + Me.Name = "SettingsForm" + Me.ShowInTaskbar = False + Me.Text = "Mastodon domains" + CONTAINER_MAIN.ContentPanel.ResumeLayout(False) + CONTAINER_MAIN.ResumeLayout(False) + CONTAINER_MAIN.PerformLayout() + TP_MAIN.ResumeLayout(False) + CType(Me.CMB_DOMAINS, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_AUTH, System.ComponentModel.ISupportInitialize).EndInit() + CType(Me.TXT_TOKEN, System.ComponentModel.ISupportInitialize).EndInit() + Me.ResumeLayout(False) + + End Sub + Private WithEvents CMB_DOMAINS As PersonalUtilities.Forms.Controls.ComboBoxExtended + Private WithEvents TXT_AUTH As PersonalUtilities.Forms.Controls.TextBoxExtended + Private WithEvents TXT_TOKEN As PersonalUtilities.Forms.Controls.TextBoxExtended + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Mastodon/SettingsForm.resx b/SCrawler/API/Mastodon/SettingsForm.resx new file mode 100644 index 0000000..da953f2 --- /dev/null +++ b/SCrawler/API/Mastodon/SettingsForm.resx @@ -0,0 +1,292 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + False + + + False + + + + + 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 + AAAASUVORK5CYII= + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO + xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go + tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX + AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC + + + \ No newline at end of file diff --git a/SCrawler/API/Mastodon/SettingsForm.vb b/SCrawler/API/Mastodon/SettingsForm.vb new file mode 100644 index 0000000..dbd329c --- /dev/null +++ b/SCrawler/API/Mastodon/SettingsForm.vb @@ -0,0 +1,154 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Forms.Controls.Base +Namespace API.Mastodon + Friend Class SettingsForm + Private WithEvents MyDefs As DefaultFormOptions + Friend ReadOnly Property MyCredentials As List(Of Credentials) + Friend ReadOnly Property MyDomains As List(Of String) + Friend Sub New(ByVal s As SiteSettings) + InitializeComponent() + MyCredentials = New List(Of Credentials) + If s.Domains.Credentials.Count > 0 Then MyCredentials.AddRange(s.Domains.Credentials) + MyDomains = New List(Of String) + MyDomains.ListAddList(s.Domains) + MyDefs = New DefaultFormOptions(Me, Settings.Design) + End Sub + Private Sub MyForm_Load(sender As Object, e As EventArgs) Handles Me.Load + With MyDefs + .MyView = New FormView(Me, Settings.Design, "MastodonSettingsForm") + .MyView.Import() + .MyView.SetFormSize() + .AddOkCancelToolbar() + RefillList() + .EndLoaderOperations() + End With + End Sub + Private Sub SettingsForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed + MyCredentials.Clear() + MyDomains.Clear() + End Sub + Private Sub RefillList() + CMB_DOMAINS.Items.Clear() + If MyDomains.Count > 0 Then + MyDomains.Sort() + CMB_DOMAINS.BeginUpdate() + CMB_DOMAINS.Items.AddRange(MyDomains.Select(Function(d) New ListItem(d))) + CMB_DOMAINS.EndUpdate(True) + End If + End Sub + Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick + ApplyCredentials() + If MyCredentials.Count > 0 Then MyCredentials.RemoveAll(Function(c) c.Domain.IsEmptyString Or c.Bearer.IsEmptyString Or c.Csrf.IsEmptyString) + If MyDomains.Count > 0 Then + If MyCredentials.Count > 0 Then + MyCredentials.RemoveAll(Function(c) Not MyDomains.Contains(c.Domain)) + Else + MyCredentials.Clear() + End If + End If + MyDefs.CloseForm() + End Sub + Private Sub CMB_DOMAINS_ActionOnButtonClick(ByVal Sender As ActionButton, ByVal e As ActionButtonEventArgs) Handles CMB_DOMAINS.ActionOnButtonClick + Try + Dim d$ + Dim i% = -1 + Select Case e.DefaultButton + Case ActionButton.DefaultButtons.Add + d = InputBoxE("Enter a new domain using the pattern [mastodon.social]:", "New domain") + If Not d.IsEmptyString Then + If MyDomains.Count > 0 Then i = MyDomains.IndexOf(d) + If i >= 0 Then + MsgBoxE({$"Domain '{d}' already exists", "Add domain"}, vbExclamation) + If i <= CMB_DOMAINS.Count - 1 Then CMB_DOMAINS.SelectedIndex = i + Else + ApplyCredentials() + ClearCredentials() + MyDomains.Add(d) + _Suspended = True + RefillList() + _Suspended = False + i = MyDomains.IndexOf(d) + If i.ValueBetween(0, CMB_DOMAINS.Count - 1) Then + CMB_DOMAINS.SelectedIndex = i + Else + _LatestSelected = -1 + _CurrentCredentialsIndex = -1 + _CurrentDomain = String.Empty + End If + End If + End If + Case ActionButton.DefaultButtons.Delete + If _LatestSelected >= 0 Then + d = CMB_DOMAINS.Items(_LatestSelected).Value(0) + If Not d.IsEmptyString AndAlso MsgBoxE({$"Are you sure you want to delete the [{d}] domain?", + "Removing domains"}, vbYesNo) = vbYes Then + i = MyDomains.IndexOf(d) + Dim l% = _LatestSelected + If i >= 0 Then + ClearCredentials() + MyDomains.RemoveAt(i) + _Suspended = True + RefillList() + _Suspended = False + If (l - 1).ValueBetween(0, CMB_DOMAINS.Count - 1) Then + CMB_DOMAINS.SelectedIndex = l - 1 + Else + _LatestSelected = -1 + _CurrentCredentialsIndex = -1 + _CurrentDomain = String.Empty + End If + End If + End If + End If + End Select + Catch ex As Exception + ErrorsDescriber.Execute(EDP.LogMessageValue, ex, "API.Mastodon.SettingsForm.ActionButtonClick") + End Try + End Sub + Private _LatestSelected As Integer = -1 + Private _CurrentCredentialsIndex As Integer = -1 + Private _CurrentDomain As String = String.Empty + Private _Suspended As Boolean = False + Private Sub CMB_DOMAINS_ActionSelectedItemChanged(ByVal Sender As Object, ByVal e As EventArgs, ByVal Item As ListViewItem) Handles CMB_DOMAINS.ActionSelectedItemChanged + If Not MyDefs.Initializing And Not _Suspended Then + Dim DropCredentials As Boolean = True + If Not Item Is Nothing Then + ApplyCredentials() + _LatestSelected = Item.Index + _CurrentDomain = Item.Text + If MyCredentials.Count > 0 And Not _CurrentDomain.IsEmptyString Then + _CurrentCredentialsIndex = MyCredentials.IndexOf(_CurrentDomain) + If _CurrentCredentialsIndex >= 0 Then + With MyCredentials(_CurrentCredentialsIndex) : TXT_AUTH.Text = .Bearer : TXT_TOKEN.Text = .Csrf : End With + DropCredentials = False + End If + Else + _CurrentCredentialsIndex = -1 + End If + End If + If DropCredentials Then ClearCredentials() + End If + End Sub + Private Sub ClearCredentials() + TXT_AUTH.Clear() + TXT_TOKEN.Clear() + End Sub + Private Sub ApplyCredentials() + Try + If _LatestSelected >= 0 And Not _CurrentDomain.IsEmptyString Then + Dim c As New Credentials With {.Domain = _CurrentDomain, .Bearer = TXT_AUTH.Text, .Csrf = TXT_TOKEN.Text} + If _CurrentCredentialsIndex.ValueBetween(0, MyCredentials.Count - 1) Then MyCredentials(_CurrentCredentialsIndex) = c Else MyCredentials.Add(c) + End If + Catch ex As Exception + End Try + End Sub + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Mastodon/SiteSettings.vb b/SCrawler/API/Mastodon/SiteSettings.vb new file mode 100644 index 0000000..4cd056e --- /dev/null +++ b/SCrawler/API/Mastodon/SiteSettings.vb @@ -0,0 +1,214 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports SCrawler.API.Base +Imports SCrawler.Plugin +Imports SCrawler.Plugin.Attributes +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.RegularExpressions +Imports PersonalUtilities.Tools.Web.Clients +Imports PersonalUtilities.Tools.Web.Documents.JSON +Imports TS = SCrawler.API.Twitter.SiteSettings +Namespace API.Mastodon + + Friend Class SiteSettings : Inherits SiteSettingsBase +#Region "Declarations" + Friend Overrides ReadOnly Property Icon As Icon + Get + Return My.Resources.SiteResources.MastodonIcon_48 + End Get + End Property + Friend Overrides ReadOnly Property Image As Image + Get + Return My.Resources.SiteResources.MastodonPic_48 + End Get + End Property +#Region "Domains" + Private ReadOnly Property SiteDomains As PropertyValue + Friend ReadOnly Property Domains As MastodonDomains + Private ReadOnly Property DomainsLastUpdateDate As PropertyValue +#End Region +#Region "Auth" + + Friend ReadOnly Property MyDomain As PropertyValue + + Friend ReadOnly Property Auth As PropertyValue + + Friend ReadOnly Property Token As PropertyValue + Private Sub ChangeResponserFields(ByVal PropName As String, ByVal Value As Object) + If Not PropName.IsEmptyString Then + Dim f$ = String.Empty + Select Case PropName + Case NameOf(Auth) : f = TS.Header_Authorization + Case NameOf(Token) : f = TS.Header_Token + End Select + If Not f.IsEmptyString Then + Responser.Headers.Remove(f) + If Not CStr(Value).IsEmptyString Then Responser.Headers.Add(f, CStr(Value)) + Responser.SaveSettings() + End If + End If + End Sub +#End Region +#Region "Other properties" + + Friend ReadOnly Property GifsDownload As PropertyValue + + Friend ReadOnly Property GifsSpecialFolder As PropertyValue + + Friend ReadOnly Property GifsPrefix As PropertyValue + + Private ReadOnly Property GifStringChecker As IFormatProvider + + Friend ReadOnly Property UseMD5Comparison As PropertyValue + + Friend ReadOnly Property UserRelatedToMyDomain As PropertyValue +#End Region +#End Region +#Region "Initializer" + Friend Sub New() + MyBase.New("Mastodon", "mastodon.social") + + Domains = New MastodonDomains(Me, "mastodon.social") + SiteDomains = New PropertyValue(Domains.DomainsDefault, GetType(String)) + Domains.DestinationProp = SiteDomains + DomainsLastUpdateDate = New PropertyValue(Now.AddYears(-1)) + + Auth = New PropertyValue(Responser.Headers.Value(TS.Header_Authorization), GetType(String), Sub(v) ChangeResponserFields(NameOf(Auth), v)) + Token = New PropertyValue(Responser.Headers.Value(TS.Header_Token), GetType(String), Sub(v) ChangeResponserFields(NameOf(Token), v)) + + GifsDownload = New PropertyValue(True) + GifsSpecialFolder = New PropertyValue(String.Empty, GetType(String)) + GifsPrefix = New PropertyValue("GIF_") + GifStringChecker = New TS.GifStringProvider + UseMD5Comparison = New PropertyValue(False) + MyDomain = New PropertyValue(String.Empty, GetType(String)) + UserRelatedToMyDomain = New PropertyValue(False) + + UserRegex = RParams.DMS("", 0, RegexReturn.ListByMatch, EDP.ReturnValue) + End Sub + Friend Overrides Sub EndInit() + Domains.PopulateInitialDomains(SiteDomains.Value) + If CDate(DomainsLastUpdateDate.Value).AddDays(7) < Now Then UpdateServersList() + MyBase.EndInit() + End Sub +#End Region +#Region "GetInstance" + Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider + Return New UserData + End Function +#End Region +#Region "Domains Support" + Protected Overrides Sub DomainsApply() + Domains.Apply() + MyBase.DomainsApply() + End Sub + Protected Overrides Sub DomainsReset() + Domains.Reset() + MyBase.DomainsReset() + End Sub + Friend Overrides Sub OpenSettingsForm() + Domains.OpenSettingsForm() + End Sub +#End Region +#Region "Update" + Friend Overrides Sub Update() + If _SiteEditorFormOpened Then + Dim tf$ = GifsSpecialFolder.Value + If Not tf.IsEmptyString Then tf = tf.StringTrim("\") : GifsSpecialFolder.Value = tf + End If + MyBase.Update() + End Sub +#End Region +#Region "UserOptions" + Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) + If Options Is Nothing OrElse (Not TypeOf Options Is Twitter.EditorExchangeOptions OrElse + Not DirectCast(Options, Twitter.EditorExchangeOptions).SiteKey = MastodonSiteKey) Then _ + Options = New Twitter.EditorExchangeOptions(Me) With {.SiteKey = MastodonSiteKey} + If OpenForm Then + Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using + End If + End Sub +#End Region +#Region "Download" + Friend Overrides Function BaseAuthExists() As Boolean + Return ACheck(Token.Value) And ACheck(Auth.Value) And ACheck(MyDomain.Value) + End Function + Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean + If What = ISiteSettings.Download.SavedPosts Or What = ISiteSettings.Download.SingleObject Then + If Not ACheck(MyDomain.Value) Then MyMainLOG = "Mastodon account domain not set" : Return False + Else + If CDate(DomainsLastUpdateDate.Value).AddDays(7) < Now Then UpdateServersList() + End If + Return MyBase.Available(What, Silent) + End Function +#End Region +#Region "GetUserUrl, GetUserPostUrl" + Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String + If Not ACheck(MyDomain.Value) Then Return String.Empty + With DirectCast(User, UserData) + If UserRelatedToMyDomain.Value Then + If MyDomain.Value = .UserDomain Then + Return $"https://{ .UserDomain}/@{ .TrueName}" + Else + Return $"https://{MyDomain.Value}/@{ .TrueName}@{ .UserDomain}" + End If + Else + Return $"https://{ .UserDomain}/@{ .TrueName}" + End If + End With + End Function + Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String + If Not ACheck(MyDomain.Value) Then Return String.Empty + Return $"{GetUserUrl(User)}/{Media.Post.ID}" + End Function +#End Region +#Region "IsMyUser, IsMyImageVideo" + Private Const UserRegexDefault As String = "https?://{0}/@([^/@]+)@?([^/]*)" + Friend Overrides Function IsMyUser(ByVal UserURL As String) As ExchangeOptions + If Domains.Count > 0 Then + Dim l As List(Of String) + For Each domain$ In Domains + UserRegex.Pattern = String.Format(UserRegexDefault, domain) + l = RegexReplace(UserURL, UserRegex) + If l.ListExists(2) Then Return New ExchangeOptions(Site, $"{l(2).IfNullOrEmpty(domain)}@{l(1)}") + Next + End If + Return Nothing + End Function + Friend Overrides Function IsMyImageVideo(ByVal URL As String) As ExchangeOptions + If Not URL.IsEmptyString And Domains.Count > 0 Then + If Domains.Domains.Exists(Function(d) URL.Contains(d)) Then Return New ExchangeOptions(Site, URL) With {.Exists = True} + End If + Return Nothing + End Function +#End Region +#Region "UpdateServersList" + Private Sub UpdateServersList() + Try + Dim r$ = GetWebString("https://api.joinmastodon.org/servers?language=&category=®ion=&ownership=®istrations=",, EDP.ThrowException) + If Not r.IsEmptyString Then + Dim j As EContainer = JsonDocument.Parse(r, EDP.ReturnValue) + If If(j?.Count, 0) > 0 Then + Domains.Domains.ListAddList(j.Select(Function(e) e.Value("domain")), LAP.NotContainsOnly, EDP.ReturnValue) + Domains.Domains.Sort() + Domains.Save() + j.Dispose() + End If + End If + DomainsLastUpdateDate.Value = Now + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, "[API.Mastodon.SiteSettings.UpdateServersList]") + End Try + End Sub +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Mastodon/UserData.vb b/SCrawler/API/Mastodon/UserData.vb new file mode 100644 index 0000000..3c7a4ad --- /dev/null +++ b/SCrawler/API/Mastodon/UserData.vb @@ -0,0 +1,287 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Threading +Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Functions.RegularExpressions +Imports PersonalUtilities.Tools.Web.Documents.JSON +Imports UTypes = SCrawler.API.Base.UserMedia.Types +Namespace API.Mastodon + Friend Class UserData : Inherits Twitter.UserData +#Region "XML names" + Private Const Name_UserDomain As String = "UserDomain" + Private Const Name_TrueName As String = "TrueName" +#End Region +#Region "Declarations" + Private _UserDomain As String = String.Empty + Friend Property UserDomain As String + Get + Return _UserDomain.IfNullOrEmpty(MySettings.MyDomain.Value) + End Get + Set(ByVal d As String) + _UserDomain = d + End Set + End Property + Friend Property TrueName As String = String.Empty + Private ReadOnly Property MySettings As SiteSettings + Get + Return HOST.Source + End Get + End Property + Private MyCredentials As Credentials + Private Sub ResetCredentials() + MyCredentials = Nothing + With MySettings + Dim setDef As Boolean = True + If Not IsSavedPosts Then + If ACheck(.MyDomain.Value) AndAlso UserDomain = .MyDomain.Value Then + setDef = True + ElseIf .Domains.Credentials.Count > 0 Then + Dim i% = .Domains.Credentials.IndexOf(UserDomain) + If i >= 0 Then + MyCredentials = .Domains.Credentials(i) + setDef = Not MyCredentials.Exists + End If + End If + End If + If setDef Then MyCredentials = New Credentials With {.Domain = UserDomain, .Bearer = MySettings.Auth.Value, .Csrf = MySettings.Token.Value} + End With + With MyCredentials + Responser.Headers.Add(Twitter.SiteSettings.Header_Authorization, .Bearer) + Responser.Headers.Add(Twitter.SiteSettings.Header_Token, .Csrf) + End With + End Sub +#End Region +#Region "LoadUserInformation" + Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean) + MyBase.LoadUserInformation_OptionalFields(Container, Loading) + Dim obtainNames As Action = Sub() + If _UserDomain.IsEmptyString And Not Name.IsEmptyString Then + Dim l$() = Name.Split("@") + If l.ListExists(2) Then + _UserDomain = l(0) + TrueName = l(1) + Else + _UserDomain = MySettings.MyDomain.Value + TrueName = Name + End If + If FriendlyName.IsEmptyString Then FriendlyName = TrueName + End If + End Sub + If Loading Then + _UserDomain = Container.Value(Name_UserDomain) + TrueName = Container.Value(Name_TrueName) + obtainNames.Invoke + Else + obtainNames.Invoke + Container.Add(Name_UserDomain, _UserDomain) + Container.Add(Name_TrueName, TrueName) + Container.Value(Name_FriendlyName) = FriendlyName + End If + End Sub +#End Region +#Region "Download functions" + Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) + ResetCredentials() + DownloadData(String.Empty, Token) + End Sub + Private Overloads Sub DownloadData(ByVal POST As String, ByVal Token As CancellationToken) + Dim URL$ = String.Empty + Try + Dim PostID$ = String.Empty + Dim PostDate$ + Dim s As EContainer, ss As EContainer + Dim NewPostDetected As Boolean = False + Dim ExistsDetected As Boolean = False + + If IsSavedPosts Then + URL = $"https://{MySettings.MyDomain.Value}/api/v1/bookmarks" + If Not POST.IsEmptyString Then URL &= $"?max_id={POST}" + Else + If POST.IsEmptyString And ID.IsEmptyString Then + ObtainUserID() + If ID.IsEmptyString Then Throw New ArgumentNullException("ID", "Unable to get user ID") With {.HelpLink = 1} + End If + URL = $"https://{MyCredentials.Domain}/api/v1/accounts/{ID}/statuses?" + If ParseUserMediaOnly Then URL &= "only_media=true&" + URL &= "limit=40" + If Not POST.IsEmptyString Then URL &= $"&max_id={POST}" + End If + + ThrowAny(Token) + Dim r$ = Responser.GetResponse(URL) + If Not r.IsEmptyString Then + Using j As EContainer = JsonDocument.Parse(r) + If If(j?.Count, 0) > 0 Then + For Each jj As EContainer In j + With jj + If Not IsSavedPosts And POST.IsEmptyString And Not .Item("account") Is Nothing Then + With .Item("account") + If .Value("id") = ID Then + UserSiteNameUpdate(.Value("display_name")) + UserDescriptionUpdate(.Value("note")) + Dim __getImage As Action(Of String) = Sub(ByVal img As String) + If Not img.IsEmptyString Then + Dim __imgFile As SFile = img + If Not __imgFile.Name.IsEmptyString Then + If __imgFile.Extension.IsEmptyString Then __imgFile.Extension = "jpg" + __imgFile.Path = MyFile.CutPath.Path + If Not __imgFile.Exists Then GetWebFile(img, __imgFile, EDP.None) + End If + End If + End Sub + __getImage.Invoke(.Value("header").IfNullOrEmpty(.Value("header_static"))) + __getImage.Invoke(.Value("avatar").IfNullOrEmpty(.Value("avatar_static"))) + End If + End With + End If + + PostID = .Value("id") + PostDate = .Value("created_at") + + If Not IsSavedPosts And Not PostDate.IsEmptyString Then + Select Case CheckDatesLimit(PostDate, DateProvider) + Case DateResult.Skip : Continue For + Case DateResult.Exit : Exit Sub + End Select + End If + + If Not _TempPostsList.Contains(PostID) Then + NewPostDetected = True + _TempPostsList.Add(PostID) + Else + ExistsDetected = True + Continue For + End If + + If IsSavedPosts OrElse (Not ParseUserMediaOnly OrElse + (If(.Item("reblog")?.Count, 0) = 0 OrElse .Value({"reblog", "account"}, "id") = ID)) Then + If If(.Item("media_attachments")?.Count, 0) > 0 Then + s = .Item("media_attachments") + Else + s = .Item({"reblog", "account"}, "media_attachments") + End If + If s.ListExists Then + For Each ss In s : ObtainMedia(ss, PostID, PostDate) : Next + End If + End If + End With + Next + End If + End Using + End If + + If POST.IsEmptyString And ExistsDetected Then Exit Sub + If Not PostID.IsEmptyString And NewPostDetected Then DownloadData(PostID, Token) + Catch ex As Exception + ProcessException(ex, Token, $"data downloading error{IIf(IsSavedPosts, " (Saved Posts)", String.Empty)} [{URL}]") + End Try + End Sub + Private Sub ObtainMedia(ByVal e As EContainer, ByVal PostID As String, ByVal PostDate As String, Optional ByVal BaseUrl As String = Nothing) + Dim t As UTypes = UTypes.Undefined + Select Case e.Value("type") + Case "video" : t = UTypes.Video + Case "image" : t = UTypes.Picture + Case "gifv" : t = UTypes.GIF + End Select + If Not t = UTypes.Undefined Then + Dim m As New UserMedia(e.Value("url"), t) With { + .Post = New UserPost(PostID, AConvert(Of Date)(PostDate, DateProvider, Nothing, EDP.ReturnValue)), + .URL_BASE = BaseUrl.IfNullOrEmpty(MySettings.GetUserPostUrl(Me, m)) + } + If Not t = UTypes.GIF Or GifsDownload Then + If t = UTypes.GIF Then + If Not GifsSpecialFolder.IsEmptyString Then m.SpecialFolder = GifsSpecialFolder + If Not GifsPrefix.IsEmptyString Then m.File.Name = $"{GifsPrefix}{m.File.Name}" + End If + If Not m.URL.IsEmptyString Then _TempMediaList.ListAddValue(m, LNC) + End If + End If + End Sub + Private Sub ObtainUserID() + Try + If ID.IsEmptyString Then + Dim url$ = $"https://{MyCredentials.Domain}/api/v1/accounts/lookup?acct=" + If Not UserDomain.IsEmptyString Then + If UserDomain = MyCredentials.Domain Then + url &= $"@{TrueName}" + Else + url &= $"@{TrueName}@{UserDomain}" + End If + Else + url &= $"@{TrueName}" + End If + Dim r$ = Responser.GetResponse(url) + If Not r.IsEmptyString Then + Using j As EContainer = JsonDocument.Parse(r) + If Not j Is Nothing Then ID = j.Value("id") + End Using + End If + End If + Catch ex As Exception + ErrorsDescriber.Execute(EDP.SendToLog, ex, $"API.Mastodon.UserData.ObtainUserID({ToStringForLog()})") + End Try + End Sub + Private Function GetSinglePostPattern(ByVal Domain As String) As String + Return $"https://{Domain}/api/v1/statuses/" & "{0}" + End Function + Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken) + SinglePostUrl = GetSinglePostPattern(MyCredentials.Domain) + MyBase.ReparseMissing(Token) + End Sub +#End Region +#Region "DownloadSingleObject" + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + Dim PostID$ = RegexReplace(Data.URL, RParams.DM("(?<=/)\d+", 0, EDP.ReturnValue)) + If Not PostID.IsEmptyString Then + ResetCredentials() + Dim pattern$ + If Not ACheck(MySettings.MyDomain.Value) Then + Throw New ArgumentNullException("Mastodon domain", "Mastodon domain not set") + Else + pattern = GetSinglePostPattern(MySettings.MyDomain.Value) + End If + Dim r$ = Responser.GetResponse(String.Format(pattern, PostID),, EDP.ReturnValue) + If Not r.IsEmptyString Then + Using j As EContainer = JsonDocument.Parse(r) + If j.ListExists AndAlso j.Contains("media_attachments") Then + For Each jj As EContainer In j("media_attachments") : ObtainMedia(jj, PostID, String.Empty, Data.URL) : Next + End If + End Using + End If + End If + End Sub +#End Region +#Region "Exception" + Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, + Optional ByVal EObj As Object = Nothing) As Integer + If TypeOf ex Is ArgumentNullException AndAlso Not ex.HelpLink.IsEmptyString And ex.HelpLink = 1 Then + Return 0 + Else + If Responser.Status = Net.WebExceptionStatus.NameResolutionFailure Then + MyMainLOG = $"User domain ({UserDomain}) not found: {ToStringForLog()}" + Return 1 + ElseIf Responser.StatusCode = Net.HttpStatusCode.NotFound Then + UserExists = False + Return 1 + ElseIf Responser.StatusCode = Net.HttpStatusCode.Unauthorized Then + MyMainLOG = $"{ToStringForLog()}: account credentials have expired" + Return 2 + ElseIf Responser.StatusCode = Net.HttpStatusCode.Gone Then + UserSuspended = True + Return 1 + Else + Return 0 + End If + End If + End Function +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/PathPlugin/SiteSettings.vb b/SCrawler/API/PathPlugin/SiteSettings.vb index ad6c15d..0387608 100644 --- a/SCrawler/API/PathPlugin/SiteSettings.vb +++ b/SCrawler/API/PathPlugin/SiteSettings.vb @@ -12,9 +12,10 @@ Imports SCrawler.Plugin.Attributes Namespace API.PathPlugin Friend Class SiteSettings : Inherits SiteSettingsBase + Private ReadOnly _Icon As Icon = Nothing Friend Overrides ReadOnly Property Icon As Icon Get - Return PersonalUtilities.Tools.ImageRenderer.GetIcon(PersonalUtilities.My.Resources.FolderOpenPic_Orange_16, EDP.ReturnValue) + Return _Icon End Get End Property Friend Overrides ReadOnly Property Image As Image @@ -24,6 +25,7 @@ Namespace API.PathPlugin End Property Friend Sub New() MyBase.New(PluginName) + _Icon = PersonalUtilities.Tools.ImageRenderer.GetIcon(PersonalUtilities.My.Resources.FolderOpenPic_Orange_16, EDP.ReturnValue) End Sub Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider Return New UserData @@ -42,7 +44,7 @@ Namespace API.PathPlugin Friend Overrides Function IsMyImageVideo(ByVal URL As String) As ExchangeOptions Return Nothing End Function - Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider, ByVal Channel As Boolean) As String + Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String Return String.Empty End Function End Class diff --git a/SCrawler/API/Pinterest/Declarations.vb b/SCrawler/API/Pinterest/Declarations.vb new file mode 100644 index 0000000..faf2bc9 --- /dev/null +++ b/SCrawler/API/Pinterest/Declarations.vb @@ -0,0 +1,21 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Globalization +Namespace API.Pinterest + Friend Module Declarations + Friend ReadOnly DateProvider As ADateTime = GetDateProvider() + Private Function GetDateProvider() As ADateTime + Dim n As DateTimeFormatInfo = CultureInfo.GetCultureInfo("en-us").DateTimeFormat.Clone + n.FullDateTimePattern = "ddd dd MMM yyyy HH:mm:ss" + n.TimeSeparator = String.Empty + 'Sat, 01 Jan 2000 01:10:15 +0000 + Return New ADateTime(DirectCast(n.Clone, DateTimeFormatInfo)) With {.DateTimeStyle = DateTimeStyles.AssumeUniversal} + End Function + End Module +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Pinterest/SiteSettings.vb b/SCrawler/API/Pinterest/SiteSettings.vb new file mode 100644 index 0000000..7eaedd7 --- /dev/null +++ b/SCrawler/API/Pinterest/SiteSettings.vb @@ -0,0 +1,101 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports SCrawler.API.Base +Imports SCrawler.Plugin +Imports SCrawler.Plugin.Attributes +Imports PersonalUtilities.Forms +Imports PersonalUtilities.Functions.RegularExpressions +Namespace API.Pinterest + + Friend Class SiteSettings : Inherits SiteSettingsBase +#Region "Declarations" + Friend Overrides ReadOnly Property Icon As Icon + Get + Return My.Resources.SiteResources.PinterestIcon_32 + End Get + End Property + Friend Overrides ReadOnly Property Image As Image + Get + Return My.Resources.SiteResources.PinterestPic_48 + End Get + End Property + Private Class ConcurrentDownloadsValidator : Inherits FieldsCheckerProviderBase + Public Overrides Function Convert(ByVal Value As Object, ByVal DestinationType As Type, ByVal Provider As IFormatProvider, + Optional ByVal NothingArg As Object = Nothing, Optional ByVal e As ErrorsDescriber = Nothing) As Object + Dim v% = AConvert(Of Integer)(Value, -1) + Dim defV% = Settings.MaxUsersJobsCount + If v.ValueBetween(1, defV) Then + Return Value + Else + ErrorMessage = $"The number of concurrent downloads must be greater than 0 and equal to or less than {defV} (global limit)." + HasError = True + Return Nothing + End If + End Function + End Class + + Private ReadOnly Property ConcurrentDownloadsProvider As IFormatProvider + + Friend ReadOnly Property ConcurrentDownloads As PropertyValue + + Friend ReadOnly Property SavedPostsUserName As PropertyValue +#End Region +#Region "Initializer" + Friend Sub New() + MyBase.New("Pinterest", "pinterest.com") + SavedPostsUserName = New PropertyValue(String.Empty, GetType(String)) + ConcurrentDownloads = New PropertyValue(1) + ConcurrentDownloadsProvider = New ConcurrentDownloadsValidator + CheckNetscapeCookiesOnEndInit = True + UseNetscapeCookies = True + UserRegex = RParams.DMS("https?://w{0,3}.?[^/]*?.?pinterest.com/([^/]+)/?(?(_)|([^/]*))", 0, RegexReturn.ListByMatch, EDP.ReturnValue) + End Sub +#End Region +#Region "GetInstance, Available" + Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider + Return New UserData + End Function + Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean + Return Settings.GalleryDLFile.Exists And (Not What = ISiteSettings.Download.SavedPosts OrElse + (Responser.CookiesExists And ACheck(SavedPostsUserName.Value))) + End Function +#End Region +#Region "IsMyUser, IsMyImageVideo, GetUserUrl, GetUserPostUrl" + Friend Overrides Function IsMyUser(ByVal UserURL As String) As ExchangeOptions + If Not UserURL.IsEmptyString Then + Dim l As List(Of String) = RegexReplace(UserURL, UserRegex) + If l.ListExists(3) Then + Dim n$ = l(1) + If Not l(2).IsEmptyString Then n &= $"@{l(2)}" + Return New ExchangeOptions(Site, n) With {.Exists = True} + End If + End If + Return Nothing + End Function + Friend Overrides Function IsMyImageVideo(ByVal URL As String) As ExchangeOptions + Return IsMyUser(URL) + End Function + Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String + With DirectCast(User, UserData) + Dim n$ = .TrueUserName + Dim c$ = .TrueBoardName + If Not c.IsEmptyString Then c &= "/" + Return $"https://www.pinterest.com/{n}/{c}" + End With + End Function + Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String + If Not Media.Post.ID.IsEmptyString Then + Return $"https://www.pinterest.com/pin/{Media.Post.ID}/" + Else + Return String.Empty + End If + End Function +#End Region + End Class +End Namespace \ No newline at end of file diff --git a/SCrawler/API/Pinterest/UserData.vb b/SCrawler/API/Pinterest/UserData.vb new file mode 100644 index 0000000..c46b2fe --- /dev/null +++ b/SCrawler/API/Pinterest/UserData.vb @@ -0,0 +1,330 @@ +' Copyright (C) 2023 Andy https://github.com/AAndyProgram +' This program is free software: you can redistribute it and/or modify +' it under the terms of the GNU General Public License as published by +' the Free Software Foundation, either version 3 of the License, or +' (at your option) any later version. +' +' This program is distributed in the hope that it will be useful, +' but WITHOUT ANY WARRANTY +Imports System.Threading +Imports SCrawler.API.Base +Imports SCrawler.API.Base.GDL +Imports SCrawler.API.YouTube.Objects +Imports PersonalUtilities.Functions.XML +Imports PersonalUtilities.Tools.Web.Documents.JSON +Namespace API.Pinterest + Friend Class UserData : Inherits UserDataBase +#Region "XML names" + Private Const Name_IsUser As String = "IsUser" + Private Const Name_TrueUserName As String = "TrueUserName" + Private Const Name_TrueBoardName As String = "TrueBoardName" +#End Region +#Region "Structures" + Private Structure BoardInfo + Friend ID As String + Friend Title As String + Friend URL As String + Friend Description As String + Friend UserID As String + Friend UserTitle As String + End Structure +#End Region +#Region "Declarations" + Private ReadOnly Property MySettings As SiteSettings + Get + Return HOST.Source + End Get + End Property + Friend Property TrueUserName As String + Friend Property TrueBoardName As String + Friend Property IsUser As Boolean +#End Region +#Region "Load" + Private Function ReconfUserName() As Boolean + If TrueUserName.IsEmptyString Then + Dim n$() = Name.Split("@") + If n.ListExists Then + TrueUserName = n(0) + IsUser = True + If n.Length > 1 Then TrueBoardName = n(1) : IsUser = False + If Not IsSavedPosts And Not IsSingleObjectDownload Then + Dim l$ = IIf(IsUser, UserLabelName, "Board") + Settings.Labels.Add(l) + Labels.ListAddValue(l, LNC) + Labels.Sort() + End If + Return True + End If + End If + Return False + End Function + Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean) + With Container + If Loading Then + TrueUserName = .Value(Name_TrueUserName) + TrueBoardName = .Value(Name_TrueBoardName) + IsUser = .Value(Name_IsUser).FromXML(Of Boolean)(False) + ReconfUserName() + Else + If ReconfUserName() Then .Value(Name_LabelsName) = Labels.ListToString("|", EDP.ReturnValue) + .Add(Name_TrueUserName, TrueUserName) + .Add(Name_TrueBoardName, TrueBoardName) + .Add(Name_IsUser, IsUser.BoolToInteger) + End If + End With + End Sub +#End Region +#Region "Download overrides" + Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken) + Dim URL$ = String.Empty + Try + If IsSavedPosts Then + IsUser = True + TrueUserName = MySettings.SavedPostsUserName.Value + If TrueUserName.IsEmptyString Then Throw New ArgumentNullException("SavedPostsUserName", "Saved posts user not set") + End If + Dim boards As List(Of BoardInfo) + Dim board As BoardInfo + Dim b$ = TrueBoardName + If Not b.IsEmptyString Then b &= "/" + URL = $"https://www.pinterest.com/{TrueUserName}/{b}" + If IsUser Then + boards = GetBoards(Token) + Else + boards = New List(Of BoardInfo) From {New BoardInfo With {.URL = URL, .ID = ID, .Title = UserSiteName}} + End If + If boards.ListExists Then + For i% = 0 To boards.Count - 1 + ThrowAny(Token) + board = boards(i) + DownloadBoardImages(board, Token) + boards(i) = board + Next + With boards.First + If IsUser Then + If ID.IsEmptyString Then ID = .UserID + UserSiteNameUpdate(.UserTitle) + Else + If ID.IsEmptyString Then ID = .ID + UserSiteNameUpdate(.Title) + UserDescriptionUpdate(.Description) + End If + End With + End If + Catch ex As Exception + ProcessException(ex, Token, $"data downloading error [{URL}]") + End Try + End Sub +#End Region +#Region "Get boards, images" + Private Function GetBoards(ByVal Token As CancellationToken) As List(Of BoardInfo) + Dim URL$ = $"https://www.pinterest.com/{TrueUserName}/" + Try + Dim boards As New List(Of BoardInfo) + Dim b As BoardInfo + Dim r$ + Dim j As EContainer, jj As EContainer + Dim rootNode$() = {"resource_response", "data"} + Dim jErr As New ErrorsDescriber(EDP.SendToLog + EDP.ReturnValue) + Dim urls As List(Of String) = GetDataFromGalleryDL(URL, True) + If urls.ListExists Then urls.RemoveAll(Function(__url) Not __url.Contains("BoardsResource/get/")) + If urls.ListExists Then + For Each URL In urls + ThrowAny(Token) + r = Responser.GetResponse(URL,, EDP.ReturnValue) + If Not r.IsEmptyString Then + j = JsonDocument.Parse(r, jErr) + If Not j Is Nothing Then + If If(j(rootNode)?.Count, 0) > 0 Then + For Each jj In j(rootNode) + b = New BoardInfo With { + .URL = jj.Value("url"), + .Title = TitleHtmlConverter(jj.Value("name")), + .ID = jj.Value("id") + } + If Not b.URL.IsEmptyString Then + b.URL = $"https://www.pinterest.com/{b.URL.StringTrimStart("/").StringTrimEnd("/")}/" + boards.Add(b) + End If + Next + End If + j.Dispose() + End If + End If + Next + End If + Return boards + Catch ex As Exception + ProcessException(ex, Token, $"data (gallery-dl boards) downloading error [{URL}]") + Return Nothing + End Try + End Function + Private Sub DownloadBoardImages(ByRef Board As BoardInfo, ByVal Token As CancellationToken) + Dim bUrl$ = String.Empty + Try + Dim r$ + Dim j As EContainer, jj As EContainer + Dim u As UserMedia + Dim folder$ = If(IsUser, Board.Title.IfNullOrEmpty(Board.ID), String.Empty) + Dim titleExists As Boolean = Not Board.Title.IsEmptyString + Dim i% = -1 + Dim jErr As New ErrorsDescriber(EDP.SendToLog + EDP.ReturnValue) + Dim rootNode$() = {"resource_response", "data"} + Dim images As List(Of Sizes) + Dim imgSelector As Func(Of EContainer, Sizes) = Function(img) New Sizes(img.Value("width"), img.Value("url")) + Dim fullData As Predicate(Of EContainer) = Function(e) e.Count > 5 + Dim l As List(Of String) = GetDataFromGalleryDL(Board.URL, False) + If l.ListExists Then l.RemoveAll(Function(ll) Not ll.Contains("BoardFeedResource/get/")) + If l.ListExists Then + For Each bUrl In l + ThrowAny(Token) + r = Responser.GetResponse(bUrl,, EDP.ReturnValue) + If Not r.IsEmptyString Then + j = JsonDocument.Parse(r, jErr) + If Not j Is Nothing Then + If If(j(rootNode)?.Count, 0) > 0 Then + For Each jj In j(rootNode) + With jj + If .Contains("images") Then + images = .Item("images").Select(imgSelector).ToList + If images.Count > 0 Then + images.Sort() + i += 1 + u = New UserMedia(images(0).Data) With { + .Post = New UserPost(jj.Value("id"), AConvert(Of Date)(jj.Value("created_at"), DateProvider, Nothing)), + .Type = UserMedia.Types.Picture, + .SpecialFolder = folder + } + If i = 0 Then + If Board.Title.IsEmptyString Or Board.ID.IsEmptyString Then + Board.Title = TitleHtmlConverter(.Value({"board"}, "name")) + Board.ID = .Value({"board"}, "id") + End If + Board.UserID = .Value({"board", "owner"}, "id") + Board.UserTitle = TitleHtmlConverter(.Value({"board", "owner"}, "full_name")) + If Not titleExists And IsUser Then + If Not Board.Title.IsEmptyString Then + folder = Board.Title + ElseIf Not Board.ID.IsEmptyString Then + folder = Board.ID + End If + u.SpecialFolder = folder + End If + End If + + If Not u.URL.IsEmptyString Then + If u.Post.Date.HasValue Then + Select Case CheckDatesLimit(u.Post.Date.Value, Nothing) + Case DateResult.Skip : _TempPostsList.ListAddValue(u.Post.ID, LNC) : Continue For + Case DateResult.Exit : Exit Sub + End Select + End If + If Not _TempPostsList.Contains(u.Post.ID) Then + _TempPostsList.ListAddValue(u.Post.ID, LNC) + _TempMediaList.ListAddValue(u, LNC) + Else + Exit For + End If + End If + End If + End If + End With + Next + End If + j.Dispose() + End If + End If + Next + End If + Catch ex As Exception + ProcessException(ex, Token, $"data (gallery-dl images) downloading error [{bUrl}]") + End Try + End Sub +#End Region +#Region "Gallery-DL Support" + Private Class GDLBatch : Inherits GDL.GDLBatch + Private ReadOnly Property Source As UserData + Private ReadOnly IsBoardsRequested As Boolean + Friend Sub New(ByRef s As UserData, ByVal IsBoardsRequested As Boolean) + MyBase.New + Source = s + Me.IsBoardsRequested = IsBoardsRequested + End Sub + Protected Overrides Async Sub OutputDataReceiver(ByVal Sender As Object, ByVal e As DataReceivedEventArgs) + If IsBoardsRequested Then + Await Validate(e.Data) + Else + MyBase.OutputDataReceiver(Sender, e) + Await Validate(e.Data) + End If + End Sub + Protected Overrides Async Function Validate(ByVal Value As String) As Task + If IsBoardsRequested Then + If ErrorOutputData.Count > 0 Then + If Await Task.Run(Of Boolean)(Function() ErrorOutputData.Exists(Function(ee) Not ee.IsEmptyString AndAlso + ee.StartsWith(UrlTextStart))) Then Kill(EDP.None) + End If + Else + If Await Task.Run(Of Boolean)(Function() Not Value.IsEmptyString AndAlso + Source._TempPostsList.Exists(Function(v) Value.Contains(v))) Then Kill(EDP.None) + End If + End Function + End Class + Private Function GetDataFromGalleryDL(ByVal URL As String, ByVal IsBoardsRequested As Boolean) As List(Of String) + Dim command$ = $"gallery-dl --verbose --simulate " + Try + If Not URL.IsEmptyString Then + If MySettings.CookiesNetscapeFile.Exists Then command &= $"--cookies ""{MySettings.CookiesNetscapeFile}"" " + command &= URL + Using batch As New GDLBatch(Me, IsBoardsRequested) + Return GetUrlsFromGalleryDl(batch, command) + End Using + End If + Return Nothing + Catch ex As Exception + HasError = True + LogError(ex, $"GetJson({command})") + Return Nothing + End Try + End Function +#End Region +#Region "DownloadContent" + Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken) + DownloadContentDefault(Token) + End Sub +#End Region +#Region "DownloadSingleObject" + Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken) + User = New UserInfo(MySettings.IsMyUser(Data.URL).UserName, HOST) + User.File.Path = Data.File.Path + SeparateVideoFolder = False + ReconfUserName() + DownloadDataF(Token) + Data.Title = UserSiteName + If Data.Title.IsEmptyString Then + Data.Title = TrueUserName + If Not TrueBoardName.IsEmptyString Then Data.Title &= $"/{TrueBoardName}" + End If + Dim additPath$ = TitleHtmlConverter(UserSiteName) + If additPath.IsEmptyString Then additPath = IIf(IsUser, TrueUserName, TrueBoardName) + If Not additPath.IsEmptyString Then + Dim f As SFile = User.File + f.Path = f.PathWithSeparator & additPath + User.File = f + f = Data.File + f.Path = User.File.Path + Data.File = f + End If + End Sub + Protected Overrides Sub DownloadSingleObject_PostProcessing(ByVal Data As IYouTubeMediaContainer, Optional ByVal ResetTitle As Boolean = True) + MyBase.DownloadSingleObject_PostProcessing(Data, Data.Title.IsEmptyString Or Not Data.Title = UserSiteName) + End Sub +#End Region +#Region "Exception" + Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False, + Optional ByVal EObj As Object = Nothing) As Integer + Return 0 + End Function + End Class +#End Region +End Namespace \ No newline at end of file diff --git a/SCrawler/API/PornHub/Declarations.vb b/SCrawler/API/PornHub/Declarations.vb index c046642..4ceeec5 100644 --- a/SCrawler/API/PornHub/Declarations.vb +++ b/SCrawler/API/PornHub/Declarations.vb @@ -23,6 +23,7 @@ Namespace API.PornHub Private ReadOnly RegexVideo_Video_Wrong_Option As RParams = RParams.DM("div class=""thumbnail-info-wrapper clearfix.+?[\r\n\s]*?\ Friend ReadOnly Property DownloadGifs As PropertyValue @@ -40,10 +39,7 @@ Namespace API.PornHub #Region "Initializer" Friend Sub New() MyBase.New("PornHub", "pornhub.com") - Responser.CurlPath = $"cURL\curl.exe" - Responser.CurlArgumentsRight = "--ssl-no-revoke" - CurlPathExists = Responser.CurlPath.Exists - Responser.DeclaredError = EDP.ThrowException + With Responser : .CurlSslNoRevoke = True : .CurlInsecure = True : End With DownloadGifsAsMp4 = New PropertyValue(True) DownloadGifs = New PropertyValue(CInt(CheckState.Indeterminate), GetType(Integer)) @@ -55,37 +51,16 @@ Namespace API.PornHub ImageVideoContains = "pornhub" End Sub #End Region -#Region "GetInstance, GetSpecialData" +#Region "GetInstance" Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider - If What = ISiteSettings.Download.SavedPosts Then - Return New UserData With { - .IsSavedPosts = True, - .VideoPageModel = UserData.VideoPageModels.Favorite, - .PersonType = UserData.PersonTypeUser, - .User = New UserInfo With {.Name = $"{UserData.PersonTypeUser}_{CStr(AConvert(Of String)(SavedPostsUserName.Value, String.Empty))}"} - } - Else - Return New UserData - End If - End Function - Friend Overrides Function GetSpecialData(ByVal URL As String, ByVal Path As String, ByVal AskForPath As Boolean) As IEnumerable - If Available(ISiteSettings.Download.Main, True) Then - Using resp As Responser = Responser.Copy - Dim spf$ = String.Empty - Dim f As SFile = GetSpecialDataFile(Path, AskForPath, spf) - Dim m As UserMedia = UserData.GetVideoInfo(URL, resp, f) - If m.State = UserMedia.States.Downloaded Then - m.SpecialFolder = f - Return {m} - End If - End Using - End If - Return Nothing + Return New UserData End Function #End Region #Region "Downloading" Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean - Return Settings.UseM3U8 And CurlPathExists And (Not What = ISiteSettings.Download.SavedPosts OrElse ACheck(SavedPostsUserName.Value)) + Responser.CurlPath = Settings.CurlFile + Return Settings.UseM3U8 And Settings.CurlFile.Exists And + (Not What = ISiteSettings.Download.SavedPosts OrElse (ACheck(SavedPostsUserName.Value) And Responser.CookiesExists)) End Function #End Region #Region "IsMyUser" @@ -97,23 +72,20 @@ Namespace API.PornHub End If Return Nothing Catch ex As Exception - Return ErrorsDescriber.Execute(EDP.SendInLog + EDP.ReturnValue, ex, $"[API.PornHub.SiteSettings.IsMyUser({UserURL})]", New ExchangeOptions) + Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, $"[API.PornHub.SiteSettings.IsMyUser({UserURL})]", New ExchangeOptions) End Try End Function #End Region -#Region "GetUserUrl, GetUserPostUrl" - Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider, ByVal Channel As Boolean) As String +#Region "GetUserUrl" + Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String With DirectCast(User, UserData) : Return String.Format(UrlPatternUser, .PersonType, .NameTrue) : End With End Function - Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String - Return Media.URL_BASE - End Function #End Region #Region "User options" Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) If Options Is Nothing OrElse Not TypeOf Options Is UserExchangeOptions Then Options = New UserExchangeOptions(Me) If OpenForm Then - Using f As New OptionsForm(Options) : f.ShowDialog() : End Using + Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using End If End Sub #End Region diff --git a/SCrawler/API/PornHub/UserData.vb b/SCrawler/API/PornHub/UserData.vb index 8c1e52f..da31b0c 100644 --- a/SCrawler/API/PornHub/UserData.vb +++ b/SCrawler/API/PornHub/UserData.vb @@ -8,6 +8,7 @@ ' but WITHOUT ANY WARRANTY Imports System.Threading Imports SCrawler.API.Base +Imports SCrawler.API.YouTube.Objects Imports PersonalUtilities.Functions.XML Imports PersonalUtilities.Functions.RegularExpressions Imports PersonalUtilities.Tools.Web.Clients @@ -136,6 +137,7 @@ Namespace API.PornHub #Region "Initializer, loader" Friend Sub New() UseInternalM3U8Function = True + UseClientTokens = True End Sub Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean) With Container @@ -179,7 +181,11 @@ Namespace API.PornHub Responser.ResetStatus() If PersonType = PersonTypeUser Then Responser.Mode = Responser.Modes.Curl - If IsSavedPosts Then VideoPageModel = VideoPageModels.Favorite + If IsSavedPosts Then + VideoPageModel = VideoPageModels.Favorite + PersonType = PersonTypeUser + NameTrue = MySettings.SavedPostsUserName.Value + End If Dim page% = 1 Dim __continue As Boolean = True @@ -295,7 +301,7 @@ Namespace API.PornHub If Not r.IsEmptyString Then Dim n$ Dim m As UserMedia = Nothing - Dim l As List(Of RegexMatchStruct) = RegexFields(Of RegexMatchStruct)(r, {Regex_Gif_Array}, {1}) + Dim l As List(Of RegexMatchStruct) = RegexFields(Of RegexMatchStruct)(r, {Regex_Gif_Array}, {1}, EDP.ReturnValue) Dim l2 As List(Of String) = Nothing Dim l3 As List(Of String) = Nothing If l.ListExists Then l2 = l.Select(Function(ll) $"gif/{ll.Arr(0).Replace("gif", String.Empty)}").ToList @@ -336,6 +342,10 @@ Namespace API.PornHub End Sub #End Region #Region "Download photo" + Private Function CreatePhotoFile(ByVal URL As String, ByVal File As SFile) As SFile + Dim pFile$ = RegexReplace(URL, Regex_Photo_File) + If Not pFile.IsEmptyString Then Return New SFile(pFile) Else Return File + End Function Private Const PhotoUrlPattern_ModelHub As String = "https://www.modelhub.com/{0}/photos" Private Const PhotoUrlPattern_PornHub As String = "https://www.pornhub.com/{0}/{1}/photos" Private Sub DownloadUserPhotos(ByVal Token As CancellationToken) @@ -365,7 +375,8 @@ Namespace API.PornHub Private Function DownloadUserPhotos_ModelHub(ByVal Token As CancellationToken) As Boolean Dim URL$ = String.Empty Try - Dim jErr As New ErrorsDescriber(EDP.SendInLog + EDP.ReturnValue) + Dim j As EContainer + Dim jErr As New ErrorsDescriber(EDP.SendToLog + EDP.ReturnValue) Dim albumName$ If PersonType = PersonTypeModel Then URL = String.Format(PhotoUrlPattern_ModelHub, NameTrue) @@ -380,15 +391,16 @@ Namespace API.PornHub albumRegex.Pattern = "

    1YEwb_;O$ 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 F{vTmJGh6@w literal 17640 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 diff --git a/ProgramScreenshots/SettingsGlobalEnvironment.png b/ProgramScreenshots/SettingsGlobalEnvironment.png new file mode 100644 index 0000000000000000000000000000000000000000..e3fe3c4046560b81f8f19e89432fce9d95aa1d59 GIT binary patch literal 20208 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 literal 0 HcmV?d00001 diff --git a/ProgramScreenshots/SettingsGlobalFeed.png b/ProgramScreenshots/SettingsGlobalFeed.png index 6ad8d213b82c166b67bdfc8803ead3fe926e3b55..e7f81304cf68491dac7df6387a1a7949d2062a5c 100644 GIT binary patch literal 14616 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