Compare commits

...

6 Commits

Author SHA1 Message Date
Andy
05772a9fc4 2025.7.18.0
API.Instagram: fix special folder issue
API.OnlyFans: bypass unpurchased videos; add support for GIF files
API.Reddit: add OAuth credentials validation; add extended 429 error handling
API.Xhamster: remove 'UserOptions' function ('SiteSettings'); add support for downloading 'moments'
API.XVIDEOS: remove 'UserOptions' function ('SiteSettings'); remove 'UserExchangeOptions' class
Add 'EditorExchangeOptionsBase_P' and update base classes for user options
2025-07-18 20:29:35 +03:00
Andy
24ad338c60 2025.6.12.0
YT
MainModShared: fix environment output
YouTubeMediaContainerBase: fix 'm3u8' audio formats

SCrawler
UserDataBase: text downloading with saved posts; update 'ID' property (handle '_ForceSaveUserInfo')
API.Bluesky: data is not downloaded
API.Reddit: update 'RedditViewExchange'; set base inheritance; inherit default settings for new users
API.ALL: update functions with property 'ID'
2025-06-12 20:29:59 +03:00
Andy
ff0c4587eb 2025.6.1.0
PluginProvider
IUserMedia, PluginUserMedia: add properties 'PostText', 'PostTextFile', 'PostTextFileSpecialFolder'

YT
YouTubeFunctions: update 'Info_GetUrlType' and 'StandardizeURL' functions: add youtu.be domain
YouTubeSettings: add 'FILTER' property
Add classes 'FilterForm', 'YTDataFilter'
VideoListForm: add filters; update 'LoadData' and 'RemoveControls' functions; add hotkey 'Ctrl+F5' for refresh
YouTubeMediaContainerBase: add support for new interface properties
Minor bugs

SCrawler
DeclaredNames: add new names
EditorExchangeOptionsBase, IUserData, SiteSettingsBase, UserMedia, UserDataBase: add support for text downloading

Sites Bluesky, Instagram, OnlyFans, Reddit, ThreadsNet, Twitter: add support for text downloading
Sites Facebook, JustForFans, LPSG, Mastodon, Pinterest, PornHub, Redgifs, ThisVid, TikTok, Xhamster, XVIDEOS, YouTube (STD): disable text downloading

UserDataBase: add 'ToStringExt' functions

API.Instagram: add 'SleepTimerRequestsNextProfile' property
API.OnlyFans: update 'DynamicRules'; fix incorrect posts opening (update 'GetUserPostUrl' function); fix limited download ('DownloadTopCount')
API.Reddit: fix post date provider; add 'Best' and 'Rising' view modes; fix request (data is not downloading); set 'BearerTokenUseCurl' to 'False' by default
API.ThreadsNet: change domain from 'net' to 'com'; fix data downloading
API.TikTok: add downloading of avatar, site name and description
API.Twitter: fix JSON error; add debug options; fix downloading
API.Xhamster: add folder 'Photo' for albums

Feed: add filters; update move/copy algo; add the ability to show test posts; update table rendering; add new 'MediaItem' handlers
FeedMedia: add text options; update 'DeleteFile' function
FeedMoveCopyTo: add text option

VideoDownloaderForm: disable filter button

GlobalSettingsForm: add 'FeedShowTextPosts' and 'FeedShowTextPostsAlwaysMove' options
SettingsCLS: add feed text properties
UserImage: add 'CreateImageFromText' function
UserInfo: update 'Equals' function

Add classes: 'FeedFilter', 'FeedFilterCollection', 'FeedFilterForm'

Minor bugs and improvements
2025-06-01 19:01:26 +03:00
Andy
fff63d0a9f 2025.3.17.0
API.SiteSettingsBase: fix incorrect class initializer
API.UserDataBase: add all objects to xml (STD)
API.Facebook: fix downloading reels from noname profiles
API.Pinterest: remove 'UserOptions' overrides (SiteSettings); add 'PwsHeader' to 'GetBoards'
API.PornHub: fix 'UpdateUserOptions' function ('NameTrue')
API.Threads: fix 'pinned' posts
API.TikTok: add photos download
2025-03-17 16:23:41 +03:00
Andy
2f838929cc 2025.2.25.0
PluginProvider
IPluginContentProvider: add 'NameTrue' property

YT
YouTubeSettings: remove the 'CreateDescriptionFiles_AddUploadDate' property
PlayListParserForm: add 'RDAMP' as default value when initializing form
YouTubeMediaContainerBase: fix line breaks

SCrawler
API.Base: add 'EditorExchangeOptionsBase' class
API.Base.GDL: move functions to Pinterest.UserData
API.Base.IUserData: add 'NameTrue' property
API.Base.SiteSettingsBase: update the 'GetUserUrl' function to use the 'NameTrue' property; update 'UserOptions' function
API.Base.UserDataBase: add 'NameTrue' property; add 'SimpleDownloadAvatar' function to get rid of the  same functions of other classes; Update 'UserDescriptionUpdate' function
API.Base.InternalSettingsForm: calculate max offset

ADD API.Bluesky

API.Facebook: add 'Reels' downloads; fix video downloading
API.Instagram: add inheritance 'EditorExchangeOptionsBase'; remove 'GetUserUrl' function from 'SiteSettings'; update 'UserData' class to new environment; add saving 'heic' file along with 'jpg'
API.LPSG.UserData: simplify 403 error
API.Mastodon: update classes to new environment
API.OnlyFans: add 'AppTokenDefault'; disable cookies update; update 'UserData' class to new environment
API.Pinterest: add sub-boards downloading; update download functions
API.PornHub: fix photo & video downloading; remove 'ModelHub' support
API.Reddit: fix token update; update 'UserData' class to new environment
API.ThisVid: update 'UserData' class to new environment
API.ThreadsNet: fix data download; update classes to new environment; fix 'UserID' extraction; add the ability to manually change the UserName
API.TikTok: update classes to new environment
API.Twitter: update classes to new environment; add sleep timers to fully download large profiles; add 'CookiesUpdate' hidden property
API.Xhamster: update classes to new environment
API.XVIDEOS: update classes to new environment

Feed: add the ability to invert selection; open post URL when double-clicking on subscription image
FeedSpecialCollection: update 'FeedsComparer'
GlobalSettingsForm: remove 'UserAgent' from the 'Basis' tab
UserDataHost: update class to new environment
SettingsCLS: set the 'UserSiteNameAsFriendly' property to 'True' by default; disable 'DownDetector'; add 'UsersListProtected' property
2025-02-25 19:47:33 +03:00
Andy
4d74f5204b 2025.1.12.0
YT
YouTubeSettings: add 'FileAddChannelToFileName' property
YouTubeMediaContainerBase: add channel name and video URL to info file; add channel name to file name

SCrawler
DownDetector: fix 403 error; add 'IDownDetector' interface and 'Checker' class; create an isolated environment
API.Instagram: update 'SiteSettings' to the new 'DownDetector' environment; make 'PostKV' public; add static function 'LoadSavePostsKV'
API.OnlyFans: add 'EnableCookiesUpdate' hidden property; add support for DRM keys; add the ability to disable cookie updates
API.Pinterest: add 'x-pinterest-pws-handler' header
API.Reddit: update 'SiteSettings' to the new 'DownDetector' environment
API.ThisVid: fix subscription videos images
API.Threads: change 'heic' extension to 'jpg'
API.Twitter: add broadcasts download
API.Xhamster: fix absolute M3U8 URLs
API.YouTube: add support of personal API instances ('YouTube-operational-API') for download communities
SiteEditorForm: add 'Ctrl+Enter' hotkey to force save settings, ignoring  requirements
PluginsEnvironment.Attributes: add 'UseDownDetectorAttribute' attribute
SettingsHost: update to the new 'DownDetector' environment; add 'AvailableDownDetector' property
SettingsHostCollection: update to the new 'DownDetector' environment; minor bugs in multiprofile
SettingsCLS: add 'DownDetectorEnabled' property
2025-01-12 23:16:57 +03:00
141 changed files with 7560 additions and 2526 deletions

View File

@@ -1,3 +1,148 @@
# 2025.7.18.0
*2025-07-18*
- Added
- Sites:
- OnlyFans:
- **bypass unpurchased videos**
- support for GIF files
- Reddit: extended `429` error handling
- Xhamster: support for downloading 'moments'
- Minor improvements
- Updated
- yt-dlp up to version **2025.06.30**
- gallery-dl up to version **1.30.0**
- Fixed
- Minor bugs
# 2025.6.12.0
*2025-06-12*
- Updated
- yt-dlp up to version **2025.06.09**
- Fixed
- Sites:
- YouTube: audio formats of protocol `m3u8` are not handled correctly
- BlueSky: data is not downloaded in some cases
- Reddit: new users do not inherit default text settings
- Saved posts: text downloading with saved posts
- Environment incorrect output
# 2025.6.1.0
*2025-06-01*
- Added
- Sites:
- YouTube (standalone app):
- support for **youtu.be** domain
- **filters**
- hotkey `Ctrl+F5` for refresh
- **Bluesky, Instagram, OnlyFans, Reddit, Threads, Twitter: the ability to download text posts**
- OnlyFans:
- updated `DynamicRules`
- `backend` option *(`aio` & `httpx`)*
- Reddit: add `Best` and `Rising` view modes
- TikTok: downloading of avatar, site name and description
- Feed:
- **filters**
- add the ability to show test posts
- Minor improvements
- Updated
- yt-dlp up to version **2025.05.22**
- gallery-dl up to version **1.29.7**
- PluginProvider
- IUserMedia: properties `PostText`, `PostTextFile`, `PostTextFileSpecialFolder`
- Fixed
- Sites:
- OnlyFans:
- DRM videos are not downloading in some cases *(only if you can't download video try changing `backend` option to `httpx`)*
- incorrect open posts
- while limited downloading, the first time, the profile still loads completely
- Reddit:
- **data is not downloading**
- post date is incorrect
- Threads
- change domain from `net` to `com`
- data is not downloading
- Twitter
- fix JSON error
- data is not downloaded in some cases
- Minor bugs
# 2025.3.17.0
*2025-03-17*
- Added
- **TikTok: downloading photos**
- Updated
- gallery-dl up to version **1.29.2**
- Fixed
- Sites
- Facebook: reels aren't downloaded from noname profiles
- PornHub: newly added users aren't downloading
- Threads: users aren't updated if there is a pinned post
# 2025.2.25.0
*2025-02-25*
- Added
- Sites:
- **Bluesky**
- Facebook: **`Reels` downloads**
- OnlyFans: default value for `App-Token`
- Pinterest: **sub-boards downloading**
- Threads: ability to manually change `UserName`
- Twitter:
- new icon support
- **sleep timers to fully download large profiles**
- Feed:
- ability to invert selection
- open post URL when double-clicking on subscription image
- Minor improvements
- Updated
- yt-dlp up to version **2025.02.19**
- gallery-dl up to version **1.28.5**
- PluginProvider
- `IPluginContentProvider`: added property `NameTrue`
- Fixed
- Sites:
- Facebook: videos are not downloading
- LPSG: simplified 403 error
- PornHub: photos & videos are not downloading
- Reddit: **token does not update automatically**
- Threads: **data is not downloading**
- Minor bugs
# 2025.1.12.0
*2025-01-12*
- Added
- Sites:
- YouTube (standalone app):
- ability to add channel name to file name (`Add channel to file name`)
- adding channel name and video URL to info file
- OnlyFans: **built-in usage of DRM keys**
- Threads: automatically change `heic` extension to `jpg`
- Twitter: download broadcasts *(user option)*
- Minor improvements
- Updated
- yt-dlp up to version **2024.12.23**
- gallery-dl up to version **1.28.3**
- **OF-Scraper** up to version **3.12.9** *(you must update it personally)*
- Fixed
- Sites:
- DownDetector: fixed 403 error
- OnlyFans: **DRM videos not downloading**
- xHamster: some videos are not downloading
- YouTube: **communities are not downloading** *(see settings in wiki)*
- Minor bugs
# 2024.11.21.0
*2024-11-21*

5
FAQ.md
View File

@@ -55,7 +55,7 @@ I strongly recommend you to **regularly** create backup copies of the settings f
- [Video how to configure](#video-how-to-configure)
- **Antivirus**
- **Antivirus detects SCrawler as a virus** :arrow_forward: SCrawler doesn't contain any viruses at all. All code is posted on GitHub. You can review it. I have nothing to hide. SCrawler just downloads pictures and videos. That's all. If you trust SCrawler, you should just add it to the antivirus exceptions, as I did. Sometimes antiviruses identify SCawler as a virus. This is usually related to the number of files being edited (users' settings files) and the number of files being downloaded. In this case, the antivirus can also remove these files, which will damage users' settings. **If you don't trust SCrawler, just delete it.**
- **Antivirus detects gallery-dl as a virus** :arrow_forward: it's a trustworthy program that is trusted by thousands of people around the world. Antiviruses identify some builds as containing viruses, but this is not true. **If you don't trust gallery-dl, you can simply delete it**. **But if you delete it, you won't be able to download [Twitter & Pinterest](https://github.com/AAndyProgram/SCrawler/wiki/Settings#gallery-dl).** You should decide for yourself.
- **Antivirus detects gallery-dl as a virus** :arrow_forward: it's a trustworthy program that is trusted by thousands of people around the world. Antiviruses identify some builds as containing viruses, but this is not true. **If you don't trust gallery-dl, you can simply delete it. But if you delete it, you won't be able to download [Twitter & Pinterest](https://github.com/AAndyProgram/SCrawler/wiki/Settings#gallery-dl).** You should decide for yourself.
## Sites questions
@@ -63,11 +63,12 @@ I strongly recommend you to **regularly** create backup copies of the settings f
- Reddit: don't use credentials at all or configure [OAuth](https://github.com/AAndyProgram/SCrawler/wiki/Settings#how-to-get-reddit-credentials). **Reddit profiles can be downloaded without any credentials at all. Subreddits require OAuth! If nothing downloads, use OAuth!** Don't use OAuth token to download saved posts (use cookies only).
- **META** (**Instagram**, Threads, Facebook): you need **cookies** and fill in **all fields**
- **Instagram [TIPS](https://github.com/AAndyProgram/SCrawler/wiki/Settings#instagram-tips)**
- **Instagram saved posts**: I don't consider questions like "I have 10k saved posts and only 1000 were downloaded". Download posts, remove them from saved posts, delete the `Saved posts` **settings folder**, repeat.
- TikTok: works via yt-dlp. If something doesn't download, we need to wait until yt-dlp fixes it. TikTok doesn't require cookies to download.
- Porn sites: **COOKIES**!
- ThisVid: https://github.com/AAndyProgram/SCrawler/wiki/Settings#thisvid-faq
- **OnlyFans**: cookies + **all fields** + [OF-Scraper (download the correct version that I pointed)](https://github.com/AAndyProgram/SCrawler/wiki/Settings#of-scraper) & [mp4decrypt](https://www.bento4.com/downloads/) to download DRM protected videos. [OF-Scraper support](https://github.com/AAndyProgram/SCrawler/wiki/Settings#of-scraper-support). Also read [this](https://github.com/AAndyProgram/SCrawler/wiki/Settings#onlyfans-faq)
- **OnlyFans**: cookies + **all fields** + [OF-Scraper (download the correct version that I pointed)](https://github.com/AAndyProgram/SCrawler/wiki/Settings#of-scraper) & [mp4decrypt](https://www.bento4.com/downloads/) & **DRM keys** to download DRM protected videos. [OF-Scraper support](https://github.com/AAndyProgram/SCrawler/wiki/Settings#of-scraper-support). Also read [this](https://github.com/AAndyProgram/SCrawler/wiki/Settings#onlyfans-faq)
- **JustForFans**: **THE VIDEO ISN'T DOWNLOADING AT THE MOMENT** ([Issue](https://discord.com/channels/1124032649682493462/1205547615199039551/1231349555132366870))
## Other questions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -35,16 +35,17 @@ A program to download photo and video from [any site](#supported-sites) (e.g. Yo
# What can program do:
- Download pictures and videos from user profiles:
- YouTube videos, shorts, community feeds, users, artists, playlists, music, tracks;
- Reddit images, galleries of images, videos, saved posts;
- Reddit images, galleries of images, videos, text, saved posts;
- Redgifs images and videos (https://www.redgifs.com/);
- Twitter images and videos, saved (bookmarked) posts, likes, communities;
- OnlyFans images and videos, saved (bookmarked) posts, stories;
- Twitter images and videos, text, saved (bookmarked) posts, likes, communities;
- Bluesky images and videos, text;
- OnlyFans images and videos, text, saved (bookmarked) posts, stories;
- JustForFans images and videos, saved (bookmarked) posts;
- Mastodon images and videos, saved (bookmarked) posts;
- Instagram images and videos, tagged posts, stories, saved posts;
- Threads images and videos, saved posts;
- Instagram images and videos, text, tagged posts, stories, saved posts;
- Threads images and videos, text, saved posts;
- Facebook images and videos, stories, saved posts;
- TikTok videos;
- TikTok images and videos;
- Pinterest boards, users, saved posts;
- Imgur images, galleries and videos;
- Gfycat videos;
@@ -78,6 +79,7 @@ A program to download photo and video from [any site](#supported-sites) (e.g. Yo
- **YouTube Music**
- **Reddit**
- **Twitter**
- **Bluesky**
- **OnlyFans** *(partial support)*[^1]
- **Instagram**
- **Threads**
@@ -131,6 +133,7 @@ First, the program downloads the full profile. After the program downloads only
- **[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)
- [Bluesky](https://github.com/AAndyProgram/SCrawler/wiki/Settings#bluesky)
- [OnlyFans](https://github.com/AAndyProgram/SCrawler/wiki/Settings#onlyfans)
- [Mastodon](https://github.com/AAndyProgram/SCrawler/wiki/Settings#mastodon)
- [Instagram](https://github.com/AAndyProgram/SCrawler/wiki/Settings#instagram)
@@ -215,16 +218,4 @@ F5-->[*]
Discord server: https://discord.gg/uFNUXvFFmg
<!--
[e-mail](mailto:andyprogram@proton.me): andyprogram@proton.me
Matrix (Element): https://matrix.to/#/@andyprogram:matrix.org
Discord (contact the developer): andyprogram
Discord server: https://discord.gg/uFNUXvFFmg
[Wire](https://account.wire.com/user-profile/?id=93985052-cf2c-4b72-ac75-bbe3231cf544): @andyprogram
-->
[^1]: Partial support means that I don't have personal accounts on paid porn sites because I don't pay for porn. If this site has stopped downloading and you want me to fix it, please be ready to give me access to an account with at least one active subscription. Otherwise, the download from this site will not be fixed.

View File

@@ -17,6 +17,7 @@ Namespace Plugin
Property Settings As ISiteSettings
Property AccountName As String
Property Name As String
Property NameTrue As String
Property ID As String
Property Options As String
Property ParseUserMediaOnly As Boolean

View File

@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
<Assembly: AssemblyDescription("Plugin provider for SCrawler")>
<Assembly: AssemblyCompany("AndyProgram")>
<Assembly: AssemblyProduct("SCrawler.PluginProvider")>
<Assembly: AssemblyCopyright("Copyright © 2024")>
<Assembly: AssemblyCopyright("Copyright © 2025")>
<Assembly: AssemblyTrademark("AndyProgram")>
<Assembly: ComVisible(False)>
@@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices
' by using the '*' as shown below:
' <Assembly: AssemblyVersion("1.0.*")>
<Assembly: AssemblyVersion("2024.5.18.0")>
<Assembly: AssemblyFileVersion("2024.5.18.0")>
<Assembly: AssemblyVersion("2025.6.1.0")>
<Assembly: AssemblyFileVersion("2025.6.1.0")>
<Assembly: NeutralResourcesLanguage("en")>

View File

@@ -35,6 +35,9 @@ Namespace Plugin
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 PostText As String Implements IUserMedia.PostText
Public Property PostTextFile As String Implements IUserMedia.PostTextFile
Public Property PostTextFileSpecialFolder As Boolean Implements IUserMedia.PostTextFileSpecialFolder
Public Property SpecialFolder As String Implements IUserMedia.SpecialFolder
Public Property Attempts As Integer Implements IUserMedia.Attempts
Public Property [Object] As Object Implements IUserMedia.Object
@@ -48,6 +51,9 @@ Namespace Plugin
Property DownloadState As UserMediaStates
Property PostID As String
Property PostDate As Date?
Property PostText As String
Property PostTextFile As String
Property PostTextFileSpecialFolder As Boolean
Property SpecialFolder As String
Property Attempts As Integer
Property [Object] As Object

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

View File

@@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices
' by using the '*' as shown below:
' <Assembly: AssemblyVersion("1.0.*")>
<Assembly: AssemblyVersion("2023.12.15.0")>
<Assembly: AssemblyFileVersion("2023.12.15.0")>
<Assembly: AssemblyVersion("2025.6.1.0")>
<Assembly: AssemblyFileVersion("2025.6.1.0")>
<Assembly: NeutralResourcesLanguage("en")>

View File

@@ -26,7 +26,7 @@ Namespace My.Resources
Global.System.Diagnostics.DebuggerNonUserCodeAttribute(), _
Global.System.Runtime.CompilerServices.CompilerGeneratedAttribute(), _
Global.Microsoft.VisualBasic.HideModuleNameAttribute()> _
Friend Module Resources
Public Module Resources
Private resourceMan As Global.System.Resources.ResourceManager
@@ -36,7 +36,7 @@ Namespace My.Resources
''' Returns the cached ResourceManager instance used by this class.
'''</summary>
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager
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)
@@ -51,7 +51,7 @@ Namespace My.Resources
''' resource lookups using this strongly typed resource class.
'''</summary>
<Global.System.ComponentModel.EditorBrowsableAttribute(Global.System.ComponentModel.EditorBrowsableState.Advanced)> _
Friend Property Culture() As Global.System.Globalization.CultureInfo
Public Property Culture() As Global.System.Globalization.CultureInfo
Get
Return resourceCulture
End Get
@@ -59,5 +59,25 @@ Namespace My.Resources
resourceCulture = value
End Set
End Property
'''<summary>
''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
'''</summary>
Public ReadOnly Property FilterIcon() As System.Drawing.Icon
Get
Dim obj As Object = ResourceManager.GetObject("FilterIcon", resourceCulture)
Return CType(obj,System.Drawing.Icon)
End Get
End Property
'''<summary>
''' Looks up a localized resource of type System.Drawing.Bitmap.
'''</summary>
Public ReadOnly Property FilterPic() As System.Drawing.Bitmap
Get
Dim obj As Object = ResourceManager.GetObject("FilterPic", resourceCulture)
Return CType(obj,System.Drawing.Bitmap)
End Get
End Property
End Module
End Namespace

View File

@@ -46,7 +46,7 @@
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
@@ -60,6 +60,7 @@
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
@@ -68,9 +69,10 @@
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
@@ -85,9 +87,10 @@
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
@@ -109,9 +112,16 @@
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
<data name="FilterIcon" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Content\Icons\FilterIcon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="FilterPic" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Content\Images\FilterPic.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
</root>

View File

@@ -82,6 +82,7 @@
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
@@ -123,7 +124,7 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="My Project\Resources.resx">
<Generator>VbMyResourcesResXFileCodeGenerator</Generator>
<Generator>PublicVbMyResourcesResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.vb</LastGenOutput>
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<SubType>Designer</SubType>
@@ -147,5 +148,9 @@
<Name>PersonalUtilities</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Content\Icons\FilterIcon.ico" />
<Content Include="Content\Images\FilterPic.png" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
</Project>

View File

@@ -18,6 +18,7 @@ Namespace API.YouTube.Base
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|))"
Public Const UrlTypePattern_BE As String = "(?<=https?://[^/]*?youtu.be/)([^\?/&]+)"
Private Sub New()
End Sub
Public Shared Function StandardizeURL(ByVal URL As String) As String
@@ -36,6 +37,7 @@ Namespace API.YouTube.Base
Next
data.Clear()
End If
If val.IsEmptyString Then val = RegexReplace(URL, RParams.DMS(UrlTypePattern_BE, 0, EDP.ReturnValue))
If Not val.IsEmptyString Then Return $"https://www.youtube.com/watch?v={val}"
End If
End If
@@ -100,6 +102,9 @@ Namespace API.YouTube.Base
Return YouTubeMediaType.Channel
End Select
End If
Else
Dim v$ = RegexReplace(URL, RParams.DMS(UrlTypePattern_BE, 0, EDP.ReturnValue))
If Not v.IsEmptyString Then Return YouTubeMediaType.Single
End If
End If
Return YouTubeMediaType.Undefined

View File

@@ -33,6 +33,7 @@ Namespace API.YouTube.Base
#Region "Declarations"
<Browsable(False)> Private ReadOnly Property XML As XmlFile Implements IXMLValuesContainer.XML
<Browsable(False)> Friend ReadOnly Property DesignXml As XmlFile
<Browsable(False)> Friend ReadOnly Property FILTER As Controls.YTDataFilter
<Browsable(False)> Private Property Mode As GridUpdateModes = GridUpdateModes.OnConfirm Implements IGridValuesContainer.Mode
<Browsable(False), XMLVV(-1)> Friend ReadOnly Property PlaylistFormSplitterDistance As XMLValue(Of Integer)
<Browsable(False)> Friend ReadOnly Property DownloadLocations As DownloadLocationsCollection
@@ -168,9 +169,6 @@ Namespace API.YouTube.Base
<Browsable(True), GridVisible, XMLVN({"Info"}), Category("Info"), DisplayName("Create description files"),
Description("Create video description files. Default: false.")>
Public ReadOnly Property CreateDescriptionFiles As XMLValue(Of Boolean)
<Browsable(True), GridVisible, XMLVN({"Info"}, True), Category("Info"), DisplayName("Create description files: add upload date"),
Description("Add the upload date to the top of the description file. Default: true.")>
Public ReadOnly Property CreateDescriptionFiles_AddUploadDate As XMLValue(Of Boolean)
<Browsable(True), GridVisible, XMLVN({"Info"}, True), Category("Info"), DisplayName("Create description files: create without description"),
Description("Create a description file with the upload date, even if the description does not exist. Default: true.")>
Public ReadOnly Property CreateDescriptionFiles_CreateWithNoDescription As XMLValue(Of Boolean)
@@ -191,7 +189,7 @@ Namespace API.YouTube.Base
<Browsable(True), GridVisible, XMLVN({"Defaults"}), Category("Defaults"), DisplayName("Use cookies"),
Description("By default, use cookies when downloading from YouTube.")>
Public ReadOnly Property DefaultUseCookies As XMLValue(Of Boolean)
<Browsable(True), GridVisible, XMLVN({"Defaults"}, Protocols.Any), Category("Defaults"), DisplayName("Protocol"),
<Browsable(True), GridVisible, XMLVN({"Defaults"}, Protocols.https), Category("Defaults"), DisplayName("Protocol"),
Description("Priority download protocol. Default: 'Any'")>
Public ReadOnly Property DefaultProtocol As XMLValue(Of Protocols)
<Browsable(True), GridVisible(False), XMLVN({"Defaults"}), Category("Defaults"),
@@ -267,6 +265,9 @@ Namespace API.YouTube.Base
<Browsable(True), GridVisible, XMLVN({"Defaults"}), Category("Defaults"), DisplayName("Add date to title: video list"),
Description("Add video upload date before video title (visual only) in the video list")>
Public ReadOnly Property FileAddDateToFileName_VideoList As XMLValue(Of Boolean)
<Browsable(True), GridVisible, XMLVN({"Defaults"}, FileDateMode.None), Category("Defaults"), DisplayName("Add channel to file name"),
Description("Add channel name before/after the file name")>
Public ReadOnly Property FileAddChannelToFileName As XMLValue(Of FileDateMode)
#End Region
#Region "Defaults ChannelsDownload"
<Browsable(True), GridVisible, XMLVN({"Defaults", "Channels"}), Category("Defaults"), DisplayName("Default download tabs for channels"),
@@ -514,6 +515,7 @@ Namespace API.YouTube.Base
XML.LoadData(EDP.None)
DesignXml = New XmlFile("Settings\DesignDownloader.xml", Protector.Modes.All, False)
DesignXml.LoadData(EDP.None)
FILTER = New Controls.YTDataFilter
InitializeXMLValueProperties(Me)
AddHandler ShowNotificationsEveryDownload.TempValueChanged, AddressOf ShowNotificationsEveryDownload_TempValueChanged
Cookies = New CookieKeeper

View File

@@ -0,0 +1,324 @@
' Copyright (C) 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
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()>
Partial Friend Class FilterForm : Inherits System.Windows.Forms.Form
<System.Diagnostics.DebuggerNonUserCode()>
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
<System.Diagnostics.DebuggerStepThrough()>
Private Sub InitializeComponent()
Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer
Dim TP_MAIN As System.Windows.Forms.TableLayoutPanel
Dim TP_TYPES As System.Windows.Forms.TableLayoutPanel
Dim TP_USERS As System.Windows.Forms.TableLayoutPanel
Dim TP_USERS_2 As System.Windows.Forms.TableLayoutPanel
Me.CH_FILTER_ALL = New System.Windows.Forms.CheckBox()
Me.CH_FILTER_SINGLE = New System.Windows.Forms.CheckBox()
Me.CH_FILTER_CHANNEL = New System.Windows.Forms.CheckBox()
Me.CH_FILTER_PLS = New System.Windows.Forms.CheckBox()
Me.TP_MUSIC = New System.Windows.Forms.TableLayoutPanel()
Me.CH_M_ALL = New System.Windows.Forms.CheckBox()
Me.CH_M_VIDEO = New System.Windows.Forms.CheckBox()
Me.CH_M_MUSIC = New System.Windows.Forms.CheckBox()
Me.LIST_USERS = New System.Windows.Forms.CheckedListBox()
Me.CH_USERS_USE = New System.Windows.Forms.CheckBox()
Me.BTT_SELECT_ALL = New System.Windows.Forms.Button()
Me.BTT_SELECT_NONE = New System.Windows.Forms.Button()
CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer()
TP_MAIN = New System.Windows.Forms.TableLayoutPanel()
TP_TYPES = New System.Windows.Forms.TableLayoutPanel()
TP_USERS = New System.Windows.Forms.TableLayoutPanel()
TP_USERS_2 = New System.Windows.Forms.TableLayoutPanel()
CONTAINER_MAIN.ContentPanel.SuspendLayout()
CONTAINER_MAIN.SuspendLayout()
TP_MAIN.SuspendLayout()
TP_TYPES.SuspendLayout()
Me.TP_MUSIC.SuspendLayout()
TP_USERS.SuspendLayout()
TP_USERS_2.SuspendLayout()
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.ColumnCount = 1
TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
TP_MAIN.Controls.Add(TP_TYPES, 0, 0)
TP_MAIN.Controls.Add(Me.TP_MUSIC, 0, 1)
TP_MAIN.Controls.Add(TP_USERS, 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.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.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20.0!))
TP_MAIN.Size = New System.Drawing.Size(384, 361)
TP_MAIN.TabIndex = 0
'
'TP_TYPES
'
TP_TYPES.ColumnCount = 4
TP_TYPES.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25.0!))
TP_TYPES.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25.0!))
TP_TYPES.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25.0!))
TP_TYPES.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 25.0!))
TP_TYPES.Controls.Add(Me.CH_FILTER_ALL, 0, 0)
TP_TYPES.Controls.Add(Me.CH_FILTER_SINGLE, 1, 0)
TP_TYPES.Controls.Add(Me.CH_FILTER_CHANNEL, 2, 0)
TP_TYPES.Controls.Add(Me.CH_FILTER_PLS, 3, 0)
TP_TYPES.Dock = System.Windows.Forms.DockStyle.Fill
TP_TYPES.Location = New System.Drawing.Point(0, 0)
TP_TYPES.Margin = New System.Windows.Forms.Padding(0)
TP_TYPES.Name = "TP_TYPES"
TP_TYPES.RowCount = 1
TP_TYPES.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
TP_TYPES.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!))
TP_TYPES.Size = New System.Drawing.Size(384, 25)
TP_TYPES.TabIndex = 0
'
'CH_FILTER_ALL
'
Me.CH_FILTER_ALL.AutoSize = True
Me.CH_FILTER_ALL.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_FILTER_ALL.Location = New System.Drawing.Point(3, 3)
Me.CH_FILTER_ALL.Name = "CH_FILTER_ALL"
Me.CH_FILTER_ALL.Size = New System.Drawing.Size(90, 19)
Me.CH_FILTER_ALL.TabIndex = 0
Me.CH_FILTER_ALL.Text = "ALL"
Me.CH_FILTER_ALL.UseVisualStyleBackColor = True
'
'CH_FILTER_SINGLE
'
Me.CH_FILTER_SINGLE.AutoSize = True
Me.CH_FILTER_SINGLE.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_FILTER_SINGLE.Location = New System.Drawing.Point(99, 3)
Me.CH_FILTER_SINGLE.Name = "CH_FILTER_SINGLE"
Me.CH_FILTER_SINGLE.Size = New System.Drawing.Size(90, 19)
Me.CH_FILTER_SINGLE.TabIndex = 1
Me.CH_FILTER_SINGLE.Text = "Single"
Me.CH_FILTER_SINGLE.UseVisualStyleBackColor = True
'
'CH_FILTER_CHANNEL
'
Me.CH_FILTER_CHANNEL.AutoSize = True
Me.CH_FILTER_CHANNEL.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_FILTER_CHANNEL.Location = New System.Drawing.Point(195, 3)
Me.CH_FILTER_CHANNEL.Name = "CH_FILTER_CHANNEL"
Me.CH_FILTER_CHANNEL.Size = New System.Drawing.Size(90, 19)
Me.CH_FILTER_CHANNEL.TabIndex = 2
Me.CH_FILTER_CHANNEL.Text = "Channel"
Me.CH_FILTER_CHANNEL.UseVisualStyleBackColor = True
'
'CH_FILTER_PLS
'
Me.CH_FILTER_PLS.AutoSize = True
Me.CH_FILTER_PLS.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_FILTER_PLS.Location = New System.Drawing.Point(291, 3)
Me.CH_FILTER_PLS.Name = "CH_FILTER_PLS"
Me.CH_FILTER_PLS.Size = New System.Drawing.Size(90, 19)
Me.CH_FILTER_PLS.TabIndex = 3
Me.CH_FILTER_PLS.Text = "Playlist"
Me.CH_FILTER_PLS.UseVisualStyleBackColor = True
'
'TP_MUSIC
'
Me.TP_MUSIC.ColumnCount = 3
Me.TP_MUSIC.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
Me.TP_MUSIC.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
Me.TP_MUSIC.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
Me.TP_MUSIC.Controls.Add(Me.CH_M_ALL, 0, 0)
Me.TP_MUSIC.Controls.Add(Me.CH_M_VIDEO, 1, 0)
Me.TP_MUSIC.Controls.Add(Me.CH_M_MUSIC, 2, 0)
Me.TP_MUSIC.Dock = System.Windows.Forms.DockStyle.Fill
Me.TP_MUSIC.Location = New System.Drawing.Point(0, 25)
Me.TP_MUSIC.Margin = New System.Windows.Forms.Padding(0)
Me.TP_MUSIC.Name = "TP_MUSIC"
Me.TP_MUSIC.RowCount = 1
Me.TP_MUSIC.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
Me.TP_MUSIC.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!))
Me.TP_MUSIC.Size = New System.Drawing.Size(384, 25)
Me.TP_MUSIC.TabIndex = 1
'
'CH_M_ALL
'
Me.CH_M_ALL.AutoSize = True
Me.CH_M_ALL.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_M_ALL.Location = New System.Drawing.Point(3, 3)
Me.CH_M_ALL.Name = "CH_M_ALL"
Me.CH_M_ALL.Size = New System.Drawing.Size(122, 19)
Me.CH_M_ALL.TabIndex = 0
Me.CH_M_ALL.Text = "ALL"
Me.CH_M_ALL.UseVisualStyleBackColor = True
'
'CH_M_VIDEO
'
Me.CH_M_VIDEO.AutoSize = True
Me.CH_M_VIDEO.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_M_VIDEO.Location = New System.Drawing.Point(131, 3)
Me.CH_M_VIDEO.Name = "CH_M_VIDEO"
Me.CH_M_VIDEO.Size = New System.Drawing.Size(122, 19)
Me.CH_M_VIDEO.TabIndex = 1
Me.CH_M_VIDEO.Text = "Video"
Me.CH_M_VIDEO.UseVisualStyleBackColor = True
'
'CH_M_MUSIC
'
Me.CH_M_MUSIC.AutoSize = True
Me.CH_M_MUSIC.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_M_MUSIC.Location = New System.Drawing.Point(259, 3)
Me.CH_M_MUSIC.Name = "CH_M_MUSIC"
Me.CH_M_MUSIC.Size = New System.Drawing.Size(122, 19)
Me.CH_M_MUSIC.TabIndex = 2
Me.CH_M_MUSIC.Text = "Music"
Me.CH_M_MUSIC.UseVisualStyleBackColor = True
'
'TP_USERS
'
TP_USERS.ColumnCount = 1
TP_USERS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
TP_USERS.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Absolute, 20.0!))
TP_USERS.Controls.Add(Me.LIST_USERS, 0, 1)
TP_USERS.Controls.Add(TP_USERS_2, 0, 0)
TP_USERS.Dock = System.Windows.Forms.DockStyle.Fill
TP_USERS.Location = New System.Drawing.Point(0, 50)
TP_USERS.Margin = New System.Windows.Forms.Padding(0)
TP_USERS.Name = "TP_USERS"
TP_USERS.RowCount = 2
TP_USERS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!))
TP_USERS.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
TP_USERS.Size = New System.Drawing.Size(384, 311)
TP_USERS.TabIndex = 3
'
'LIST_USERS
'
Me.LIST_USERS.Dock = System.Windows.Forms.DockStyle.Fill
Me.LIST_USERS.FormattingEnabled = True
Me.LIST_USERS.Location = New System.Drawing.Point(3, 28)
Me.LIST_USERS.Name = "LIST_USERS"
Me.LIST_USERS.Size = New System.Drawing.Size(378, 280)
Me.LIST_USERS.TabIndex = 1
'
'TP_USERS_2
'
TP_USERS_2.ColumnCount = 3
TP_USERS_2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
TP_USERS_2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
TP_USERS_2.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
TP_USERS_2.Controls.Add(Me.CH_USERS_USE, 0, 0)
TP_USERS_2.Controls.Add(Me.BTT_SELECT_ALL, 1, 0)
TP_USERS_2.Controls.Add(Me.BTT_SELECT_NONE, 2, 0)
TP_USERS_2.Dock = System.Windows.Forms.DockStyle.Fill
TP_USERS_2.Location = New System.Drawing.Point(0, 0)
TP_USERS_2.Margin = New System.Windows.Forms.Padding(0)
TP_USERS_2.Name = "TP_USERS_2"
TP_USERS_2.RowCount = 1
TP_USERS_2.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
TP_USERS_2.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25.0!))
TP_USERS_2.Size = New System.Drawing.Size(384, 25)
TP_USERS_2.TabIndex = 0
'
'CH_USERS_USE
'
Me.CH_USERS_USE.AutoSize = True
Me.CH_USERS_USE.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_USERS_USE.Location = New System.Drawing.Point(3, 3)
Me.CH_USERS_USE.Name = "CH_USERS_USE"
Me.CH_USERS_USE.Size = New System.Drawing.Size(122, 19)
Me.CH_USERS_USE.TabIndex = 0
Me.CH_USERS_USE.Text = "Filter users"
Me.CH_USERS_USE.UseVisualStyleBackColor = True
'
'BTT_SELECT_ALL
'
Me.BTT_SELECT_ALL.Dock = System.Windows.Forms.DockStyle.Fill
Me.BTT_SELECT_ALL.Location = New System.Drawing.Point(129, 1)
Me.BTT_SELECT_ALL.Margin = New System.Windows.Forms.Padding(1)
Me.BTT_SELECT_ALL.Name = "BTT_SELECT_ALL"
Me.BTT_SELECT_ALL.Size = New System.Drawing.Size(126, 23)
Me.BTT_SELECT_ALL.TabIndex = 1
Me.BTT_SELECT_ALL.Text = "All"
Me.BTT_SELECT_ALL.UseVisualStyleBackColor = True
'
'BTT_SELECT_NONE
'
Me.BTT_SELECT_NONE.Dock = System.Windows.Forms.DockStyle.Fill
Me.BTT_SELECT_NONE.Location = New System.Drawing.Point(257, 1)
Me.BTT_SELECT_NONE.Margin = New System.Windows.Forms.Padding(1)
Me.BTT_SELECT_NONE.Name = "BTT_SELECT_NONE"
Me.BTT_SELECT_NONE.Size = New System.Drawing.Size(126, 23)
Me.BTT_SELECT_NONE.TabIndex = 2
Me.BTT_SELECT_NONE.Text = "None"
Me.BTT_SELECT_NONE.UseVisualStyleBackColor = True
'
'FilterForm
'
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.MinimizeBox = False
Me.MinimumSize = New System.Drawing.Size(400, 400)
Me.Name = "FilterForm"
Me.ShowInTaskbar = False
Me.Text = "Filter"
CONTAINER_MAIN.ContentPanel.ResumeLayout(False)
CONTAINER_MAIN.ResumeLayout(False)
CONTAINER_MAIN.PerformLayout()
TP_MAIN.ResumeLayout(False)
TP_TYPES.ResumeLayout(False)
TP_TYPES.PerformLayout()
Me.TP_MUSIC.ResumeLayout(False)
Me.TP_MUSIC.PerformLayout()
TP_USERS.ResumeLayout(False)
TP_USERS_2.ResumeLayout(False)
TP_USERS_2.PerformLayout()
Me.ResumeLayout(False)
End Sub
Private WithEvents CH_FILTER_ALL As CheckBox
Private WithEvents CH_FILTER_SINGLE As CheckBox
Private WithEvents CH_FILTER_CHANNEL As CheckBox
Private WithEvents CH_FILTER_PLS As CheckBox
Private WithEvents TP_MUSIC As TableLayoutPanel
Private WithEvents CH_M_ALL As CheckBox
Private WithEvents CH_M_VIDEO As CheckBox
Private WithEvents CH_M_MUSIC As CheckBox
Private WithEvents LIST_USERS As CheckedListBox
Private WithEvents CH_USERS_USE As CheckBox
Private WithEvents BTT_SELECT_ALL As Button
Private WithEvents BTT_SELECT_NONE As Button
End Class
End Namespace

View File

@@ -0,0 +1,135 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="CONTAINER_MAIN.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
<metadata name="TP_MAIN.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
<metadata name="TP_TYPES.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
<metadata name="TP_USERS.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
<metadata name="TP_USERS_2.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
</root>

View File

@@ -0,0 +1,181 @@
' Copyright (C) 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 SCrawler.API.YouTube.Objects
Imports PersonalUtilities.Forms
Imports SimpleUser = SCrawler.API.YouTube.Controls.YTDataFilter.SimpleUser
Namespace API.YouTube.Controls
Friend Class FilterForm
Private WithEvents MyDefs As DefaultFormOptions
Friend ReadOnly Property DATA As List(Of IYouTubeMediaContainer)
Private ReadOnly Property DataTMP As List(Of IYouTubeMediaContainer)
Private ReadOnly Property MyFilterTmp As YTDataFilter
Private ReadOnly Property MyTmpUsers As List(Of SimpleUser)
Friend Sub New(ByVal d As IEnumerable(Of IYouTubeMediaContainer))
InitializeComponent()
MyDefs = New DefaultFormOptions(Me, MyYouTubeSettings.DesignXml)
DATA = New List(Of IYouTubeMediaContainer)
DATA.ListAddList(d)
DataTMP = New List(Of IYouTubeMediaContainer)
DataTMP.ListAddList(d)
MyFilterTmp = New YTDataFilter(MyYouTubeSettings.FILTER)
MyTmpUsers = New List(Of SimpleUser)
Icon = My.Resources.FilterIcon
End Sub
Private Sub FilterForm_Load(sender As Object, e As EventArgs) Handles Me.Load
Try
With MyDefs
.MyViewInitialize()
.AddOkCancelToolbar(True)
With MyYouTubeSettings.FILTER
With .Types
If .Count = 0 Or (.Count = 1 AndAlso .Item(0) = YouTubeMediaType.Undefined) Then
CH_FILTER_ALL.Checked = True
Else
If .Contains(YouTubeMediaType.Single) Then CH_FILTER_SINGLE.Checked = True
If .Contains(YouTubeMediaType.Channel) Then CH_FILTER_CHANNEL.Checked = True
If .Contains(YouTubeMediaType.PlayList) Then CH_FILTER_PLS.Checked = True
End If
End With
If .IsMusic And .IsVideo Then
CH_M_ALL.Checked = True
Else
CH_M_MUSIC.Checked = .IsMusic
CH_M_VIDEO.Checked = .IsVideo
End If
CH_USERS_USE.Checked = .UserList.Count > 0
End With
.EndLoaderOperations()
_RefillEnabled = True
RefillList(True)
CH_USERS_USE_CheckedChanged()
End With
Catch ex As Exception
MyDefs.InvokeLoaderError(ex)
End Try
End Sub
Private Sub FilterForm_Disposed(sender As Object, e As EventArgs) Handles Me.Disposed
DATA.ListClearDispose
DataTMP.ListClearDispose
MyTmpUsers.ListClearDispose
End Sub
Private _RefillEnabled As Boolean = False
Private Sub RefillList(Optional ByVal Init As Boolean = False)
If Not MyDefs.Initializing And _RefillEnabled Then
ApplyFilter(MyFilterTmp, Init)
MyFilterTmp.Populate(DATA, DataTMP, CH_USERS_USE.Checked)
With DataTMP
If .Count > 0 Then
Dim tmpUsers As New List(Of SimpleUser)
tmpUsers.ListAddList(DataTMP, LAP.NotContainsOnly, CType(Function(obj As YouTubeMediaContainerBase) CType(obj, SimpleUser), Func(Of Object, Object)))
If tmpUsers.Count > 0 Then
tmpUsers.Sort()
LIST_USERS.BeginUpdate()
LIST_USERS.Items.Clear()
tmpUsers.ForEach(Sub(u) LIST_USERS.Items.Add(u, True))
If CH_USERS_USE.Checked And MyFilterTmp.UserList.Count > 0 Then
For i% = 0 To LIST_USERS.Items.Count - 1
LIST_USERS.SetItemChecked(i, MyFilterTmp.UserList.Contains(LIST_USERS.Items(i)))
Next
End If
LIST_USERS.EndUpdate()
End If
End If
End With
End If
End Sub
Private Sub ApplyFilter(ByRef Filter As YTDataFilter, ByVal Init As Boolean)
With Filter
.Reset(False, Not Init)
With .Types
If CH_FILTER_ALL.Checked Then
.Add(YouTubeMediaType.Undefined)
Else
If CH_FILTER_SINGLE.Checked Then .Add(YouTubeMediaType.Single)
If CH_FILTER_CHANNEL.Checked Then .Add(YouTubeMediaType.Channel)
If CH_FILTER_PLS.Checked Then .Add(YouTubeMediaType.PlayList)
End If
If .Count = 0 Then .Add(YouTubeMediaType.Undefined)
End With
If CH_M_ALL.Checked Then
.IsMusic = True
.IsVideo = True
Else
If CH_M_MUSIC.Checked Then .IsMusic = True
If CH_M_VIDEO.Checked Then .IsVideo = True
End If
If Not .IsVideo And Not .IsMusic Then .IsMusic = True : .IsVideo = True
If CH_USERS_USE.Checked And Not Init Then
.UserList.Clear()
If LIST_USERS.CheckedItems.Count > 0 Then
For Each item In LIST_USERS.CheckedItems : .UserList.Add(item) : Next
End If
End If
End With
End Sub
Private Sub MyDefs_ButtonOkClick(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonOkClick
ApplyFilter(MyYouTubeSettings.FILTER, False)
MyYouTubeSettings.FILTER.RemoveAll(DATA)
MyYouTubeSettings.FILTER.Update()
MyDefs.CloseForm()
End Sub
Private Sub MyDefs_ButtonDeleteClickOC(ByVal Sender As Object, ByVal e As KeyHandleEventArgs) Handles MyDefs.ButtonDeleteClickOC
With MyYouTubeSettings.FILTER : .Reset() : .Update() : End With
MyDefs.CloseForm()
End Sub
Private Sub UpdateControlOptions(ByRef CNT As CheckBox, ByVal v As Boolean)
If v Then CNT.Checked = True
CNT.Enabled = Not v
End Sub
Private Sub CH_FILTER_ALL_CheckedChanged(sender As Object, e As EventArgs) Handles CH_FILTER_ALL.CheckedChanged
_RefillEnabled = False
Dim v As Boolean = CH_FILTER_ALL.Checked
UpdateControlOptions(CH_FILTER_SINGLE, v)
UpdateControlOptions(CH_FILTER_CHANNEL, v)
UpdateControlOptions(CH_FILTER_PLS, v)
_RefillEnabled = True
RefillList()
End Sub
Private Sub CH_FILTER_SINGLE_CHANNEL_PLS_CheckedChanged(sender As Object, e As EventArgs) Handles CH_FILTER_SINGLE.CheckedChanged,
CH_FILTER_CHANNEL.CheckedChanged,
CH_FILTER_PLS.CheckedChanged
RefillList()
End Sub
Private Sub CH_M_ALL_CheckedChanged(sender As Object, e As EventArgs) Handles CH_M_ALL.CheckedChanged
_RefillEnabled = False
Dim v As Boolean = CH_M_ALL.Checked
UpdateControlOptions(CH_M_VIDEO, v)
UpdateControlOptions(CH_M_MUSIC, v)
_RefillEnabled = True
RefillList()
End Sub
Private Sub CH_M_VIDEO_MUSIC_CheckedChanged(sender As Object, e As EventArgs) Handles CH_M_VIDEO.CheckedChanged, CH_M_MUSIC.CheckedChanged
RefillList()
End Sub
Private Sub CH_USERS_USE_CheckedChanged() Handles CH_USERS_USE.CheckedChanged
LIST_USERS.Enabled = CH_USERS_USE.Checked
BTT_SELECT_ALL.Enabled = CH_USERS_USE.Checked
BTT_SELECT_NONE.Enabled = CH_USERS_USE.Checked
End Sub
Private Sub BTT_SELECT_ALL_NONE_Click(sender As Object, e As EventArgs) Handles BTT_SELECT_ALL.Click, BTT_SELECT_NONE.Click
Dim checked As Boolean = sender Is BTT_SELECT_ALL
With LIST_USERS
If .Items.Count > 0 Then
For i% = 0 To .Items.Count - 1 : .SetItemChecked(i, checked) : Next
End If
End With
End Sub
End Class
End Namespace

View File

@@ -155,7 +155,7 @@ Namespace API.YouTube.Controls
With DirectCast(MyContainer, YouTubeMediaContainerBase)
Select Case Sender.DefaultButton
Case ADB.Open
Using f As New SimpleListForm(Of String)(IIf(isLyrics, AvailableSubtitlesFormats, AvailableAudioFormats)) With {
Using f As New SimpleListForm(Of String)(If(isLyrics, AvailableSubtitlesFormats, AvailableAudioFormats)) With {
.DesignXML = DesignXML,
.DesignXMLNodeName = SimpleArraysFormNode,
.FormText = DirectCast(e.AssociatedControl, TextBoxExtended).CaptionText,

View File

@@ -121,9 +121,10 @@ Namespace API.YouTube.Controls
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.PlaceholderText = "e.g. RDAMP"
Me.TXT_LIMIT.Size = New System.Drawing.Size(378, 22)
Me.TXT_LIMIT.TabIndex = 2
Me.TXT_LIMIT.Text = "RDAMP"
'
'CONTAINER_MAIN
'

View File

@@ -0,0 +1,122 @@
' Copyright (C) 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 SCrawler.API.YouTube.Objects
Imports PersonalUtilities.Functions.XML
Imports PersonalUtilities.Functions.XML.Base
Namespace API.YouTube.Controls
Friend Class YTDataFilter
Private Const Name_Types As String = "Types"
Private Const Name_IsMusic As String = "IsMusic"
Private Const Name_IsVideo As String = "IsVideo"
Private Const Name_UserList As String = "UserList"
Friend Structure SimpleUser : Implements IEContainerProvider, IComparable(Of SimpleUser)
Private Const Name_UID As String = "UID"
Friend Title As String
Friend ID As String
Friend Sub New(ByVal _Title As String, ByVal _ID As String)
Title = _Title
ID = _ID
End Sub
Private Sub New(ByVal u As YouTubeMediaContainerBase)
Title = u.Title
ID = u.ID
End Sub
Public Shared Widening Operator CType(ByVal u As YouTubeMediaContainerBase) As SimpleUser
Return New SimpleUser(u.UserTitle, u.UserID)
End Operator
Public Shared Widening Operator CType(ByVal e As EContainer) As SimpleUser
Return New SimpleUser(e.Value, e.Attribute(Name_UID).Value)
End Operator
Public Overrides Function ToString() As String
Return String.Format(CStr(Interaction.Switch(Title.IsEmptyString, "{1}", ID.IsEmptyString, "{0}", True, "{0} ({1})")), Title, ID)
End Function
Public Overrides Function Equals(ByVal Obj As Object) As Boolean
Return Not IsDBNull(Obj) AndAlso ToString() = DirectCast(Obj, SimpleUser).ToString
End Function
Private Function CompareTo(ByVal Other As SimpleUser) As Integer Implements IComparable(Of SimpleUser).CompareTo
Return ToString.CompareTo(Other.ToString)
End Function
Private Function ToEContainer(Optional ByVal e As ErrorsDescriber = Nothing) As EContainer Implements IEContainerProvider.ToEContainer
Return New EContainer("User", Title, {New EAttribute(Name_UID, ID)})
End Function
End Structure
Friend ReadOnly Property Types As List(Of YouTubeMediaType)
Friend Property IsMusic As Boolean = True
Friend Property IsVideo As Boolean = True
Friend ReadOnly Property UserList As List(Of SimpleUser)
Private ReadOnly File As New SFile("Settings\YouTubeFilter.xml")
Friend Sub New(Optional ByVal LoadFromFile As Boolean = True)
Types = New List(Of YouTubeMediaType) From {YouTubeMediaType.Undefined}
UserList = New List(Of SimpleUser)
If LoadFromFile AndAlso File.Exists Then
Using e As New XmlFile(File, Protector.Modes.All, False) With {.AllowSameNames = True}
e.LoadData()
If e.Count > 0 Then
Types.Clear()
Types.ListAddList(e.Value(Name_Types).StringToList(Of Integer)(","), LAP.NotContainsOnly)
IsMusic = e.Value(Name_IsMusic).FromXML(Of Boolean)(True)
IsVideo = e.Value(Name_IsVideo).FromXML(Of Boolean)(True)
UserList.ListAddList(e(Name_UserList), LAP.IgnoreICopier)
End If
End Using
End If
End Sub
Friend Sub New(ByVal f As YTDataFilter)
Me.New(False)
With f
Types.ListAddList(.Types, LAP.NotContainsOnly)
IsMusic = .IsMusic
IsVideo = .IsVideo
UserList.ListAddList(.UserList)
End With
End Sub
Friend Sub Reset(Optional ByVal AddDefType As Boolean = True, Optional ByVal ClearUserList As Boolean = True)
Types.Clear()
If AddDefType Then Types.Add(YouTubeMediaType.Undefined)
IsMusic = True
IsVideo = True
If ClearUserList Then UserList.Clear()
End Sub
Friend Sub Update()
Using x As New XmlFile With {.AllowSameNames = True}
With x
.Add(Name_Types, Types.ListToStringE(","))
.Add(Name_IsMusic, IsMusic.BoolToInteger)
.Add(Name_IsVideo, IsVideo.BoolToInteger)
.Add(Name_UserList, String.Empty)
.Self()(Name_UserList).AddRange(UserList)
.Name = "FILTER"
.Save(File)
End With
End Using
End Sub
Friend Function Ready(ByVal Item As YouTubeMediaContainerBase, Optional ByVal IgnoreUserList As Boolean = False) As Boolean
With Item
If Not IsMusic Or Not IsVideo Then
If .IsMusic And Not IsMusic Then Return False
If Not .IsMusic And Not IsVideo Then Return False
End If
If Not Types.Contains(YouTubeMediaType.Undefined) AndAlso Not Types.Contains(.ObjectType) Then Return False
If Not IgnoreUserList AndAlso UserList.Count > 0 AndAlso Not UserList.Contains(Item) Then Return False
End With
Return True
End Function
Friend Overloads Sub RemoveAll(ByRef Data As List(Of IYouTubeMediaContainer))
Data.RemoveAll(Function(item) Not Ready(item))
End Sub
Friend Overloads Sub RemoveAll(ByRef Data As List(Of YouTubeMediaContainerBase))
Data.RemoveAll(Function(item) Not Ready(item))
End Sub
Friend Sub Populate(ByVal InitList As List(Of IYouTubeMediaContainer), ByVal DestList As List(Of IYouTubeMediaContainer), ByVal IgnoreUserList As Boolean)
DestList.Clear()
If InitList.Count > 0 Then InitList.ForEach(Sub(item) If Ready(item, IgnoreUserList) Then DestList.ListAddValue(item))
End Sub
End Class
End Namespace

View File

@@ -33,6 +33,7 @@ Namespace DownloadObjects.STDownloader
Me.BTT_CLEAR_DONE = New System.Windows.Forms.ToolStripMenuItem()
Me.BTT_CLEAR_ALL = New System.Windows.Forms.ToolStripMenuItem()
Me.BTT_SELECT_ALL = New System.Windows.Forms.ToolStripMenuItem()
Me.BTT_SELECT_NONE = New System.Windows.Forms.ToolStripMenuItem()
Me.TOOLBAR_BOTTOM = New System.Windows.Forms.StatusStrip()
Me.PR_MAIN = New System.Windows.Forms.ToolStripProgressBar()
Me.LBL_INFO = New System.Windows.Forms.ToolStripStatusLabel()
@@ -43,6 +44,7 @@ Namespace DownloadObjects.STDownloader
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_FILTER = New System.Windows.Forms.ToolStripButton()
Me.BTT_DOWN = New System.Windows.Forms.ToolStripButton()
Me.BTT_STOP = New System.Windows.Forms.ToolStripButton()
Me.SEP_LOG = New System.Windows.Forms.ToolStripSeparator()
@@ -50,7 +52,7 @@ Namespace DownloadObjects.STDownloader
Me.BTT_INFO = New System.Windows.Forms.ToolStripButton()
Me.BTT_DONATE = New System.Windows.Forms.ToolStripButton()
Me.BTT_BUG_REPORT = New System.Windows.Forms.ToolStripButton()
Me.BTT_SELECT_NONE = New System.Windows.Forms.ToolStripMenuItem()
Me.SEP_FILTER = New System.Windows.Forms.ToolStripSeparator()
SEP_2 = New System.Windows.Forms.ToolStripSeparator()
SEP_3 = New System.Windows.Forms.ToolStripSeparator()
MENU_DEL_CLEAR = New System.Windows.Forms.ToolStripDropDownButton()
@@ -134,6 +136,12 @@ Namespace DownloadObjects.STDownloader
Me.BTT_SELECT_ALL.Size = New System.Drawing.Size(185, 22)
Me.BTT_SELECT_ALL.Text = "Select all"
'
'BTT_SELECT_NONE
'
Me.BTT_SELECT_NONE.Name = "BTT_SELECT_NONE"
Me.BTT_SELECT_NONE.Size = New System.Drawing.Size(185, 22)
Me.BTT_SELECT_NONE.Text = "Select none"
'
'TOOLBAR_BOTTOM
'
Me.TOOLBAR_BOTTOM.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.PR_MAIN, Me.LBL_INFO})
@@ -169,7 +177,7 @@ Namespace DownloadObjects.STDownloader
'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, MENU_DEL_CLEAR, Me.SEP_LOG, Me.BTT_LOG, Me.BTT_INFO, Me.BTT_DONATE, Me.BTT_BUG_REPORT})
Me.TOOLBAR_TOP.Items.AddRange(New System.Windows.Forms.ToolStripItem() {Me.BTT_SETTINGS, Me.SEP_1, Me.MENU_ADD, Me.SEP_FILTER, Me.BTT_FILTER, SEP_2, Me.BTT_DOWN, Me.BTT_STOP, SEP_3, MENU_DEL_CLEAR, Me.SEP_LOG, Me.BTT_LOG, Me.BTT_INFO, Me.BTT_DONATE, Me.BTT_BUG_REPORT})
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)
@@ -222,6 +230,15 @@ Namespace DownloadObjects.STDownloader
Me.BTT_ADD_PLS_ARR.Text = "Add URL 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)." & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "Shift to a" &
"dd without downloading."
'
'BTT_FILTER
'
Me.BTT_FILTER.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Image
Me.BTT_FILTER.Image = CType(resources.GetObject("BTT_FILTER.Image"), System.Drawing.Image)
Me.BTT_FILTER.ImageTransparentColor = System.Drawing.Color.Magenta
Me.BTT_FILTER.Name = "BTT_FILTER"
Me.BTT_FILTER.Size = New System.Drawing.Size(23, 22)
Me.BTT_FILTER.Text = "Filter"
'
'BTT_DOWN
'
@@ -286,11 +303,10 @@ Namespace DownloadObjects.STDownloader
Me.BTT_BUG_REPORT.Size = New System.Drawing.Size(23, 22)
Me.BTT_BUG_REPORT.Text = "Bug report"
'
'BTT_SELECT_NONE
'SEP_FILTER
'
Me.BTT_SELECT_NONE.Name = "BTT_SELECT_NONE"
Me.BTT_SELECT_NONE.Size = New System.Drawing.Size(185, 22)
Me.BTT_SELECT_NONE.Text = "Select none"
Me.SEP_FILTER.Name = "SEP_FILTER"
Me.SEP_FILTER.Size = New System.Drawing.Size(6, 25)
'
'VideoListForm
'
@@ -337,5 +353,7 @@ Namespace DownloadObjects.STDownloader
Private WithEvents BTT_CLEAR_SELECTED As ToolStripMenuItem
Private WithEvents BTT_SELECT_ALL As ToolStripMenuItem
Private WithEvents BTT_SELECT_NONE As ToolStripMenuItem
Public WithEvents BTT_FILTER As ToolStripButton
Public WithEvents SEP_FILTER As ToolStripSeparator
End Class
End Namespace

View File

@@ -130,30 +130,31 @@
<data name="BTT_DELETE.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3
FeVFihZsqaBjRM2ouILiC0oUozGaaBSTJfuwfdg+bJnJnFuiyzKZMOp8ATJl4IYLAjqspfS9cNlLskjP
/qe0OmPZeJJfbu85z/k//z7n3Ht5NPqLimLMSUkfXV20aPiKTPbhOqn0FQxHBSb/J4ZUKvY3mez6yMKF
fReTkw0YigERgUkaAyaT8FZR0ZdDzc1k9OxZ0tHU5L/Csp15ItFSTEfPZIWPQYZ507Z580Pu9Gky1dpK
hisrPe+nptbLYmPjMT1TpHPJkg/u7d1LHhw7RkaPHiVWJFsOHCBtLHtdLhK9jpSwRUbU6jUQt3BnzhAO
a7mWFjJ15Ai5p9V6v5ZK30OKGETxrkokVir+oKmJPDx0iFiRNH7yJLnW3Oy/olLdyBKJUpD4TBG0pdhW
VWWlzqn4FMS5/fvJFIz5amqIOSHhLtJYIOJ1JCaeG66tJaMoYAHWgweJDQscJ06QG2hXB8t2ZwqFqUgO
FBlSKIrHKyutk6dOEQ5mpmCKw7qpPXvIxI4dpE2lcm2RSC4gtRSIefrFi19tUyqvt2/b5n+EpDEwDjcO
7IkbLXPu20euokiuUJj2E8MUjRkMtsnjxwMt4eCYQ3u5xkYyCXFzbq4nRSD4FMJGIAd0w3nRjESSiiLd
li1biG3nTmJvaCCO3buJC8W8EOpuaPD3qNV37peXW7nDhwOOOcxxyOXq65+IpwkE56BXCzKC4pEgEPxc
sTjtO4Wi64fqar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t0up9MqFQipOnaeD+eDpUQ0GXyUW
p3dkZ3fbNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jKy/PliETnsX47oM4F4DnxUPCXxcdn
dMjl3QMZGY89mzYRt0ZD3AxDXFIpcYlExBUdTVzz5pFuodDfnpTkZePi6IbWgUzwIphVPBSBdt2Sy/vH
IWiHmAM4ARUOEBVF+rKz/zKkpn6L/HdBFpiTeCBsNTUmm1ptsQsEz4sDOjaQnPzYrNXeT0tMLMeSWDA3
cZ/RuM9VUOCyw/1s4mPgIbBkZJA+ne7uW8uX52Dpf75WAuE2Gluca9e67TExz4k70XvaLio+CoYjI8kA
uJyT4++vqPjVwLK0TbMX8W7ffshRUuIJ59wpkZBHJSV/30hJ8VPxoaB4L+gCZoaZ7qf/ZLYiOIot9oIC
r10oDAg/05YFC8hgaenvx1eu7Oldv37ckpZGfoHobXATXKMFKMuWTf9oMNwpkMvpC/Lpq95TW9tMxR3h
xBMSyOCqVX/UM0wHUhtKMjPfvltWdn8QvQ+Jd4J20EaLqFTTPRUVvZ9otXTj+SCC59mwweqIiwvvHOJ1
CkU7Ek2AbqR4d37+G316/cidrKwn4pfBN+AS6E9Pf3xbr7+H3MWAz7PrdIMemSyscxPDfI+k+qB46EvF
31NYyPZVVIxcyMry/1v8q4gIchsFzBrNGPKKQDyvp7p687hG43ZDNOR8oLj4z53hxUPBb8zPz8PGDt/K
ziYXg+LmvLzpzzWaR6VLl36MnJkCiPntlZWHrWVl3h6W9VPnjUplJ8ZnEw8F37hihYK24wusM6vV0xfg
/CWBoBVzOjDTIkQEk5QU26vXf/agrMx5vrAQpy+yEeMMENJ5mjRL8C9VVb3zs043dlOrdZWkpNBvgR5I
AT2uT9bSH3FACdYFr3N9/F8A9GjSk7MevAbCPnDzAHVMP9b0Su/nEtQEff+/HIQWDBrj8f4B7zPYbtFn
HR8AAAAASUVORK5CYII=
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAV6SURBVEhLjZVrTFNnGMcfT4Wto2dcOtRkyBzigALlcmo5
dVNwXKRowRYEGSNiRi3jXiwQqYEQTVxQkyX7sH3YPmyZicYt0WUXBEaJjkumDNxwQUCHWErpFQ5zSxbo
s7xd200Fxz95c9qcc37//3me9wIAAKOZmQGGiIiP+7ZsmewKD/9of1jYywCwkdz7P01Ipexv4eE3pjZv
HrkSGVkAAAEAsMH3wFhlpeBmZuYXE83NOH3+PHbpdK4ulu1NoekdAOD3GO0JjTPMG6bi4gdcRwcunTuH
k4WF9g+io6vCAwODfSa927d/eLehAe+3t+N0WxsaOzpwpqkJO1n2hpimX1vLZEome9NUXDzDnT2LXHs7
cno9LrW24l2FwvFVWNj7ABDirkKfUGgk8Ps6HT5oaUFjayvOnTmDfc3Nri6p9Id4mo560mRCKs0yFRUZ
SXICX9LrkWtsxKWmJnSWlqIhNPQOALAAQEPP1q0XJsvLcVqnwxmdDo0nTqBJr8f506fxuk7n6mHZwTiB
INprMpGcnDVXWGhcfO895FpbcamlBTmdDpe0WlyoqMBOqdR6RCi8DAA57q9Qbdv2SqdEcuPa0aOuh1ot
zmq1ONfYiPPNzWhra0PL8ePYx7KDSQJBzE8MkzlbUGBaPHXKXRKuqQm5hgbk6upwsaICDUlJ9ig+/zMA
UAOA2NNw8GOEwuhOiWRw5sgRNNXUoLm6Gufr69Gq1aJDr8f+6mrXkEx2+15enpE7edKdmNNqkaupQa6q
ygeP4fMvAEA5AMR64JS3rP5JISEx15KTBwwlJS6zRoMWjQZtlZVor6pCZ309OmtrcbGhARcJuL4eudra
f+Dl5TggkTjEAgGBk+QiAHj+sanqNZGGhIh6EhIGTYcOoaWsDG1lZehQq9FZUYELGg0ulJbiQl4eOvfu
RadMhnaZDAdSUpyJNH0JAI55kvNXg3vlvzM4OLZHLB4cE4mW7YcPo00uRxvDoDUsDK00jVY/P7TyeNgv
ELi6IyIcbFAQaagGAOIA4IVnwb1yl+umWDw6R9No5vFwnsdDC4/nBrvHxo04kpDwZ0F09LcA8C4AxK8X
7paptLTSJJPNmPn8p+Eew7HIyGWDQnEvZuvWPAAIXDfcqVYft6anW800vSZ8lsfDBzwezsTG4ohSeeet
XbsSn1yMq8qmVust+/bZzAEBT8Etfn7uchH4NI+HkxSFYxSF3yQmukbz838tYFlSprVNHMeOtcxnZ9tX
S24RCvFhdvZf16OiXAQ+4YEPUxQOUBQaGGZllHzJWiYOtVpvTk93mAUCN/ixsmzahOM5Ob+f2rNnaPjA
gbmZmBj8haLwFkVhP0XhdWJAxs6dKz8WFNxOF4vJBvnvVm8vL28m8PnV4KGhOJ6a+qiKYXoAoDo7Lu7t
O7m598ZjY33wXorCborCTmIila4M5ecPf6pQkMb7uxtvP3jQOB8U9DScJE9NfaRJTu4GgEoAII0MqU9L
e31EpZq6HR/vg39HUfg1ReFVisJRkWj5lkp1FwC2uU3MSuW4PTx81eSVDPM9AFR54N6Tyl+bkcGO5OdP
XYqPd/0X/uWGDXhLJFo2yOWzAJAJAMEwVFJSPCeX22yhob7kY1lZf9SsDvfKvy4tLWVUqZy8mZCAVzxw
Q0rKykW5/GHOjh2f+AzIxtRdWHjSmJvrGGBZF0leJ5H0PgPulb969+5kUo6LLOsyyGQrl+Xy2Rf5/HMA
oPSViLzMREQEDqtUn9/PzbVcysgYpiiqDgAYABCsAffK/2pR0Ts/K5Wz/QqFNTsqipwFKgAI80xX37vk
RxAASABgv+e63uX/HACQqUlmzgEAeHXVtQAAPE9icliTK/m/HpEQZP9/yTOIoS/Y32gy2FQGa9GfAAAA
AElFTkSuQmCC
</value>
</data>
<metadata name="MENU_DEL_SEP_1.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
@@ -162,88 +163,91 @@
<data name="BTT_CLEAR_SELECTED.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
</value>
</data>
<data name="BTT_CLEAR_DONE.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3
FeVFihZsqaBjRM2ouILiC0oUozGaaBSTJfuwfdg+bJnJnFuiyzKZMOp8ATJl4IYLAjqspfS9cNlLskjP
/qe0OmPZeJJfbu85z/k//z7n3Ht5NPqLimLMSUkfXV20aPiKTPbhOqn0FQxHBSb/J4ZUKvY3mez6yMKF
fReTkw0YigERgUkaAyaT8FZR0ZdDzc1k9OxZ0tHU5L/Csp15ItFSTEfPZIWPQYZ507Z580Pu9Gky1dpK
hisrPe+nptbLYmPjMT1TpHPJkg/u7d1LHhw7RkaPHiVWJFsOHCBtLHtdLhK9jpSwRUbU6jUQt3BnzhAO
a7mWFjJ15Ai5p9V6v5ZK30OKGETxrkokVir+oKmJPDx0iFiRNH7yJLnW3Oy/olLdyBKJUpD4TBG0pdhW
VWWlzqn4FMS5/fvJFIz5amqIOSHhLtJYIOJ1JCaeG66tJaMoYAHWgweJDQscJ06QG2hXB8t2ZwqFqUgO
FBlSKIrHKyutk6dOEQ5mpmCKw7qpPXvIxI4dpE2lcm2RSC4gtRSIefrFi19tUyqvt2/b5n+EpDEwDjcO
7IkbLXPu20euokiuUJj2E8MUjRkMtsnjxwMt4eCYQ3u5xkYyCXFzbq4nRSD4FMJGIAd0w3nRjESSiiLd
li1biG3nTmJvaCCO3buJC8W8EOpuaPD3qNV37peXW7nDhwOOOcxxyOXq65+IpwkE56BXCzKC4pEgEPxc
sTjtO4Wi64fqar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t0up9MqFQipOnaeD+eDpUQ0GXyUW
p3dkZ3fbNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jKy/PliETnsX47oM4F4DnxUPCXxcdn
dMjl3QMZGY89mzYRt0ZD3AxDXFIpcYlExBUdTVzz5pFuodDfnpTkZePi6IbWgUzwIphVPBSBdt2Sy/vH
IWiHmAM4ARUOEBVF+rKz/zKkpn6L/HdBFpiTeCBsNTUmm1ptsQsEz4sDOjaQnPzYrNXeT0tMLMeSWDA3
cZ/RuM9VUOCyw/1s4mPgIbBkZJA+ne7uW8uX52Dpf75WAuE2Gluca9e67TExz4k70XvaLio+CoYjI8kA
uJyT4++vqPjVwLK0TbMX8W7ffshRUuIJ59wpkZBHJSV/30hJ8VPxoaB4L+gCZoaZ7qf/ZLYiOIot9oIC
r10oDAg/05YFC8hgaenvx1eu7Oldv37ckpZGfoHobXATXKMFKMuWTf9oMNwpkMvpC/Lpq95TW9tMxR3h
xBMSyOCqVX/UM0wHUhtKMjPfvltWdn8QvQ+Jd4J20EaLqFTTPRUVvZ9otXTj+SCC59mwweqIiwvvHOJ1
CkU7Ek2AbqR4d37+G316/cidrKwn4pfBN+AS6E9Pf3xbr7+H3MWAz7PrdIMemSyscxPDfI+k+qB46EvF
31NYyPZVVIxcyMry/1v8q4gIchsFzBrNGPKKQDyvp7p687hG43ZDNOR8oLj4z53hxUPBb8zPz8PGDt/K
ziYXg+LmvLzpzzWaR6VLl36MnJkCiPntlZWHrWVl3h6W9VPnjUplJ8ZnEw8F37hihYK24wusM6vV0xfg
/CWBoBVzOjDTIkQEk5QU26vXf/agrMx5vrAQpy+yEeMMENJ5mjRL8C9VVb3zs043dlOrdZWkpNBvgR5I
AT2uT9bSH3FACdYFr3N9/F8A9GjSk7MevAbCPnDzAHVMP9b0Su/nEtQEff+/HIQWDBrj8f4B7zPYbtFn
HR8AAAAASUVORK5CYII=
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAV6SURBVEhLjZVrTFNnGMcfT4Wto2dcOtRkyBzigALlcmo5
dVNwXKRowRYEGSNiRi3jXiwQqYEQTVxQkyX7sH3YPmyZicYt0WUXBEaJjkumDNxwQUCHWErpFQ5zSxbo
s7xd200Fxz95c9qcc37//3me9wIAAKOZmQGGiIiP+7ZsmewKD/9of1jYywCwkdz7P01Ipexv4eE3pjZv
HrkSGVkAAAEAsMH3wFhlpeBmZuYXE83NOH3+PHbpdK4ulu1NoekdAOD3GO0JjTPMG6bi4gdcRwcunTuH
k4WF9g+io6vCAwODfSa927d/eLehAe+3t+N0WxsaOzpwpqkJO1n2hpimX1vLZEome9NUXDzDnT2LXHs7
cno9LrW24l2FwvFVWNj7ABDirkKfUGgk8Ps6HT5oaUFjayvOnTmDfc3Nri6p9Id4mo560mRCKs0yFRUZ
SXICX9LrkWtsxKWmJnSWlqIhNPQOALAAQEPP1q0XJsvLcVqnwxmdDo0nTqBJr8f506fxuk7n6mHZwTiB
INprMpGcnDVXWGhcfO895FpbcamlBTmdDpe0WlyoqMBOqdR6RCi8DAA57q9Qbdv2SqdEcuPa0aOuh1ot
zmq1ONfYiPPNzWhra0PL8ePYx7KDSQJBzE8MkzlbUGBaPHXKXRKuqQm5hgbk6upwsaICDUlJ9ig+/zMA
UAOA2NNw8GOEwuhOiWRw5sgRNNXUoLm6Gufr69Gq1aJDr8f+6mrXkEx2+15enpE7edKdmNNqkaupQa6q
ygeP4fMvAEA5AMR64JS3rP5JISEx15KTBwwlJS6zRoMWjQZtlZVor6pCZ309OmtrcbGhARcJuL4eudra
f+Dl5TggkTjEAgGBk+QiAHj+sanqNZGGhIh6EhIGTYcOoaWsDG1lZehQq9FZUYELGg0ulJbiQl4eOvfu
RadMhnaZDAdSUpyJNH0JAI55kvNXg3vlvzM4OLZHLB4cE4mW7YcPo00uRxvDoDUsDK00jVY/P7TyeNgv
ELi6IyIcbFAQaagGAOIA4IVnwb1yl+umWDw6R9No5vFwnsdDC4/nBrvHxo04kpDwZ0F09LcA8C4AxK8X
7paptLTSJJPNmPn8p+Eew7HIyGWDQnEvZuvWPAAIXDfcqVYft6anW800vSZ8lsfDBzwezsTG4ohSeeet
XbsSn1yMq8qmVust+/bZzAEBT8Etfn7uchH4NI+HkxSFYxSF3yQmukbz838tYFlSprVNHMeOtcxnZ9tX
S24RCvFhdvZf16OiXAQ+4YEPUxQOUBQaGGZllHzJWiYOtVpvTk93mAUCN/ixsmzahOM5Ob+f2rNnaPjA
gbmZmBj8haLwFkVhP0XhdWJAxs6dKz8WFNxOF4vJBvnvVm8vL28m8PnV4KGhOJ6a+qiKYXoAoDo7Lu7t
O7m598ZjY33wXorCborCTmIila4M5ecPf6pQkMb7uxtvP3jQOB8U9DScJE9NfaRJTu4GgEoAII0MqU9L
e31EpZq6HR/vg39HUfg1ReFVisJRkWj5lkp1FwC2uU3MSuW4PTx81eSVDPM9AFR54N6Tyl+bkcGO5OdP
XYqPd/0X/uWGDXhLJFo2yOWzAJAJAMEwVFJSPCeX22yhob7kY1lZf9SsDvfKvy4tLWVUqZy8mZCAVzxw
Q0rKykW5/GHOjh2f+AzIxtRdWHjSmJvrGGBZF0leJ5H0PgPulb969+5kUo6LLOsyyGQrl+Xy2Rf5/HMA
oPSViLzMREQEDqtUn9/PzbVcysgYpiiqDgAYABCsAffK/2pR0Ts/K5Wz/QqFNTsqipwFKgAI80xX37vk
RxAASABgv+e63uX/HACQqUlmzgEAeHXVtQAAPE9icliTK/m/HpEQZP9/yTOIoS/Y32gy2FQGa9GfAAAA
AElFTkSuQmCC
</value>
</data>
<data name="BTT_CLEAR_ALL.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVDSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcTxXqC3lls3
FeVFihZsqaBjRM2ouILiC0oUozGaaBSTJfuwfdg+bJnJnFuiyzKZMOp8ATJl4IYLAjqspfS9cNlLskjP
/qe0OmPZeJJfbu85z/k//z7n3Ht5NPqLimLMSUkfXV20aPiKTPbhOqn0FQxHBSb/J4ZUKvY3mez6yMKF
fReTkw0YigERgUkaAyaT8FZR0ZdDzc1k9OxZ0tHU5L/Csp15ItFSTEfPZIWPQYZ507Z580Pu9Gky1dpK
hisrPe+nptbLYmPjMT1TpHPJkg/u7d1LHhw7RkaPHiVWJFsOHCBtLHtdLhK9jpSwRUbU6jUQt3BnzhAO
a7mWFjJ15Ai5p9V6v5ZK30OKGETxrkokVir+oKmJPDx0iFiRNH7yJLnW3Oy/olLdyBKJUpD4TBG0pdhW
VWWlzqn4FMS5/fvJFIz5amqIOSHhLtJYIOJ1JCaeG66tJaMoYAHWgweJDQscJ06QG2hXB8t2ZwqFqUgO
FBlSKIrHKyutk6dOEQ5mpmCKw7qpPXvIxI4dpE2lcm2RSC4gtRSIefrFi19tUyqvt2/b5n+EpDEwDjcO
7IkbLXPu20euokiuUJj2E8MUjRkMtsnjxwMt4eCYQ3u5xkYyCXFzbq4nRSD4FMJGIAd0w3nRjESSiiLd
li1biG3nTmJvaCCO3buJC8W8EOpuaPD3qNV37peXW7nDhwOOOcxxyOXq65+IpwkE56BXCzKC4pEgEPxc
sTjtO4Wi64fqar+9ro44gdtkIh4I+FDMt2sXmYTbSSqMew73AXG0t0up9MqFQipOnaeD+eDpUQ0GXyUW
p3dkZ3fbNm4kzq1biRt4jUbig8MJFJzABk6UlxPf6tXEp1YTD+jKy/PliETnsX47oM4F4DnxUPCXxcdn
dMjl3QMZGY89mzYRt0ZD3AxDXFIpcYlExBUdTVzz5pFuodDfnpTkZePi6IbWgUzwIphVPBSBdt2Sy/vH
IWiHmAM4ARUOEBVF+rKz/zKkpn6L/HdBFpiTeCBsNTUmm1ptsQsEz4sDOjaQnPzYrNXeT0tMLMeSWDA3
cZ/RuM9VUOCyw/1s4mPgIbBkZJA+ne7uW8uX52Dpf75WAuE2Gluca9e67TExz4k70XvaLio+CoYjI8kA
uJyT4++vqPjVwLK0TbMX8W7ffshRUuIJ59wpkZBHJSV/30hJ8VPxoaB4L+gCZoaZ7qf/ZLYiOIot9oIC
r10oDAg/05YFC8hgaenvx1eu7Oldv37ckpZGfoHobXATXKMFKMuWTf9oMNwpkMvpC/Lpq95TW9tMxR3h
xBMSyOCqVX/UM0wHUhtKMjPfvltWdn8QvQ+Jd4J20EaLqFTTPRUVvZ9otXTj+SCC59mwweqIiwvvHOJ1
CkU7Ek2AbqR4d37+G316/cidrKwn4pfBN+AS6E9Pf3xbr7+H3MWAz7PrdIMemSyscxPDfI+k+qB46EvF
31NYyPZVVIxcyMry/1v8q4gIchsFzBrNGPKKQDyvp7p687hG43ZDNOR8oLj4z53hxUPBb8zPz8PGDt/K
ziYXg+LmvLzpzzWaR6VLl36MnJkCiPntlZWHrWVl3h6W9VPnjUplJ8ZnEw8F37hihYK24wusM6vV0xfg
/CWBoBVzOjDTIkQEk5QU26vXf/agrMx5vrAQpy+yEeMMENJ5mjRL8C9VVb3zs043dlOrdZWkpNBvgR5I
AT2uT9bSH3FACdYFr3N9/F8A9GjSk7MevAbCPnDzAHVMP9b0Su/nEtQEff+/HIQWDBrj8f4B7zPYbtFn
HR8AAAAASUVORK5CYII=
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAV6SURBVEhLjZVrTFNnGMcfT4Wto2dcOtRkyBzigALlcmo5
dVNwXKRowRYEGSNiRi3jXiwQqYEQTVxQkyX7sH3YPmyZicYt0WUXBEaJjkumDNxwQUCHWErpFQ5zSxbo
s7xd200Fxz95c9qcc37//3me9wIAAKOZmQGGiIiP+7ZsmewKD/9of1jYywCwkdz7P01Ipexv4eE3pjZv
HrkSGVkAAAEAsMH3wFhlpeBmZuYXE83NOH3+PHbpdK4ulu1NoekdAOD3GO0JjTPMG6bi4gdcRwcunTuH
k4WF9g+io6vCAwODfSa927d/eLehAe+3t+N0WxsaOzpwpqkJO1n2hpimX1vLZEome9NUXDzDnT2LXHs7
cno9LrW24l2FwvFVWNj7ABDirkKfUGgk8Ps6HT5oaUFjayvOnTmDfc3Nri6p9Id4mo560mRCKs0yFRUZ
SXICX9LrkWtsxKWmJnSWlqIhNPQOALAAQEPP1q0XJsvLcVqnwxmdDo0nTqBJr8f506fxuk7n6mHZwTiB
INprMpGcnDVXWGhcfO895FpbcamlBTmdDpe0WlyoqMBOqdR6RCi8DAA57q9Qbdv2SqdEcuPa0aOuh1ot
zmq1ONfYiPPNzWhra0PL8ePYx7KDSQJBzE8MkzlbUGBaPHXKXRKuqQm5hgbk6upwsaICDUlJ9ig+/zMA
UAOA2NNw8GOEwuhOiWRw5sgRNNXUoLm6Gufr69Gq1aJDr8f+6mrXkEx2+15enpE7edKdmNNqkaupQa6q
ygeP4fMvAEA5AMR64JS3rP5JISEx15KTBwwlJS6zRoMWjQZtlZVor6pCZ309OmtrcbGhARcJuL4eudra
f+Dl5TggkTjEAgGBk+QiAHj+sanqNZGGhIh6EhIGTYcOoaWsDG1lZehQq9FZUYELGg0ulJbiQl4eOvfu
RadMhnaZDAdSUpyJNH0JAI55kvNXg3vlvzM4OLZHLB4cE4mW7YcPo00uRxvDoDUsDK00jVY/P7TyeNgv
ELi6IyIcbFAQaagGAOIA4IVnwb1yl+umWDw6R9No5vFwnsdDC4/nBrvHxo04kpDwZ0F09LcA8C4AxK8X
7paptLTSJJPNmPn8p+Eew7HIyGWDQnEvZuvWPAAIXDfcqVYft6anW800vSZ8lsfDBzwezsTG4ohSeeet
XbsSn1yMq8qmVust+/bZzAEBT8Etfn7uchH4NI+HkxSFYxSF3yQmukbz838tYFlSprVNHMeOtcxnZ9tX
S24RCvFhdvZf16OiXAQ+4YEPUxQOUBQaGGZllHzJWiYOtVpvTk93mAUCN/ixsmzahOM5Ob+f2rNnaPjA
gbmZmBj8haLwFkVhP0XhdWJAxs6dKz8WFNxOF4vJBvnvVm8vL28m8PnV4KGhOJ6a+qiKYXoAoDo7Lu7t
O7m598ZjY33wXorCborCTmIila4M5ecPf6pQkMb7uxtvP3jQOB8U9DScJE9NfaRJTu4GgEoAII0MqU9L
e31EpZq6HR/vg39HUfg1ReFVisJRkWj5lkp1FwC2uU3MSuW4PTx81eSVDPM9AFR54N6Tyl+bkcGO5OdP
XYqPd/0X/uWGDXhLJFo2yOWzAJAJAMEwVFJSPCeX22yhob7kY1lZf9SsDvfKvy4tLWVUqZy8mZCAVzxw
Q0rKykW5/GHOjh2f+AzIxtRdWHjSmJvrGGBZF0leJ5H0PgPulb969+5kUo6LLOsyyGQrl+Xy2Rf5/HMA
oPSViLzMREQEDqtUn9/PzbVcysgYpiiqDgAYABCsAffK/2pR0Ts/K5Wz/QqFNTsqipwFKgAI80xX37vk
RxAASABgv+e63uX/HACQqUlmzgEAeHXVtQAAPE9icliTK/m/HpEQZP9/yTOIoS/Y32gy2FQGa9GfAAAA
AElFTkSuQmCC
</value>
</data>
<metadata name="MENU_DEL_SEP_2.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
@@ -252,30 +256,31 @@
<data name="MENU_DEL_CLEAR.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVGSURBVEhLjZVtTFNXGMcLQmdHO6AdarLSOcQBAgV6a1/c
NGgBKVqwpYKOETWj4gqKLyhRjMZoovElWbIP24ftw5aZzLgluiwbE0aNjpdNGejUIW9iKaX0FS5zSxbo
2f+UVmcsG0/yy+095zn/59/nnHsvh0ZPfn6MJSnpk+tLlvRfk0g+3iAWv4bhqMDk/0SfQqF6JJHcHFi8
uPtKcrIRQzEgIjBJ457ZzL+Vn/9VX0MDGb5wgfxeX++/plK1KgWC5ZiOns0KH70M87Z969bH7NmzZOr8
edJfVub5MDW1RhIbG4/p2SKty5Z99HD/fjJ04gQZPn6c2JBsPXSINKlUN6UCwZtICVtkQK1eB3Ere+4c
YbGWbWwkU8eOkYc6nfcbsfgDpAhBFOe6SGSj4kP19eTxkSPEhqSx06fJYEOD/5pC8VOmQJCCxOeKoC0F
9vJyG3VOxacgzh48SKZgzFdZSSwJCfeRpgICTkti4sX+qioyjAJWYDt8mNixYPzUKfII7WpRqToy+PxU
JAeK9MlkBWNlZbbJM2cICzNTMMVi3dS+fWRi1y7SpFC4tolEl5FaBIQcw9KlrzfJ5Tcf7NjhH0HSKBiD
m3HsiRstcx44QK6jSA6fn/Yrw+SPGo32yZMnAy1h4ZhFe9m6OjIJcUtOjieFx/scwiYgBXTDOdGMSJSK
Ih3WbduIffdu4qitJeN79xIXinkhZK2t9Xeq1XcGS0ps7NGjAccs5ljksjU1T8XTeLyL0KsC6UHxSBAI
bo5QmPaDTNY+UFHhd1RXEydwm83EAwEfivn27CGTcDtJhXHP4j4gjva2y+VeKZ9PxanzFWAheHZUg8FV
CIUrWrKyOuybNxPn9u3EDbwmE/HB4QQKTmADJ0pKiG/tWuJTq4kHtCuVvmyB4BLW7wTUOQ+8IB4K7sr4
+PQWqbRjqKBg2rNlC3FrtcTNMMQlFhOXQEBc0dHEtWABsfL5/uakJK8qLo5uaDXIAC+DOcVDEWjXLam0
ZwyCDoiNAyegwgGiokhPVtZfxtTU75D/PsgE8xIPhL2y0mxXq60OHu9FcUDHBpOTpy063WBaYmIJlsSC
+Yn7TKYDLo3G5YD7ucRHwWNgTU8n3Xr9/XdWrcrG0v98rQTCbTI1OtevdztiYl4Qd6L3tF1UfBj0R0aS
e+Budra/p7T0gVGlom2au4h3584j44WFnnDOnSIRGSks/PtRSoqfivcFxbtAO7jNMDM99J/MVQRHsdGh
0XgdfH5A+Lm2LFpEeouK/ji5Zk1n18aNY9a0NPIbFQVt4AawgJ9Xrpz5xWi8o5FK6Qvy2aveU1XVQMXH
w4knJJDevLwnNQzTgtTawoyMd+8XFw/2ovch8VbQDJpAm0Ix01la2vWZTkc3ngsiOJ5Nm2zjcXHhnUO8
WiZrRqIZ0I0U7s3NfavbYBi4k5n5VPx78C24CnqUyunbBsND5C4FXI5Dr+/1SCRhnZsZ5kck1QTFQ18q
7r68PFV3aelAe2am/9/iX0dEkNsKxbRFqx1FXj6I53RWVGwd02rdboiGnN8rKPhzd3jxUHDrcnOV2Nj+
W1lZ5EpQ3KJUznyp1Y4ULV/+KXJmCyAWNpeVHbUVF3tHVCp/r0bzpE4ub8X4XOKh4JpWr5bRdrRh3Q21
euYynL/C453HnB7MtggRwSQlxXYZDF8MFRc7L+Xl4fRF1mGcAXw6T5PmCO7V8vL37ur1o206naswJYV+
CwxADOhxfbqW/ogDcrAheJ3v4/8SoEeTnpyN4A0Q9oFbAKhj+rGmV3o/n6Am6Pv/1SC0YNAYh/MPME3a
dCWdzmEAAAAASUVORK5CYII=
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAV8SURBVEhLjZVrTFNnGMcf3wpbR8+4dKjJgDnEAQLl0lJO
3TTcpWjBFgQZI2JGLeNeLBCpgRBNNIjJkn3YPmwftsxkxi3RZdmYMCBDgagM3GBBLg6wlNIrlLklC/RZ
3q7tpoLjn5ycNuec3///Ps97AQCA0YwMn97Q0I/7du2auhkS8tHhoKBXAWA7ffZ/mhSL2d9CQvqnd+4c
uR4Wlg8APgCwzfPCWEUF725GxpeTTU04e/kyjms0jpss25PEMHsBwOsJ2lOaEArf0hcVzdnb23G1owOn
CgosH0REVIb4+vp7THr27PnwQX09Pmxrw9nWVtS1t+N8YyN2smy/gGHe2MxkWiJJ1RcVzdsvXUJ7Wxva
tVpcbWnBBzKZ9eugoPcBIMBZhT4+X0fhDzUanGtuRl1LCy5euIBTTU2Om2LxrRiGCX/aZFIsztQXFupo
cgpf1WrR3tCAq42NaCspwd7AwHEAYAGAge7g4CtTZWU4q9HgvEaDujNnUK/V4tL58zij0Ti6WXYwmseL
cJtMJiRkLhYU6FYuXkR7SwuuNjejXaPBVbUal8vLsVMsNp3g868BQLZzFIrdu1/rFIn6x06edDxSq3FB
rcbFhgZcampCc2srGk+fxj6WHYzn8SJ/EgozFvLz9SvnzjlLYm9sRHt9Pdpra3GlvBx74+Mt4VzuZwCg
BACBq+HgJeTzIzpFosH5EydQX12NhqoqXKqrQ5NajVatFmerqhxDEsn9mdxcnf3sWWdiu1qN9upqtFdW
euCRXO4VACgDgCgXnLjL6h0fEBD5fULCwGRxscOgUqFRpUJzRQVaKivRVleHtpoaXKmvxxUKrqtDe03N
P/CyMhwQiawCHo/CafJ9APDiE1PVbSIOCNjXHRs7qD92DI2lpWguLUWrUom28nJcVqlwuaQEl3Nz0ZaS
gjaJBC0SCQ4kJdniGOYqAJxyJeduBHfLO9HfP6pbIBicTk9fsxw/jmapFM1CIZqCgtDEMGjy8kITh4Oz
PJ6jKzTUyvr50YaqACAaAF56HtwtZ7nuCgSjiwyDBg4HlzgcNHI4TrDz2r4dR2Jj/8yPiPgWAN4DgJit
wp3Sl5RU6CWSeQOX+yzcZTgVFrbWK5PNRAYH5wKA75bhNqXytCktzWRgmE3hCxwOznE4OB8VhSNy+fjb
+/fHPb0YN5RZqdQaDx0yG3x8noEbvbyc5aLwWToCQnCMEByNi3OM5uX9ms+ytEybm1hPnWpeysqybJTc
yOfjo6ysv2bCwx0UPumCDxOCA4TgHaFwfZSOZDMTq1KpNaSlWQ08nhP8RFl27MCJ7Ozfzx08ODR85Mji
fGQk/kII3iMEbxOCPxKCvYTgYGLi+p38/PtpAgHdIP/d6i1lZU0UvrQRPDAQJ1JTH1cKhd0AUJUVHf3O
eE7OzERUlAfeQwh2EYKdhGC/WLw+lJc3/KlMRhvv7Wy85ehR3ZKf37Nwmjw19bEqIaELACoAgDYyoC45
+c0RhWL6fkyMB/4dIfgNIXiD9iQxce2eQvEAAHY7TQxy+YQlJGTD5BVC4Q8AUOmCu08qb3V6OjuSlzd9
KybG8V/4V9u24T2RaK1XKl0AgAwA8Ieh4uKiRanUbA4M9CQfy8z8o3pjuFvetcnJSaNy+dTd2Fi87oL3
JiWtfyGVPsreu/cTjwHdmLoKCs7qcnKscyzrmEhJeVwrEvU8B+6Wt/LAgQRajn6WdfRJJOvXpNKFl7nc
DgCQe0pEPxaGhvoOKxSfP8zJMV5NTx8mhNQCgBAAeJvA3fK+UVj47s9y+cJtmcyUFR5OzwIFAAS5pqvn
W/rDDwBEAHDYdd/q8n8BAOjUpDPnCAC8vuFaAACOKzE9rOmd/t+KaAi6/7/iuqihJ9jfOenaM1O34/0A
AAAASUVORK5CYII=
</value>
</data>
<metadata name="TOOLBAR_BOTTOM.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
@@ -287,105 +292,123 @@
<data name="BTT_ADD.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAANoSURBVEhLrZVrSFNhGMePTbwkzoVlzUuUZdkizzIvFE5r
K51a0bzU8kYXp2W4El1SGdL1axh9iz5IRPQljD51gUyitIvOOjNdytxNZ4Hh9xP/nvc0yQ+iYeeBPwze
l//veZ/3/55xi1VYh6Iz/LYC84mtBbctvZiRxq4R9QM67BvUwSiQHDqUuwwSJLht6SUBPmvE5T0clO84
xH7gkPApFBZfkYwAQSPG9HJY9SEE6wbCoRWUOB8olRfAOk+2hyN9KAa7h1fj+o8q+QA8AZLtEcgcUqHA
GU/z34iO6Tr5ACcIkOFQociZgIrxzahza3FvxiofoFlIE43fElHt3oIGTzqavbvwYMb2b4CFcj6r0td5
v6rcqWj0ZKB1QocrgXx0/mzByoeRC0r9OKpT6tAwm3HKd/GwDiVOPWpcBWj0HsQFfzluBGrodwYu+nNx
NVCAa1NGdE634P60TQLdnbbi1o9aXJ2qxHl/GY2wGA3uI1B3RUECMHPlW8p4H4fEgTC6VKWUFNNYMo67
t+KsNxuXJvaQgRE3p4okwJXJfDLToc6jxeGxFBhG1oB3KJHUHwYV+RxxmP4CWOfMnC3yjmjarEZp0Pyc
Lxttfj2uBYwEyMflST1s/hy6ix04Nq7BodH1yBuOwzYhGvEDoVC95xBJj7J0cA6geEiHtWSupQ4MTrUU
Q4uHR5MvCxcndqONTC/482Dz5eCcNxP1tFbt1uDA6Drk0kkl8/5QsMfIzEOeczC9nwM46tyLnUOxFMNE
VLpSYaXLbKGktJIhM2327ZLURKOqp5GwNO0fXYucr3HY8iUK6o8KRNOII7o5cM9IXQR4MwdwmnJuE7Ri
u1BNOkmqJ9WRTpHOiHe+nybzLFjcPMzjm1A0moRWbw0KX+X9qhW2i1UCL5pJWhL/mfSJ1MeLEoDFdL6I
zYptahdaRYsnDWZXCgrplNl02gaPWVpbUCymixXb2C40iYfpXgpGErCDXvQGeySOuUokk+C2pdcfQIOY
PxIvfejY1zSWklLhDM74f+sPoFbc7ohBIiVlBcWZ/TeUzeb8f4uZWIVKMak/HCqK4fLXHBQvKCV2GQEn
KGHMPII6X8Zi+JQAvTICKil+kdQ5Rw+Ie0J6RIAeGQFlX0zSSEx9JHpApm7SS7kAlGVmNK8WzTnH/Qao
hKygM1JCJAAAAABJRU5ErkJggg==
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAOLSURBVEhLrZVfTFNXHMePQvhjQ+nSTVcoxjH8d7dLO0XJ
CFdc62hX1OwKKJM/cQ7K7AKTQCE6TSM6X43L3pY9NMuy7MWw7GlqohLjKM5S9dwOOkzpvS20M2Hx/Zjv
cm9FkRjUcL/J7+mcfD8nJ59zLyEvSd7FnGD+dzl40ahrS/e/dtQiLsIxx4SAj+8KcFMBbklAc9ypQZbu
f+1ogHscWzNKYPyTwHyboPROLrqSHh0BlGPFYwRv3V6FDRP5sFMjBtON+gLUk5dH8rEtWozdk+tw7mGb
fgCecqw8UoAdURNcsRI0xytwcd6rH+Ao5ViVZIInVorDM5vhTdjx46Ne/QD99H3m/seK9sRW+ORt6Fdq
8PMj/6sBlvN8YcRrdY/bElvQI1dhaFbAmXQ9gv8N4M1fCpcdyyVDUDuhc8FxSUDDpIADMQc64i70KPtx
ItWMb9Md6FGqcDK1C8NpF85m3AjOD+Cneb8G+mG+FxcedmI404rBVBO8iQb4EodgGTFAA6jlxlsE5hCB
dSIPNmrUTBEflOPzxHv4WqnGN7MfYTjjxvmMRwOcmavHYEqAV7bj4IONcE69DZtkRFk4D6YQwSFJfAZQ
T66Wq4s2qQjOKQsan5QfT1bjVMqBs2k3hjP1OD3ngD9VC5+8HUdmOHw6/Q7qJteCp0UomciFaZygcJSg
8e4iQENUwPpwHuySEc6YRdOwS7ahL7kTJ2d349ScAydSdfAna3Fc2YFu2Yb2BId90xuwa3JdtjycC/Ux
quWrLhOI44sAn8X24MOoGZ6YFa3xLeiVqzCg1GAoWauV9idrtOlTqtEt2zWb9k6vR+3fa7H1vgGWv3JQ
dIug4DoB+YOAjBCINxcBjlGO+WklC9B2FqBfsADtZgHqZQH6JQvQr9j3/x5Dn7ITXQkbWmY2wTNdhiGl
A65rdY87qY21UZ61UJ5VUp7x93jG3+EZH+KZBlA1XarXc6qNGBCgQ6xLrkRLfCM+iVlRHTXDJ7doa8uO
qunLkgX0sYPxCrimSrFdMuHdSCGOxA9kr2ClyQJ8rH6qRPvQqV9T8zjB4diTO15psoBO9oFUDGs4F2+E
CNR/Q9OC5yuNWtJLW1lZOB+mMYI1NwhyrhCIER0BR2klU8sLRglWqxr+TiCO6QhopTwrvEFALhOQ3wjI
rwTiqI6ApvuidiViSNQekHhdhHhVL8AlQ/Cp10vnFTz/HxZurHotmkteAAAAAElFTkSuQmCC
</value>
</data>
<data name="BTT_ADD_PLS_ARR.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAANqSURBVEhLrZVbSJNhGMe/mjhN1IWdpjPMssOitqklidPS
cksrmufyQAcPZWSKzqEZUla3UXQXXYyI6CaMrjpcmEV5KF31zXQt5k42Cxbdf/Hveb8meSEa+j3wh8H7
8v897/P+32/cQhV+U2aV35JhLrG10LbFFzNS29RC7qgeBz7oYeRJdj1KXXkiJLRt8SUCPqqFFf0cYt5y
iBvmkPA+DHW+AgkBvFqIHeCwengZkkbl0PIxaA8USwfYTgDWebJNjtSxWOwdX4urP6qkA+gIkGyLwK4x
BQyOeJr/JtwM1ksHOEWAdLsCBY4EHJ/cgnq3Fnd/NUkHaOW1gvGLCtXubWj0pKLVm4n7v8z/B5gv5zMq
fZXzu8q9Fec96bBM6XE5kA/rzzasehA5r5SPoqxih3kzGad8F47rUeTIRY3LgPPeI+jwl+JaoIZ+p6PT
n40rAQN6po2wBttwL2gWQXeCTbjxoxZXpivR7i+hERai0V0OZW8URAAzj3lDGR/koBoNh4ZiyJJi+pqM
k+7tuODNwMWpfWRgxPXpAhFw+Vs+melR79Gi7GsK8ibWQWOPQeJIOBTkU243/QOwzpk5W9TYo2mzEsUh
82ZfBrr8uegJGAmQj0vfcmH2Z9FdpOHEpBpHnRuQM74GO/hoxI+GQTHEIZIeZfGHWYDCMT3Wk7mWOshz
KMUY1nk0aPHtRufUXnSRaYc/B2ZfFpq9u9BAa9VuNQ47k5BNJxXNR8LAHiMzX/aMg2loFuCYYz/2jMVR
DFWodG1FE11mGyXFQobMtNWXKaqFRtVAI2FpOuRcj6zPa7DtUxSU72SIphFH9HHgnpJ6CfB6FuAs5dzM
pwrdfDXpNKmBVE86Qzon3P5+lsx3o86tQcXkZhQ4E2Hx1qCwL+d3LZ8mVPE6oYKUStJ9JL0nDeoEEcBi
OlfEZsQ2dfMWoc6zExWuFBykU2bQaRs9FeLavGIxXajYxm6+RSijezFMJCCNXvRGWyROuIpEk9C2xddf
QKOQPxEvfujY1zSOknLcEZrxUusvoFbQ2WOhoqSspDiz/4aSmZwvtZhJE18pJI7IoaAYrnjJQfacUmKT
EHCKEsbMI6jz5SyGTwgwICGgkuIXSZ1z9IC4x6SHBOiXEFDyySSOxDRIogdk6iO9kApAWWZGc2rBnHPc
H0WkrMjY2947AAAAAElFTkSuQmCC
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAOKSURBVEhLrZVfTFNXHMePg/DHhlLTqSsU41A3vUZPVZSM
cMW1G9SyLV4BZfIn6qBoF5gECtG5NOK218Vlb8seGrMsvhjMnuaWTMiiFLVUPbdChym9t4V2Jiy+H/M1
91Y2RhbUcL/J7+mcfD8nJ59zLyEvSN6lnGD+tzn4v9HWlu5/5WhFQkTgzgkR798T4WYi3LKIprhLhyzd
/8rRAfcFvnqUwHyLwHqboPRuLjqTHgMBTODFYwRrb6/Cxol8OJgZA+kGYwHaycsj+dgdLcaByfX48nGr
cQDKBF4eKcDeqAV1sRI0xTfj0rzXOMBJJvAK2QJPrBTHZt6GN+HAD096jAP0sZ3c/acdbYlt8Cm70adW
4ccn/pcDLOf5wjSM1DxtTWxFt1KBwVkRF9K1CP7dj9d/Klx2bFdNQf2ErgXHZRH1kyIOx5xoj9ehW/0I
Z1NN+Crdjm61AudS+zGUrsPFjBvB+X5cnvfroO/ne/DN4w4MZVowkGqEN1EPX+IobMMm6ACt3HyTwBoi
sE/kgTKzbor0qBwnEtvxmVqJz2ffxVDGja8zHh1wYa4WAykRXsWBI4+2wDX1BqhsRlk4D5YQwVFZ+heg
nVwr1xapXATXlA0Nz8vPJCtxPuXExbQbQ5lafDHnhD9VDZ+yB8dnBByafhM1k+uwgxWhZCIXlnGCwlGC
hnuLAPVRERvCeXDIZrhiNl3DToWiN7kP52YP4PycE2dTNfAnq3FG3YsuhaItIeDD6Y3YP7k+Wx7OhfYY
tfJV1wmk8UWAj2Pv4Z2oFZ6YHS3xrehRKtCvVmEwWa2X9iWr9OlVK9GlOHSbPpjegOqH67DtgQm2Ozko
uklQcIOA/EJAhgmkPxYBTjOB+5mDB1gbD7BPeIB18QDz8gA7xQPsU/7dX6fRq+5DZ4KieeYteKbLMKi2
4+DvNU872C7eyihvZpQ7GOX0PuX0LuU0RLkO0DRdqtd/VBs2IcAGeaeyE83xLTgYs6MyaoVPadbXlh1N
0xclC+jlR+KbUTdVij2yBZsihTgeP5y9gpUmC/Dx2qkS/UOnfU2t4wTHYs/veKXJAjr4LrkY9nAu1oQI
tH9D44LnK41W0sNaeFk4H5YxgtUjBDm/EkgRAwEnmYNr5QWjBK9pGv5MII0ZCGhhlBeOEJDrBOQaAblC
II0aCGh8IOlXIoUk/QFJNyRIvxkFuGoK/uP10nkJz58BqISsoFjBRioAAAAASUVORK5CYII=
</value>
</data>
<data name="MENU_ADD.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
QmCC
</value>
</data>
<data name="BTT_FILTER.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIFSURBVDhPpZLtS1NhGMbPPxJmmlYSgqHiKzGU1EDxg4iK
YKyG2WBogqMYJQOtCEVRFBGdTBCJfRnkS4VaaWNT5sqx1BUxRXxDHYxAJLvkusEeBaPAB+5z4Jzn+t3X
/aLhnEfjo8m+dCoa+7/C3O2Hqe0zDC+8KG+cRZHZhdzaaWTVTCLDMIY0vfM04Nfh77/G/sEhwpEDbO3t
I7TxE8urEVy99fT/AL5gWDLrTB/hnF4XsW0khCu5ln8DmJliT2AXrcNBsU1gj/MH4nMeKwBrPktM28xM
cX79DFKrHHD5d9D26hvicx4pABt2lpg10zYzU0zr7+e3xXGcrkEB2O2TNec9nJFwB3alZn5jZorfeDZh
6Q3g8s06BeCoKF4MRURoH1+BY2oNCbeb0TIclIYxOhzf8frTOuo7FxCbbVIAzpni0iceEc8vhzEwGkJD
lx83ymxifejdKjRNk/8PWnyIyTQqAJek0jqHwfEVscu31baIu8+90sTE4nY025dQ2/5FIPpnXlzKuK8A
HBUzHot52djqQ6HZhfR7IwK4mKpHtvEDMqvfCiQ6zaAAXM8x94aIWTNrLLG4kVUzgaTSPlzLtyJOZxbb
1wtfyg4Q+AfA3aZlButjSfxGcUJBk4g5tuP3haQKRKXcUQDOmbvNTpPOJeFFjordZmbWTNvMTHFUcpUC
nOccAdABIDXXE1nzAAAAAElFTkSuQmCC
</value>
</data>
<data name="BTT_STOP.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAVCSURBVEhLjZVtTFNXGMcLQmdHO6AdarLCHOJ4L9Bby62b
iuNFihZsqaBjRM2ouILiC0oUozGauPiSLNmH7cP2YctMZtwSdcvGhFEzFJjCihsuCOiwltL3wmUvySI9
+5/S6oxl40l+ub3nPOf//Pucc+/l0RgoLo4xJSd/dG3JkpGrSUkfrpdKX8JwVGDyf2JYqWR/S0rqGl28
2HwpJUWPoRgQEZikMWg0Cm8VF38x3NJCxs6dI9ebm/1XWbYzXyRajuno2azwMcQwr9u2bHnAnT5Nps+e
JSNVVZ7309IakmJj4zE9W6Rz2bIP7u7bR+4fP07Gjh0jViRbDh4kbSzbJROJXkVK2CKjKtUbELdwZ84Q
Dmu51lYyffQouavReK9Ipe8hRQyieNckEisVv9/cTB4cPkysSJo4dYrcbGnxX1Uqr2eLRKlIfKoI2lJi
q662UudUfBri3IEDZBrGfLW1xJSQcAdpLBDxOhITz4/U1ZExFLAA66FDxIYFjpMnSR/a1cGyPVlCYRqS
A0WG5fKSiaoq69S77xIOZqZhisO66b17yeTOnaRNqXRtlUguIrUMiHm6pUtfblMourq2b/c/RNI4mIAb
B/bEjZY59+8n11AkTyhM/4lhisf1etvUiROBlnBwzKG9XFMTmYK4KS/PkyoQfAphA5ABuuG8aEYiSUOR
HsvWrcS2axexNzYSx549xIViXgiZGxv9vSrV7XsVFVbuyJGAYw5zHHK5hobH4ukCwXno1YHMoHgkCAQ/
TyxO/04u7/6xpsZvr68nTuA2GokHAj4U8+3eTabgdooK457DfUAc7e1WKLwyoZCKU+cZYCF4clSDwVeK
xRkdOTk9tk2biHPbNuIGXoOB+OBwEgUnsYGTFRXEt3Yt8alUxAO68/N9uSLRBazfAahzAXhGPBT8FfHx
mR0yWc8gwzzybN5M3Go1cTMMcUmlxCUSEVd0NHEtWEDMQqG/PTnZy8bF0Q2tB1ngeTCneCgC7bolkw1M
QNAOMQdwAiocICqKmHNy/tKnpX2D/HdANpiXeCBstbVGm0plsQsEz4oDOjaYkvLIpNHcS09MrMCSWDA/
cZ/BsN9VWOiyw/1c4uPgAbBkZhKzVnvnzZUrc7H0P18rgXAbDK3Odevc9piYZ8Sd6D1tFxUfAyORkWQQ
mHJz/QOVlb/qWZa2ae4i3h07DjtKSz3hnDslEvKwtPTvvtRUPxUfDor3g25ahGFmBug/masIjmKrvbDQ
axcKA8JPtWXRIjJUVvb7idWre/s3bJiwpKeTXyDaB26AH2gByooVMzf1+tuFMhl9QT551Xvq6lqouCOc
eEICGVqz5o8GhulAamNpVtZbd8rL7w2h9yHxTtAO2mgRpXKmt7Ky/xONhm48H0TwPBs3Wh1xceGdQ7xe
Lm9HohHQjRTvKSh4zazTjd7Ozn4s/i34GlwGAxkZj/p0urvIXQr4PLtWO+RJSgrr3Mgw3yOpISge+lLx
9xYVsebKytGvsrP9/xb/MiKC9KGASa0eR14xiOf11tRsmVCr3W6IhpwPlpT8uSu8eCj4TQUF+djYkVs5
OeRSUNyUnz/zuVr9sGz58o+RM1sAsbC9quqItbzcO8Cyfuq8SaHoxPhc4qHgG1atktN2XME6k0o1cxHO
XxAIzmJOC2ZbhIhgkpNj+3W6z+6XlzsvFBXh9EU2YZwBQjpPk+YI/uXq6rd/1mrHb2g0rtLUVPot0AEp
oMf18Vr6Iw4owPrgdb6P/3OAHk16cjaAV0DYB24BoI7px5pe6f18gpqg7/8Xg9CCQWM83j84CNjeVkuE
bAAAAABJRU5ErkJggg==
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAV6SURBVEhLjZVrTFNnGMcfzxG2jp5x6VCTIXOI414up5ZT
NwXHRYoWbEGQMSJm1DLuxQKRGgjRRIOaLNmH7cP2YctMZtwSXZaNCVKnE4iK4IYLAjrEUkqvcJhbskCf
5e3abio4/smb0+ac8/v/z/O8FwAAGM7KCjBERHxyZcOG8Uvh4R/vDgt7FQDWknv/pzGplPstPPzaxPr1
QxciIwsBIAAA1vgeGKmqEt7MyvpqrKUFJ8+cwas6nesSx/WmMswWAPB7gvaURln2LVNJyUO+sxMXTp/G
8aIi+4fR0dXhgYHBPpPezZs/utfYiA86OnCyvR2NnZ041dyMXRx3Tcwwb6xkMiGTvW0qKZniT51CvqMD
eb0eF9ra8J5C4fgmLOwDAAhxV+GKSGQk8Ac6HT5sbUVjWxvOnDiBAy0trktS6U8JDBP1tMmYVJptKi42
kuQEvqDXI9/UhAvNzegsK0NDaOhdAOAAgIGejRvPjldU4KROh1M6HRqPHEGTXo+zx4/jDZ3O1cNx/fFC
YbTXZCwlJXumqMg4f/Ik8m1tuNDairxOhwtaLc5VVmKXVGo9IBKdB4Bc91eoNm16rUsiufbjwYOuR1ot
Tmu1ONPUhLMtLWhrb0fL4cN4heP6k4XCmNssmzVdWGiaP3bMXRK+uRn5xkbk6+txvrISDcnJ9iiB4HMA
UAOA2NNw8GNFouguiaR/6sABNNXWormmBmcbGtCq1aJDr8fBmhrXgEx2535+vpE/etSdmNdqka+tRb66
2gePEQjOAkAFAMR54JS3rP7JISExP6Sk9PWXlrrMGg1aNBq0VVWhvboanQ0N6Kyrw/nGRpwn4IYG5Ovq
/oFXVGCfROIQC4UETpLHAsCLT0xVr4k0JCS2JzGx37RvH1rKy9FWXo4OtRqdlZU4p9HgXFkZzuXno3Pn
TnTKZGiXybAvNdWZxDDnAOCQJ7lgObhX/luDg+N6xOL+keTkRfv+/WiTy9HGsmgNC0Mrw6DVzw+tNI2D
QqGrOyLCwQUFkYZqACAeAF56Htwrd7luisXDMwyDZprGWZpGC027we6xdi0OJSb+WRgd/R0AvA8ACauF
u2UqK6syyWRTZoHgWbjHcCQyctGgUNyP2bgxHwACVw13qtWHrRkZVjPDrAifpml8SNM4FReHQ0rl3Xe2
bUt6ejEuK5tarbfs2mUzBwQ8A7f4+bnLReCTNI3jFIUjFIWXk5JcwwUFvxZyHCnTyiaOQ4daZ3Ny7Msl
t4hE+Cgn568bUVEuAh/zwAcpCvsoCg0suzRMvmQlE4darTdnZDjMQqEb/ERZ1q3D0dzc34/t2DEwuGfP
zFRMDP5CUXiLovA6ReFVYkDG1q1LNwoL72SIxWSD/Hert1dUtBD47HLw0FAcTUt7XM2yPQBQkxMf/+7d
vLz7o3FxPngvRWE3RWEXMZFKlwYKCgY/UyhI4/3djbfv3WucDQp6Fk6Sp6U91qSkdANAFQCQRoY0pKe/
OaRSTdxJSPDBv6co/Jai8CJF4XBs7OItleoeAGxym5iVylF7ePiyyatY9jIAVHvg3pPKX5uZyQ0VFExc
TEhw/Rf+9Zo1eCs2dtEgl08DQBYABMNAaWnJjFxus4WG+pKPZGf/Ubs83Cv/+vT01GGlcvxmYiJe8MAN
qalLX8rlj3K3bPnUZ0A2pu6ioqPGvDzHbY5zkeT1Eknvc+Be+au3b08h5bjAcS6DTLZ0Xi6fflkgOA0A
Sl+JyMtsRETgoEr1xYO8PMu5zMxBiqLqAYAFAOEKcK/8LxYXv/ezUjl9XaGw5kRFkbNABQBhnunqe5f8
CAIACQDs9lxXu/xfAAAyNcnM2QMAry+7FgCA9iQmhzW5kv+rEQlB9v9XPIMY+oL9DaXP2MIw0VMVAAAA
AElFTkSuQmCC
</value>
</data>
<data name="BTT_LOG.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFmSURBVFhH1dc/K4VhHMbxJ5EFEQbFiERKCotIrMJIiYEi
pbwCZcOqJC9AikUWiqRkJYtSRDbESMT3V07dna7zHHru+9T51me+Ts//E+V7LRjFFAZRiZzUhDVc4/vX
B47Rh6D14Aqp4XQ36ECQ2nALNezaQjG8Vo5DqMF0bxiA1+bwCTWoLMFbNTiDGsrkABXw0jDsKldDmdyj
HokrwCrUSBz7wXbRJs4eLkdQI9m0I3ENeIAaiGN3QjMSZ4fxv+ffnKIKibOnmhqI84V5eMleOHY41VAm
9k7wdgtW4wRqSHlCP7y2AjWmbMB7Y7DzqgZdz2iF9zrxCDXq2oU9uLz31+tgAcHahhp1DSFY9pGhRl29
CFYXxrMoQ7BmsZfFPkoRpHWow+56hX26BWkRatR1gRIEaQLvUMMpOyhCkBpxBzWcMoOgLUMNm0vUIWj2
ebaJF7jj5+hGTiqE/f+bxDRGUIt8LIp+AC/GHt3tQnwvAAAAAElFTkSuQmCC
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGTSURBVFhH1ZfPK21RGIaf5GaCCAPF0JVISWEiElNhSIkB
RUr5C5QZpkryB0gxkQlFUjKlO1HKjcwQQyJatdXp7TtnH3t/Z+CpZ7be7z3tH2uvA7+cFmAUmAIGgSpd
UCiagDXgCviMfAOOgT5d7E0P8C+jWL0GOjTkRRtwY5SqW0CJhtNSARwaZZYvwIAOSMsc8G6UZXNJB6Sh
FjgzSnJ5AFTqoKQMR0+5luTyFmjQQUkoAlaNgjjDDw4PbWrC5nJkFORjuw5Lwl/gzhgeZ3gTmnVYEsJl
/On9D54C1TosCWFX0+FxfgDzOigp4YMTLqeW5DJ8E9xewRrgxCjJ5gPQr0PSsmIUZXNDwx6MRfdVy9RH
oFXDHnQC90ahuhttXO7k+xwsaNCTbaNQHdKQJ+GQoYVqr4Y86QLGYyzXkCezwF6M+0CZBr1YNy65+hwd
3QrColGoXgClGvRiAng1SjPdAf5o0ItG4L9RmumMhrxZNkq/vQTqNeBNOJ5tAk9Sfg506+JCURz9/5sE
poERoE4X/Rq+AC/GHt09Rk0KAAAAAElFTkSuQmCC
</value>
</data>
</root>

View File

@@ -39,6 +39,7 @@ Namespace DownloadObjects.STDownloader
MyView = New FormView(Me)
MyProgress = New MyProgress(TOOLBAR_BOTTOM, PR_MAIN, LBL_INFO)
MyJob = New JobThread(Of MediaItem)
BTT_FILTER.Image = My.Resources.FilterPic
End Sub
#End Region
#Region "Form handlers"
@@ -95,15 +96,18 @@ Namespace DownloadObjects.STDownloader
Dim b As Boolean = True
Select Case e.KeyCode
Case Keys.Insert : BTT_ADD.PerformClick()
Case Keys.F5 : BTT_DOWN.PerformClick()
Case Keys.F5
If e.Control Then LoadData(True) Else BTT_DOWN.PerformClick()
Case Else : b = False
End Select
If b Then e.Handled = True
End Sub
#End Region
#Region "Refill, save list"
Protected Sub LoadData()
Protected Sub LoadData(Optional ByVal ClearTable As Boolean = False)
If ClearTable Then RemoveControls(,, False)
Dim c As List(Of IYouTubeMediaContainer) = LoadData_GetFiles()
If c.ListExists Then MyYouTubeSettings.FILTER.RemoveAll(c)
If c.ListExists Then
c.Sort(New ContainerDateComparer)
SuspendLayout()
@@ -415,6 +419,12 @@ Namespace DownloadObjects.STDownloader
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, "[VideoListForm.ValidateContainerURL]", True)
End Try
End Function
Private Sub BTT_FILTER_Click(sender As Object, e As EventArgs) Handles BTT_FILTER.Click
Using f As New FilterForm(LoadData_GetFiles)
f.ShowDialog()
If f.DialogResult = DialogResult.OK Or f.DialogResult = DialogResult.Abort Then LoadData(True)
End Using
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
@@ -494,7 +504,8 @@ Namespace DownloadObjects.STDownloader
Protected Sub CheckVersionImpl(ByVal Force As Boolean)
CheckVersion(Force)
End Sub
Protected Overloads Sub RemoveControls(Optional ByVal Predicate As Predicate(Of MediaItem) = Nothing, Optional ByVal RemoveFiles As Boolean = False)
Protected Overloads Sub RemoveControls(Optional ByVal Predicate As Predicate(Of MediaItem) = Nothing, Optional ByVal RemoveFiles As Boolean = False,
Optional ByVal ProcessDelete As Boolean = True)
ControlInvokeFast(TP_CONTROLS, Sub()
With TP_CONTROLS
If .Controls.Count > 0 Then
@@ -509,7 +520,10 @@ Namespace DownloadObjects.STDownloader
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(RemoveFiles) : cnt.MyContainer.Dispose()
If Not cnt.MyContainer Is Nothing Then
If ProcessDelete Then cnt.MyContainer.Delete(RemoveFiles)
cnt.MyContainer.Dispose()
End If
cnt.Dispose()
Next
End If

View File

@@ -24,7 +24,6 @@ Namespace Editors
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 ActionButton1 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton()
Dim resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(BugReporterForm))
Dim ActionButton2 As PersonalUtilities.Forms.Controls.Base.ActionButton = New PersonalUtilities.Forms.Controls.Base.ActionButton()
@@ -33,31 +32,32 @@ Namespace Editors
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 TP_BUTTONS As System.Windows.Forms.TableLayoutPanel
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.BTT_EMAIL = New System.Windows.Forms.Button()
Me.BTT_GITHUB = New System.Windows.Forms.Button()
Me.BTT_COPY = New System.Windows.Forms.Button()
Me.BTT_CANCEL = New System.Windows.Forms.Button()
Me.BTT_ANON = New System.Windows.Forms.Button()
Me.TT_MAIN = New System.Windows.Forms.ToolTip(Me.components)
Me.TXT_DESCR = New PersonalUtilities.Forms.Controls.TextBoxExtended()
Me.TXT_URL_PROFILE = New PersonalUtilities.Forms.Controls.TextBoxExtended()
Me.TXT_URL_POST = New PersonalUtilities.Forms.Controls.TextBoxExtended()
Me.TXT_REPRODUCE = New PersonalUtilities.Forms.Controls.TextBoxExtended()
Me.TXT_EXPECT = New PersonalUtilities.Forms.Controls.TextBoxExtended()
Me.TXT_LOG = New PersonalUtilities.Forms.Controls.TextBoxExtended()
Me.BTT_EMAIL = New System.Windows.Forms.Button()
Me.BTT_GITHUB = New System.Windows.Forms.Button()
Me.BTT_COPY = New System.Windows.Forms.Button()
Me.BTT_CANCEL = New System.Windows.Forms.Button()
Me.BTT_ANON = New System.Windows.Forms.Button()
Me.TXT_FILES = New PersonalUtilities.Forms.Controls.TextBoxExtended()
Me.TT_MAIN = New System.Windows.Forms.ToolTip(Me.components)
TP_MAIN = New System.Windows.Forms.TableLayoutPanel()
TP_BUTTONS = New System.Windows.Forms.TableLayoutPanel()
TP_MAIN.SuspendLayout()
TP_BUTTONS.SuspendLayout()
CType(Me.TXT_DESCR, System.ComponentModel.ISupportInitialize).BeginInit()
CType(Me.TXT_URL_PROFILE, System.ComponentModel.ISupportInitialize).BeginInit()
CType(Me.TXT_URL_POST, System.ComponentModel.ISupportInitialize).BeginInit()
CType(Me.TXT_REPRODUCE, System.ComponentModel.ISupportInitialize).BeginInit()
CType(Me.TXT_EXPECT, System.ComponentModel.ISupportInitialize).BeginInit()
CType(Me.TXT_LOG, System.ComponentModel.ISupportInitialize).BeginInit()
TP_BUTTONS.SuspendLayout()
CType(Me.TXT_FILES, System.ComponentModel.ISupportInitialize).BeginInit()
Me.SuspendLayout()
'
@@ -88,6 +88,123 @@ Namespace Editors
TP_MAIN.Size = New System.Drawing.Size(584, 461)
TP_MAIN.TabIndex = 0
'
'TXT_DESCR
'
ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image)
ActionButton1.Dock = System.Windows.Forms.DockStyle.Top
ActionButton1.Name = "Clear"
ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_DESCR.Buttons.Add(ActionButton1)
Me.TXT_DESCR.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_DESCR.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_DESCR.CaptionVisible = False
Me.TXT_DESCR.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_DESCR.GroupBoxed = True
Me.TXT_DESCR.GroupBoxText = "Describe the bug or write your message"
Me.TXT_DESCR.Location = New System.Drawing.Point(3, 3)
Me.TXT_DESCR.Multiline = True
Me.TXT_DESCR.Name = "TXT_DESCR"
Me.TXT_DESCR.Size = New System.Drawing.Size(578, 69)
Me.TXT_DESCR.TabIndex = 0
Me.TXT_DESCR.TextToolTip = "A clear and concise description of what the bug is"
Me.TXT_DESCR.TextToolTipEnabled = True
'
'TXT_URL_PROFILE
'
ActionButton2.BackgroundImage = CType(resources.GetObject("ActionButton2.BackgroundImage"), System.Drawing.Image)
ActionButton2.Name = "Clear"
ActionButton2.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_URL_PROFILE.Buttons.Add(ActionButton2)
Me.TXT_URL_PROFILE.CaptionText = "Profile URL"
Me.TXT_URL_PROFILE.CaptionWidth = 75.0R
Me.TXT_URL_PROFILE.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_URL_PROFILE.Location = New System.Drawing.Point(3, 78)
Me.TXT_URL_PROFILE.Name = "TXT_URL_PROFILE"
Me.TXT_URL_PROFILE.Size = New System.Drawing.Size(578, 22)
Me.TXT_URL_PROFILE.TabIndex = 1
'
'TXT_URL_POST
'
ActionButton3.BackgroundImage = CType(resources.GetObject("ActionButton3.BackgroundImage"), System.Drawing.Image)
ActionButton3.Name = "Clear"
ActionButton3.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_URL_POST.Buttons.Add(ActionButton3)
Me.TXT_URL_POST.CaptionText = "Post URL"
Me.TXT_URL_POST.CaptionWidth = 75.0R
Me.TXT_URL_POST.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_URL_POST.Location = New System.Drawing.Point(3, 106)
Me.TXT_URL_POST.Name = "TXT_URL_POST"
Me.TXT_URL_POST.Size = New System.Drawing.Size(578, 22)
Me.TXT_URL_POST.TabIndex = 2
'
'TXT_REPRODUCE
'
ActionButton4.BackgroundImage = CType(resources.GetObject("ActionButton4.BackgroundImage"), System.Drawing.Image)
ActionButton4.Dock = System.Windows.Forms.DockStyle.Top
ActionButton4.Name = "Clear"
ActionButton4.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_REPRODUCE.Buttons.Add(ActionButton4)
Me.TXT_REPRODUCE.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_REPRODUCE.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_REPRODUCE.CaptionVisible = False
Me.TXT_REPRODUCE.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_REPRODUCE.GroupBoxed = True
Me.TXT_REPRODUCE.GroupBoxText = "Steps to reproduce"
Me.TXT_REPRODUCE.Location = New System.Drawing.Point(3, 134)
Me.TXT_REPRODUCE.Multiline = True
Me.TXT_REPRODUCE.Name = "TXT_REPRODUCE"
Me.TXT_REPRODUCE.Size = New System.Drawing.Size(578, 69)
Me.TXT_REPRODUCE.TabIndex = 3
Me.TXT_REPRODUCE.TextToolTip = "Steps to reproduce the behavior:" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "1. Do something" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "2. See error"
Me.TXT_REPRODUCE.TextToolTipEnabled = True
'
'TXT_EXPECT
'
ActionButton5.BackgroundImage = CType(resources.GetObject("ActionButton5.BackgroundImage"), System.Drawing.Image)
ActionButton5.Dock = System.Windows.Forms.DockStyle.Top
ActionButton5.Name = "Clear"
ActionButton5.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_EXPECT.Buttons.Add(ActionButton5)
Me.TXT_EXPECT.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_EXPECT.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_EXPECT.CaptionVisible = False
Me.TXT_EXPECT.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_EXPECT.GroupBoxed = True
Me.TXT_EXPECT.GroupBoxText = "Expected behavior"
Me.TXT_EXPECT.Location = New System.Drawing.Point(3, 209)
Me.TXT_EXPECT.Multiline = True
Me.TXT_EXPECT.Name = "TXT_EXPECT"
Me.TXT_EXPECT.Size = New System.Drawing.Size(578, 69)
Me.TXT_EXPECT.TabIndex = 4
Me.TXT_EXPECT.TextToolTip = "A clear and concise description of what you expected to happen."
Me.TXT_EXPECT.TextToolTipEnabled = True
'
'TXT_LOG
'
ActionButton6.BackgroundImage = CType(resources.GetObject("ActionButton6.BackgroundImage"), System.Drawing.Image)
ActionButton6.Dock = System.Windows.Forms.DockStyle.Top
ActionButton6.Name = "Open"
ActionButton6.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open
ActionButton6.ToolTipText = "Select log files to add their text to the message"
ActionButton7.BackgroundImage = CType(resources.GetObject("ActionButton7.BackgroundImage"), System.Drawing.Image)
ActionButton7.Dock = System.Windows.Forms.DockStyle.Top
ActionButton7.Name = "Clear"
ActionButton7.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
ActionButton7.ToolTipText = "Empty"
Me.TXT_LOG.Buttons.Add(ActionButton6)
Me.TXT_LOG.Buttons.Add(ActionButton7)
Me.TXT_LOG.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_LOG.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_LOG.CaptionVisible = False
Me.TXT_LOG.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_LOG.GroupBoxed = True
Me.TXT_LOG.GroupBoxText = "Log data"
Me.TXT_LOG.Location = New System.Drawing.Point(3, 284)
Me.TXT_LOG.Multiline = True
Me.TXT_LOG.Name = "TXT_LOG"
Me.TXT_LOG.Size = New System.Drawing.Size(578, 69)
Me.TXT_LOG.TabIndex = 5
'
'TP_BUTTONS
'
TP_BUTTONS.ColumnCount = 6
@@ -119,7 +236,7 @@ Namespace Editors
Me.BTT_EMAIL.Name = "BTT_EMAIL"
Me.BTT_EMAIL.Size = New System.Drawing.Size(94, 24)
Me.BTT_EMAIL.TabIndex = 1
Me.BTT_EMAIL.Text = "email"
Me.BTT_EMAIL.Text = "Email"
Me.TT_MAIN.SetToolTip(Me.BTT_EMAIL, "Create a message to send via email.")
Me.BTT_EMAIL.UseVisualStyleBackColor = True
'
@@ -167,129 +284,6 @@ Namespace Editors
Me.TT_MAIN.SetToolTip(Me.BTT_ANON, resources.GetString("BTT_ANON.ToolTip"))
Me.BTT_ANON.UseVisualStyleBackColor = True
'
'TXT_DESCR
'
ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image)
ActionButton1.Dock = System.Windows.Forms.DockStyle.Top
ActionButton1.Name = "Clear"
ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_DESCR.Buttons.Add(ActionButton1)
Me.TXT_DESCR.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_DESCR.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_DESCR.CaptionVisible = False
Me.TXT_DESCR.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_DESCR.GroupBoxed = True
Me.TXT_DESCR.GroupBoxText = "Describe the bug or write your message"
Me.TXT_DESCR.Lines = New String(-1) {}
Me.TXT_DESCR.Location = New System.Drawing.Point(3, 3)
Me.TXT_DESCR.Multiline = True
Me.TXT_DESCR.Name = "TXT_DESCR"
Me.TXT_DESCR.Size = New System.Drawing.Size(578, 69)
Me.TXT_DESCR.TabIndex = 0
Me.TXT_DESCR.TextToolTip = "A clear and concise description of what the bug is"
Me.TXT_DESCR.TextToolTipEnabled = True
'
'TXT_URL_PROFILE
'
ActionButton2.BackgroundImage = CType(resources.GetObject("ActionButton2.BackgroundImage"), System.Drawing.Image)
ActionButton2.Name = "Clear"
ActionButton2.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_URL_PROFILE.Buttons.Add(ActionButton2)
Me.TXT_URL_PROFILE.CaptionText = "Profile URL"
Me.TXT_URL_PROFILE.CaptionWidth = 75.0R
Me.TXT_URL_PROFILE.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_URL_PROFILE.Lines = New String(-1) {}
Me.TXT_URL_PROFILE.Location = New System.Drawing.Point(3, 78)
Me.TXT_URL_PROFILE.Name = "TXT_URL_PROFILE"
Me.TXT_URL_PROFILE.Size = New System.Drawing.Size(578, 22)
Me.TXT_URL_PROFILE.TabIndex = 1
'
'TXT_URL_POST
'
ActionButton3.BackgroundImage = CType(resources.GetObject("ActionButton3.BackgroundImage"), System.Drawing.Image)
ActionButton3.Name = "Clear"
ActionButton3.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_URL_POST.Buttons.Add(ActionButton3)
Me.TXT_URL_POST.CaptionText = "Post URL"
Me.TXT_URL_POST.CaptionWidth = 75.0R
Me.TXT_URL_POST.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_URL_POST.Lines = New String(-1) {}
Me.TXT_URL_POST.Location = New System.Drawing.Point(3, 106)
Me.TXT_URL_POST.Name = "TXT_URL_POST"
Me.TXT_URL_POST.Size = New System.Drawing.Size(578, 22)
Me.TXT_URL_POST.TabIndex = 2
'
'TXT_REPRODUCE
'
ActionButton4.BackgroundImage = CType(resources.GetObject("ActionButton4.BackgroundImage"), System.Drawing.Image)
ActionButton4.Dock = System.Windows.Forms.DockStyle.Top
ActionButton4.Name = "Clear"
ActionButton4.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_REPRODUCE.Buttons.Add(ActionButton4)
Me.TXT_REPRODUCE.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_REPRODUCE.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_REPRODUCE.CaptionVisible = False
Me.TXT_REPRODUCE.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_REPRODUCE.GroupBoxed = True
Me.TXT_REPRODUCE.GroupBoxText = "To Reproduce"
Me.TXT_REPRODUCE.Lines = New String(-1) {}
Me.TXT_REPRODUCE.Location = New System.Drawing.Point(3, 134)
Me.TXT_REPRODUCE.Multiline = True
Me.TXT_REPRODUCE.Name = "TXT_REPRODUCE"
Me.TXT_REPRODUCE.Size = New System.Drawing.Size(578, 69)
Me.TXT_REPRODUCE.TabIndex = 3
Me.TXT_REPRODUCE.TextToolTip = "Steps to reproduce the behavior:" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "1. Do something" & Global.Microsoft.VisualBasic.ChrW(13) & Global.Microsoft.VisualBasic.ChrW(10) & "2. See error"
Me.TXT_REPRODUCE.TextToolTipEnabled = True
'
'TXT_EXPECT
'
ActionButton5.BackgroundImage = CType(resources.GetObject("ActionButton5.BackgroundImage"), System.Drawing.Image)
ActionButton5.Dock = System.Windows.Forms.DockStyle.Top
ActionButton5.Name = "Clear"
ActionButton5.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
Me.TXT_EXPECT.Buttons.Add(ActionButton5)
Me.TXT_EXPECT.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_EXPECT.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_EXPECT.CaptionVisible = False
Me.TXT_EXPECT.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_EXPECT.GroupBoxed = True
Me.TXT_EXPECT.GroupBoxText = "Expected behavior"
Me.TXT_EXPECT.Lines = New String(-1) {}
Me.TXT_EXPECT.Location = New System.Drawing.Point(3, 209)
Me.TXT_EXPECT.Multiline = True
Me.TXT_EXPECT.Name = "TXT_EXPECT"
Me.TXT_EXPECT.Size = New System.Drawing.Size(578, 69)
Me.TXT_EXPECT.TabIndex = 4
Me.TXT_EXPECT.TextToolTip = "A clear and concise description of what you expected to happen."
Me.TXT_EXPECT.TextToolTipEnabled = True
'
'TXT_LOG
'
ActionButton6.BackgroundImage = CType(resources.GetObject("ActionButton6.BackgroundImage"), System.Drawing.Image)
ActionButton6.Dock = System.Windows.Forms.DockStyle.Top
ActionButton6.Name = "Open"
ActionButton6.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Open
ActionButton6.ToolTipText = "Select log files to add their text to the message"
ActionButton7.BackgroundImage = CType(resources.GetObject("ActionButton7.BackgroundImage"), System.Drawing.Image)
ActionButton7.Dock = System.Windows.Forms.DockStyle.Top
ActionButton7.Name = "Clear"
ActionButton7.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.Clear
ActionButton7.ToolTipText = "Empty"
Me.TXT_LOG.Buttons.Add(ActionButton6)
Me.TXT_LOG.Buttons.Add(ActionButton7)
Me.TXT_LOG.CaptionDock = System.Windows.Forms.DockStyle.Top
Me.TXT_LOG.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.None
Me.TXT_LOG.CaptionVisible = False
Me.TXT_LOG.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_LOG.GroupBoxed = True
Me.TXT_LOG.GroupBoxText = "Log data"
Me.TXT_LOG.Lines = New String(-1) {}
Me.TXT_LOG.Location = New System.Drawing.Point(3, 284)
Me.TXT_LOG.Multiline = True
Me.TXT_LOG.Name = "TXT_LOG"
Me.TXT_LOG.Size = New System.Drawing.Size(578, 69)
Me.TXT_LOG.TabIndex = 5
'
'TXT_FILES
'
ActionButton8.BackgroundImage = CType(resources.GetObject("ActionButton8.BackgroundImage"), System.Drawing.Image)
@@ -310,7 +304,6 @@ Namespace Editors
Me.TXT_FILES.Dock = System.Windows.Forms.DockStyle.Fill
Me.TXT_FILES.GroupBoxed = True
Me.TXT_FILES.GroupBoxText = "Files"
Me.TXT_FILES.Lines = New String(-1) {}
Me.TXT_FILES.Location = New System.Drawing.Point(3, 359)
Me.TXT_FILES.Multiline = True
Me.TXT_FILES.Name = "TXT_FILES"
@@ -332,13 +325,13 @@ Namespace Editors
Me.Name = "BugReporterForm"
Me.Text = "New message"
TP_MAIN.ResumeLayout(False)
TP_BUTTONS.ResumeLayout(False)
CType(Me.TXT_DESCR, System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.TXT_URL_PROFILE, System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.TXT_URL_POST, System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.TXT_REPRODUCE, System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.TXT_EXPECT, System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.TXT_LOG, System.ComponentModel.ISupportInitialize).EndInit()
TP_BUTTONS.ResumeLayout(False)
CType(Me.TXT_FILES, System.ComponentModel.ISupportInitialize).EndInit()
Me.ResumeLayout(False)

View File

@@ -124,60 +124,60 @@
<data name="ActionButton1.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go
tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX
AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC
xAAADsQBlSsOGwAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25
DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2
gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC
</value>
</data>
<data name="ActionButton2.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go
tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX
AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC
xAAADsQBlSsOGwAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25
DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2
gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC
</value>
</data>
<data name="ActionButton3.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go
tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX
AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC
xAAADsQBlSsOGwAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25
DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2
gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC
</value>
</data>
<data name="ActionButton4.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go
tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX
AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC
xAAADsQBlSsOGwAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25
DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2
gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC
</value>
</data>
<data name="ActionButton5.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go
tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX
AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC
xAAADsQBlSsOGwAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25
DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2
gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC
</value>
</data>
<data name="ActionButton6.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAR5JREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbGujg3ZATZPKYdC6FQhPwlAMkg3dP
WQwhyWIyJIUW5NqyPb7oCVtIlhVTwYf8nv7/t2zJagel9KmqKsIACYL9RjI8UHz5zshougZr/AEvbxEP
aZCDBY3VslixaJvX3wzkkDiOwbZtDRGA5vdNAg+TL27qgmt5XkBG/gTdAG7Gt+3PP9oOaEGFCVEC6rp+
5g9MfM/c5e4OsEZMZkQEtGL5H2DdZ5JRArDwPA+iKII0TfkC9vroC9j5vq8JTWw3WzWgLMtZGIaa0MR8
vlAD8PYlSaIJTTiOowY0p0Bc19XEJo6HE59FAPuMzyAINKGJ1XLFZxHALtMrnkBXOIQIIIQ8YvF/KrgB
cMaRN0UdBBkAAAAASUVORK5CYII=
wwAADsMBx2+oZAAAARlJREFUOE+VkjFqwzAUhn2D9iShRyi+QhYbWujg3dATZPKYZC6BQhvw1AMkg3dP
XQyl7WIyJIEW5CbS0/jKE5GwpCghgg9s6/8/y5Kj6DA45zcAwAAAezB6rjNnB4XX244NHt8wGs7wblop
yRGxwZQBYKIfbn477EvqusY4jj2MgMpPiwav7l9UyYXmdrs9duzP4ApUmd72sfrxVsD33JQISyClvFUX
w9nJssvJFei9CJUtgQ7394Du3YKLJaCbLMuwqips21ZNuDve/35X8J7nuRcMsVwsbYEQYlSWpRcMMR5P
bAH9fU3TeMEQSZLYgsMpsDRNvXCIr89vWyCEeC6KwguGmL/ObYGU8oFOwA2ewwgYY9f6f7iUf3DGkTcu
khP7AAAAAElFTkSuQmCC
</value>
</data>
<data name="ActionButton7.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go
tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX
AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC
xAAADsQBlSsOGwAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25
DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2
gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC
</value>
</data>
<metadata name="TP_BUTTONS.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
@@ -195,31 +195,31 @@ If you would like a response from the developer, response, please add your conta
<data name="ActionButton8.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
</value>
</data>
<data name="ActionButton9.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
xAAADsQBlSsOGwAAAIZJREFUOE+1j10KwCAMgz2b755xl/IsvnaL2K20UfbDAmEako+ZROSTafjE12Go
tbbB43rK5xSAQq1VYFtmeQBoqZTSreVZvgTknM8yyyjA/qodsDF9gspD2Bj6B+DH+NqzhQQAG+POMnSX
AFuc5QFgn6ClHh5iOQVAKNixyucB8NY0vG9JOzzyhrdq5IRgAAAAAElFTkSuQmCC
xAAADsQBlSsOGwAAAHpJREFUOE+1kVEKgDAMQ3e2/e+MXqpn6W/HxM7SpkIVB4HxSMKiTUTaFwVQ1X25
DjMfSxskHBYsAxHJkjUjHgrUNMY4peaMPxb03rcZMVhgn2oDKAwn+L0aROH/Cny4NAGFSx8x+10ZDwV2
gt+LOCxQsw1nPBS8VQBVTTzyhrdZSUm7AAAAAElFTkSuQmCC
</value>
</data>
</root>

View File

@@ -10,6 +10,7 @@ Imports System.Threading
Imports PersonalUtilities.Tools
Imports PersonalUtilities.Tools.Web
Imports PersonalUtilities.Functions.Messaging
Imports PersonalUtilities.Functions.RegularExpressions
Imports SCrawler.DownloadObjects.STDownloader
Public Module MainModShared
Public Property BATCH As BatchExecutor
@@ -135,9 +136,11 @@ Namespace Editors
Public Shared Function GetProgramEnvirText(ByVal EnvirData As IDownloaderSettings, ByVal IsYouTube As Boolean) As String
Try
Dim output$ = String.Empty
Dim verAfter As RParams = RParams.DM("\A\w\:\\.*", 0, EDP.ReturnValue)
Using b As New BatchExecutor(True)
Dim f As SFile
Dim cmd$, ff$, vText$
Dim ii%
For i% = 0 To IIf(IsYouTube, 1, 3)
cmd = "--version"
@@ -154,7 +157,17 @@ Namespace Editors
Else
b.Reset()
b.Execute($"""{f}"" {cmd}", EDP.None)
If b.OutputData.Count > 3 Then vText = b.OutputData(3) Else vText = "undefined"
'If b.OutputData.Count > 3 Then vText = b.OutputData(3) Else vText = "undefined"
vText = String.Empty
With b.OutputData
If .Count > 0 Then
ii = .FindIndex(Function(bb) Not CStr(RegexReplace(bb, verAfter)).IsEmptyString)
If ii >= 0 And ii + 1 <= .Count - 1 Then vText = .Item(ii + 1)
End If
End With
If vText.IsEmptyString Then vText = "undefined"
output.StringAppendLine($"{ff} version: {vText}")
End If
End If

View File

@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
<Assembly: AssemblyDescription("YouTube plugin environment")>
<Assembly: AssemblyCompany("AndyProgram")>
<Assembly: AssemblyProduct("SCrawler.YouTube")>
<Assembly: AssemblyCopyright("Copyright © 2024")>
<Assembly: AssemblyCopyright("Copyright © 2025")>
<Assembly: AssemblyTrademark("AndyProgram")>
<Assembly: ComVisible(False)>
@@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices
' by using the '*' as shown below:
' <Assembly: AssemblyVersion("1.0.*")>
<Assembly: AssemblyVersion("2024.10.24.0")>
<Assembly: AssemblyFileVersion("2024.10.24.0")>
<Assembly: AssemblyVersion("2025.6.12.0")>
<Assembly: AssemblyFileVersion("2025.6.12.0")>
<Assembly: NeutralResourcesLanguage("en")>

View File

@@ -116,7 +116,9 @@ Namespace API.YouTube.Objects
<XMLEC> Public Property IsShorts As Boolean = False Implements IYouTubeMediaContainer.IsShorts
<XMLEC> Public Property ID As String Implements IYouTubeMediaContainer.ID, IUserMedia.PostID
<XMLEC> Public Property Title As String Implements IDownloadableMedia.Title
<XMLEC> Public Property Description As String Implements IYouTubeMediaContainer.Description
<XMLEC> Public Property Description As String Implements IYouTubeMediaContainer.Description, IUserMedia.PostText
Private Property IUserMedia_PostTextFile As String Implements IUserMedia.PostTextFile
Private Property IUserMedia_PostTextFileSpecialFolder As Boolean Implements IUserMedia.PostTextFileSpecialFolder
<XMLEC> Public Property PlaylistID As String Implements IYouTubeMediaContainer.PlaylistID
<XMLEC> Public Property PlaylistTitle As String Implements IYouTubeMediaContainer.PlaylistTitle
<XMLEC> Public Property UserID As String Implements IYouTubeMediaContainer.UserID
@@ -685,10 +687,17 @@ Namespace API.YouTube.Objects
Friend Sub FileDateUpdate()
Dim n$ = _File.Name.StringTrim
Dim s$ = IIf(n.IsEmptyString, String.Empty, " ")
Dim c$ = AccountName.IfNullOrEmpty(UserID)
Select Case MyYouTubeSettings.FileAddDateToFileName.Value
Case FileDateMode.Before : n = $"[{DateAdded:yyyy-MM-dd}]{s}{n}"
Case FileDateMode.After : n = $"{n}{s}[{DateAdded:yyyy-MM-dd}]"
End Select
If Not c.IsEmptyString Then
Select Case MyYouTubeSettings.FileAddChannelToFileName.Value
Case FileDateMode.Before : n = $"[{c}] {n}"
Case FileDateMode.After : n = $"{n} [{c}]"
End Select
End If
_File.Name = n
End Sub
Public Property FileSettings As SFile
@@ -729,6 +738,7 @@ Namespace API.YouTube.Objects
#Region "Command"
<XMLEC> Public Property UseCookies As Boolean = MyYouTubeSettings.DefaultUseCookies Implements IYouTubeMediaContainer.UseCookies
Protected Const mp3 As String = "mp3"
Private Const mp4 As String = "mp4"
Private Const aac As String = "aac"
Private Const ac3 As String = "ac3"
Protected PostProcessing_AudioAC3 As Boolean = False
@@ -764,7 +774,12 @@ Namespace API.YouTube.Objects
'2023.3.4 -> 2023.7.6
'cmd.StringAppend($"ba*[format_id={SelectedAudio.ID}]", "+")
cmd.StringAppend(SelectedAudio.ID, "+")
If OutputAudioCodec.StringToLower = ac3 Then
If SelectedVideoIndex >= 0 And SelectedAudio.ProtocolType = Protocols.m3u8 And
(SelectedAudio.Codec.StringToLower = mp4 Or OutputAudioCodec.StringToLower = mp4) Then
PostProcessing_AudioAC3 = True
formats.StringAppend($"--merge-output-format ""{mp4}{IIf(OutputVideoExtension.IsEmptyString, String.Empty, $"/{OutputVideoExtension.StringToLower}")}""", " ")
atCodec = aac
ElseIf OutputAudioCodec.StringToLower = ac3 Then
PostProcessing_AudioAC3 = True
formats.StringAppend($"--audio-format {aac}", " ")
atCodec = aac
@@ -1208,12 +1223,15 @@ Namespace API.YouTube.Objects
End If
With MyYouTubeSettings
If .CreateDescriptionFiles And (Not Description.IsEmptyString Or
(.CreateDescriptionFiles_CreateWithNoDescription And .CreateDescriptionFiles_AddUploadDate)) Then
If .CreateDescriptionFiles And (Not Description.IsEmptyString Or .CreateDescriptionFiles_CreateWithNoDescription) Then
Dim fileDesr As SFile = File
fileDesr.Extension = "txt"
Using fileDesrText As New TextSaver(fileDesr)
If .CreateDescriptionFiles_AddUploadDate Then fileDesrText.Append($"Uploaded: {DateAdded:yyyy-MM-dd HH:mm:ss}")
fileDesrText.Append($"Uploaded: {DateAdded:yyyy-MM-dd HH:mm:ss}")
fileDesrText.AppendLine()
fileDesrText.AppendLine($"URL: {URL}")
fileDesrText.AppendLine($"Channel name: {AccountName}")
fileDesrText.AppendLine($"Channel ID: {UserID}")
If Not Description.IsEmptyString Then
If Not fileDesrText.IsEmptyString Then fileDesrText.AppendLine.AppendLine()
fileDesrText.Append(Description)
@@ -1741,9 +1759,12 @@ Namespace API.YouTube.Objects
If If(e({"formats"})?.Count, 0) > 0 Then
Dim obj As MediaObject
Dim nValue#
Dim sValue$
Dim sValue$ = String.Empty
Dim allowWebm As Boolean = MyYouTubeSettings.DefaultVideoAllowWebm
Dim validCodecValue As Func(Of String, Boolean) = Function(codec) Not codec.IsEmptyString AndAlso Not codec = "none"
Dim validCodecValue As Func(Of String, Boolean) = Function(ByVal codec As String) As Boolean
sValue = codec
Return Not codec.IsEmptyString AndAlso Not codec = "none"
End Function
For Each ee In e({"formats"})
obj = New MediaObject With {
@@ -1767,19 +1788,30 @@ Namespace API.YouTube.Objects
If obj.Size <= 0 And obj.Bitrate > 0 And Duration.TotalSeconds > 0 Then _
obj.Size = (obj.Bitrate / 8 * Duration.TotalSeconds).RoundVal(2)
sValue = ee.Value("vcodec")
If validCodecValue(sValue) Then
'sValue = ee.Value("vcodec")
If validCodecValue(ee.Value("vcodec")) Then
obj.Type = UMTypes.Video
obj.Codec = sValue.Split(".").First
If validCodecValue(ee.Value("acodec")) Then obj.Type = av
ElseIf validCodecValue(ee.Value("acodec")) Then
obj.Type = UMTypes.Audio
obj.Codec = sValue.Split(".").First
Else
sValue = ee.Value("acodec")
If validCodecValue(sValue) Then
obj.Type = UMTypes.Audio
obj.Codec = sValue.Split(".").First
Else
Continue For
Dim fd As Boolean = False
sValue = ee.Value("format_note")
If Not sValue.IsEmptyString Then
With ListAddList(Nothing, sValue.Split(","), CType(Function(v) CStr(v).StringToLower.StringTrim, Func(Of Object, Object)), EDP.ReturnValue)
If .ListContains({"high", "low"}) Then
obj.Type = UMTypes.Audio
obj.Codec = ee.Value("ext")
If obj.Protocol.StringToLower.StartsWith("m3u8") Then obj.Protocol = "m3u8"
If obj.Bitrate <= 0 Then obj.Bitrate = IIf(.Contains("high"), 129, 53)
If obj.Size <= 0 Then obj.Size = 1
fd = True
End If
End With
End If
If Not fd Then Continue For
End If
MediaObjects.Add(obj)
Next
@@ -1791,8 +1823,9 @@ Namespace API.YouTube.Objects
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 allWebm As Boolean = False, allAVC As Boolean = False
Dim expWebm As Predicate(Of MediaObject) = Function(mo) Not allWebm And mo.Extension = webm
Dim expAVC As Predicate(Of MediaObject) = Function(mo) Not allAVC And mo.Codec.IfNullOrEmpty("/").ToLower.StartsWith(avc)
Dim comp As Func(Of MediaObject, Predicate(Of MediaObject), Boolean, Boolean, Boolean) =
Function(mo, exp, isTrue, checkHttp) mo.Type = t And exp.Invoke(mo) = isTrue And mo.Width = d.Width And
(Not checkHttp OrElse mo.ProtocolType = Protocols.https)
@@ -1800,6 +1833,8 @@ Namespace API.YouTube.Objects
Dim RemoveWebm As Predicate(Of MediaObject) = Function(mo) comp.Invoke(mo, expWebm, True, allowWebm)
Dim CountAVC As Func(Of MediaObject, Boolean) = Function(mo) comp.Invoke(mo, expAVC, True, False)
Dim RemoveAVC As Predicate(Of MediaObject) = Function(mo) comp.Invoke(mo, expAVC, False, False)
allWebm = data.All(FPredicate(Of MediaObject).ToFunc(expWebm))
allAVC = data.All(FPredicate(Of MediaObject).ToFunc(expAVC))
For Each d In data
If MediaObjects.Count = 0 Then Exit For
If MediaObjects.LongCount(CountWebm) > 0 Then MediaObjects.RemoveAll(RemoveWebm)

View File

@@ -124,12 +124,19 @@
<Compile Include="Controls\ChannelTabsChooserForm.vb">
<SubType>Form</SubType>
</Compile>
<Compile Include="Controls\FilterForm.Designer.vb">
<DependentUpon>FilterForm.vb</DependentUpon>
</Compile>
<Compile Include="Controls\FilterForm.vb">
<SubType>Form</SubType>
</Compile>
<Compile Include="Controls\PlayListParserForm.Designer.vb">
<DependentUpon>PlayListParserForm.vb</DependentUpon>
</Compile>
<Compile Include="Controls\PlayListParserForm.vb">
<SubType>Form</SubType>
</Compile>
<Compile Include="Controls\YTDataFilter.vb" />
<Compile Include="Downloader\DownloadLocationsCollection.vb" />
<Compile Include="Downloader\IDownloaderSettings.vb" />
<Compile Include="Downloader\Notificator.vb" />
@@ -220,6 +227,9 @@
<EmbeddedResource Include="Controls\ChannelTabsChooserForm.resx">
<DependentUpon>ChannelTabsChooserForm.vb</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Controls\FilterForm.resx">
<DependentUpon>FilterForm.vb</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Controls\PlayListParserForm.resx">
<DependentUpon>PlayListParserForm.vb</DependentUpon>
</EmbeddedResource>

View File

@@ -13,7 +13,7 @@ Imports System.Runtime.InteropServices
<Assembly: AssemblyDescription("SCrawler YouTube downloader")>
<Assembly: AssemblyCompany("AndyProgram")>
<Assembly: AssemblyProduct("SCrawler.YouTubeDownloader")>
<Assembly: AssemblyCopyright("Copyright © 2024")>
<Assembly: AssemblyCopyright("Copyright © 2025")>
<Assembly: AssemblyTrademark("AndyProgram")>
<Assembly: ComVisible(False)>
@@ -32,6 +32,6 @@ Imports System.Runtime.InteropServices
' by using the '*' as shown below:
' <Assembly: AssemblyVersion("1.0.*")>
<Assembly: AssemblyVersion("2024.10.24.0")>
<Assembly: AssemblyFileVersion("2024.10.24.0")>
<Assembly: AssemblyVersion("2025.6.12.0")>
<Assembly: AssemblyFileVersion("2025.6.12.0")>
<Assembly: NeutralResourcesLanguage("en")>

View File

@@ -30,6 +30,13 @@ Namespace API.Base
Friend Const UseMD5ComparisonToolTip As String = "Each image will be checked for existence using MD5"
Friend Const UserNameChangeCaption As String = "UserName"
Friend Const UserNameChangeToolTip As String = "If the user has changed their UserName, you can set a new name here. Not required for new users."
Friend Const DownloadTextCaption As String = "Download text"
Friend Const DownloadTextTip As String = "Download text (if available) for posts with image and video" & vbCr & "If this checkbox is checked, the post text will be downloaded along with the file and saved under the same name but with the 'txt' extension."
Friend Const DownloadTextPostsCaption As String = "Download text posts"
Friend Const DownloadTextPostsTip As String = "Download text (if available) for text posts (no image and video)"
Friend Const DownloadTextSpecialFolderCaption As String = "Text special folder"
Friend Const DownloadTextSpecialFolderTip As String = "If checked, text files will be saved to a separate folder"
Private Sub New()
End Sub
End Class

View File

@@ -6,8 +6,9 @@
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports System.Net
Imports SCrawler.Plugin
Imports PersonalUtilities.Functions.RegularExpressions
Imports Download = SCrawler.Plugin.ISiteSettings.Download
Namespace API.Base
Friend NotInheritable Class DownDetector
Private Shared ReadOnly Property Params As New RParams("x:.'([\S]+?)',.y:.(\d+)", -1, Nothing, RegexReturn.List)
@@ -34,34 +35,106 @@ Namespace API.Base
Try
Dim l As List(Of Data) = Nothing
Dim l2 As List(Of Data) = Nothing
Using w As New WebClient
Dim r$ = w.DownloadString($"https://downdetector.co.uk/status/{Site}/")
If Not r.IsEmptyString Then
l = RegexFields(Of Data)(r, {Params}, {1, 2})
If l.ListExists(2) Then
l.Sort()
l2 = New List(Of Data)
Dim d As Data
Dim eDates As New List(Of Date)
Dim MaxValue As Func(Of Date, Integer) = Function(dd) (From ddd In l Where ddd.Date = dd Select ddd.Value).DefaultIfEmpty(0).Max
For i% = 0 To l.Count - 1
If Not eDates.Contains(l(i).Date) Then
d = l(i)
d.Value = MaxValue(d.Date)
l2.Add(d)
eDates.Add(d.Date)
End If
Next
eDates.Clear()
l.Clear()
l2.Sort()
End If
Dim r$ = GetWebString($"https://downdetector.co.uk/status/{Site}/",, EDP.ThrowException)
If Not r.IsEmptyString Then
l = RegexFields(Of Data)(r, {Params}, {1, 2})
If l.ListExists(2) Then
l.Sort()
l2 = New List(Of Data)
Dim d As Data
Dim eDates As New List(Of Date)
Dim MaxValue As Func(Of Date, Integer) = Function(dd) (From ddd As Data In l Where ddd.Date = dd Select ddd.Value).DefaultIfEmpty(0).Max
For i% = 0 To l.Count - 1
If Not eDates.Contains(l(i).Date) Then
d = l(i)
d.Value = MaxValue(d.Date)
l2.Add(d)
eDates.Add(d.Date)
End If
Next
eDates.Clear()
l.Clear()
l2.Sort()
End If
End Using
End If
Return l2
Catch ex As Exception
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, $"[DownDetector.GetData({Site})]")
End Try
End Function
Friend Interface IDownDetector
ReadOnly Property Value As Integer
ReadOnly Property AddToLog As Boolean
ReadOnly Property CheckSite As String
Function Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean
End Interface
Friend Class Checker(Of T As {ISiteSettings, IDownDetector})
Protected ReadOnly Property Source As T
Private ReadOnly NP As New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral}
Friend Sub New(ByRef _Source As T)
Source = _Source
End Sub
Private ____AvailableChecked As Boolean = False
Private ____AvailableResult As Boolean = False
Friend Overridable Function Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean
If Settings.DownDetectorEnabled And Source.Value >= 0 Then
If Not ____AvailableChecked Then
____AvailableResult = AvailableImpl(What, Silent)
____AvailableChecked = True
End If
Return ____AvailableResult
Else
Return True
End If
End Function
Protected Overridable Function AvailableImpl(ByVal What As Download, ByVal Silent As Boolean) As Boolean
Try
Source.AvailableText = String.Empty
If Source.Value < 0 Then
Return True
Else
Dim dl As List(Of Data) = GetData(Source.CheckSite)
If dl.ListExists Then
dl = dl.Take(4).ToList
Dim avg% = dl.Average(Function(d) d.Value)
If avg > Source.Value Then
Source.AvailableText = $"Over the past hour, {Source.Site} has received an average of {avg.NumToString(NP)} outage reports:{vbCr}{dl.ListToString(vbCr)}"
If Source.AddToLog Then MyMainLOG = Source.AvailableText
If Silent Then
Return AvailableImpl_FALSE_SILENT()
Else
If MsgBoxE({$"{Source.AvailableText}{vbCr}{vbCr}Do you want to continue parsing {Source.Site} data?",
$"There are outage reports on {Source.Site}"}, vbYesNo) = vbYes Then
Return AvailableImpl_FALSE_SILENT_NOT_MSG_YES()
Else
Return AvailableImpl_FALSE_SILENT_NOT_MSG_NO()
End If
End If
End If
End If
Return AvailableImpl_TRUE()
End If
Catch ex As Exception
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, $"[API.{Source.Site}.SiteSettings.Available([DownDetector])]", True)
End Try
End Function
Protected Overridable Function AvailableImpl_TRUE() As Boolean
Return True
End Function
Protected Overridable Function AvailableImpl_FALSE_SILENT() As Boolean
Return False
End Function
Protected Overridable Function AvailableImpl_FALSE_SILENT_NOT_MSG_YES() As Boolean
Return True
End Function
Protected Overridable Function AvailableImpl_FALSE_SILENT_NOT_MSG_NO() As Boolean
Return False
End Function
Friend Overridable Sub Reset()
____AvailableChecked = False
____AvailableResult = False
Source.AvailableText = String.Empty
End Sub
End Class
End Class
End Namespace

View File

@@ -0,0 +1,46 @@
' Copyright (C) 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 DN = SCrawler.API.Base.DeclaredNames
Namespace API.Base
Friend Class EditorExchangeOptionsBase
Friend Overridable Property SiteKey As String
<PSetting(Address:=SettingAddress.User, Caption:=DN.UserNameChangeCaption, ToolTip:=DN.UserNameChangeToolTip)>
Friend Overridable Property UserName As String = String.Empty
<PSetting(Address:=SettingAddress.User, Caption:=DN.DownloadTextCaption, ToolTip:=DN.DownloadTextTip)>
Friend Overridable Property DownloadText As Boolean = False
<PSetting(Address:=SettingAddress.User, Caption:=DN.DownloadTextPostsCaption, ToolTip:=DN.DownloadTextPostsTip)>
Friend Overridable Property DownloadTextPosts As Boolean = False
<PSetting(Address:=SettingAddress.User, Caption:=DN.DownloadTextSpecialFolderCaption, ToolTip:=DN.DownloadTextSpecialFolderTip)>
Friend Overridable Property DownloadTextSpecialFolder As Boolean = False
Friend Sub New(ByVal u As UserDataBase)
UserName = u.NameTrue(True)
DownloadText = u.DownloadText
DownloadTextPosts = u.DownloadTextPosts
DownloadTextSpecialFolder = u.DownloadTextSpecialFolder
End Sub
Friend Sub New(ByVal s As SiteSettingsBase)
DownloadText = s.DownloadText.Value
DownloadTextPosts = s.DownloadTextPosts.Value
DownloadTextSpecialFolder = s.DownloadTextSpecialFolder.Value
End Sub
Friend Sub New()
End Sub
Protected _ApplyBase_Name As Boolean = True
Protected _ApplyBase_Text As Boolean = True
Friend Sub ApplyBase(ByRef u As UserDataBase)
If _ApplyBase_Name Then u.NameTrue = UserName
If _ApplyBase_Text Then
u.DownloadText = DownloadText
u.DownloadTextPosts = DownloadTextPosts
u.DownloadTextSpecialFolder = DownloadTextSpecialFolder
End If
End Sub
End Class
End Namespace

View File

@@ -0,0 +1,43 @@
' Copyright (C) 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.Base
Friend Interface IPSite
Property QueryString As String
End Interface
Friend Class EditorExchangeOptionsBase_P : Inherits EditorExchangeOptionsBase : Implements IPSite
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property UserName As String
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property DownloadText As Boolean
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property DownloadTextPosts As Boolean
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property DownloadTextSpecialFolder As Boolean
<PSetting(Address:=SettingAddress.User, Caption:="Query",
ToolTip:="Query string. Don't change this field when creating a user! Change it only for the same request.")>
Friend Property QueryString As String Implements IPSite.QueryString
Friend Sub New()
DisableBase()
End Sub
Friend Sub New(ByVal u As UserDataBase)
MyBase.New(u)
DisableBase()
If TypeOf u Is IPSite Then QueryString = DirectCast(u, IPSite).QueryString
End Sub
Friend Sub New(ByVal s As SiteSettingsBase)
MyBase.New(s)
DisableBase()
End Sub
Friend Overridable Sub Apply(ByRef u As IPSite)
ApplyBase(u)
u.QueryString = QueryString
End Sub
Protected Overridable Sub DisableBase()
_ApplyBase_Name = False
_ApplyBase_Text = False
End Sub
End Class
End Namespace

View File

@@ -6,66 +6,7 @@
'
' 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 TokenBatch
Friend Const UrlLibStart As String = "[urllib3.connectionpool][debug]"
Friend Const UrlTextStart As String = UrlLibStart & " https"

View File

@@ -18,6 +18,7 @@ Namespace API.Base
End Enum
ReadOnly Property Site As String
ReadOnly Property Name As String
Property NameTrue As String
Property ID As String
Property Options As String
Property FriendlyName As String
@@ -56,6 +57,7 @@ Namespace API.Base
Property FileExists As Boolean
Property DownloadedPictures(ByVal Total As Boolean) As Integer
Property DownloadedVideos(ByVal Total As Boolean) As Integer
Property DownloadedTexts(ByVal Total As Boolean) As Integer
ReadOnly Property DownloadedTotal(Optional ByVal Total As Boolean = True) As Integer
ReadOnly Property DownloadedInformation As String
Property HasError As Boolean

View File

@@ -14,6 +14,7 @@ Imports PersonalUtilities.Tools.Web.Cookies
Imports PersonalUtilities.Tools.Web.Clients
Imports PersonalUtilities.Functions.RegularExpressions
Imports Download = SCrawler.Plugin.ISiteSettings.Download
Imports DN = SCrawler.API.Base.DeclaredNames
Namespace API.Base
Friend MustInherit Class SiteSettingsBase : Implements ISiteSettings, IResponserContainer
#Region "Declarations"
@@ -33,7 +34,7 @@ Namespace API.Base
End Property
Friend Property AccountName As String Implements ISiteSettings.AccountName
Friend Property Temporary As Boolean = False Implements ISiteSettings.Temporary
Friend Property DefaultInstance As ISiteSettings = Nothing Implements ISiteSettings.DefaultInstance
Friend Overridable Property DefaultInstance As ISiteSettings = Nothing Implements ISiteSettings.DefaultInstance
Protected _UserAgentDefault As String = String.Empty
Friend Overridable Property UserAgentDefault As String Implements ISiteSettings.UserAgentDefault
Get
@@ -55,6 +56,11 @@ Namespace API.Base
Friend Overridable ReadOnly Property Responser As Responser
Private _UserOptionsExists As Boolean = False
Private _UserOptionsType As Type = Nothing
Protected Overridable Function UserOptionsValid(ByVal Options As Object) As Boolean
Return True
End Function
Protected Overridable Sub UserOptionsSetParameters(ByRef Options As Object)
End Sub
Protected Property UserOptionsType As Type
Get
Return _UserOptionsType
@@ -64,6 +70,14 @@ Namespace API.Base
_UserOptionsExists = Not t Is Nothing
End Set
End Property
#Region "New user defaults"
<PropertyOption(ControlText:=DN.DownloadTextCaption, ControlToolTip:=DN.DownloadTextTip, Category:=DN.CAT_UserDefs), PXML, PClonable>
Friend Overridable Property DownloadText As PropertyValue
<PropertyOption(ControlText:=DN.DownloadTextPostsCaption, ControlToolTip:=DN.DownloadTextPostsTip, Category:=DN.CAT_UserDefs), PXML, PClonable>
Friend Overridable Property DownloadTextPosts As PropertyValue
<PropertyOption(ControlText:=DN.DownloadTextSpecialFolderCaption, ControlToolTip:=DN.DownloadTextSpecialFolderTip, Category:=DN.CAT_UserDefs), PXML, PClonable>
Friend Overridable Property DownloadTextSpecialFolder As PropertyValue
#End Region
#End Region
#Region "EnvironmentPrograms"
Private Property CMDEncoding As String Implements ISiteSettings.CMDEncoding
@@ -119,6 +133,9 @@ Namespace API.Base
_Image = __Image
Responser = New Responser With {.DeclaredError = EDP.ThrowException}
SettingsVersion = New PropertyValue(0)
DownloadText = New PropertyValue(False)
DownloadTextPosts = New PropertyValue(False)
DownloadTextSpecialFolder = New PropertyValue(True)
UpdateResponserFile()
End Sub
Friend Sub New(ByVal SiteName As String, ByVal CookiesDomain As String, ByVal AccName As String, ByVal Temp As Boolean,
@@ -243,7 +260,7 @@ Namespace API.Base
#Region "User info"
Protected UrlPatternUser As String = String.Empty
Friend Overridable Function GetUserUrl(ByVal User As IPluginContentProvider) As String Implements ISiteSettings.GetUserUrl
If Not UrlPatternUser.IsEmptyString Then Return String.Format(UrlPatternUser, User.Name)
If Not UrlPatternUser.IsEmptyString Then Return String.Format(UrlPatternUser, User.NameTrue.IfNullOrEmpty(User.Name))
Return String.Empty
End Function
Private Function ISiteSettings_GetUserPostUrl(ByVal User As IPluginContentProvider, ByVal Media As IUserMedia) As String Implements ISiteSettings.GetUserPostUrl
@@ -380,11 +397,41 @@ Namespace API.Base
End Sub
Friend Overridable Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean) Implements ISiteSettings.UserOptions
If _UserOptionsExists Then
If Options Is Nothing OrElse Not Options.GetType Is _UserOptionsType Then
Options = AConvert(Me, AModes.Var, _UserOptionsType,, True, Nothing)
If Options Is Nothing OrElse (Not Options.GetType Is _UserOptionsType OrElse Not UserOptionsValid(Options)) Then
Dim args% = 0
Dim constructor As ConstructorInfo = Nothing
With _UserOptionsType.GetTypeInfo.DeclaredConstructors
If .ListExists Then
With .Where(Function(ByVal c As ConstructorInfo) As Boolean
With c.GetParameters
If .ListExists Then
If .Count = 1 Then
Return .Self()(0).ParameterType Is Me.GetType
Else
Return False
End If
Else
Return True
End If
End With
Return If(c.GetParameters?.Count, 0).ValueBetween(0, 1)
End Function)
If .ListExists Then
args = .Max(Of Integer)(Function(c) If(c.GetParameters?.Count, 0))
constructor = .First(Function(c) If(c.GetParameters?.Count, 0) = args)
End If
End With
End If
End With
If Not constructor Is Nothing Then
If args > 0 AndAlso constructor.GetParameters()(0).ParameterType.GetInterface(GetType(ISiteSettings).Name) Is Nothing Then _
Throw New Exception("Class Interface type is incompatible")
If args = 0 Then Options = constructor.Invoke(Nothing) Else Options = constructor.Invoke({Me})
End If
If Options Is Nothing Then Options = Activator.CreateInstance(_UserOptionsType)
If Not Options Is Nothing Then UserOptionsSetParameters(Options)
End If
If OpenForm Then
If Not Options Is Nothing And OpenForm Then
Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using
End If
Else

View File

@@ -32,6 +32,8 @@ Namespace API.Base
Private Const Name_MediaPostID As String = "ID"
Private Const Name_MediaPostDate As String = "Date"
Private Const Name_SpecialFolder As String = "SpecialFolder"
Private Const Name_PostTextFile As String = "PostTextFile"
Private Const Name_PostTextFileSpecialFolder As String = "PostTextFileSpecialFolder"
#End Region
Friend Enum Types As Integer
Undefined = 0
@@ -46,12 +48,25 @@ Namespace API.Base
End Enum
Friend Enum States As Integer : Unknown = 0 : Tried = 1 : Downloaded = 2 : Skipped = 3 : Missing = 4 : End Enum
Friend [Type] As Types
Friend ReadOnly Property IsVideoType As Boolean
Get
Return Type = Types.m3u8 Or Type = Types.Video Or Type = Types.VideoPre
End Get
End Property
Friend ReadOnly Property IsAudioType As Boolean
Get
Return Type = Types.Audio Or Type = Types.AudioPre
End Get
End Property
Friend URL_BASE As String
Friend URL As String
Friend MD5 As String
Friend [File] As SFile
Friend Post As UserPost
Friend PictureOption As String
Friend PostText As String
Friend PostTextFile As SFile
Friend PostTextFileSpecialFolder As Boolean
Friend State As States
Friend Attempts As Integer
''' <summary>
@@ -125,6 +140,30 @@ Namespace API.Base
Post = New UserPost(Post.ID, PostDate)
End Set
End Property
Private Property IUserMedia_PostText As String Implements IUserMedia.PostText
Get
Return PostText
End Get
Set(ByVal NewText As String)
PostText = NewText
End Set
End Property
Private Property IUserMedia_PostTextFile As String Implements IUserMedia.PostTextFile
Get
Return PostTextFile.ToString
End Get
Set(ByVal NewPostFile As String)
If Not NewPostFile.IsEmptyString Then PostTextFile = New SFile(NewPostFile) Else PostTextFile = Nothing
End Set
End Property
Private Property IUserMedia_PostTextFileSpecialFolder As Boolean Implements IUserMedia.PostTextFileSpecialFolder
Get
Return PostTextFileSpecialFolder
End Get
Set(ByVal IsSpecialFolder As Boolean)
PostTextFileSpecialFolder = IsSpecialFolder
End Set
End Property
Private Property IUserMedia_SpecialFolder As String Implements IUserMedia.SpecialFolder
Get
Return SpecialFolder
@@ -171,6 +210,9 @@ Namespace API.Base
SpecialFolder = m.SpecialFolder
Attempts = m.Attempts
Me.Object = m.Object
PostText = m.PostText
PostTextFile = m.PostTextFile
PostTextFileSpecialFolder = m.PostTextFileSpecialFolder
End Sub
Friend Sub New(ByVal e As EContainer, ByVal UserInstance As IUserData)
Type = e.Attribute(Name_MediaType).Value.FromXML(Of Integer)(CInt(Types.Undefined))
@@ -180,6 +222,8 @@ Namespace API.Base
URL_BASE = e.Value
MD5 = e.Attribute(Name_MediaHash).Value
File = e.Attribute(Name_MediaFile).Value
PostTextFile = e.Attribute(Name_PostTextFile).Value
PostTextFileSpecialFolder = e.Attribute(Name_PostTextFileSpecialFolder).Value.FromXML(Of Boolean)(False)
Dim vp As Boolean? = Nothing
Dim upath$ = String.Empty
@@ -194,7 +238,13 @@ Namespace API.Base
SpecialFolder = e.Attribute(Name_SpecialFolder).Value
If Not SpecialFolder.IsEmptyString Then upath &= $"{SpecialFolder}\"
If vp.HasValue AndAlso vp.Value Then upath &= $"Video\"
If Not upath.IsEmptyString Then File = $"{upath.CSFilePS}{File.File}"
If Not upath.IsEmptyString Then
File = $"{upath.CSFilePS}{File.File}"
If Not PostTextFile.IsEmptyString Then
PostTextFile = $"{upath.CSFilePS}{IIf(PostTextFileSpecialFolder, $"{UserDataBase.PostTextSpecialFolderDefault}\", String.Empty)}{PostTextFile.File}"
If Type = Types.Text Then File = PostTextFile
End If
End If
Post = New UserPost With {
.ID = e.Attribute(Name_MediaPostID).Value,
@@ -234,7 +284,9 @@ Namespace API.Base
New EAttribute(Name_MediaURL, URL),
New EAttribute(Name_MediaHash, MD5),
New EAttribute(Name_MediaFile, File.File),
New EAttribute(Name_PostTextFile, PostTextFile.File),
New EAttribute(Name_SpecialFolder, SpecialFolder),
New EAttribute(Name_PostTextFileSpecialFolder, PostTextFileSpecialFolder.BoolToInteger),
New EAttribute(Name_MediaPostID, Post.ID),
New EAttribute(Name_MediaPostDate, AConvert(Of String)(Post.Date, DateTimeDefaultProvider, String.Empty))
}

View File

@@ -27,6 +27,7 @@ Imports CookieUpdateModes = PersonalUtilities.Tools.Web.Cookies.CookieKeeper.Upd
Namespace API.Base
Friend MustInherit Class UserDataBase : Implements IUserData, IPluginContentProvider, IThrower
Friend Const UserFileAppender As String = "User"
Friend Const PostTextSpecialFolderDefault As String = "txt"
#Region "Events"
Private ReadOnly UserUpdatedEventHandlers As List(Of IUserData.UserUpdatedEventHandler)
Friend Custom Event UserUpdated As IUserData.UserUpdatedEventHandler Implements IUserData.UserUpdated
@@ -152,6 +153,9 @@ Namespace API.Base
Private Const Name_IsSubscription As String = UserInfo.Name_IsSubscription
Private Const Name_Temporary As String = "Temporary"
Private Const Name_Favorite As String = "Favorite"
Private Const Name_DownloadText As String = "DownloadText"
Private Const Name_DownloadTextPosts As String = "DownloadTextPosts"
Private Const Name_DownloadTextSpecialFolder As String = "DownloadTextSpecialFolder"
Private Const Name_BackColor As String = "BackColor"
Private Const Name_ForeColor As String = "ForeColor"
Private Const Name_CreatedByChannel As String = "CreatedByChannel"
@@ -167,6 +171,7 @@ Namespace API.Base
Private Const Name_VideoCount As String = "VideoCount"
Private Const Name_PicturesCount As String = "PicturesCount"
Private Const Name_TextCount As String = "TextCount"
Private Const Name_LastUpdated As String = "LastUpdated"
Private Const Name_ScriptUse As String = "ScriptUse"
@@ -178,6 +183,8 @@ Namespace API.Base
#Region "Additional names"
Protected Const Name_SiteMode As String = "SiteMode"
Protected Const Name_TrueName As String = "TrueName"
'TODELETE Name_TrueName2
<Obsolete> Protected Const Name_TrueName2 As String = "NameTrue"
Protected Const Name_Arguments As String = "Arguments"
#End Region
#End Region
@@ -245,7 +252,20 @@ Namespace API.Base
#End Region
#Region "User name, ID, exist, suspend, options"
Friend User As UserInfo
Private _IsSavedPosts As Boolean = False
Friend Property IsSavedPosts As Boolean Implements IPluginContentProvider.IsSavedPosts
Get
Return _IsSavedPosts
End Get
Set(ByVal __IsSavedPosts As Boolean)
_IsSavedPosts = __IsSavedPosts
If _IsSavedPosts Then
DownloadText = True
DownloadTextPosts = True
DownloadTextSpecialFolder = True
End If
End Set
End Property
Private _UserExists As Boolean = True
Friend Overridable Property UserExists As Boolean Implements IUserData.Exists, IPluginContentProvider.UserExists
Get
@@ -278,7 +298,31 @@ Namespace API.Base
Return User.Name
End Get
End Property
Friend Overridable Property ID As String = String.Empty Implements IUserData.ID, IPluginContentProvider.ID
Private _NameTrue As String = String.Empty
Friend Overridable Overloads Property NameTrue As String Implements IUserData.NameTrue, IPluginContentProvider.NameTrue
Get
Return NameTrue(False)
End Get
Set(ByVal NewName As String)
If Not _NameTrue = NewName Then EnvirChanged(NewName)
_NameTrue = NewName
End Set
End Property
Friend Overloads ReadOnly Property NameTrue(ByVal Exact As Boolean) As String
Get
Return If(Exact, _NameTrue, _NameTrue.IfNullOrEmpty(Name))
End Get
End Property
Private _ID As String = String.Empty
Friend Property ID As String Implements IUserData.ID, IPluginContentProvider.ID
Get
Return _ID
End Get
Set(ByVal NewId As String)
If Not _ID = NewId Then EnvirChanged(NewId)
_ID = NewId
End Set
End Property
Protected _FriendlyName As String = String.Empty
Friend Overridable Property FriendlyName As String Implements IUserData.FriendlyName
Get
@@ -348,12 +392,20 @@ Namespace API.Base
Protected Function UserDescriptionNeedToUpdate() As Boolean
Return (UserDescription.IsEmptyString Or _DescriptionEveryTime) And Not _DescriptionChecked
End Function
Protected Sub UserDescriptionUpdate(ByVal Descr As String)
If UserDescriptionNeedToUpdate() Then
Protected Sub UserDescriptionUpdate(ByVal Descr As String, Optional ByVal Force As Boolean = False,
Optional ByVal InsertFirst As Boolean = False, Optional ByVal AppendDate As Boolean = False)
If UserDescriptionNeedToUpdate() Or Force Then
If AppendDate Then Descr = $"{Now.ToStringDateDef}: {Descr}"
If UserDescription.IsEmptyString Then
UserDescription = Descr
_ForceSaveUserInfo = True
ElseIf Not UserDescription.Contains(Descr) Then
UserDescription &= $"{vbNewLine}----{vbNewLine}{Descr}"
If InsertFirst Then
UserDescription = $"{Descr}{vbNewLine}{UserDescription}"
Else
UserDescription &= $"{vbNewLine}----{vbNewLine}{Descr}"
End If
_ForceSaveUserInfo = True
End If
_DescriptionChecked = True
End If
@@ -614,6 +666,9 @@ BlockNullPicture:
Friend Overridable Property ReadyForDownload As Boolean = True Implements IUserData.ReadyForDownload
Friend Property DownloadImages As Boolean = True Implements IUserData.DownloadImages
Friend Property DownloadVideos As Boolean = True Implements IUserData.DownloadVideos
Friend Overridable Property DownloadText As Boolean = False
Friend Overridable Property DownloadTextPosts As Boolean = False
Friend Overridable Property DownloadTextSpecialFolder As Boolean = True
Friend Property DownloadMissingOnly As Boolean = False Implements IUserData.DownloadMissingOnly
Private _IconBannerDownloaded As Boolean = False
Friend WriteOnly Property IconBannerDownloaded As Boolean
@@ -725,9 +780,23 @@ BlockNullPicture:
End If
End Set
End Property
Private _DownloadedTextsTotal As Integer = 0
Private _DownloadedTextsSession As Integer = 0
Friend Property DownloadedTexts(ByVal Total As Boolean) As Integer Implements IUserData.DownloadedTexts
Get
Return IIf(Total, _DownloadedTextsTotal, _DownloadedTextsSession)
End Get
Set(ByVal NewValue As Integer)
If Total Then
_DownloadedTextsTotal = NewValue
Else
_DownloadedTextsSession = NewValue
End If
End Set
End Property
Friend Overridable ReadOnly Property DownloadedTotal(Optional ByVal Total As Boolean = True) As Integer Implements IUserData.DownloadedTotal
Get
Return DownloadedPictures(Total) + DownloadedVideos(Total)
Return DownloadedPictures(Total) + DownloadedVideos(Total) + DownloadedTexts(Total)
End Get
End Property
Friend ReadOnly Property DownloadedInformation As String Implements IUserData.DownloadedInformation
@@ -907,6 +976,10 @@ BlockNullPicture:
FileExists = True
Using x As New XmlFile(MyFileSettings) With {.XmlReadOnly = True}
If User.Name.IsEmptyString Then User.Name = x.Value(Name_UserName)
_NameTrue = x.Value(Name_TrueName)
#Disable Warning BC40008
If _NameTrue.IsEmptyString AndAlso x.Contains(Name_TrueName2) Then _NameTrue = x.Value(Name_TrueName2)
#Enable Warning
UserExists = x.Value(Name_UserExists).FromXML(Of Boolean)(True)
UserSuspended = x.Value(Name_UserSuspended).FromXML(Of Boolean)(False)
ID = x.Value(Name_UserID)
@@ -934,9 +1007,13 @@ BlockNullPicture:
ReadyForDownload = x.Value(Name_ReadyForDownload).FromXML(Of Boolean)(True)
DownloadImages = x.Value(Name_DownloadImages).FromXML(Of Boolean)(True)
DownloadVideos = x.Value(Name_DownloadVideos).FromXML(Of Boolean)(True)
DownloadText = x.Value(Name_DownloadText).FromXML(Of Boolean)(IsSavedPosts)
DownloadTextPosts = x.Value(Name_DownloadTextPosts).FromXML(Of Boolean)(IsSavedPosts)
DownloadTextSpecialFolder = x.Value(Name_DownloadTextSpecialFolder).FromXML(Of Boolean)(True)
_IconBannerDownloaded = x.Value(Name_IconBannerDownloaded).FromXML(Of Boolean)(False)
DownloadedVideos(True) = x.Value(Name_VideoCount).FromXML(Of Integer)(0)
DownloadedPictures(True) = x.Value(Name_PicturesCount).FromXML(Of Integer)(0)
DownloadedTexts(True) = x.Value(Name_TextCount).FromXML(Of Integer)(0)
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)
@@ -967,6 +1044,7 @@ BlockNullPicture:
x.Add(Name_Plugin, HOST.Key)
x.Add(Name_AccountName, AccountName)
x.Add(Name_UserName, User.Name)
x.Add(Name_TrueName, _NameTrue)
x.Add(Name_Model_User, CInt(UserModel))
x.Add(Name_Model_Collection, CInt(CollectionModel))
x.Add(Name_SpecialPath, User.SpecialPath)
@@ -995,9 +1073,13 @@ BlockNullPicture:
x.Add(Name_ReadyForDownload, ReadyForDownload.BoolToInteger)
x.Add(Name_DownloadImages, DownloadImages.BoolToInteger)
x.Add(Name_DownloadVideos, DownloadVideos.BoolToInteger)
x.Add(Name_DownloadText, DownloadText.BoolToInteger)
x.Add(Name_DownloadTextPosts, DownloadTextPosts.BoolToInteger)
x.Add(Name_DownloadTextSpecialFolder, DownloadTextSpecialFolder.BoolToInteger)
x.Add(Name_IconBannerDownloaded, _IconBannerDownloaded.BoolToInteger)
x.Add(Name_VideoCount, DownloadedVideos(True))
x.Add(Name_PicturesCount, DownloadedPictures(True))
x.Add(Name_TextCount, DownloadedTexts(True))
x.Add(Name_LastUpdated, AConvert(Of String)(LastUpdated, ADateTime.Formats.BaseDateTime, String.Empty))
x.Add(Name_ScriptUse, ScriptUse.BoolToInteger)
x.Add(Name_ScriptData, ScriptData)
@@ -1162,6 +1244,8 @@ BlockNullPicture:
Select Case Caller
Case NameOf(UserExists) : If Not _EnvirUserExists = CBool(NewValue) Then _EnvirChanged = True : _EnvirInvokeUserUpdated = True
Case NameOf(UserSuspended) : If Not _EnvirUserSuspended = CBool(NewValue) Then _EnvirChanged = True : _EnvirInvokeUserUpdated = True
Case NameOf(NameTrue) : _EnvirChanged = True : _ForceSaveUserInfo = True : _ForceSaveUserInfoOnException = True
Case NameOf(ID) : _EnvirChanged = True : _ForceSaveUserInfo = True : _ForceSaveUserInfoOnException = True
Case Else : _EnvirChanged = True
End Select
End If
@@ -1215,6 +1299,7 @@ BlockNullPicture:
If Not DownloadImages Then _TempMediaList.RemoveAll(Function(m) m.Type = UTypes.GIF Or m.Type = UTypes.Picture)
If Not DownloadVideos Then _TempMediaList.RemoveAll(Function(m) m.Type = UTypes.Video Or
m.Type = UTypes.VideoPre Or m.Type = UTypes.m3u8)
If Not DownloadTextPosts Then _TempMediaList.RemoveAll(Function(m) m.Type = UTypes.Text)
If DownloadMissingOnly Then _TempMediaList.RemoveAll(Function(m) Not m.State = UStates.Missing)
End If
@@ -1282,9 +1367,9 @@ BlockNullPicture:
UpdateUserInformation_Ex()
If Not exit_ex.Silent Then
If exit_ex.SimpleLogLine Then
MyMainLOG = $"{ToStringForLog()}: downloading interrupted (exit) ({exit_ex.Message})"
LogError(Nothing, $"downloading interrupted (exit) ({exit_ex.Message})")
Else
ErrorsDescriber.Execute(EDP.SendToLog, exit_ex, $"{ToStringForLog()}: downloading interrupted (exit)")
LogError(exit_ex, "downloading interrupted (exit)")
End If
End If
If _EnvirInvokeUserUpdated Then OnUserUpdated()
@@ -1430,6 +1515,7 @@ BlockNullPicture:
Data.DownloadState = UserMediaStates.Missing
End If
YouTube.Objects.YouTubeMediaContainerBase.Update(_ContentNew(0), Data)
If _ContentNew.Count > 1 Then Data.Files.ListAddList(_ContentNew.Select(Function(cc) cc.File), LNC)
If ResetTitle And Not _ContentNew(0).File.Name.IsEmptyString Then Data.Title = _ContentNew(0).File.Name
Else
Data.DownloadState = UserMediaStates.Missing
@@ -1627,7 +1713,8 @@ BlockNullPicture:
Dim dCount% = 0, dTotal% = 0
ThrowAny(Token)
If _ContentNew.Count > 0 Then
_ContentNew.RemoveAll(Function(c) c.URL.IsEmptyString)
_ContentNew.RemoveAll(Function(c) Not c.Type = UTypes.Text And c.URL.IsEmptyString)
If Not DownloadText Or Not DownloadTextPosts Then _ContentNew.RemoveAll(Function(c) c.Type = UTypes.Text)
If _ContentNew.Count > 0 Then
If UseMD5Comparison Then LoadMD5()
MyFile.Exists(SFO.Path)
@@ -1636,7 +1723,7 @@ BlockNullPicture:
Dim vsf As Boolean = SeparateVideoFolderF
Dim __isVideo As Boolean
Dim __interrupt As Boolean
Dim f As SFile
Dim f As SFile, fTxt As SFile
Dim v As UserMedia
Dim __fileDeleted As Boolean
Dim fileNumProvider As SFileNumbers = SFileNumbers.Default
@@ -1650,18 +1737,20 @@ BlockNullPicture:
ErrorsDescriber.Execute(EDP.SendToLog, file_del_ex)
End Try
End Sub
Dim updateDownCount As Action = Sub()
Dim __n% = IIf(__fileDeleted, -1, 1)
If __isVideo Then
v.Type = UTypes.Video
DownloadedVideos(False) += __n
ElseIf v.Type = UTypes.GIF Then
DownloadedPictures(False) += __n
Else
v.Type = UTypes.Picture
DownloadedPictures(False) += __n
End If
End Sub
Dim updateDownCount As Action(Of Boolean) = Sub(ByVal forceText As Boolean)
Dim __n% = IIf(__fileDeleted And Not forceText, -1, 1)
If v.Type = UTypes.Text Or forceText Then
DownloadedTexts(False) += __n
ElseIf __isVideo Then
v.Type = UTypes.Video
DownloadedVideos(False) += __n
ElseIf v.Type = UTypes.GIF Then
DownloadedPictures(False) += __n
Else
v.Type = UTypes.Picture
DownloadedPictures(False) += __n
End If
End Sub
Using w As New OptionalWebClient(Me)
If vsf Then CSFileP($"{MyDir}\{VideoFolderName}\").Exists(SFO.Path)
@@ -1694,8 +1783,9 @@ BlockNullPicture:
__fileDeleted = False
If Not f.IsEmptyString And Not v.URL.IsEmptyString Then
If (v.Type = UTypes.Text And DownloadText) Or (Not f.IsEmptyString And Not v.URL.IsEmptyString) Then
Try
If v.Type = UTypes.Text Then GoTo stxt
__isVideo = v.Type = UTypes.Video Or f.Extension = "mp4" Or v.Type = UTypes.m3u8
If f.Extension.IsEmptyString Then
@@ -1738,7 +1828,7 @@ BlockNullPicture:
End If
End If
updateDownCount()
updateDownCount(False)
v.File = ChangeFileNameByProvider(f, v)
v.State = UStates.Downloaded
@@ -1749,7 +1839,7 @@ BlockNullPicture:
If Not v.MD5.IsEmptyString Then
If _MD5List.Contains(v.MD5) Then
__fileDeleted = v.File.Delete(SFO.File, SFODelete.DeletePermanently, EDP.ReturnValue)
If __fileDeleted Then dCount -= 1 : updateDownCount()
If __fileDeleted Then dCount -= 1 : updateDownCount(False)
Else
_MD5List.Add(v.MD5)
End If
@@ -1758,7 +1848,33 @@ BlockNullPicture:
dCount -= 1
End If
End If
stxt:
If DownloadText And Not v.PostText.IsEmptyString And (v.Type = UTypes.Text Or v.File.Exists) Then
fTxt = v.File
If fTxt.IsEmptyString Then
If DownloadTextPosts And Not f.IsEmptyString Then
fTxt = f
If v.Type = UTypes.Text Then fTxt.Name &= IIf(fTxt.Name.IsEmptyString, String.Empty, "_") &
v.Post.ID.StringRemoveWinForbiddenSymbols
If fTxt.IsEmptyString Then Throw New ArgumentNullException("Text", "Error downloading text") With {.HelpLink = 10}
Else
Continue For
End If
End If
v.PostTextFileSpecialFolder = DownloadTextSpecialFolder
If DownloadTextSpecialFolder Then fTxt.Path = $"{fTxt.Path.StringTrimEnd("\")}\{PostTextSpecialFolderDefault}"
fTxt.Extension = "txt"
v.PostTextFile = TextSaver.SaveTextToFile(v.PostText, fTxt,,, Settings.FeedShowTextPosts_LogErrors_E)
If Not v.PostTextFile.Exists Then Throw New ArgumentNullException("Text", "Error downloading text") With {.HelpLink = 10}
If v.Type = UTypes.Text Then v.File = v.PostTextFile
v.State = UStates.Downloaded
updateDownCount(Not v.Type = UTypes.Text)
If v.URL.IsEmptyString Then v.URL = v.PostTextFile.File
If v.URL_BASE.IsEmptyString Then v.URL_BASE = v.URL
End If
dCount += 1
Catch anex As ArgumentNullException When anex.HelpLink = 10
LogError(anex, anex.Message, Settings.FeedShowTextPosts_LogErrors_E)
Catch woex As OperationCanceledException When Token.IsCancellationRequested
__deleteFile.Invoke(f, v.URL_BASE)
v.State = UStates.Missing
@@ -1766,7 +1882,7 @@ BlockNullPicture:
_ContentNew(i) = v
Throw woex
Catch wex As Exception
If DownloadContentDefault_ProcessDownloadException() Then
If Not v.Type = UTypes.Text AndAlso DownloadContentDefault_ProcessDownloadException() Then
v.Attempts += 1
v.State = UStates.Missing
If MissingErrorsAdd Then ErrorDownloading(f, v.URL)
@@ -1846,6 +1962,31 @@ BlockNullPicture:
Protected Overridable Function CreateFileFromUrl(ByVal URL As String) As SFile
Return New SFile(URL)
End Function
Protected Overridable Function SimpleDownloadAvatar(ByVal ImageAddress As String, Optional ByVal FileCreateFunc As Func(Of String, SFile) = Nothing,
Optional ByVal e As ErrorsDescriber = Nothing) As SFile
Try
If Not ImageAddress.IsEmptyString Then
Dim f As SFile
If FileCreateFunc Is Nothing Then
f = CreateFileFromUrl(ImageAddress)
Else
f = FileCreateFunc.Invoke(ImageAddress)
End If
If Not f.Name.IsEmptyString Then f.Name = f.Name.StringRemoveWinForbiddenSymbols.StringTrim
If Not f.Name.IsEmptyString Then
f.Path = DownloadContentDefault_GetRootDir()
f.Separator = "\"
If f.Extension.IsEmptyString Then f.Extension = "jpg"
If Not f.Exists Then GetWebFile(ImageAddress, f, EDP.ReturnValue)
If f.Exists Then IconBannerDownloaded = True : Return f
End If
End If
Return Nothing
Catch ex As Exception
If Not e.Exists Then e = New ErrorsDescriber(EDP.ReturnValue)
Return ErrorsDescriber.Execute(e, ex, $"SimpleDownloadAvatar({ImageAddress})", New SFile)
End Try
End Function
Protected Overridable Function ChangeFileNameByProvider(ByVal f As SFile, ByVal m As UserMedia) As SFile
Dim ff As SFile = Nothing
Try
@@ -1934,7 +2075,7 @@ BlockNullPicture:
End If
If m.Contains(IUserData.EraseMode.Data) Then
Dim files As List(Of SFile) = SFile.GetFiles(DownloadContentDefault_GetRootDir.CSFileP,, SearchOption.AllDirectories, e)
If files.ListExists Then files.RemoveAll(Function(f) Not f.Extension.IsEmptyString AndAlso (f.Extension = "txt" Or f.Extension = "xml"))
If files.ListExists Then files.RemoveAll(Function(f) Not f.Extension.IsEmptyString AndAlso ((f.Path.EndsWith(SettingsFolderName) And f.Extension = "txt") Or f.Extension = "xml"))
If files.ListExists Then files.ForEach(Sub(f) f.Delete(SFO.File, Settings.DeleteMode, e))
LatestData.Clear()
result = True
@@ -2147,6 +2288,7 @@ BlockNullPicture:
End Function
#End Region
#Region "Errors functions"
''' <summary>ToStringForLog(): Message</summary>
Protected Sub LogError(ByVal ex As Exception, ByVal Message As String, Optional ByVal e As ErrorsDescriber = Nothing)
ErrorsDescriber.Execute(If(e.Exists, e, New ErrorsDescriber(EDP.SendToLog)), ex, $"{ToStringForLog()}: {Message}")
End Sub
@@ -2177,6 +2319,17 @@ BlockNullPicture:
Friend Function ToStringForLog() As String
Return $"{IIf(IncludedInCollection, $"[{CollectionName}] - ", String.Empty)}[{Site}] - {Name}"
End Function
Friend Overloads Function ToStringExt(ByVal UseFriendlyName As Boolean) As String
Return $"{IIf(IncludedInCollection, $"[{CollectionName}] - ", String.Empty)}[{Site}] - {String.Format(CStr(IIf(Not FriendlyName.IsEmptyString And
UseFriendlyName, "{1} ({0})", "{0}")), Name, FriendlyName)}"
End Function
Friend Overloads Shared Function ToStringExt(ByVal User As UserInfo) As String
If Not IsDBNull(User) Then
With User : Return $"{IIf(.IncludedInCollection, $"[{ .CollectionName}] - ", String.Empty)}[{ .Site}] - { .Name}" : End With
Else
Return String.Empty
End If
End Function
Public Overrides Function ToString() As String
If IsCollection Then
Return CollectionName

View File

@@ -134,6 +134,7 @@ Namespace API.Base
m.GetMemberCustomAttributes(Of Provider).ListExists
Dim m1 As MemberInfo, m2 As MemberInfo
Dim tmpObj As Object
Dim maxOffset%
members = GetObjectMembers(MyObject, Function(m) (m.MemberType = MemberTypes.Field Or m.MemberType = MemberTypes.Property) AndAlso
Not m.GetCustomAttribute(Of PSettingAttribute) Is Nothing,, True,
@@ -175,6 +176,9 @@ Namespace API.Base
If MyMembers.Count > 0 Then
maxOffset = MyMembers.Max(Function(mm) mm.LeftOffset)
If maxOffset > 0 Then MyMembers.ForEach(Sub(mm) mm.LeftOffset = maxOffset)
Dim prov As IEnumerable(Of Provider)
Dim _prov As Provider
Dim si% = -1

View File

@@ -0,0 +1,18 @@
' Copyright (C) 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.Bluesky
Friend Module Declarations
Friend Const BlueskySiteKey As String = "AndyProgram_Bluesky"
Friend ReadOnly DateProvider As New ADateTime("yyyy-MM-ddTHH:mm:ss.FFF%K")
Friend ReadOnly RegEx_PlayLists As RParams = RParams.DM("RESOLUTION=\d+x(\d+)\s*(\S+)", 0, RegexReturn.List, EDP.ReturnValue)
Friend ReadOnly RegEx_FilePattern As RParams = RParams.DM("(.+?)(\.|@)(gif|m3u8|[^/\?\&]+)", 0, RegexReturn.ListByMatch, EDP.ReturnValue)
Friend ReadOnly RegEx_SinglePostPattern As RParams = RParams.DM("profile/([^/]+)/post/([^/\?\&]+)", 0, RegexReturn.ListByMatch, EDP.ReturnValue)
End Module
End Namespace

View File

@@ -0,0 +1,51 @@
' Copyright (C) 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 PersonalUtilities.Forms.Toolbars
Imports PersonalUtilities.Tools.Web.Clients
Imports PersonalUtilities.Functions.RegularExpressions
Namespace API.Bluesky
Friend NotInheritable Class M3U8
Private Sub New()
End Sub
Private Shared Function GetUrlsList(ByVal URL As String) As List(Of String)
Using resp As New Responser With {.AllowAutoRedirect = False}
Dim r$ = resp.GetResponse(URL)
If Not r.IsEmptyString Then
Dim file$ = String.Empty, appender$
Dim files As List(Of Sizes) = RegexFields(Of Sizes)(r, {RegEx_PlayLists}, {1, 2})
If files.ListExists Then files.RemoveAll(Function(ff) ff.Value = 0 Or ff.Data.IsEmptyString)
If files.ListExists Then
files.Sort()
file = files(0).Data
appender = URL.Replace(URL.Split("/").Last, String.Empty)
file = M3U8Base.CreateUrl(appender, file)
If Not file.IsEmptyString Then
r = resp.GetResponse(file)
If Not r.IsEmptyString Then
Dim l As List(Of String) = RegexReplace(r, M3U8Declarations.TsFilesRegEx)
If l.ListExists Then
appender = file.Replace(file.Split("/").Last, String.Empty)
For i% = 0 To l.Count - 1 : l(i) = M3U8Base.CreateUrl(appender, l(i)) : Next
Return l
End If
End If
End If
End If
End If
End Using
Return Nothing
End Function
Friend Shared Function Download(ByVal URL As String, ByVal Destination As SFile, ByVal Token As CancellationToken,
ByVal Progress As MyProgress, ByVal UsePreProgress As Boolean) As SFile
Return M3U8Base.Download(GetUrlsList(URL), Destination,, Token, Progress, UsePreProgress)
End Function
End Class
End Namespace

View File

@@ -0,0 +1,100 @@
' Copyright (C) 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
Namespace API.Bluesky
<Manifest(BlueskySiteKey), SpecialForm(False)>
Friend Class SiteSettings : Inherits SiteSettingsBase
<PropertyOption(ControlText:="Cookies enabled", ControlToolTip:="If checked, cookies will be used in requests", IsAuth:=True), PXML, PClonable, HiddenControl>
Friend ReadOnly Property CookiesEnabled As PropertyValue
<PropertyOption(ControlText:="User name", IsAuth:=True, AllowNull:=False), PXML>
Friend ReadOnly Property UserHandle As PropertyValue
<PropertyOption(ControlText:="Password", IsAuth:=True, AllowNull:=False), PXML>
Friend ReadOnly Property UserPassword As PropertyValue
<PXML> Friend ReadOnly Property Token As PropertyValue
<PXML> Friend ReadOnly Property TokenUpdateTime As PropertyValue
<PropertyOption(ControlText:="Token update", ControlToolTip:="Token refresh interval (in minutes)." & vbCr & "Default: 120.", IsAuth:=True), PXML, PClonable, HiddenControl>
Friend ReadOnly Property TokenRefreshInterval As PropertyValue
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
MyBase.New("Bluesky", "bsky.app", AccName, Temp, My.Resources.SiteResources.BlueskyIcon_32, My.Resources.SiteResources.BlueskyPic_32)
Responser.ContentType = "application/json"
CookiesEnabled = New PropertyValue(False)
UserHandle = New PropertyValue(String.Empty, GetType(String))
UserPassword = New PropertyValue(String.Empty, GetType(String))
Token = New PropertyValue(String.Empty, GetType(String))
TokenUpdateTime = New PropertyValue(Now.AddYears(-1))
TokenRefreshInterval = New PropertyValue(120)
_AllowUserAgentUpdate = False
UrlPatternUser = "https://bsky.app/profile/{0}"
ImageVideoContains = "bsky.app"
UserRegex = RParams.DMS("bsky.app/profile/([^/\?]+)", 1, EDP.ReturnValue)
UserOptionsType = GetType(EditorExchangeOptionsBase)
End Sub
Protected Overrides Function UserOptionsValid(ByVal Options As Object) As Boolean
Return DirectCast(Options, EditorExchangeOptionsBase).SiteKey = BlueskySiteKey
End Function
Protected Overrides Sub UserOptionsSetParameters(ByRef Options As Object)
DirectCast(Options, EditorExchangeOptionsBase).SiteKey = BlueskySiteKey
End Sub
Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider
Return New UserData
End Function
Friend Overrides Function BaseAuthExists() As Boolean
Return Not CStr(UserHandle.Value).IsEmptyString And Not CStr(UserPassword.Value).IsEmptyString
End Function
Friend Overrides Function Available(ByVal What As ISiteSettings.Download, ByVal Silent As Boolean) As Boolean
Return MyBase.Available(What, Silent) AndAlso UpdateToken()
End Function
Private _TokenUpdating As Boolean = False
Friend Function UpdateToken(Optional ByVal Force As Boolean = False) As Boolean
Try
While _TokenUpdating : Threading.Thread.Sleep(100) : End While
_TokenUpdating = True
If BaseAuthExists() Then
If CDate(TokenUpdateTime.Value).AddMinutes(TokenRefreshInterval.Value) < Now Or Force Then
Using resp As Responser = Responser.Copy
With resp
.Mode = Responser.Modes.Curl
.Method = "POST"
.CurlSslNoRevoke = True
.CurlInsecure = True
.CurlArgumentsLeft = "-d ""{\" & $"""identifier\"": \""{UserHandle.Value}\"", \""password\"": \""{UserPassword.Value}\""" & "}"""
Dim r$ = .GetResponse("https://bsky.social/xrpc/com.atproto.server.createSession")
If Not r.IsEmptyString Then
Using j As EContainer = JsonDocument.Parse(r, EDP.ReturnValue)
If j.ListExists Then
Dim t$ = j.Value("accessJwt")
If Not t.IsEmptyString Then Token.Value = $"Bearer {t}" : TokenUpdateTime.Value = Now : Return True
End If
End Using
End If
End With
End Using
Else
Return True
End If
End If
Return False
Catch ex As Exception
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, "Bluesky.SiteSettings.UpdateToken", False)
Finally
_TokenUpdating = False
End Try
End Function
End Class
End Namespace

View File

@@ -0,0 +1,363 @@
' Copyright (C) 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
Imports UStates = SCrawler.API.Base.UserMedia.States
Namespace API.Bluesky
Friend Class UserData : Inherits UserDataBase
#Region "Declarations"
Private ReadOnly Property MySettings As SiteSettings
Get
Return HOST.Source
End Get
End Property
Private ReadOnly Property ID_Encoded As String
Get
Return If(ID.IsEmptyString, String.Empty, SymbolsConverter.ASCII.EncodeSymbolsOnly(ID))
End Get
End Property
Private ReadOnly _TmpPosts2 As List(Of String)
#End Region
#Region "Loader"
Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean)
End Sub
Friend Overrides Function ExchangeOptionsGet() As Object
Return New EditorExchangeOptionsBase(Me) With {.SiteKey = BlueskySiteKey}
End Function
Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
If Not Obj Is Nothing AndAlso TypeOf Obj Is EditorExchangeOptionsBase AndAlso
DirectCast(Obj, EditorExchangeOptionsBase).SiteKey = BlueskySiteKey Then DirectCast(Obj, EditorExchangeOptionsBase).ApplyBase(Me)
End Sub
#End Region
#Region "Initializer"
Friend Sub New()
UseInternalM3U8Function = True
_TmpPosts2 = New List(Of String)
End Sub
#End Region
#Region "Token"
Private Function UpdateToken(Optional ByVal Force As Boolean = False, Optional ByVal OnlyAddHeader As Boolean = False) As Boolean
Dim process As Boolean = True
If CDate(MySettings.TokenUpdateTime.Value).AddHours(2) <= Now Or Force Then
process = MySettings.UpdateToken(Force)
If process Then _TokenUpdateCount += 1
End If
If process Or OnlyAddHeader Then Responser.Headers.Add("authorization", MySettings.Token.Value)
Return Not Responser.Headers.Value("authorization").IsEmptyString
End Function
Private _TokenUpdateCount As Integer = 0
Private Sub TokenUpdateCountReset()
_TokenUpdateCount = 0
End Sub
#End Region
#Region "Download"
Private _PostCount As Integer = 0
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
_TmpPosts2.Clear()
Try
If Not CBool(MySettings.CookiesEnabled.Value) Then Responser.Cookies.Clear()
UpdateToken(, True)
_TokenUpdateCount = 0
_PostCount = 0
DownloadData(String.Empty, Token)
Finally
_TempPostsList.ListAddList(_TmpPosts2, LNC)
_TmpPosts2.Clear()
End Try
End Sub
Private Overloads Sub DownloadData(ByVal Cursor As String, ByVal Token As CancellationToken)
Dim URL$ = String.Empty
Try
If ID.IsEmptyString Then GetProfileInfo(Token)
If ID.IsEmptyString Then Throw New ArgumentNullException("ID", "ID is null")
If UpdateToken() Then
Dim nextCursor$ = String.Empty
Dim c%
URL = $"https://bsky.social/xrpc/app.bsky.feed.getAuthorFeed?actor={ID_Encoded}&filter=posts_and_author_threads&includePins=false&limit=99"
If Not Cursor.IsEmptyString Then URL &= $"&cursor={SymbolsConverter.ASCII.EncodeSymbolsOnly(Cursor)}"
Dim r$ = Responser.GetResponse(URL)
TokenUpdateCountReset()
If Not r.IsEmptyString Then
Using j As EContainer = JsonDocument.Parse(r)
If j.ListExists Then
With j("feed")
If .ListExists Then
For Each post As EContainer In .Self
With post({"post"})
c = DefaultParser(.Self,, nextCursor)
Select Case c
Case CInt(DateResult.Skip) * -1 : Continue For
Case CInt(DateResult.Exit) * -1 : Exit Sub
Case Is > 0 : _PostCount += c
End Select
If DownloadTopCount.HasValue AndAlso DownloadTopCount.Value <= _PostCount Then Exit Sub
End With
Next
End If
End With
End If
End Using
If Not nextCursor.IsEmptyString Then DownloadData(nextCursor, Token)
End If
End If
Catch ex As Exception
ProcessException(ex, Token, $"DownloadData({URL})")
End Try
End Sub
#End Region
#Region "DefaultParser"
Private Const Down_ImageAddress As String = "https://cdn.bsky.app/img/feed_fullsize/plain/{0}/{1}"
Private Function GetPostID(ByVal PostUri As String) As String
Return If(PostUri.IsEmptyString, String.Empty, PostUri.Split("/").LastOrDefault)
End Function
Private Function DefaultParser(ByVal e As EContainer, Optional ByVal CheckDateLimits As Boolean = True, Optional ByRef NextCursor As String = Nothing,
Optional ByVal CheckTempPosts As Boolean = True, Optional ByVal State As UStates = UStates.Unknown) As Integer
Const exitReturn% = CInt(DateResult.Exit) * -1
Const skipReturn% = CInt(DateResult.Skip) * -1
Dim postID$, postDate$, __url$, __urlBase$, __txt$, __userId$
Dim updateUrl As Boolean
Dim c% = 0
Dim m As UserMedia
Dim d As EContainer
With e
If .ListExists Then
postID = GetPostID(.Value("uri"))
postDate = String.Empty
__urlBase = String.Empty
__txt = String.Empty
__userId = .Value({"author"}, "did")
With .Item({"record"})
If .ListExists Then
'2025-01-28T02:42:12.415Z
postDate = .Value("createdAt")
NextCursor = postDate
If CheckDateLimits Then
Select Case CheckDatesLimit(postDate, DateProvider)
Case DateResult.Skip : Return skipReturn 'Continue For
Case DateResult.Exit : Return exitReturn 'Exit Sub
End Select
End If
If CheckTempPosts Then
'If _TempPostsList.Contains(postID) Then Return exitReturn Else _TempPostsList.Add(postID)
If _TempPostsList.Contains(postID) Then Return exitReturn Else _TmpPosts2.Add(postID)
End If
If ParseUserMediaOnly And Not ID.IsEmptyString And Not __userId.IsEmptyString And Not ID = __userId Then Return skipReturn
__urlBase = $"https://bsky.app/profile/{NameTrue}/post/{postID}"
End If
End With
Dim createMedia As Func(Of String, UTypes, UserMedia) =
Function(ByVal url As String, ByVal type As UTypes) As UserMedia
m = New UserMedia(url, type) With {
.URL_BASE = __urlBase,
.File = CreateFileFromUrl(url, type),
.Post = New UserPost(postID, If(AConvert(Of Date)(postDate, DateProvider, Nothing, EDP.ReturnValue), Nothing)),
.State = State,
.PostText = __txt,
.PostTextFileSpecialFolder = DownloadTextSpecialFolder
}
If type = UTypes.Text Then m.PostTextFile = $"{postID}.txt"
_TempMediaList.ListAddValue(m, LNC)
c += 1
Return m
End Function
__txt = .Value({"record"}, "text").IfNullOrEmpty(__txt)
For Each SecondExtraction As Boolean In {False, True}
With If(SecondExtraction, .Item({"record", "embed"}), .Item("embed"))
If .ListExists Then
If If(.Item("images")?.Count, 0) > 0 Then
With .Item("images")
For Each d In .Self
updateUrl = False
__url = d.Value("fullsize")
If __url.IsEmptyString Then __url = d.Value({"image", "ref"}, "$link") : updateUrl = True
If __url.IsEmptyString And SecondExtraction Then updateUrl = False : __url = e.Value({"embed"}, "thumb")
If Not __url.IsEmptyString Then createMedia(__url, UTypes.Picture)
Next
End With
End If
If Not .Value("playlist").IsEmptyString Then createMedia(.Value("playlist"), UTypes.m3u8)
If If(.Item("external")?.Count, 0) > 0 Then
__txt = .Value({"external"}, "title").IfNullOrEmpty(__txt)
createMedia(.Value({"external"}, "uri"), UTypes.GIF)
End If
If If(.Item({"media"}, "external")?.Count, 0) > 0 Then
__txt = .Value({"media", "external"}, "title").IfNullOrEmpty(__txt)
createMedia(.Value({"media", "external"}, "uri"), UTypes.GIF)
End If
End If
End With
If c > 0 Then Exit For
Next
End If
End With
Return c
End Function
#End Region
#Region "GetProfileInfo"
Private Sub GetProfileInfo(ByVal Token As CancellationToken)
Try
If UpdateToken() Then
Dim r$ = Responser.GetResponse($"https://bsky.social/xrpc/app.bsky.actor.getProfile?actor={ID.IfNullOrEmpty(NameTrue)}")
TokenUpdateCountReset()
If Not r.IsEmptyString Then
Using j As EContainer = JsonDocument.Parse(r)
If j.ListExists Then
ID = j.Value("did")
UserSiteNameUpdate(j.Value("displayName"))
UserDescriptionUpdate(j.Value("description"))
NameTrue = j.Value("handle")
SimpleDownloadAvatar(j.Value("avatar"))
SimpleDownloadAvatar(j.Value("banner"))
End If
End Using
End If
Else
Throw New ArgumentException("Token is null", "Token")
End If
Catch ex As Exception
ProcessException(ex, Token, "GetProfileInfo")
End Try
End Sub
#End Region
#Region "ReparseMissing"
Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken)
Const uriPattern$ = "at://{0}/app.bsky.feed.post/{1}"
Dim rList As New List(Of Integer)
Try
If ContentMissingExists AndAlso UpdateToken() Then
Dim r$, url$, uri$
Dim tu As Byte
Dim m As UserMedia
Dim j As EContainer
For i% = 0 To _ContentList.Count - 1
m = _ContentList(i)
If m.State = UStates.Missing Then
uri = SymbolsConverter.ASCII.EncodeSymbolsOnly(String.Format(uriPattern, NameTrue, m.Post.ID))
url = $"https://bsky.social/xrpc/app.bsky.feed.getPostThread?uri={uri}&depth=10"
For tu = 0 To 1
Try
Responser.ResetStatus()
r = Responser.GetResponse(url)
TokenUpdateCountReset()
If Not r.IsEmptyString Then
j = JsonDocument.Parse(r)
If j.ListExists Then
If DefaultParser(j({"thread", "post"}), False,, False, UStates.Missing) > 0 Then rList.Add(i)
j.Dispose()
End If
End If
Exit For
Catch eex As Exception
If ProcessException(eex, Token, $"ReparseMissing({url})",,, False) <> 1 Then Throw eex
End Try
Next
End If
Next
Else
Throw New ArgumentException("Token is null", "Token")
End If
Catch ex As Exception
ProcessException(ex, Token, "ReparseMissing error")
Finally
If rList.Count > 0 Then
For i% = rList.Count - 1 To 0 Step -1 : _ContentList.RemoveAt(rList(i)) : Next
rList.Clear()
End If
End Try
End Sub
#End Region
#Region "CreateFileFromUrl"
Protected Overloads Overrides Function CreateFileFromUrl(ByVal URL As String) As SFile
Return CreateFileFromUrl(URL, UTypes.Undefined)
End Function
Protected Overloads Function CreateFileFromUrl(ByVal URL As String, ByVal Type As UTypes) As SFile
Dim f As SFile = MyBase.CreateFileFromUrl(URL)
Dim force As Boolean = False
f.Separator = "\"
With URL.Split("/")
If .ListExists Then
With DirectCast(RegexReplace(.Last, RegEx_FilePattern), List(Of String))
If .ListExists(4) Then
f.Name = .Item(1).IfNullOrEmpty(f.Name)
f.Extension = .Item(3)
End If
End With
End If
End With
If Not f.Extension.IsEmptyString AndAlso f.Extension.ToLower = "m3u8" Then force = True : Type = UTypes.m3u8
If f.Extension.IsEmptyString Or force Then
Select Case Type
Case UTypes.Picture : f.Extension = "jpg"
Case UTypes.GIF : f.Extension = "gif"
Case UTypes.m3u8 : f.Name = "Video" : f.Extension = "mp4"
End Select
End If
Return f
End Function
#End Region
#Region "DownloadContent"
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, ByVal Token As CancellationToken) As SFile
Return M3U8.Download(URL, DestinationFile, Token, Progress, Not IsSingleObjectDownload)
End Function
#End Region
#Region "DownloadSingleObject"
Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
_TokenUpdateCount = 0
UpdateToken()
Dim l As List(Of String) = RegexReplace(Data.URL, RegEx_SinglePostPattern)
If l.ListExists(3) Then
NameTrue = l(1)
_ContentList.Add(New UserMedia(Data.URL) With {.State = UStates.Missing, .Post = l(2)})
ReparseMissing(Token)
End If
MyBase.DownloadSingleObject_GetPosts(Data, Token)
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 Responser.StatusCode = Net.HttpStatusCode.BadRequest Then '400
If _TokenUpdateCount = 0 AndAlso UpdateToken(True) Then
Return 1
Else
Return 0
End If
Else
Return 0
End If
End Function
#End Region
#Region "IDisposable Support"
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then _TmpPosts2.Clear()
MyBase.Dispose(disposing)
End Sub
#End Region
End Class
End Namespace

View File

@@ -18,6 +18,8 @@ Namespace API.Facebook
Friend ReadOnly Regex_FileName As RParams = RParams.DM("([^/\?]+\..{3,4})(?=(\?|\Z))", 0, EDP.ReturnValue)
Friend ReadOnly Regex_ProfileUrlID As RParams = RParams.DMS("profile.php\?id=(\d+)", 1, EDP.ReturnValue)
Friend ReadOnly Regex_VideoPageID As RParams = RParams.DMS("pageid.:.(\d+)", 1, RegexOptions.IgnoreCase, EDP.ReturnValue)
Friend ReadOnly Regex_ReelsPageID As RParams = RParams.DMS("\{[^\}]*""tab_key"":""owner_reels"",?[^\}]*""id"":""([^\}""]+)""", 1, RegexOptions.IgnoreCase, EDP.ReturnValue)
Friend ReadOnly Regex_ReelsFilePattern As RParams = RParams.DM("[^/]+\.mp4", 0, EDP.ReturnValue)
Friend ReadOnly Regex_StoryBucket As RParams = RParams.DMS("story_bucket[^\>]*?(\d+)", 1, EDP.ReturnValue)
Friend ReadOnly Regex_VideoIDFromURL As RParams = RParams.DMS("facebook.com/([^/]+/videos/|watch/\D*[\?&]{1}v=)(\d+)", 2, EDP.ReturnValue)

View File

@@ -36,8 +36,13 @@ Namespace API.Facebook
Friend ReadOnly Property ParsePhotoBlock As PropertyValue
<PropertyOption(ControlText:="Download videos", IsAuth:=False, Category:=DN.CAT_UserDefs), PXML, PClonable>
Friend ReadOnly Property ParseVideoBlock As PropertyValue
<PropertyOption(ControlText:="Download reels", IsAuth:=False, Category:=DN.CAT_UserDefs), PXML, PClonable>
Friend ReadOnly Property ParseReelsBlock As PropertyValue
<PropertyOption(ControlText:="Download stories", IsAuth:=False, Category:=DN.CAT_UserDefs), PXML, PClonable>
Friend ReadOnly Property ParseStoriesBlock As PropertyValue
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
#End Region
#End Region
#Region "Initializer"
@@ -52,6 +57,7 @@ Namespace API.Facebook
Header_Accept = New PropertyValue(String.Empty, GetType(String))
ParsePhotoBlock = New PropertyValue(True)
ParseVideoBlock = New PropertyValue(True)
ParseReelsBlock = New PropertyValue(False)
ParseStoriesBlock = New PropertyValue(True)
UrlPatternUser = "https://www.facebook.com/{0}"

View File

@@ -23,9 +23,11 @@ Namespace API.Facebook
Private Const Name_IsNoNameProfile As String = "IsNoNameProfile"
Private Const Name_OptionsParsed As String = "OptionsParsed"
Private Const Name_VideoPageID As String = "VideoPageID"
Private Const Name_ReelsPageID As String = "ReelsPageID"
Private Const Name_StoryBucket As String = "StoryBucket"
Private Const Name_ParsePhotoBlock As String = "ParsePhotoBlock"
Private Const Name_ParseVideoBlock As String = "ParseVideoBlock"
Private Const Name_ParseReelsBlock As String = "ParseReelsBlock"
Private Const Name_ParseStoriesBlock As String = "ParseStoriesBlock"
#End Region
#Region "Declarations"
@@ -37,15 +39,18 @@ Namespace API.Facebook
Private IsNoNameProfile As Boolean = False
Private OptionsParsed As Boolean = False
Private Property VideoPageID As String = String.Empty
Private Property ReelsPageID As String = String.Empty
Private Property StoryBucket As String = String.Empty
Friend Property ParsePhotoBlock As Boolean = True
Friend Property ParseVideoBlock As Boolean = True
Friend Property ParseReelsBlock As Boolean = False
Friend Property ParseStoriesBlock As Boolean = True
Private Enum PageBlock As Integer
Timeline = Sections.Timeline
Stories = Sections.Stories
Photos = 100
Videos = 101
Reels = Sections.Reels
Undefined = -1
End Enum
#End Region
@@ -67,6 +72,7 @@ Namespace API.Facebook
With DirectCast(Obj, UserExchangeOptions)
ParsePhotoBlock = .ParsePhotoBlock
ParseVideoBlock = .ParseVideoBlock
ParseReelsBlock = .ParseReelsBlock
ParseStoriesBlock = .ParseStoriesBlock
End With
End If
@@ -90,18 +96,22 @@ Namespace API.Facebook
End If
OptionsParsed = .Value(Name_OptionsParsed).FromXML(Of Boolean)(False)
VideoPageID = .Value(Name_VideoPageID)
ReelsPageID = .Value(Name_ReelsPageID)
StoryBucket = .Value(Name_StoryBucket)
ParsePhotoBlock = .Value(Name_ParsePhotoBlock).FromXML(Of Boolean)(True)
ParseVideoBlock = .Value(Name_ParseVideoBlock).FromXML(Of Boolean)(True)
ParseReelsBlock = .Value(Name_ParseReelsBlock).FromXML(Of Boolean)(False)
ParseStoriesBlock = .Value(Name_ParseStoriesBlock).FromXML(Of Boolean)(True)
Else
updateNames.Invoke
.Add(Name_IsNoNameProfile, IsNoNameProfile.BoolToInteger)
.Add(Name_OptionsParsed, OptionsParsed.BoolToInteger)
.Add(Name_VideoPageID, VideoPageID)
.Add(Name_ReelsPageID, ReelsPageID)
.Add(Name_StoryBucket, StoryBucket)
.Add(Name_ParsePhotoBlock, ParsePhotoBlock.BoolToInteger)
.Add(Name_ParseVideoBlock, ParseVideoBlock.BoolToInteger)
.Add(Name_ParseReelsBlock, ParseReelsBlock.BoolToInteger)
.Add(Name_ParseStoriesBlock, ParseStoriesBlock.BoolToInteger)
End If
End With
@@ -146,6 +156,7 @@ Namespace API.Facebook
Else
If DownloadImages And ParsePhotoBlock Then DownloadData_Photo(String.Empty, Token)
If DownloadVideos And ParseVideoBlock Then DownloadData_Video(String.Empty, Token)
If DownloadVideos And ParseReelsBlock Then DownloadData_Reels(String.Empty, Token)
If (DownloadImages Or DownloadVideos) And ParseStoriesBlock Then DownloadData_Stories(Token)
End If
LoadSavePostsKV(False)
@@ -158,10 +169,12 @@ Namespace API.Facebook
Private Const Header_fb_fr_name_Video As String = "PagesCometChannelTabAllVideosCardImplPaginationQuery"
Private Const Header_fb_fr_name_Stories As String = "StoriesSuspenseContentPaneRootWithEntryPointQuery"
Private Const Header_fb_fr_name_SavedPosts As String = "CometSaveDashboardAllItemsPaginationQuery"
Private Const Header_fb_fr_name_Reels As String = "ProfileCometAppCollectionReelsRendererPaginationQuery"
Private Const DocID_Photo As String = "6684543058255697"
Private Const DocID_Video As String = "24545934291687581"
Private Const DocID_Stories As String = "6771064226315961"
Private Const DocID_SavedPosts As String = "7112228098805003"
Private Const DocID_Reels As String = "28517740954539304"
Private Const Graphql_UrlPattern As String = "https://www.facebook.com/api/graphql?lsd={0}&doc_id={1}&server_timestamps=true&fb_dtsg={3}&fb_api_req_friendly_name={2}&variables={4}"
Private Const VideoHtmlUrlPattern As String = "https://www.facebook.com/watch/?v={0}"
Private Sub DownloadData_Photo(ByVal Cursor As String, ByVal Token As CancellationToken)
@@ -238,7 +251,7 @@ Namespace API.Facebook
Dim newPostsDetected As Boolean = False
Dim pid As PostKV
If VideoPageID.IsEmptyString Then GetVideoPageID(Token)
If VideoPageID.IsEmptyString Then GetVideoPageID(False, Token)
If VideoPageID.IsEmptyString Then Throw New TokensException("Unable to obtain 'VideoPageID'", False)
ValidateBaseTokens()
@@ -355,6 +368,123 @@ Namespace API.Facebook
ProcessException(ex, Token, $"data (stories) downloading error [{URL}]",, Responser)
End Try
End Sub
Private Sub DownloadData_Reels(ByVal Cursor As String, ByVal Token As CancellationToken)
Dim URL$ = String.Empty
Const VarPattern$ = """count"":10,""cursor"":{0},""feedLocation"":""COMET_MEDIA_VIEWER"",""feedbackSource"":65,""focusCommentID"":null,""renderLocation"":null,""scale"":1,""useDefaultActor"":true,""id"":""{1}"",""__relay_internal__pv__FBReelsMediaFooter_comet_enable_reels_ads_gkrelayprovider"":true,""__relay_internal__pv__IsWorkUserrelayprovider"":false"
Try
Dim nextCursor$ = String.Empty
Dim newPostsDetected As Boolean = False
Dim nodeFound As Boolean = False
Dim pid As PostKV = Nothing
Dim __urlBase$ = String.Empty
Dim lines As List(Of String)
Dim j As EContainer, rr As EContainer
Dim jDataRoot As EContainer = Nothing
Dim indx% = -1
Dim s As New List(Of Sizes)
Dim videoIdNode$() = {"profile_reel_node", "node", "video", "id"}
Dim obtainBasePostData As Action = Sub()
If indx.ValueBetween(0, jDataRoot.Count - 1) Then
With jDataRoot(indx)
pid = New PostKV(String.Empty, .Item(videoIdNode).XmlIfNothingValue.
IfNullOrEmpty(.Value({"node"}, "id")), PageBlock.Reels)
pid.Code = $"Reels:{pid.ID}"
nextCursor = .Value("cursor")
If Not .Item(videoIdNode).XmlIfNothing.IsEmptyString Then
__urlBase = $"https://www.facebook.com/reel/{pid.ID}"
Else
__urlBase = String.Empty
End If
End With
Else
pid = Nothing
nextCursor = String.Empty
__urlBase = String.Empty
End If
End Sub
Dim createFile As Func(Of String, SFile, SFile) = Function(ByVal __url As String, ByVal cFile As SFile) As SFile
Dim f As New SFile(RegexReplace(__url, Regex_ReelsFilePattern))
If Not f.IsEmptyString Then Return f Else Return cFile
End Function
If ReelsPageID.IsEmptyString Then GetVideoPageID(True, Token)
If ReelsPageID.IsEmptyString Then Throw New TokensException("Unable to obtain 'ReelsPageID'", False)
ValidateBaseTokens()
URL = String.Format(Graphql_UrlPattern, Token_lsd, DocID_Reels, Header_fb_fr_name_Reels, Token_dtsg_Var,
SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & String.Format(VarPattern, If(Cursor.IsEmptyString, "null", $"""{Cursor}"""), ReelsPageID) & "}"))
ResponserApplyDefs(Header_fb_fr_name_Reels)
ThrowAny(Token)
WaitTimer()
Dim r$ = Responser.GetResponse(URL)
If Not r.IsEmptyString Then
lines = r.StringToList(Of String)(vbCrLf).ListIfNothing
If lines.ListExists Then
For Each line$ In lines
j = JsonDocument.Parse(line, EDP.ReturnValue)
If j.ListExists Then
jDataRoot = j({"data", "node", "aggregated_fb_shorts", "edges"})
If jDataRoot.ListExists Then
With j({"extensions", "all_video_dash_prefetch_representations"})
If .ListExists Then
ProgressPre.ChangeMax(.Count)
For indx = 0 To .Count - 1
ProgressPre.Perform()
obtainBasePostData()
If Not pid.ID.IsEmptyString AndAlso Not PostKvExists(pid) Then
newPostsDetected = True
PostsKVIDs.ListAddValue(pid, LNC)
_TempPostsList.Add(pid.Code)
With .ItemF({indx, "representations"})
If .ListExists Then
s.Clear()
For Each rr In .Self : s.Add(New Sizes(rr.Value("width"), rr.Value("base_url"))) : Next
If s.Count > 0 Then s.RemoveAll(Function(ss) ss.Value = 0 Or ss.Data.IsEmptyString)
If s.Count > 0 Then
s.Sort()
_TempMediaList.ListAddValue(New UserMedia(s(0).Data, UTypes.Video) With {
.URL_BASE = __urlBase.IfNullOrEmpty(.URL_BASE),
.Post = pid.ID,
.File = createFile(s(0).Data, .File),
.SpecialFolder = "Reels*"
}, LNC)
s.Clear()
End If
End If
End With
If Limit > 0 And _TempMediaList.Count >= Limit Then j.Dispose() : Exit Sub
Else
j.Dispose()
Exit Sub
End If
Next
End If
End With
End If
j.Dispose()
End If
Next
End If
End If
If newPostsDetected And Not nextCursor.IsEmptyString Then DownloadData_Reels(nextCursor, Token)
Catch tex As TokensException When Not tex.BasicTokens
TokensException.SendToLog(Me, tex, "data (reels)")
Catch ex As Exception
ProcessException(ex, Token, $"data (reels) downloading error [{URL}]",, Responser)
End Try
End Sub
Private Sub DownloadData_SavedPosts(ByVal Cursor As String, ByVal Token As CancellationToken)
Dim URL$ = String.Empty
Const VarPattern$ = """content_filter"":[],""count"":10,""cursor"":{0},""scale"":1,""use_case"":""SAVE_DEFAULT"""
@@ -507,13 +637,19 @@ Namespace API.Facebook
Return True
End If
End Function
Private Sub GetVideoPageID(ByVal Token As CancellationToken)
Dim URL$ = $"{GetProfileUrl()}\videos"
Private Sub GetVideoPageID(ByVal GetReels As Boolean, ByVal Token As CancellationToken)
Dim URL$ = $"{GetProfileUrl()}{IIf(IsNoNameProfile, "&sk=", "/")}{IIf(GetReels, IIf(IsNoNameProfile, "reels_tab", "reels"), "videos")}"
Dim resp As Responser = HtmlResponserCreate()
Try
WaitTimer()
Dim r$ = resp.GetResponse(URL)
If Not r.IsEmptyString Then VideoPageID = RegexReplace(r, Regex_VideoPageID)
If Not r.IsEmptyString Then
If GetReels Then
ReelsPageID = RegexReplace(r, Regex_ReelsPageID)
Else
VideoPageID = RegexReplace(r, Regex_VideoPageID)
End If
End If
Catch ex As Exception
ProcessException(ex, Token, "get video page ID",, resp)
Finally
@@ -540,10 +676,7 @@ Namespace API.Facebook
End If
Token_Photosby = RegexReplace(r, Regex_Photos_by)
If StoryBucket.IsEmptyString Then StoryBucket = RegexReplace(r, Regex_StoryBucket)
If ID.IsEmptyString Then
ID = RegexReplace(r, Regex_UserID)
If Not ID.IsEmptyString Then _ForceSaveUserInfo = True
End If
If ID.IsEmptyString Then ID = RegexReplace(r, Regex_UserID)
End If
Catch ex As Exception
ProcessException(ex, Token, "get user token",, resp)
@@ -658,17 +791,39 @@ Namespace API.Facebook
HtmlResponserDispose(resp)
End Try
End Sub
Private Structure VideoResolution : Implements IComparable(Of VideoResolution)
Friend W As Integer
Friend H As Integer
Friend B As Integer
Friend U As String
Friend ReadOnly Property Wrong As Boolean
Get
Return W = 0 Or H = 0 Or B = 0 Or U.IsEmptyString
End Get
End Property
Private Function CompareTo(ByVal Other As VideoResolution) As Integer Implements IComparable(Of VideoResolution).CompareTo
Return CLng(Math.Max(W, H) * B).CompareTo(CLng(Math.Max(Other.W, Other.H) * Other.B)) * -1
End Function
End Structure
Protected Function ReparseSingleVideo(ByVal m As UserMedia, ByVal resp As Responser, ByRef result As Boolean) As UserMedia
Const nameSD$ = "browser_native_sd_url"
Const nameHD$ = "browser_native_hd_url"
Const nameDPR$ = "all_video_dash_prefetch_representations"
Const pattern$ = "<script type=""application/json""[^\>]*data-sjs>([^<]+?{0}[^<]+)<"
Dim URL$ = String.Empty
Dim j As EContainer = Nothing
Try
Dim r$, script$, __url$
Dim r$ = String.Empty, script$ = String.Empty, __url$ = String.Empty
Dim isNewNodes As Boolean = False
Dim __date As Date? = Nothing
Dim jNode As EContainer
Dim jf As Predicate(Of EContainer) = Function(ee) Not ee.Name.IsEmptyString AndAlso (ee.Name.ToLower = nameSD Or ee.Name.ToLower = nameHD)
Dim re As RParams = RParams.DMS("", 1, RegexOptions.IgnoreCase, EDP.ReturnValue)
Dim nf As New XML.Base.NodeParams(nameDPR, True, True, True, True, 20)
Dim __extractScript As Action(Of String) = Sub(ByVal inputName As String)
re.Pattern = String.Format(pattern, inputName)
script = RegexReplace(r, re)
End Sub
If m.Post.ID.IsEmptyString Then
URL = m.URL_BASE
Else
@@ -677,30 +832,47 @@ Namespace API.Facebook
WaitTimer()
r = resp.GetResponse(URL)
If Not r.IsEmptyString Then
re.Pattern = String.Format(pattern, nameHD)
script = RegexReplace(r, re)
If script.IsEmptyString Then
re.Pattern = String.Format(pattern, nameSD)
script = RegexReplace(r, re)
End If
__extractScript(nameHD)
If script.IsEmptyString Then __extractScript(nameSD)
If script.IsEmptyString Then __extractScript(nameDPR) : isNewNodes = True
If Not script.IsEmptyString Then
j = JsonDocument.Parse(script)
If j.ListExists Then
j.SetSourceReferences()
jNode = j.Find(jf, True)
If Not jNode Is Nothing Then
With DirectCast(jNode.Source, EContainer)
__url = .Value(nameHD).IfNullOrEmpty(.Value(nameSD))
If Not __url.IsEmptyString Then
m.URL = __url
m.URL_BASE = URL
m.Type = UTypes.Video
m.File = CreateFileFromUrl(__url)
m.Post.Date = AConvert(Of Date)(.Value("publish_time"), UnixDate32Provider, Nothing)
result = True
Return m
End If
End With
If isNewNodes Then
jNode = j.GetNode({nf})
If Not jNode Is Nothing Then
With jNode.ItemF({0, "representations"})
If .ListExists Then
Dim intE As New ErrorsDescriber(False, False, False, 0)
Dim intC As Func(Of String, Integer) = Function(__input) AConvert(Of Integer)(__input, intE)
Dim dataV As List(Of VideoResolution) = .Select(Function(jj) New VideoResolution With {
.W = intC(jj.Value("width")),
.H = intC(jj.Value("height")),
.B = intC(jj.Value("bandwidth")),
.U = jj.Value("base_url")}).ListIfNothing
If dataV.ListExists Then dataV.RemoveAll(Function(dd) dd.Wrong)
If dataV.ListExists Then dataV.Sort() : __url = dataV(0).U : dataV.Clear() : __date = m.Post.Date
End If
End With
End If
Else
jNode = j.Find(jf, True)
If Not jNode Is Nothing Then
With DirectCast(jNode.Source, EContainer)
__url = .Value(nameHD).IfNullOrEmpty(.Value(nameSD))
If Not __url.IsEmptyString Then __date = AConvert(Of Date)(.Value("publish_time"), UnixDate32Provider, Nothing)
End With
End If
End If
If Not __url.IsEmptyString Then
m.URL = __url
m.URL_BASE = URL
m.Type = UTypes.Video
m.File = CreateFileFromUrl(__url)
m.Post.Date = __date
result = True
Return m
End If
End If
End If
@@ -738,7 +910,10 @@ Namespace API.Facebook
#End Region
#Region "DownloadSingleObject"
Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
_ContentList.Add(New UserMedia(Data.URL, UTypes.VideoPre) With {.Post = CStr(AConvert(Of String)(Data.URL, Regex_VideoIDFromURL, String.Empty))})
_ContentList.Add(New UserMedia(Data.URL, UTypes.VideoPre) With {
.Post = CStr(AConvert(Of String)(Data.URL, Regex_VideoIDFromURL, String.Empty)),
.State = UStates.Missing
})
ReparseMissing(Token)
End Sub
#End Region

View File

@@ -13,6 +13,8 @@ Namespace API.Facebook
Friend Property ParsePhotoBlock As Boolean = True
<PSetting(NameOf(SiteSettings.ParseVideoBlock), NameOf(MySettings))>
Friend Property ParseVideoBlock As Boolean = True
<PSetting(NameOf(SiteSettings.ParseReelsBlock), NameOf(MySettings))>
Friend Property ParseReelsBlock As Boolean = False
<PSetting(NameOf(SiteSettings.ParseStoriesBlock), NameOf(MySettings))>
Friend Property ParseStoriesBlock As Boolean = True
Private ReadOnly Property MySettings As SiteSettings
@@ -20,12 +22,14 @@ Namespace API.Facebook
MySettings = u.HostCollection.Default.Source
ParsePhotoBlock = u.ParsePhotoBlock
ParseVideoBlock = u.ParseVideoBlock
ParseReelsBlock = u.ParseReelsBlock
ParseStoriesBlock = u.ParseStoriesBlock
End Sub
Friend Sub New(ByVal s As SiteSettings)
MySettings = s
ParsePhotoBlock = s.ParsePhotoBlock.Value
ParseVideoBlock = s.ParseVideoBlock.Value
ParseReelsBlock = s.ParseReelsBlock.Value
ParseStoriesBlock = s.ParseStoriesBlock.Value
End Sub
End Class

View File

@@ -7,9 +7,8 @@
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports SCrawler.Plugin.Attributes
Imports DN = SCrawler.API.Base.DeclaredNames
Namespace API.Instagram
Friend NotInheritable Class EditorExchangeOptions
Friend NotInheritable Class EditorExchangeOptions : Inherits Base.EditorExchangeOptionsBase
#Region "Download"
<PSetting(Caption:="Get timeline", ToolTip:="Download user timeline")>
Friend Property GetTimeline As Boolean
@@ -36,13 +35,13 @@ Namespace API.Instagram
#End Region
<PSetting(Caption:="Place the extracted image into the video folder")>
Friend Property PutImageVideoFolder As Boolean
<PSetting(Address:=SettingAddress.User, Caption:=DN.UserNameChangeCaption, ToolTip:=DN.UserNameChangeToolTip)>
Friend Property UserName As String = String.Empty
Friend Overrides Property UserName As String
<PSetting(Address:=SettingAddress.User, Caption:="Force update UserName", ToolTip:="Try to force update UserName if it is not found on the site")>
Friend Property ForceUpdateUserName As Boolean = False
<PSetting(Address:=SettingAddress.User, Caption:="Force update user information")>
Friend Property ForceUpdateUserInfo As Boolean = False
Friend Sub New(ByVal u As UserData)
MyBase.New(u)
With u
GetTimeline = .GetTimeline
GetReels = .GetReels
@@ -58,12 +57,12 @@ Namespace API.Instagram
PutImageVideoFolder = .PutImageVideoFolder
UserName = .NameTrue(True)
ForceUpdateUserName = .ForceUpdateUserName
ForceUpdateUserInfo = .ForceUpdateUserInfo
End With
End Sub
Friend Sub New(ByVal s As SiteSettings)
MyBase.New(s)
With s
GetTimeline = CBool(.GetTimeline.Value)
GetReels = CBool(.GetReels.Value)

View File

@@ -16,8 +16,8 @@ Imports PersonalUtilities.Tools.Web.Cookies
Imports Download = SCrawler.Plugin.ISiteSettings.Download
Imports DN = SCrawler.API.Base.DeclaredNames
Namespace API.Instagram
<Manifest(InstagramSiteKey), SeparatedTasks(1), SavedPosts, SpecialForm(False)>
Friend Class SiteSettings : Inherits SiteSettingsBase
<Manifest(InstagramSiteKey), SeparatedTasks(1), SavedPosts, SpecialForm(False), UseDownDetector>
Friend Class SiteSettings : Inherits SiteSettingsBase : Implements DownDetector.IDownDetector
#Region "Declarations"
#Region "Providers"
Friend Class TimersChecker : Inherits FieldsCheckerProviderBase
@@ -196,6 +196,18 @@ Namespace API.Instagram
Friend ReadOnly Property SleepTimerOnPostsLimit As PropertyValue
<Provider(NameOf(SleepTimerOnPostsLimit), FieldsChecker:=True)>
Private ReadOnly Property SleepTimerOnPostsLimitProvider As IFormatProvider
<PropertyOption(ControlText:="Next profile timer",
ControlToolTip:="The time value (in milliseconds) the program will wait before processing the next profile." &
vbCr & "-2 to use max timer." & vbCr & "-1 to disable." & vbCr & "The default value is -2" & TimersUrgentTip,
AllowNull:=False, Category:=DN.CAT_Timers), PXML, PClonable>
Friend ReadOnly Property SleepTimerRequestsNextProfile As PropertyValue
Friend ReadOnly Property SleepTimerRequestsNextProfileMax As Integer
Get
Return {RequestsWaitTimer_Any, RequestsWaitTimer, SleepTimerOnPostsLimit}.Max(Function(obj) CInt(obj.Value))
End Get
End Property
<Provider(NameOf(SleepTimerRequestsNextProfile), FieldsChecker:=True)>
Private ReadOnly Property SleepTimerRequestsNextProfileProvider As IFormatProvider
#End Region
#Region "New user defaults"
<PropertyOption(ControlText:="Get timeline", ControlToolTip:="Default value for new users", Category:=DN.CAT_UserDefs), PXML, PClonable>
@@ -270,6 +282,26 @@ Namespace API.Instagram
Private ReadOnly Property TaggedNotifyLimitProvider As IFormatProvider
#End Region
#End Region
#Region "IDownDetector Support"
Private ReadOnly Property IDownDetector_Value As Integer Implements DownDetector.IDownDetector.Value
Get
Return DownDetectorValue.Value
End Get
End Property
Private ReadOnly Property IDownDetector_AddToLog As Boolean Implements DownDetector.IDownDetector.AddToLog
Get
Return DownDetectorValueAddToLog.Value
End Get
End Property
Private ReadOnly Property IDownDetector_CheckSite As String Implements DownDetector.IDownDetector.CheckSite
Get
Return "instagram"
End Get
End Property
Private Function IDownDetector_Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean Implements DownDetector.IDownDetector.Available
Return MDD.Available(What, Silent)
End Function
#End Region
#Region "429 bypass"
<PXML("InstagramDownloadingErrorDate")>
Private ReadOnly Property DownloadingErrorDate As PropertyValue
@@ -473,6 +505,8 @@ Namespace API.Instagram
RequestsWaitTimerTaskCountProvider = New TimersChecker(1)
SleepTimerOnPostsLimit = New PropertyValue(60000)
SleepTimerOnPostsLimitProvider = New TimersChecker(10000)
SleepTimerRequestsNextProfile = New PropertyValue(-2)
SleepTimerRequestsNextProfileProvider = New TimersChecker(-2)
GetTimeline = New PropertyValue(True)
GetTimeline_VideoPic = New PropertyValue(True)
@@ -504,6 +538,8 @@ Namespace API.Instagram
LastRequestsCountLabel = New PropertyValue(String.Empty, GetType(String))
MyLastRequests = New Dictionary(Of Date, Integer)
MDD = New DownDetector.Checker(Of SiteSettings)(Me)
_AllowUserAgentUpdate = False
UrlPatternUser = "https://www.instagram.com/{0}/"
UserRegex = RParams.DMS(String.Format(UserRegexDefaultPattern, "instagram.com/"), 1)
@@ -551,20 +587,13 @@ Namespace API.Instagram
End Function
#End Region
#Region "Downloading"
Private ____DownloadStarted As Boolean = False
Private ____AvailableRequested As Boolean = False
Private ____AvailableSilent As Boolean = True
Private ____AvailableChecked As Boolean = False
Private ____AvailableResult As Boolean = False
Private ReadOnly MDD As DownDetector.Checker(Of SiteSettings)
Private Sub ResetDownloadOptions()
If ActiveJobs < 1 Then
____DownloadStarted = False
____AvailableRequested = False
____AvailableChecked = False
____AvailableSilent = True
____AvailableResult = False
MDD.Reset()
If ActiveSessionRequestsExists Then RefreshMyLastRequests(Now)
ActiveSessionRequestsExists = False
ActiveSessionLastProfileRequests = False
_NextWNM = UserData.WNM.Notify
_NextTagged = True
SkipUntilNextSession = False
@@ -573,79 +602,22 @@ Namespace API.Instagram
End If
End Sub
Friend Overrides Function Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean
If MyBase.Available(What, Silent) And ActiveJobs < 2 Then
If CInt(DownDetectorValue.Value) >= 0 Then
If ____DownloadStarted Then
____AvailableRequested = True
____AvailableSilent = Silent
Return True
Else
Return AvailableImpl(What, Silent)
End If
Else
Return True
End If
Else
Return False
End If
End Function
#Disable Warning IDE0060
Private Function AvailableImpl(ByVal What As Download, ByVal Silent As Boolean) As Boolean
#Enable Warning
Try
AvailableText = String.Empty
If CInt(DownDetectorValue.Value) = -1 Then
Return True
Else
Dim dl As List(Of DownDetector.Data) = DownDetector.GetData("instagram")
If dl.ListExists Then
dl = dl.Take(4).ToList
Dim avg% = dl.Average(Function(d) d.Value)
If avg > CInt(DownDetectorValue.Value) Then
AvailableText = "Over the past hour, Instagram has received an average of " &
avg.NumToString(New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral}) & " outage reports:" & vbCr &
dl.ListToString(vbCr)
If CBool(DownDetectorValueAddToLog.Value) Then MyMainLOG = AvailableText
If Silent Then
Return False
Else
Return MsgBoxE({$"{AvailableText}{vbCr}{vbCr}Do you want to continue parsing Instagram data?",
"There are outage reports on Instagram"}, vbYesNo) = vbYes
End If
End If
End If
Return True
End If
Catch ex As Exception
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, "[API.Instagram.SiteSettings.Available]", True)
End Try
Return MyBase.Available(What, Silent) And ActiveJobs < 2
End Function
Friend Property SkipUntilNextSession As Boolean = False
Friend Overrides Function ReadyToDownload(ByVal What As Download) As Boolean
If ActiveJobs < 2 AndAlso Not SkipUntilNextSession AndAlso ReadyForDownload AndAlso BaseAuthExists() AndAlso CBool(DownloadTimeline.Value) Then
If ____DownloadStarted And ____AvailableRequested Then
____AvailableResult = AvailableImpl(What, ____AvailableSilent)
____AvailableChecked = True
____AvailableRequested = False
Return ____AvailableResult
ElseIf ____AvailableChecked Then
Return ____AvailableResult
Else
Return True
End If
Else
Return False
End If
Return ActiveJobs < 2 AndAlso Not SkipUntilNextSession AndAlso ReadyForDownload AndAlso BaseAuthExists() AndAlso CBool(DownloadTimeline.Value)
End Function
Private ActiveJobs As Integer = 0
Private ActiveSessionDate As Date
Private ActiveSessionRequestsExists As Boolean = False
Friend ActiveSessionLastProfileRequests As Boolean = False
Private _NextWNM As UserData.WNM = UserData.WNM.Notify
Private _NextTagged As Boolean = True
Friend Overrides Sub DownloadStarted(ByVal What As Download)
ResetDownloadOptions()
ActiveJobs += 1
If ActiveJobs = 1 Then ____DownloadStarted = True : ActiveSessionDate = Now
If ActiveJobs = 1 Then ActiveSessionDate = Now
If Not HH_IG_WWW_CLAIM_IS_ZERO AndAlso
(
(CBool(HH_IG_WWW_CLAIM_USE_DEFAULT_ALGO.Value) AndAlso MyLastRequestsDate.AddMinutes(HH_IG_WWW_CLAIM_UPDATE_INTERVAL.Value) < Now) Or
@@ -676,6 +648,7 @@ Namespace API.Instagram
_NextWNM = .WaitNotificationMode
If _NextWNM = UserData.WNM.SkipTemp Or _NextWNM = UserData.WNM.SkipCurrent Then _NextWNM = UserData.WNM.Notify
_NextTagged = .TaggedCheckSession
If MyLastRequestsCount <> .RequestsCountSession Then ActiveSessionLastProfileRequests = True
MyLastRequestsCount = .RequestsCountSession
If .RequestsCountSession > 0 Then ActiveSessionRequestsExists = True
_FieldsChangerSuspended = True
@@ -776,9 +749,6 @@ Namespace API.Instagram
Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using
End If
End Sub
Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String
Return String.Format(UrlPatternUser, DirectCast(User, UserData).NameTrue)
End Function
Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String
Try
Dim code$ = DirectCast(User, UserData).GetPostCodeById(Media.Post.ID)

View File

@@ -37,12 +37,11 @@ Namespace API.Instagram
Private Const Name_GetTagged_VideoPic As String = "GetTaggedData_VideoPic"
Private Const Name_PutImageVideoFolder As String = "PutImageVideoFolder"
Private Const Name_TaggedChecked As String = "TaggedChecked"
Private Const Name_NameTrue As String = "NameTrue"
Private Const Name_ForceUpdateUserName As String = "ForceUpdateUserName"
Private Const Name_ForceUpdateUserInfo As String = "ForceUpdateUserInfo"
#End Region
#Region "Declarations"
Protected Structure PostKV : Implements IEContainerProvider
Friend Structure PostKV : Implements IEContainerProvider
Private Const Name_Code As String = "Code"
Private Const Name_Section As String = "Section"
Friend Code As String
@@ -113,12 +112,6 @@ Namespace API.Instagram
Case Else : Return True
End Select
End Function
Protected _NameTrue As String = String.Empty
Friend ReadOnly Property NameTrue(Optional ByVal Exact As Boolean = False) As String
Get
Return If(Exact, _NameTrue, _NameTrue.IfNullOrEmpty(Name))
End Get
End Property
Private UserNameRequested As Boolean = False
Friend Property ForceUpdateUserName As Boolean = False
Friend Property ForceUpdateUserInfo As Boolean = False
@@ -141,7 +134,6 @@ Namespace API.Instagram
GetTaggedData = .Value(Name_GetTagged).FromXML(Of Boolean)(CBool(MySiteSettings.GetTagged.Value))
GetTaggedData_VideoPic = .Value(Name_GetTagged_VideoPic).FromXML(Of Boolean)(CBool(MySiteSettings.GetTagged_VideoPic.Value))
TaggedChecked = .Value(Name_TaggedChecked).FromXML(Of Boolean)(False)
_NameTrue = .Value(Name_NameTrue)
ForceUpdateUserName = .Value(Name_ForceUpdateUserName).FromXML(Of Boolean)(False)
ForceUpdateUserInfo = .Value(Name_ForceUpdateUserInfo).FromXML(Of Boolean)(False)
Else
@@ -159,7 +151,6 @@ Namespace API.Instagram
.Add(Name_GetTagged_VideoPic, GetTaggedData_VideoPic.BoolToInteger)
.Add(Name_PutImageVideoFolder, PutImageVideoFolder.BoolToInteger)
.Add(Name_TaggedChecked, TaggedChecked.BoolToInteger)
.Add(Name_NameTrue, _NameTrue)
.Add(Name_ForceUpdateUserName, ForceUpdateUserName.BoolToInteger)
.Add(Name_ForceUpdateUserInfo, ForceUpdateUserInfo.BoolToInteger)
End If
@@ -173,6 +164,7 @@ Namespace API.Instagram
Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
If Not Obj Is Nothing AndAlso TypeOf Obj Is EditorExchangeOptions Then
With DirectCast(Obj, EditorExchangeOptions)
.ApplyBase(Me)
GetTimeline = .GetTimeline
GetReels = .GetReels
GetStories = .GetStories
@@ -187,7 +179,6 @@ Namespace API.Instagram
PutImageVideoFolder = .PutImageVideoFolder
_NameTrue = .UserName
ForceUpdateUserName = .ForceUpdateUserName
ForceUpdateUserInfo = .ForceUpdateUserInfo
End With
@@ -252,25 +243,28 @@ Namespace API.Instagram
End If
End Get
End Property
Protected Sub LoadSavePostsKV(ByVal Load As Boolean)
Friend Overloads Shared Sub LoadSavePostsKV(ByVal Load As Boolean, ByVal fPosts As SFile, ByRef List As List(Of PostKV))
Dim x As XmlFile
Dim f As SFile = MyFilePostsKV
Dim f As SFile = fPosts
If Not f.IsEmptyString Then
If Load Then
PostsKVIDs.Clear()
List.Clear()
x = New XmlFile(f, Protector.Modes.All, False) With {.AllowSameNames = True, .XmlReadOnly = True}
x.LoadData()
If x.Count > 0 Then PostsKVIDs.ListAddList(x, LAP.IgnoreICopier)
If x.Count > 0 Then List.ListAddList(x, LAP.IgnoreICopier)
x.Dispose()
Else
x = New XmlFile With {.AllowSameNames = True}
x.AddRange(PostsKVIDs)
x.AddRange(List)
x.Name = "Posts"
x.Save(f, EDP.SendToLog)
x.Dispose()
End If
End If
End Sub
Protected Overloads Sub LoadSavePostsKV(ByVal Load As Boolean)
LoadSavePostsKV(Load, MyFilePostsKV, PostsKVIDs)
End Sub
Protected Overloads Function PostKvExists(ByVal pkv As PostKV) As Boolean
Return PostKvExists(pkv.ID, False, pkv.Section) OrElse PostKvExists(pkv.Code, True, pkv.Section)
End Function
@@ -391,6 +385,20 @@ Namespace API.Instagram
Dim s As Sections = Sections.Timeline
Dim errorFound As Boolean = False
Dim firstWait As Boolean = False
Dim __firstWait As Action = Sub()
With MySiteSettings
If Not firstWait And .ActiveSessionLastProfileRequests And CInt(.SleepTimerRequestsNextProfile.Value) <> -1 Then
Dim ____v% = 0
If CInt(.SleepTimerRequestsNextProfile.Value) = -2 Then
____v = .SleepTimerRequestsNextProfileMax
Else
____v = CInt(.SleepTimerRequestsNextProfile.Value)
End If
If ____v > 0 Then firstWait = True : Thread.Sleep(____v)
End If
End With
End Sub
Try
Err5xx = -1
ErrHandling = -1
@@ -407,6 +415,7 @@ Namespace API.Instagram
If dt.Invoke And Not LastCursor.IsEmptyString Then
s = IIf(IsSavedPosts, Sections.SavedPosts, Sections.Timeline)
upClaimRequest.Invoke
__firstWait.Invoke
DownloadData(LastCursor, s, Token)
ProgressPre.Done()
ThrowAny(Token)
@@ -416,6 +425,7 @@ Namespace API.Instagram
s = IIf(IsSavedPosts, Sections.SavedPosts, Sections.Timeline)
upClaimRequest.Invoke
ChangeResponserMode(_UseGQL)
__firstWait.Invoke
DownloadData(String.Empty, s, Token)
ProgressPre.Done()
ThrowAny(Token)
@@ -431,6 +441,7 @@ Namespace API.Instagram
DefaultParser_ElemNode = {"node", "media"}
upClaimRequest.Invoke
ChangeResponserMode(True)
__firstWait.Invoke
DownloadData(String.Empty, s, Token)
GetReelsGQL_SetEnvir = False
ProgressPre.Done()
@@ -440,6 +451,7 @@ Namespace API.Instagram
If CBool(MySiteSettings.DownloadStories.Value) And GetStories Then
s = Sections.Stories
upClaimRequest.Invoke
__firstWait.Invoke
DownloadData(String.Empty, s, Token)
ProgressPre.Done()
End If
@@ -448,6 +460,7 @@ Namespace API.Instagram
If CBool(MySiteSettings.DownloadStoriesUser.Value) And GetStoriesUser Then
s = Sections.UserStories
upClaimRequest.Invoke
__firstWait.Invoke
DownloadData(String.Empty, s, Token)
ProgressPre.Done()
End If
@@ -456,6 +469,7 @@ Namespace API.Instagram
If CBool(MySiteSettings.DownloadTagged.Value) And GetTaggedData Then
s = Sections.Tagged
upClaimRequest.Invoke
__firstWait.Invoke
DownloadData(String.Empty, s, Token)
ProgressPre.Done()
DefaultParser_ElemNode = Nothing
@@ -476,18 +490,25 @@ Namespace API.Instagram
If Not errorFound Then LoadSavePostsKV(False)
End Try
End Sub
Private Sub ValidateExtension()
Protected Sub ValidateExtension()
Dim tmpList As List(Of UserMedia) = Nothing
Try
Const heic$ = "heic"
If _TempMediaList.Count > 0 AndAlso _TempMediaList.Exists(Function(mm) mm.File.Extension = heic) Then
Dim m As UserMedia
For i% = 0 To _TempMediaList.Count - 1
m = _TempMediaList(i)
tmpList = New List(Of UserMedia)
tmpList.ListAddList(_TempMediaList)
_TempMediaList.Clear()
For i% = 0 To tmpList.Count - 1
m = tmpList(i)
_TempMediaList.Add(m)
If m.Type = UTypes.Picture AndAlso Not m.File.Extension.IsEmptyString AndAlso m.File.Extension = heic Then _
m.File.Extension = "jpg" : _TempMediaList(i) = m
m.File.Extension = "jpg" : _TempMediaList.Add(m)
Next
tmpList.Clear()
End If
Catch ex As Exception
If tmpList.ListExists Then _TempMediaList.Clear() : _TempMediaList.ListAddList(tmpList) : tmpList.Clear()
End Try
End Sub
Protected Overridable Sub UpdateResponser()
@@ -503,7 +524,7 @@ Namespace API.Instagram
Protected Overrides Sub Responser_ResponseReceived(ByVal Sender As Object, ByVal e As EventArguments.WebDataResponse)
Declarations.UpdateResponser(e, Responser, WwwClaimUpdate)
End Sub
Protected Enum Sections : Timeline : Reels : Tagged : Stories : UserStories : SavedPosts : End Enum
Friend Enum Sections : Timeline : Reels : Tagged : Stories : UserStories : SavedPosts : End Enum
Protected Const StoriesFolder As String = "Stories"
Private Const TaggedFolder As String = "Tagged"
#Region "429 bypass"
@@ -967,10 +988,12 @@ NextPageBlock:
Dim PostIDKV As PostKV
Dim Pinned As Boolean
Dim PostDate$, PostOriginUrl$
Dim PostText$ = String.Empty
Dim i%, before%
Dim usePinFunc As Boolean = Not DefaultParser_Pinned Is Nothing
Dim skipPostFuncExists As Boolean = Not DefaultParser_SkipPost Is Nothing
Dim nn As EContainer
Dim textMedia As UserMedia
If SpecFolder.IsEmptyString Then
Select Case Section
Case Sections.Tagged : SpecFolder = TaggedFolder
@@ -1005,9 +1028,17 @@ NextPageBlock:
Case DateResult.Exit : If Not Pinned Then Return False
End Select
End If
If DownloadTextPosts Then PostText = DefaultParser_GetCaption(.Self)
before = _TempMediaList.Count
ObtainMedia(.Self, PostIDKV.ID, SpecFolder, PostDate,, PostOriginUrl, State, Attempts,, Section)
If Not before = _TempMediaList.Count Then _TotalPostsParsed += 1
ObtainMedia(.Self, PostIDKV.ID, SpecFolder, PostDate,, PostOriginUrl, State, Attempts,, Section, PostText)
If Not before = _TempMediaList.Count Then
_TotalPostsParsed += 1
ElseIf DownloadTextPosts And DownloadText And Not PostText.IsEmptyString Then
textMedia = MediaFromData(UTypes.Text, PostIDKV.ID, PostIDKV.ID, PostDate, SpecFolder, PostOriginUrl, State, Attempts, PostText)
textMedia.URL = PostIDKV.ID
textMedia.PostTextFile = $"{PostIDKV.ID}.txt"
_TempMediaList.ListAddValue(textMedia, LNC)
End If
If _Limit > 0 And _TotalPostsParsed >= _Limit Then Return False
End If
Else
@@ -1020,6 +1051,9 @@ NextPageBlock:
Return False
End If
End Function
Protected Overridable Function DefaultParser_GetCaption(ByVal e As EContainer) As String
Return e.Value({"caption"}, "text")
End Function
#End Region
#Region "Code ID converters"
Protected Function CodeToID(ByVal Code As String) As String
@@ -1068,7 +1102,8 @@ NextPageBlock:
Optional ByVal PostOriginUrl As String = Nothing,
Optional ByVal State As UStates = UStates.Unknown, Optional ByVal Attempts As Integer = 0,
Optional ByVal TryExtractImage As Boolean = False,
Optional ByVal Section As Sections = ObtainMedia_NoSection)
Optional ByVal Section As Sections = ObtainMedia_NoSection,
Optional ByVal Text As String = Nothing)
Try
Dim maxSize As Func(Of EContainer, Integer) = Function(ByVal _ss As EContainer) As Integer
Dim w% = AConvert(Of Integer)(_ss.Value("width"), 0)
@@ -1103,6 +1138,7 @@ NextPageBlock:
End Function
If Not ObtainMedia_SizeFuncVid Is Nothing Then ssVid = ObtainMedia_SizeFuncVid
If Not ObtainMedia_SizeFuncPic Is Nothing Then ssPic = ObtainMedia_SizeFuncPic
If DownloadTextPosts And Text.IsEmptyString Then Text = DefaultParser_GetCaption(n)
If n.Count > 0 Then
Dim l As New List(Of Sizes)
Dim d As EContainer
@@ -1115,12 +1151,30 @@ NextPageBlock:
If TryExtractImage Then
t = 1
abstractDecision = True
If Not SpecialFolder.IsEmptyString AndAlso PutImageVideoFolder Then
Dim endsAbs As Boolean = SpecialFolder.EndsWith("*")
If endsAbs Then SpecialFolder = SpecialFolder.TrimEnd("*")
If Not SpecialFolder.IsEmptyString Then SpecialFolder = $"{SpecialFolder.TrimEnd("\")}\{VideoFolderName}{IIf(Not endsAbs, "*", String.Empty)}"
If endsAbs Then SpecialFolder &= "*"
Dim endsAbs As Boolean
Dim newFolderName$
If PutImageVideoFolder Then
If SpecialFolder.IsEmptyString Then
newFolderName = $"{VideoFolderName}\*"
Else
endsAbs = SpecialFolder.EndsWith("*")
SpecialFolder = SpecialFolder.TrimEnd({CChar("\"), CChar("*")})
If Not endsAbs Then SpecialFolder = $"{SpecialFolder}\{VideoFolderName}"
newFolderName = $"{SpecialFolder}*"
End If
'Dim endsAbs As Boolean = SpecialFolder.EndsWith("*")
'If endsAbs Then SpecialFolder = SpecialFolder.TrimEnd("*")
'If Not SpecialFolder.IsEmptyString Then SpecialFolder = $"{SpecialFolder.TrimEnd("\")}\{VideoFolderName}{IIf(Not endsAbs, "*", String.Empty)}"
'If endsAbs Then SpecialFolder &= "*"
ElseIf Not SpecialFolder.IsEmptyString Then
endsAbs = SpecialFolder.EndsWith("*")
SpecialFolder = SpecialFolder.TrimEnd({CChar("\"), CChar("*")})
If endsAbs Then SpecialFolder = $"{SpecialFolder}\Photos"
newFolderName = $"{SpecialFolder}*"
Else
newFolderName = SpecialFolder
End If
SpecialFolder = newFolderName
ElseIf t = -1 And InitialType = 8 And ObtainMedia_AllowAbstract Then
If n.Contains(vid) Then
t = 2
@@ -1144,7 +1198,7 @@ NextPageBlock:
If l.Count > 0 Then l.RemoveAll(wrongData)
If l.Count > 0 Then
l.Sort()
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, l.First.Data, PostID, DateObj, SpecialFolder, PostOriginUrl, State, Attempts), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, l.First.Data, PostID, DateObj, SpecialFolder, PostOriginUrl, State, Attempts, Text), LNC)
l.Clear()
End If
End If
@@ -1161,19 +1215,19 @@ NextPageBlock:
If l.Count > 0 Then l.RemoveAll(wrongData)
If l.Count > 0 Then
l.Sort()
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, l.First.Data, PostID, DateObj, SpecialFolder, PostOriginUrl, State, Attempts), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, l.First.Data, PostID, DateObj, SpecialFolder, PostOriginUrl, State, Attempts, Text), LNC)
l.Clear()
End If
End If
End With
End If
If Not TryExtractImage And Not Section = ObtainMedia_NoSection And ExtractImageFrom(Section) Then _
ObtainMedia(n, PostID, SpecialFolder, DateObj, InitialType, PostOriginUrl, State, Attempts, True, Section)
ObtainMedia(n, PostID, SpecialFolder, DateObj, InitialType, PostOriginUrl, State, Attempts, True, Section, Text)
Case 8 'gallery
DateObj = mDate(n)
With n("carousel_media").XmlIfNothing
If .Count > 0 Then
For Each d In .Self : ObtainMedia(d, PostID, SpecialFolder, DateObj, 8, PostOriginUrl) : Next
For Each d In .Self : ObtainMedia(d, PostID, SpecialFolder, DateObj, 8, PostOriginUrl,,,,, Text) : Next
End If
End With
End Select
@@ -1198,7 +1252,6 @@ NextPageBlock:
If Not j Is Nothing AndAlso j.Contains({"data", "user"}) Then
With j({"data", "user"})
ID = .Value("id")
_ForceSaveUserData = True
__idFound = True
UserSiteNameUpdate(.Value("full_name"))
Dim descr$ = .Value("biography")
@@ -1209,11 +1262,8 @@ NextPageBlock:
Dim f As New SFile With {.Path = DownloadContentDefault_GetRootDir(), .Name = "ProfilePicture", .Extension = "jpg"}
f = SFile.IndexReindex(f)
If Not f.Exists Then
Dim profilePicture$ = .Value("profile_pic_url_hd")
If profilePicture.IsEmptyString OrElse Not GetWebFile(profilePicture, f, EDP.ReturnValue) Then
profilePicture = .Value("profile_pic_url")
If Not profilePicture.IsEmptyString Then GetWebFile(profilePicture, f, EDP.ReturnValue)
End If
If SimpleDownloadAvatar(.Value("profile_pic_url_hd"), Function(ff) f).IsEmptyString Then _
SimpleDownloadAvatar(.Value("profile_pic_url"), Function(ff) f)
End If
End With
End If
@@ -1246,11 +1296,10 @@ NextPageBlock:
If Not newName.IsEmptyString Then
Dim oldName$ = NameTrue
If Not newName = oldName Then
MyMainLOG = $"{ToStringForLog()}: username changed from '{oldName}' to '{newName}'"
_NameTrue = newName
Dim descr$ = $"Username changed from '{oldName}' to '{newName}' ({Now.ToStringDate(ADateTime.Formats.BaseDateTime)})!"
descr.StringAppendLine(UserDescription)
UserDescription = descr
Dim uStr$ = $"username changed from '{oldName}' to '{newName}'"
LogError(Nothing, uStr)
NameTrue = newName
UserDescriptionUpdate(uStr, True, True, True)
_ForceSaveUserInfo = True
End If
Return True
@@ -1441,13 +1490,16 @@ NextPageBlock:
#Region "Create media"
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, Optional ByVal PostOriginUrl As String = Nothing,
Optional ByVal State As UStates = UStates.Unknown, Optional ByVal Attempts As Integer = 0) As UserMedia
Optional ByVal State As UStates = UStates.Unknown, Optional ByVal Attempts As Integer = 0,
Optional ByVal Text As String = Nothing) As UserMedia
_URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern))
Dim m As New UserMedia(_URL, t) With {.URL_BASE = PostOriginUrl.IfNullOrEmpty(_URL), .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, UnixDate32Provider, Nothing) Else m.Post.Date = Nothing
m.SpecialFolder = SpecialFolder
If State = UStates.Missing Then m.State = UStates.Missing : m.Attempts = Attempts
m.PostText = Text
m.PostTextFileSpecialFolder = DownloadTextSpecialFolder
Return m
End Function
#End Region

View File

@@ -34,6 +34,9 @@ Namespace API.JustForFans
Case NameOf(UserAgent) : If Not HeaderValue.IsEmptyString Then Responser.UserAgent = HeaderValue
End Select
End Sub
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
MyBase.New("JustForFans", "justfor.fans", AccName, Temp, My.Resources.SiteResources.JFFIcon_64, My.Resources.SiteResources.JFFPic_76)

View File

@@ -12,6 +12,9 @@ Imports PersonalUtilities.Functions.RegularExpressions
Namespace API.LPSG
<Manifest("AndyProgram_LPSG")>
Friend Class SiteSettings : Inherits Base.SiteSettingsBase
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
MyBase.New("LPSG", "www.lpsg.com", AccName, Temp, My.Resources.SiteResources.LPSGIcon_48, My.Resources.SiteResources.LPSGPic_32)
UrlPatternUser = "https://www.lpsg.com/threads/{0}/"

View File

@@ -106,7 +106,8 @@ Namespace API.LPSG
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
If Responser.StatusCode = Net.HttpStatusCode.ServiceUnavailable Then '503
If Responser.StatusCode = Net.HttpStatusCode.ServiceUnavailable Or
Responser.StatusCode = Net.HttpStatusCode.Forbidden Then '503, 403
MyMainLOG = $"{ToStringForLog()}: LPSG not available"
Return 1
ElseIf Responser.StatusCode = Net.HttpStatusCode.NotFound Then '404

View File

@@ -15,6 +15,7 @@ Namespace API.Mastodon
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property DownloadModelSearch As Boolean
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property DownloadModelForceApply As Boolean
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property DownloadModelLikes As Boolean
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property DownloadBroadcasts As Boolean
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property UserName As String
Friend Sub New(ByVal s As SiteSettings)
MyBase.New(s)

View File

@@ -76,6 +76,9 @@ Namespace API.Mastodon
<PropertyOption(IsAuth:=False, ControlText:="User related to my domain",
ControlToolTip:="Open user profiles and user posts through my domain."), PXML, PClonable>
Friend ReadOnly Property UserRelatedToMyDomain As PropertyValue
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
#End Region
#End Region
#Region "Initializer"
@@ -171,12 +174,12 @@ Namespace API.Mastodon
With DirectCast(User, UserData)
If UserRelatedToMyDomain.Value Then
If MyDomain.Value = .UserDomain Then
Return $"https://{ .UserDomain}/@{ .TrueName}"
Return $"https://{ .UserDomain}/@{ .NameTrue}"
Else
Return $"https://{MyDomain.Value}/@{ .TrueName}@{ .UserDomain}"
Return $"https://{MyDomain.Value}/@{ .NameTrue}@{ .UserDomain}"
End If
Else
Return $"https://{ .UserDomain}/@{ .TrueName}"
Return $"https://{ .UserDomain}/@{ .NameTrue}"
End If
End With
End Function

View File

@@ -29,7 +29,6 @@ Namespace API.Mastodon
_UserDomain = d
End Set
End Property
Friend Property TrueName As String = String.Empty
Private ReadOnly Property MySettings As SiteSettings
Get
Return HOST.Source
@@ -52,22 +51,21 @@ Namespace API.Mastodon
Dim l$() = Name.Split("@")
If l.ListExists(2) Then
_UserDomain = l(0)
TrueName = l(1)
NameTrue = l(1)
Else
_UserDomain = MySettings.MyDomain.Value
TrueName = Name
NameTrue = Name
End If
If FriendlyName.IsEmptyString Then FriendlyName = TrueName
If FriendlyName.IsEmptyString Then FriendlyName = NameTrue
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.Add(Name_TrueName, NameTrue(True))
Container.Value(Name_FriendlyName) = FriendlyName
End If
End Sub
@@ -208,12 +206,12 @@ Namespace API.Mastodon
Dim url$ = $"https://{MyCredentials.Domain}/api/v1/accounts/lookup?acct="
If Not UserDomain.IsEmptyString Then
If UserDomain = MyCredentials.Domain Then
url &= $"@{TrueName}"
url &= $"@{NameTrue}"
Else
url &= $"@{TrueName}@{UserDomain}"
url &= $"@{NameTrue}@{UserDomain}"
End If
Else
url &= $"@{TrueName}"
url &= $"@{NameTrue}"
End If
Dim r$ = Responser.GetResponse(url)
If Not r.IsEmptyString Then

View File

@@ -3,5 +3,4 @@ https://github.com/riley-access-labs/onlyfans-dynamic-rules-1/blob/main/dynamicR
https://github.com/riley-access-labs/onlyfans-dynamic-rules-1/blob/patch-1/dynamicRules.json
https://github.com/DATAHOARDERS/dynamic-rules/blob/main/onlyfans.json
https://github.com/DIGITALCRIMINAL/dynamic-rules/blob/main/onlyfans.json
https://github.com/deviint/onlyfans-dynamic-rules/blob/main/dynamicRules.json
https://github.com/rafa-9/dynamic-rules/blob/main/rules.json

View File

@@ -4,8 +4,8 @@ https://github.com/riley-access-labs/onlyfans-dynamic-rules-1/blob/main/dynamicR
https://github.com/riley-access-labs/onlyfans-dynamic-rules-1/blob/patch-1/dynamicRules.json
https://github.com/DATAHOARDERS/dynamic-rules/blob/main/onlyfans.json
https://github.com/DIGITALCRIMINAL/dynamic-rules/blob/main/onlyfans.json
https://github.com/deviint/onlyfans-dynamic-rules/blob/main/dynamicRules.json
https://github.com/rafa-9/dynamic-rules/blob/main/rules.json
https://github.com/SneakyOvis/onlyfans-dynamic-rules/blob/main/rules.json
https://github.com/Growik/onlyfans-dynamic-rules/blob/main/rules.json
https://github.com/Growik/onlyfans-dynamic-rules/blob/main/rules.json
https://github.com/deviint/onlyfans-dynamic-rules/blob/main/dynamicRules.json

View File

@@ -36,12 +36,20 @@ Namespace API.OnlyFans
Private Const HeaderUserID As String = "User-Id"
Friend Const HeaderXBC As String = "X-Bc"
Friend Const HeaderAppToken As String = "App-Token"
Private Const AppTokenDefault As String = "33d57ade8c02dbc5a333db99ff9ae26a"
Private Const BackendDefault As String = "aio"
Private Const Backendhttpx As String = "httpx"
<PropertyOption(ControlText:=HeaderUserID, AllowNull:=False, IsAuth:=True), PClonable(Clone:=False)>
Friend ReadOnly Property HH_USER_ID As PropertyValue
<PropertyOption(ControlText:=HeaderXBC, AllowNull:=False, IsAuth:=True), PClonable(Clone:=False)>
Private ReadOnly Property HH_X_BC As PropertyValue
<PropertyOption(ControlText:=HeaderAppToken, AllowNull:=False, IsAuth:=True), PClonable(Clone:=False)>
Private ReadOnly Property HH_APP_TOKEN As PropertyValue
<PropertyUpdater(NameOf(HH_APP_TOKEN))>
Private Function UpdateAppToken() As Boolean
HH_APP_TOKEN.Value = AppTokenDefault
Return True
End Function
<PropertyOption(ControlText:=HeaderBrowser, ControlToolTip:="Can be null", AllowNull:=True,
InheritanceName:=SettingsCLS.HEADER_DEF_sec_ch_ua, IsAuth:=True), PClonable, PXML(OnlyForChecked:=True)>
Private ReadOnly Property HH_BROWSER As PropertyValue
@@ -73,6 +81,10 @@ Namespace API.OnlyFans
End If
Return String.Empty
End Function
<PropertyOption(ControlText:="Update cookies during requests",
ControlToolTip:="If unchecked, cookies will not be updated during requests. Initial cookies will always be used.", IsAuth:=True),
PClonable, PXML, HiddenControl>
Friend ReadOnly Property EnableCookiesUpdate As PropertyValue
#End Region
#Region "Errors"
<PClonable, PXML("UpdateRules401")> Private ReadOnly Property UpdateRules401_XML As PropertyValue
@@ -112,7 +124,7 @@ Namespace API.OnlyFans
End Property
Friend Const KeyModeDefault_Default As String = "cdrm"
<PClonable, PXML("KeyModeDefault")> Private ReadOnly Property KeyModeDefault_XML As PropertyValue
<PropertyOption(ControlText:="key-mode-default", Category:=CAT_OFS)>
<PropertyOption(ControlText:="key-mode-default", ControlToolTip:="Examples: cdrm, cdrm2, keydb, manual", Category:=CAT_OFS)>
Friend ReadOnly Property KeyModeDefault As PropertyValue
Get
If Not DefaultInstance Is Nothing Then
@@ -133,6 +145,80 @@ Namespace API.OnlyFans
End If
End Get
End Property
<PClonable, PXML("KEYS_Key")> Private ReadOnly Property OFS_KEYS_Key_XML As PropertyValue
<PropertyOption(ControlText:="Private key", ControlToolTip:="Path to the DRM key file 'private_key.pem'", Category:=CAT_OFS)>
Friend ReadOnly Property OFS_KEYS_Key As PropertyValue
Get
If Not DefaultInstance Is Nothing Then
Return DirectCast(DefaultInstance, SiteSettings).OFS_KEYS_Key_XML
Else
Return OFS_KEYS_Key_XML
End If
End Get
End Property
<PClonable, PXML("KEYS_ClientID")> Private ReadOnly Property OFS_KEYS_ClientID_XML As PropertyValue
<PropertyOption(ControlText:="Client ID", ControlToolTip:="Path to the DRM key file 'client_id.bin'", Category:=CAT_OFS)>
Friend ReadOnly Property OFS_KEYS_ClientID As PropertyValue
Get
If Not DefaultInstance Is Nothing Then
Return DirectCast(DefaultInstance, SiteSettings).OFS_KEYS_ClientID_XML
Else
Return OFS_KEYS_ClientID_XML
End If
End Get
End Property
<PropertiesDataChecker({NameOf(KeyModeDefault), NameOf(OFS_KEYS_Key), NameOf(OFS_KEYS_ClientID)})>
Private Function OFS_KEYS_CHECKER(ByVal p As IEnumerable(Of PropertyData)) As Boolean
Const manualMode$ = "manual"
If p.ListExists Then
Dim m$ = String.Empty, k$ = String.Empty, cid$ = String.Empty
For Each pp As PropertyData In p
Select Case pp.Name
Case NameOf(KeyModeDefault) : m = pp.Value
Case NameOf(OFS_KEYS_Key) : k = pp.Value
Case NameOf(OFS_KEYS_ClientID) : cid = pp.Value
Case Else : Throw New ArgumentException($"Property name '{pp.Name}' is not implemented", "Property Name")
End Select
Next
If k.IsEmptyString And cid.IsEmptyString Then
Return True
ElseIf Not k.IsEmptyString And Not cid.IsEmptyString Then
If m = manualMode Then
Return True
Else
Return MsgBoxE({$"You are using key files and have selected '{m}' mode." & vbCr &
$"To use key files, you should use the '{manualMode}' mode" & vbCr &
"Are you sure you want to use this mode?", "Incorrect mode"}, vbExclamation + vbYesNo) = vbYes
End If
End If
Dim t As New MMessage("", "Key missing",, vbCritical)
If k.IsEmptyString Then
t.Text = "'Private key' is missing"
ElseIf cid.IsEmptyString Then
t.Text = "'Client ID' is missing"
End If
If Not t.Text.IsEmptyString Then t.Show()
End If
Return False
End Function
<PClonable, PXML("OFS_BACKEND")> Private ReadOnly Property OFS_BACKEND_XML As PropertyValue
<PropertyOption(ControlText:="backend", ControlToolTip:="The value of 'advanced_options' in the configuration" & vbCr &
"If you can't download the video, try using 'httpx'", AllowNull:=False, Category:=CAT_OFS)>
Friend ReadOnly Property OFS_BACKEND As PropertyValue
Get
If Not DefaultInstance Is Nothing Then
Return DirectCast(DefaultInstance, SiteSettings).OFS_BACKEND_XML
Else
Return OFS_BACKEND_XML
End If
End Get
End Property
<PropertyUpdater(NameOf(OFS_BACKEND))>
Private Function OFS_BACKEND_Update() As Boolean
DirectCast(If(DefaultInstance, Me), SiteSettings).OFS_BACKEND_XML.Value =
CStr(IIf(MsgBoxE({"Select a value for the 'backend' option", "'backend' value"}, vbQuestion,,, {BackendDefault, Backendhttpx}) = 0, BackendDefault, Backendhttpx))
Return True
End Function
#End Region
#End Region
#Region "Initializer"
@@ -165,12 +251,14 @@ Namespace API.OnlyFans
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.AcceptEncoding))
HH_USER_ID = New PropertyValue(.Value(HeaderUserID), GetType(String), Sub(v) UpdateHeader(NameOf(HH_USER_ID), v))
HH_X_BC = New PropertyValue(.Value(HeaderXBC), GetType(String), Sub(v) UpdateHeader(NameOf(HH_X_BC), v))
HH_APP_TOKEN = New PropertyValue(.Value(HeaderAppToken), GetType(String), Sub(v) UpdateHeader(NameOf(HH_APP_TOKEN), v))
HH_APP_TOKEN = New PropertyValue(.Value(HeaderAppToken).IfNullOrEmpty(AppTokenDefault), GetType(String), Sub(v) UpdateHeader(NameOf(HH_APP_TOKEN), v))
HH_BROWSER = New PropertyValue(.Value(HeaderBrowser), GetType(String), Sub(v) UpdateHeader(NameOf(HH_BROWSER), v))
End With
UserAgent = New PropertyValue(IIf(.UserAgentExists, .UserAgent, String.Empty), GetType(String), Sub(v) UpdateHeader(NameOf(UserAgent), v))
End With
EnableCookiesUpdate = New PropertyValue(False)
DownloadTimeline = New PropertyValue(True)
DownloadStories = New PropertyValue(True)
DownloadHighlights = New PropertyValue(True)
@@ -191,6 +279,9 @@ Namespace API.OnlyFans
OFScraperMP4decrypt_XML = New PropertyValue(String.Empty, GetType(String))
KeyModeDefault_XML = New PropertyValue(KeyModeDefault_Default)
Keydb_Api_XML = New PropertyValue(String.Empty, GetType(String))
OFS_KEYS_Key_XML = New PropertyValue(String.Empty, GetType(String))
OFS_KEYS_ClientID_XML = New PropertyValue(String.Empty, GetType(String))
OFS_BACKEND_XML = New PropertyValue(BackendDefault)
UpdateRules401_XML = New PropertyValue(False)
@@ -198,6 +289,15 @@ Namespace API.OnlyFans
UrlPatternUser = "https://onlyfans.com/{0}"
ImageVideoContains = "onlyfans.com"
End Sub
Private Const SettingsVersionCurrent As Integer = 1
Friend Overrides Sub EndInit()
If CInt(SettingsVersion.Value) < SettingsVersionCurrent Then
If CStr(HH_APP_TOKEN.Value).IsEmptyString Then HH_APP_TOKEN.Value = AppTokenDefault
EnableCookiesUpdate.Value = False
SettingsVersion.Value = SettingsVersionCurrent
End If
MyBase.EndInit()
End Sub
#End Region
#Region "GetInstance"
Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider
@@ -248,7 +348,7 @@ Namespace API.OnlyFans
#Region "GetUserUrl, GetUserPostUrl, UserOptions"
Friend Const UserPostPattern As String = "https://onlyfans.com/{0}/{1}"
Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String
Return String.Format(UrlPatternUser, If(User.ID.IsEmptyString, User.Name, $"u{User.ID}"))
Return String.Format(UrlPatternUser, If(User.ID.IsEmptyString, User.NameTrue.IfNullOrEmpty(User.Name), $"u{User.ID}"))
End Function
Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String
If Not Media.Post.ID.IsEmptyString Then
@@ -264,7 +364,8 @@ Namespace API.OnlyFans
If p.IsEmptyString Then
Return GetUserUrl(User)
Else
Return String.Format(UserPostPattern, p, If(User.ID.IsEmptyString, User.Name, $"u{User.ID}"))
'Return String.Format(UserPostPattern, p, If(User.ID.IsEmptyString, User.Name, $"u{User.ID}"))
Return String.Format(UserPostPattern, p, User.NameTrue)
End If
Else
Return String.Empty

View File

@@ -66,6 +66,7 @@ Namespace API.OnlyFans
Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
If Not Obj Is Nothing AndAlso TypeOf Obj Is UserExchangeOptions Then
With DirectCast(Obj, UserExchangeOptions)
.ApplyBase(Me)
MediaDownloadTimeline = .DownloadTimeline
MediaDownloadStories = .DownloadStories
MediaDownloadHighlights = .DownloadHighlights
@@ -84,6 +85,7 @@ Namespace API.OnlyFans
Private _OFScraperExists As Boolean = False
Private OFSCache As CacheKeeper = Nothing
Private _AbsMediaIndex As Integer = 0
Private _DownloadedPostsSession As Integer = 0
Private FunctionErr As Integer = FunctionErrDef
Private Const FunctionErrDef As Integer = -100
Private Sub ValidateOFScraper()
@@ -95,11 +97,12 @@ Namespace API.OnlyFans
If Not MySettings.SessionAborted Then
ValidateOFScraper()
_AbsMediaIndex = 0
_DownloadedPostsSession = 0
FunctionErr = FunctionErrDef
If Not CCookie Is Nothing Then CCookie.Dispose()
CCookie = Responser.Cookies.Copy
Responser.Cookies.Clear()
AddHandler Responser.ResponseReceived, AddressOf Responser_ResponseReceived
If MySettings.EnableCookiesUpdate.Value Then AddHandler Responser.ResponseReceived, AddressOf Responser_ResponseReceived
UpdateCookieHeader()
If Not IsSavedPosts Then
@@ -113,13 +116,14 @@ Namespace API.OnlyFans
If MediaDownloadHighlights And FunctionErr = FunctionErrDef Then DownloadHighlights(Token)
If MediaDownloadChatMedia And FunctionErr = FunctionErrDef Then DownloadChatMedia(0, Token)
End If
If _TempMediaList.Count > 0 And Not _NameUpdated Then GetUserID(True)
End If
Finally
Responser_ResponseReceived_RemoveHandler()
End Try
End Sub
Protected Overrides Sub Responser_ResponseReceived(ByVal Sender As Object, ByVal e As WebDataResponse)
If e.CookiesExists Then
If e.CookiesExists And CBool(MySettings.EnableCookiesUpdate.Value) Then
CCookie.Update(e.Cookies, CookieKeeper.UpdateModes.ReplaceByNameAll,, EDP.ReturnValue)
UpdateCookieHeader()
End If
@@ -144,7 +148,7 @@ Namespace API.OnlyFans
Dim tmpCursor$ = String.Empty
Dim hasMore As Boolean = False
Dim path$ = String.Empty
Dim postDate$, postID$
Dim postDate$, postID$, txt$
Dim n As EContainer
Dim mediaList As List(Of UserMedia)
Dim mediaResult As Boolean
@@ -189,9 +193,21 @@ Namespace API.OnlyFans
Case DateResult.Exit : Exit Sub
End Select
txt = n.Value("text")
mediaResult = False
mediaList = TryCreateMedia(n, postID, postDate, mediaResult)
If mediaResult Then _TempMediaList.ListAddList(mediaList, LNC)
mediaList = TryCreateMedia(n, postID, postDate, mediaResult,,,,, txt)
If mediaResult Then
_TempMediaList.ListAddList(mediaList, LNC)
_DownloadedPostsSession += 1
ElseIf Not txt.IsEmptyString Then
_TempMediaList.ListAddValue(New UserMedia(postID, UTypes.Text) With {
.Post = New UserPost(postID, AConvert(Of Date)(postDate, DateProvider, Nothing)),
.PostText = txt,
.PostTextFileSpecialFolder = DownloadTextSpecialFolder,
.PostTextFile = $"{postID}.txt"
}, LNC)
End If
Next
Else
hasMore = False
@@ -202,7 +218,10 @@ Namespace API.OnlyFans
End If
End If
If hasMore Then
If DownloadTopCount.HasValue AndAlso DownloadTopCount.Value <= _DownloadedPostsSession Then
_complete = True
Exit Sub
ElseIf hasMore Then
If IsSavedPosts Then tmpCursor = CInt(Cursor.IfNullOrEmpty(0)) + 10
DownloadTimeline(tmpCursor, Token)
End If
@@ -405,14 +424,14 @@ Namespace API.OnlyFans
Private Function TryCreateMedia(ByVal n As EContainer, ByVal PostID As String, Optional ByVal PostDate As String = Nothing,
Optional ByRef Result As Boolean = False, Optional ByVal IsHL As Boolean = False,
Optional ByVal SpecFolder As String = Nothing, Optional ByVal PostUserID As String = Nothing,
Optional ByVal TryUseOFS As Boolean = True) As List(Of UserMedia)
Optional ByVal TryUseOFS As Boolean = True, Optional ByVal PostText As String = Nothing) As List(Of UserMedia)
Dim postUrl$, postUrlBase$, ext$
Dim t As UTypes
Dim mList As New List(Of UserMedia)
Result = False
With n("media")
If .ListExists Then
For Each m In .Self
For Each m As EContainer In .Self
postUrl = GetMediaURL(m)
'If IsHL Then
' 'postUrl = m.Value({"files", "source"}, "url")
@@ -421,62 +440,61 @@ Namespace API.OnlyFans
' 'postUrl = m.Value({"source"}, "source").IfNullOrEmpty(m.Value("full"))
' postUrl = GetMediaURL(m)
'End If
postUrlBase = String.Empty
Select Case m.Value("type")
Case "photo" : t = UTypes.Picture : ext = "jpg"
Case "video"
t = UTypes.Video
ext = "mp4"
If postUrl.IsEmptyString And Not IsHL And TryUseOFS Then
t = UTypes.VideoPre
_AbsMediaIndex += 1
If Not PostUserID.IsEmptyString And IsSingleObjectDownload Then _
postUrlBase = String.Format(SiteSettings.UserPostPattern, PostID, $"u{PostUserID}")
End If
Case Else : t = UTypes.Undefined : ext = String.Empty
End Select
If Not t = UTypes.Undefined And (Not postUrl.IsEmptyString Or t = UTypes.VideoPre) Then
Dim media As New UserMedia(postUrl.IfNullOrEmpty(IIf(t = UTypes.VideoPre, $"{t}{_AbsMediaIndex}", String.Empty)), t) With {
.Post = New UserPost(PostID, AConvert(Of Date)(PostDate, DateProvider, Nothing)),
.SpecialFolder = SpecFolder
}
If postUrlBase.IsEmptyString And Not IsSingleObjectDownload Then postUrlBase = GetPostUrl(Me, media)
If Not postUrlBase.IsEmptyString Then media.URL_BASE = postUrlBase
media.File.Extension = ext
Result = True
mList.Add(media)
If m.Value("canView").FromXML(Of Boolean)(True) Then
postUrlBase = String.Empty
Select Case m.Value("type")
Case "photo" : t = UTypes.Picture : ext = "jpg"
Case "video", "gif"
t = UTypes.Video
ext = "mp4"
If postUrl.IsEmptyString And Not IsHL And TryUseOFS Then
t = UTypes.VideoPre
_AbsMediaIndex += 1
If Not PostUserID.IsEmptyString And IsSingleObjectDownload Then _
postUrlBase = String.Format(SiteSettings.UserPostPattern, PostID, $"u{PostUserID}")
End If
Case Else : t = UTypes.Undefined : ext = String.Empty
End Select
If Not t = UTypes.Undefined And (Not postUrl.IsEmptyString Or t = UTypes.VideoPre) Then
Dim media As New UserMedia(postUrl.IfNullOrEmpty(IIf(t = UTypes.VideoPre, $"{t}{_AbsMediaIndex}", String.Empty)), t) With {
.Post = New UserPost(PostID, AConvert(Of Date)(PostDate, DateProvider, Nothing)),
.SpecialFolder = SpecFolder,
.PostText = PostText,
.PostTextFileSpecialFolder = DownloadTextSpecialFolder
}
If postUrlBase.IsEmptyString And Not IsSingleObjectDownload Then postUrlBase = GetPostUrl(Me, media)
If Not postUrlBase.IsEmptyString Then media.URL_BASE = postUrlBase
media.File.Extension = ext
Result = True
mList.Add(media)
End If
End If
Next
End If
End With
Return mList
End Function
Private Sub GetUserID()
Private _NameUpdated As Boolean = False
Private Sub GetUserID(Optional ByVal UpdateNameOnly As Boolean = False)
Const brTag$ = "<br />"
Dim path$ = $"/api2/v2/users/{Name}"
Dim path$ = $"/api2/v2/users/{IIf(UpdateNameOnly, $"u{ID}", Name)}"
Dim url$ = String.Format(BaseUrlPattern, path)
Try
If ID.IsEmptyString AndAlso UpdateSignature(path) Then
If (ID.IsEmptyString Or UpdateNameOnly) AndAlso UpdateSignature(path) Then
Dim r$ = Responser.GetResponse(url)
If Not r.IsEmptyString Then
Using j As EContainer = JsonDocument.Parse(r)
If j.ListExists Then
NameTrue = j.Value("username")
_NameUpdated = True
If UpdateNameOnly Then Exit Sub
ID = j.Value("id")
If Not ID.IsEmptyString Then _ForceSaveUserInfo = True
UserSiteNameUpdate(j.Value("name"))
Dim descr$ = j.Value("about")
If Not descr.IsEmptyString Then descr = descr.Replace(brTag, String.Empty)
UserDescriptionUpdate(descr)
Dim a As Action(Of String) = Sub(ByVal address As String)
If Not address.IsEmptyString Then
Dim f As SFile = address
f.Separator = "\"
f.Path = DownloadContentDefault_GetRootDir()
If Not f.Exists Then GetWebFile(address, f, EDP.None)
End If
End Sub
a.Invoke(j.Value("avatar"))
a.Invoke(j.Value("header"))
SimpleDownloadAvatar(j.Value("avatar"))
SimpleDownloadAvatar(j.Value("header"))
End If
End Using
End If
@@ -608,11 +626,12 @@ Namespace API.OnlyFans
Const requestPattern$ = """{0}"" manual --config ""{1}"" --url {2}"
Dim conf As SFile = OFS_CreateConfig()
If conf.Exists Then
If Not _NameUpdated Then GetUserID(True)
Dim command$ = String.Format(requestPattern, MySettings.OFScraperPath.Value, conf, URL)
'#If DEBUG Then
'Debug.WriteLine(command)
'#End If
Using b As New TokenBatch(Token) : b.Execute(command) : End Using
Using b As New TokenBatch(Token) With {.DebugMode = False} : b.Execute(command) : End Using
Return SFile.GetFiles(conf, "*.mp4", IO.SearchOption.AllDirectories, EDP.ReturnValue)
End If
Return Nothing
@@ -623,7 +642,13 @@ Namespace API.OnlyFans
Private Function OFS_CreateConfig() As SFile
Try
Const confMainPattern$ = "{0}"": ""([^""]*)"""
Const confMainPattern_Keys$ = "{0}"": ([^,]*)"
Const confMainPatternRulesManual$ = "DYNAMIC_RULE"": (""[^""]*"")"
Const m1 As Byte = 0 'not rules
Const m2 As Byte = 1 'rules
Const m3 As Byte = 2 'keys
If OFSCache Is Nothing Then OFSCache = If(IsSingleObjectDownload, Settings.Cache.NewInstance, CreateCache())
Dim currentCache As CacheKeeper = OFSCache.NewInstance
currentCache.Validate()
@@ -637,35 +662,48 @@ Namespace API.OnlyFans
CType(Function(input) replaceValue, Func(Of String, String)), String.Empty, EDP.ReturnValue)
Dim ff As SFile
configText = f.GetText
Dim updateConf As Action(Of String, String, Boolean) =
Sub(ByVal patternValue As String, ByVal __replaceValue As String, ByVal __isRules As Boolean)
rp.Pattern = String.Format(IIf(__isRules, confMainPatternRulesManual, confMainPattern), patternValue)
Dim updateConf As Action(Of String, String, Byte) =
Sub(ByVal patternValue As String, ByVal __replaceValue As String, ByVal mode As Byte)
Select Case mode
Case m1 : rp.Pattern = String.Format(confMainPattern, patternValue)
Case m2 : rp.Pattern = String.Format(confMainPatternRulesManual, patternValue)
Case m3 : rp.Pattern = String.Format(confMainPattern_Keys, patternValue) : __replaceValue = $"""{__replaceValue}"""
Case Else : Throw New ArgumentException($"Mode '{mode}' is not implemented", "mode")
End Select
rp.Nothing = configText
replaceValue = __replaceValue
configText = RegexReplace(configText, rp)
End Sub
If Not configText.IsEmptyString Then
updateConf("save_location", cacheRoot.PathNoSeparator.Replace("\", "/"), False)
updateConf("save_location", cacheRoot.PathNoSeparator.Replace("\", "/"), m1)
If ACheck(MySettings.OFScraperMP4decrypt.Value) Then
ff = CStr(MySettings.OFScraperMP4decrypt.Value)
If ff.Exists Then updateConf("mp4decrypt", ff.ToString.Replace("\", "/"), False)
If ff.Exists Then updateConf("mp4decrypt", ff.ToString.Replace("\", "/"), m1)
End If
If Settings.FfmpegFile.Exists Then updateConf("ffmpeg", Settings.FfmpegFile.File.ToString.Replace("\", "/"), False)
updateConf("key-mode-default", CStr(MySettings.KeyModeDefault.Value).IfNullOrEmpty(SiteSettings.KeyModeDefault_Default), False)
updateConf("keydb_api", CStr(MySettings.Keydb_Api.Value), False)
If Settings.FfmpegFile.Exists Then updateConf("ffmpeg", Settings.FfmpegFile.File.ToString.Replace("\", "/"), m1)
updateConf("key-mode-default", CStr(MySettings.KeyModeDefault.Value).IfNullOrEmpty(SiteSettings.KeyModeDefault_Default), m1)
updateConf("keydb_api", CStr(MySettings.Keydb_Api.Value), m1)
updateConf("backend", CStr(MySettings.OFS_BACKEND.Value), m1)
If Not CStr(MySettings.OFS_KEYS_Key.Value).IsEmptyString And Not CStr(MySettings.OFS_KEYS_ClientID.Value).IsEmptyString Then
updateConf("private-key", CStr(MySettings.OFS_KEYS_Key.Value).Replace("\", "/"), m3)
updateConf("client-id", CStr(MySettings.OFS_KEYS_ClientID.Value).Replace("\", "/"), m3)
End If
If Rules.RulesReplaceConfig Then
If Rules.RulesConfigManualMode Then
updateConf(DynamicRulesEnv.DynamicRulesConfig_Mode_NodeName, "manual", False)
updateConf(DynamicRulesEnv.DynamicRulesConfig_Mode_NodeName, "manual", m1)
configText = configText.Replace(DynamicRulesEnv.DynamicRulesConfigNodeName_URL, DynamicRulesEnv.DynamicRulesConfigNodeName_RULES)
updateConf(DynamicRulesEnv.DynamicRulesConfigNodeName_RULES, Rules.CurrentContainerRulesText, True)
updateConf(DynamicRulesEnv.DynamicRulesConfigNodeName_RULES, Rules.CurrentContainerRulesText, m2)
Else
Dim confUrlNode$ = If(Rules.RulesConstants.ContainsKey(DynamicRulesEnv.DynamicRulesConfigNodeName_URL_CONST_NAME),
Rules.RulesConstants(DynamicRulesEnv.DynamicRulesConfigNodeName_URL_CONST_NAME),
DynamicRulesEnv.DynamicRulesConfigNodeName_URL)
updateConf(DynamicRulesEnv.DynamicRulesConfigNodeName_URL, Rules.CurrentRule.UrlRaw, False)
updateConf(DynamicRulesEnv.DynamicRulesConfigNodeName_URL, Rules.CurrentRule.UrlRaw, m1)
configText = configText.Replace(DynamicRulesEnv.DynamicRulesConfigNodeName_URL, confUrlNode)
If Rules.RulesConstants.ContainsKey(DynamicRulesEnv.DynamicRulesConfig_Mode_NodeName) Then _
updateConf(DynamicRulesEnv.DynamicRulesConfig_Mode_NodeName, Rules.RulesConstants(DynamicRulesEnv.DynamicRulesConfig_Mode_NodeName), False)
updateConf(DynamicRulesEnv.DynamicRulesConfig_Mode_NodeName, Rules.RulesConstants(DynamicRulesEnv.DynamicRulesConfig_Mode_NodeName), m1)
End If
End If
f = currentCache
@@ -720,6 +758,7 @@ Namespace API.OnlyFans
If IsSingleObjectDownload Then
URL = Media.URL_BASE
Else
If Not _NameUpdated Then GetUserID(True)
URL = GetPostUrl(Me, Media)
End If
If Not URL.IsEmptyString Then

View File

@@ -8,7 +8,7 @@
' but WITHOUT ANY WARRANTY
Imports SCrawler.Plugin.Attributes
Namespace API.OnlyFans
Friend Class UserExchangeOptions
Friend Class UserExchangeOptions : Inherits Base.EditorExchangeOptionsBase
<PSetting(NameOf(SiteSettings.DownloadTimeline), NameOf(MySettings))>
Friend Property DownloadTimeline As Boolean
<PSetting(NameOf(SiteSettings.DownloadStories), NameOf(MySettings))>
@@ -18,7 +18,12 @@ Namespace API.OnlyFans
<PSetting(NameOf(SiteSettings.DownloadChatMedia), NameOf(MySettings))>
Friend Property DownloadChatMedia As Boolean
Private ReadOnly MySettings As SiteSettings
<PSetting(Address:=SettingAddress.None)> Friend Overrides Property UserName As String
Private Sub New()
End Sub
Friend Sub New(ByVal u As UserData)
MyBase.New(u)
_ApplyBase_Name = False
DownloadTimeline = u.MediaDownloadTimeline
DownloadStories = u.MediaDownloadStories
DownloadHighlights = u.MediaDownloadHighlights
@@ -26,6 +31,8 @@ Namespace API.OnlyFans
MySettings = u.HOST.Source
End Sub
Friend Sub New(ByVal s As SiteSettings)
MyBase.New(s)
_ApplyBase_Name = False
DownloadTimeline = s.DownloadTimeline.Value
DownloadStories = s.DownloadStories.Value
DownloadHighlights = s.DownloadHighlights.Value

View File

@@ -7,9 +7,18 @@
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports System.Globalization
Imports System.Text.RegularExpressions
Imports PersonalUtilities.Tools.Web.Clients
Imports PersonalUtilities.Functions.RegularExpressions
Namespace API.Pinterest
Friend Module Declarations
Friend ReadOnly DateProvider As ADateTime = GetDateProvider()
Friend ReadOnly PwsHeader As New HttpHeader("x-pinterest-pws-handler", "www/[username]/pins.js")
Friend ReadOnly GdlUrlPattern As RParams = RParams.DM(Base.GDL.GDLBatch.UrlLibStart.Replace("[", "\[").Replace("]", "\]") &
"([^""]+?)""(GET [^""]+)""", 0, EDP.ReturnValue)
Friend ReadOnly SubBoardRegEx As RParams = RParams.DMS("\[pinterest\]\[debug\] Using PinterestSectionExtractor for '[^']+id:(\d+)'", 1,
RegexOptions.IgnoreCase, EDP.ReturnValue)
Friend ReadOnly BoardInfoRootNode As String() = {"resource_response", "data"}
Private Function GetDateProvider() As ADateTime
Dim n As DateTimeFormatInfo = CultureInfo.GetCultureInfo("en-us").DateTimeFormat.Clone
n.FullDateTimePattern = "ddd dd MMM yyyy HH:mm:ss"

View File

@@ -1,4 +1,4 @@
' Copyright (C) 2023 Andy https://github.com/AAndyProgram
' Copyright (C) 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
@@ -6,12 +6,15 @@
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Namespace API.XVIDEOS
Friend Class UserExchangeOptions : Inherits Xhamster.UserExchangeOptions
Friend Sub New()
End Sub
Imports SCrawler.Plugin.Attributes
Namespace API.Pinterest
Friend Class EditorExchangeOptions
<PSetting(Caption:="Get Sub-Boards", ToolTip:="Extract the Sub-Boards from the boards and download them")>
Friend Property ExtractSubBoards As Boolean = True
Friend Sub New(ByVal u As UserData)
QueryString = u.QueryString
ExtractSubBoards = u.ExtractSubBoards
End Sub
Friend Sub New()
End Sub
End Class
End Namespace

View File

@@ -11,7 +11,7 @@ Imports SCrawler.Plugin
Imports SCrawler.Plugin.Attributes
Imports PersonalUtilities.Functions.RegularExpressions
Namespace API.Pinterest
<Manifest("AndyProgram_Pinterest"), SavedPosts, SeparatedTasks>
<Manifest("AndyProgram_Pinterest"), SavedPosts, SeparatedTasks, SpecialForm(False)>
Friend Class SiteSettings : Inherits SiteSettingsBase
#Region "Declarations"
<PropertyOption(ControlText:=DeclaredNames.ConcurrentDownloadsCaption,
@@ -21,6 +21,9 @@ Namespace API.Pinterest
Private ReadOnly Property MyConcurrentDownloadsProvider As IFormatProvider
<PropertyOption(ControlText:=DeclaredNames.SavedPostsUserNameCaption, ControlToolTip:=DeclaredNames.SavedPostsUserNameToolTip), PXML, PClonable(Clone:=False)>
Friend ReadOnly Property SavedPostsUserName As PropertyValue
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
#End Region
#Region "Initializer"
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
@@ -30,7 +33,8 @@ Namespace API.Pinterest
MyConcurrentDownloadsProvider = New ConcurrentDownloadsProvider
CheckNetscapeCookiesOnEndInit = True
UseNetscapeCookies = True
UserRegex = RParams.DMS("https?://w{0,3}.?[^/]*?.?pinterest.com/([^/]+)/?(?(_)|([^/]*))", 0, RegexReturn.ListByMatch, EDP.ReturnValue)
UserRegex = RParams.DMS("https?://w{0,3}.?[^/]*?.?pinterest.com/([^/]+)/?(?(_)|([^/]*))/?([^/\?]*)", 0, RegexReturn.ListByMatch, EDP.ReturnValue)
UserOptionsType = GetType(EditorExchangeOptions)
End Sub
#End Region
#Region "GetInstance, Available"
@@ -41,13 +45,14 @@ Namespace API.Pinterest
Return Settings.GalleryDLFile.Exists And (Not What = ISiteSettings.Download.SavedPosts OrElse ACheck(SavedPostsUserName.Value))
End Function
#End Region
#Region "IsMyUser, IsMyImageVideo, GetUserUrl, GetUserPostUrl"
#Region "IsMyUser, IsMyImageVideo, GetUserUrl, GetUserPostUrl, UserOptions"
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)}"
If l.Count > 3 AndAlso Not l(3).IsEmptyString Then n &= $"@{l(3)}"
Return New ExchangeOptions(Site, n) With {.Exists = True}
End If
End If

View File

@@ -7,10 +7,12 @@
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports System.Threading
Imports System.Text.RegularExpressions
Imports SCrawler.API.Base
Imports SCrawler.API.Base.GDL
Imports SCrawler.API.YouTube.Objects
Imports PersonalUtilities.Functions.XML
Imports PersonalUtilities.Functions.RegularExpressions
Imports PersonalUtilities.Tools.Web.Documents.JSON
Namespace API.Pinterest
Friend Class UserData : Inherits UserDataBase
@@ -18,6 +20,8 @@ Namespace API.Pinterest
Private Const Name_IsUser As String = "IsUser"
Private Const Name_TrueUserName As String = "TrueUserName"
Private Const Name_TrueBoardName As String = "TrueBoardName"
Private Const Name_ExtractSubBoards As String = "ExtractSubBoards"
Private Const Name_IsSubBoard As String = "IsSubBoard"
#End Region
#Region "Structures"
Private Structure BoardInfo
@@ -38,6 +42,8 @@ Namespace API.Pinterest
Friend Property TrueUserName As String
Friend Property TrueBoardName As String
Friend Property IsUser_NB As Boolean
Private Property IsSubBoard As Boolean = False
Friend Property ExtractSubBoards As Boolean = True
Private Const BoardLabelName As String = "Board"
Friend Overrides ReadOnly Property SpecialLabels As IEnumerable(Of String)
Get
@@ -45,14 +51,18 @@ Namespace API.Pinterest
End Get
End Property
#End Region
#Region "Load"
#Region "Load, Exchange"
Private Function ReconfUserName() As Boolean
If TrueUserName.IsEmptyString Then
Dim n$() = Name.Split("@")
If n.ListExists Then
TrueUserName = n(0)
IsUser_NB = True
If n.Length > 1 Then TrueBoardName = n(1) : IsUser_NB = False
If n.Length > 1 Then
TrueBoardName = n(1)
If n.Length > 2 AndAlso Not n(2).IsEmptyString Then TrueBoardName &= $"/{n(2)}" : IsSubBoard = True
IsUser_NB = False
End If
If Not IsSavedPosts And Not IsSingleObjectDownload Then
Dim l$ = IIf(IsUser_NB, UserLabelName, BoardLabelName)
Settings.Labels.Add(l)
@@ -70,15 +80,25 @@ Namespace API.Pinterest
TrueUserName = .Value(Name_TrueUserName)
TrueBoardName = .Value(Name_TrueBoardName)
IsUser_NB = .Value(Name_IsUser).FromXML(Of Boolean)(False)
ExtractSubBoards = .Value(Name_ExtractSubBoards).FromXML(Of Boolean)(True)
IsSubBoard = .Value(Name_IsSubBoard).FromXML(Of Boolean)(False)
ReconfUserName()
Else
If ReconfUserName() Then .Value(Name_LabelsName) = LabelsString
.Add(Name_TrueUserName, TrueUserName)
.Add(Name_TrueBoardName, TrueBoardName)
.Add(Name_IsUser, IsUser_NB.BoolToInteger)
.Add(Name_ExtractSubBoards, ExtractSubBoards.BoolToInteger)
.Add(Name_IsSubBoard, IsSubBoard.BoolToInteger)
End If
End With
End Sub
Friend Overrides Function ExchangeOptionsGet() As Object
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 ExtractSubBoards = DirectCast(Obj, EditorExchangeOptions).ExtractSubBoards
End Sub
#End Region
#Region "Download overrides"
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
@@ -125,6 +145,19 @@ Namespace API.Pinterest
End Sub
#End Region
#Region "Get boards, images"
Private Function GetBoardInfo(ByVal e As EContainer) As BoardInfo
If Not e Is Nothing Then
Dim b As New BoardInfo With {
.URL = e.Value("url"),
.Title = TitleHtmlConverter(e.Value("name")).IfNullOrEmpty(TitleHtmlConverter(e.Value("title"))),
.ID = e.Value("id")
}
If Not b.URL.IsEmptyString Then b.URL = $"https://www.pinterest.com/{b.URL.StringTrimStart("/").StringTrimEnd("/")}/"
Return b
Else
Return Nothing
End If
End Function
Private Function GetBoards(ByVal Token As CancellationToken) As List(Of BoardInfo)
Dim URL$ = $"https://www.pinterest.com/{TrueUserName}/"
Try
@@ -132,11 +165,12 @@ Namespace API.Pinterest
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, Token)
Dim urls As New List(Of String)
urls.ListAddList(GetDataFromGalleryDL(URL, True, Token), LNC)
If urls.ListExists Then urls.RemoveAll(Function(__url) Not __url.Contains("BoardsResource/get/"))
If urls.ListExists Then
Responser.Headers.Add(PwsHeader)
ProgressPre.ChangeMax(urls.Count)
For Each URL In urls
ProgressPre.Perform()
@@ -145,17 +179,10 @@ Namespace API.Pinterest
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
If If(j(BoardInfoRootNode)?.Count, 0) > 0 Then
For Each jj In j(BoardInfoRootNode)
b = GetBoardInfo(jj)
If Not b.URL.IsEmptyString Then boards.Add(b)
Next
End If
j.Dispose()
@@ -167,95 +194,161 @@ Namespace API.Pinterest
Catch ex As Exception
ProcessException(ex, Token, $"data (gallery-dl boards) downloading error [{URL}]")
Return Nothing
Finally
Responser.Headers.Remove(PwsHeader)
End Try
End Function
Private Sub DownloadBoardImages(ByRef Board As BoardInfo, ByVal Token As CancellationToken)
Dim bUrl$ = String.Empty
Dim bUrl As GDLURL = Nothing
Try
Dim r$
Dim j As EContainer, jj As EContainer
Dim u As UserMedia
Dim folder$ = If(IsUser_NB, Board.Title.IfNullOrEmpty(Board.ID), String.Empty)
Dim __getBoardTitle As Func(Of BoardInfo, String) = Function(__board) __board.Title.IfNullOrEmpty(__board.ID)
Dim folderDef$ = If(IsUser_NB, __getBoardTitle(Board), 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 rErr As New ErrorsDescriber(EDP.ReturnValue)
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, Token)
If l.ListExists Then l.RemoveAll(Function(ll) Not ll.Contains("BoardFeedResource/get/"))
Dim subBoard As BoardInfo = Nothing
Dim subBoardAppender As Func(Of String) = Function() _
If(Not __getBoardTitle(subBoard).IsEmptyString,
$"{IIf(folderDef.IsEmptyString, String.Empty, "\")}{__getBoardTitle(subBoard)}",
String.Empty)
Dim __getSubBoard As Func(Of Boolean) = Function() ExtractSubBoards Or (IsSubBoard And i = -1)
Dim sbCount% = 0
Dim __getBoardInfo As Action(Of GDLURL) = Sub(ByVal sb As GDLURL)
sbCount += 1
r = Responser.GetResponse(sb.URL,, rErr)
If Not r.IsEmptyString Then
Using jsb As EContainer = JsonDocument.Parse(r, jErr)
If jsb.ListExists Then subBoard = GetBoardInfo(jsb(BoardInfoRootNode)) : Exit Sub
End Using
End If
subBoard = Nothing
End Sub
Dim l As List(Of GDLURL) = GetDataFromGalleryDL(Board.URL, False, Token)
If l.ListExists Then l.RemoveAll(Function(ll) ll.URL.IsEmptyString)
If l.ListExists Then
Responser.Headers.Add(PwsHeader)
ProgressPre.ChangeMax(l.Count)
For Each bUrl In l
ProgressPre.Perform()
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
ProgressPre.ChangeMax(j(rootNode).Count)
For Each jj In j(rootNode)
ProgressPre.Perform()
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_NB 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
If bUrl.URL.Contains("BoardFeedResource/get/") Or (bUrl.URL.Contains("BoardSectionPinsResource/get/") And (ExtractSubBoards Or (IsSubBoard And sbCount = 1))) Then
r = Responser.GetResponse(bUrl.URL,, rErr)
If Not r.IsEmptyString Then
j = JsonDocument.Parse(r, jErr)
If Not j Is Nothing Then
If If(j(BoardInfoRootNode)?.Count, 0) > 0 Then
ProgressPre.ChangeMax(j(BoardInfoRootNode).Count)
For Each jj In j(BoardInfoRootNode)
ProgressPre.Perform()
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 = folderDef & subBoardAppender.Invoke
}
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_NB Then
folderDef = Board.Title.IfNullOrEmpty(Board.ID).IfNullOrEmpty(folderDef)
u.SpecialFolder = folderDef & subBoardAppender.Invoke
End If
End If
If Not _TempPostsList.Contains(u.Post.ID) Then
_TempPostsList.ListAddValue(u.Post.ID, LNC)
_TempMediaList.ListAddValue(u, LNC)
Else
Exit For
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 If
End With
Next
End With
Next
End If
j.Dispose()
End If
j.Dispose()
End If
ElseIf bUrl.URL.Contains("BoardSectionResource/get/") And (ExtractSubBoards Or (IsSubBoard And i = -1)) Then
__getBoardInfo(bUrl)
If IsSubBoard And i = -1 And Board.Title.IsEmptyString Then
Board.Title = subBoard.Title
If Board.ID.IsEmptyString Then Board.ID = subBoard.ID
subBoard = Nothing
folderDef = String.Empty
End If
End If
Next
End If
Catch ex As Exception
ProcessException(ex, Token, $"data (gallery-dl images) downloading error [{bUrl}]")
ProcessException(ex, Token, $"data (gallery-dl images) downloading error [{bUrl.URL}]")
Finally
Responser.Headers.Remove(PwsHeader)
End Try
End Sub
#End Region
#Region "Gallery-DL Support"
Private Structure GDLURL : Implements IRegExCreator
Friend URL As String
Friend BoardId As String
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 Class GDLBatch : Inherits GDL.GDLBatch
Private ReadOnly Property Source As UserData
Private ReadOnly IsBoardsRequested As Boolean
@@ -286,14 +379,30 @@ Namespace API.Pinterest
End If
End Function
End Class
Private Function GetDataFromGalleryDL(ByVal URL As String, ByVal IsBoardsRequested As Boolean, ByVal Token As CancellationToken) As List(Of String)
Private Function GetDataFromGalleryDL(ByVal URL As String, ByVal IsBoardsRequested As Boolean, ByVal Token As CancellationToken) As List(Of GDLURL)
Dim command$ = $"""{Settings.GalleryDLFile.File}"" --verbose --simulate "
Try
If Not URL.IsEmptyString Then
Dim urls As New List(Of GDLURL)
Dim u As GDLURL
Dim s$ = String.Empty
If MySettings.CookiesNetscapeFile.Exists Then command &= $"--cookies ""{MySettings.CookiesNetscapeFile}"" "
command &= URL
Using batch As New GDLBatch(Me, IsBoardsRequested, Token)
Return GetUrlsFromGalleryDl(batch, command)
With batch
.Execute(command)
If .ErrorOutputData.Count > 0 Then
For Each eValue$ In .ErrorOutputData
s = CStr(RegexReplace(eValue, SubBoardRegEx)).IfNullOrEmpty(s)
u = RegexFields(Of GDLURL)(eValue, {GdlUrlPattern}, {1, 2}, EDP.ReturnValue).ListIfNothing.FirstOrDefault
If Not u.URL.IsEmptyString Then
If Not s.IsEmptyString Then u.BoardId = s
urls.Add(u)
End If
Next
Return urls
End If
End With
End Using
End If
Return Nothing

View File

@@ -36,11 +36,12 @@ Namespace API.PornHub
Friend ReadOnly Regex_Gif_UrlName As RParams = RParams.DMS("""name"":.*?""([^""]*)""[^\}]+?""contentUrl"":.*?""([^""]+)""", 0, RegexReturn.ListByMatch, EDP.ReturnValue)
#End Region
#Region "Declarations photo"
Friend ReadOnly Regex_Photo_ModelHub_PhotoBlocks As RParams = RParams.DM("var PHOTOS_ARRAY_(\d+) = \{[\r\n\s]*?(urls:.*?\[[^]]*\])", 0, RegexReturn.List, EDP.ReturnValue)
Friend ReadOnly Regex_Photo_PornHub_PhotoBlocks As RParams = RParams.DM("photoAlbumListContainer[\r\n\s\S]+?title=""([^""]+)""[\r\n\s\S]+?a href=""(/album/\d+)""", 0, RegexReturn.List)
Friend ReadOnly Regex_Photo_PornHub_AlbumPhotoArr As RParams = RParams.DMS("\<a href=""(/photo/\d+)""", 1, RegexReturn.List, EDP.ReturnValue,
Friend ReadOnly Regex_Photo_PornHub_PhotoBlocks2 As RParams = RParams.DM("albumInfoTitle"" href=""([^""]+)""\>([^\<]+)", 0, RegexReturn.List)
Friend ReadOnly Regex_Photo_PornHub_AlbumPhotoArr As RParams = RParams.DMS("href=""(/photo/\d+)""", 1, RegexReturn.List, EDP.ReturnValue,
CType(Function(Input$) If(Input.IsEmptyString, String.Empty, $"https://www.pornhub.com{Input.Trim}"), Func(Of String, String)))
Friend ReadOnly Regex_Photo_PornHub_SinglePhoto As RParams = RParams.DMS("(?<!thumbImage.+?)<img src=""(https://[^""]+\d+[^""]+)""", 1, EDP.ReturnValue)
Friend ReadOnly Regex_Photo_PornHub_SinglePhoto As RParams = RParams.DM("data-image=""([^""]+)""\s*src=""([^""]+)""", 0, RegexReturn.ListByMatch, EDP.ReturnValue)
Friend ReadOnly Regex_Photo_PornHub_SinglePhoto2 As RParams = RParams.DMS("image:src"" content=""([^""]+)""", 1, EDP.ReturnValue)
Friend ReadOnly Regex_Photo_File As RParams = RParams.DM("\d+\.[\w]{3,4}", 0, EDP.ReturnValue)
#End Region
End Module

View File

@@ -29,12 +29,11 @@ Namespace API.PornHub
Friend ReadOnly Property DownloadGifs As PropertyValue
<PropertyOption(ControlText:="Download GIFs as mp4", ControlToolTip:="Download gifs in 'mp4' format instead of native 'webm'"), PXML, PClonable>
Friend ReadOnly Property DownloadGifsAsMp4 As PropertyValue
<PropertyOption(ControlText:="Photo ModelHub only",
ControlToolTip:="Download photo only from ModelHub. Prornstar photos hosted on PornHub itself will not be downloaded." & vbCr &
"Attention! Downloading photos hosted on PornHub is a very heavy job."), PXML, PClonable>
Friend ReadOnly Property DownloadPhotoOnlyFromModelHub As PropertyValue
<PropertyOption(ControlText:=DeclaredNames.SavedPostsUserNameCaption, ControlToolTip:=DeclaredNames.SavedPostsUserNameToolTip), PXML, PClonable(Clone:=False)>
Friend ReadOnly Property SavedPostsUserName As PropertyValue
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
#End Region
#Region "Initializer"
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
@@ -48,7 +47,6 @@ Namespace API.PornHub
DownloadFavorite = New PropertyValue(False)
DownloadGifsAsMp4 = New PropertyValue(True)
DownloadGifs = New PropertyValue(CInt(CheckState.Indeterminate), GetType(Integer))
DownloadPhotoOnlyFromModelHub = New PropertyValue(True)
SavedPostsUserName = New PropertyValue(String.Empty, GetType(String))
_SubscriptionsAllowed = True

View File

@@ -15,20 +15,17 @@ Imports PersonalUtilities.Tools.Web.Clients
Imports PersonalUtilities.Tools.Web.Documents.JSON
Imports UTypes = SCrawler.API.Base.UserMedia.Types
Namespace API.PornHub
Friend Class UserData : Inherits UserDataBase
Friend Class UserData : Inherits UserDataBase : Implements IPSite
Private Const UrlPattern As String = "https://www.pornhub.com/{0}"
#Region "Declarations"
#Region "XML names"
Private Const Name_PersonType As String = "PersonType"
Private Const Name_NameTrue As String = "NameTrue"
Private Const Name_PhotoPageModel As String = "PhotoPageModel"
Private Const Name_DownloadUHD As String = "DownloadUHD"
Private Const Name_DownloadUploaded As String = "DownloadUploaded"
Private Const Name_DownloadTagged As String = "DownloadTagged"
Private Const Name_DownloadPrivate As String = "DownloadPrivate"
Private Const Name_DownloadFavorite As String = "DownloadFavorite"
Private Const Name_DownloadGifs As String = "DownloadGifs"
Private Const Name_DownloadPhotoOnlyFromModelHub As String = "DownloadPhotoOnlyFromModelHub"
#End Region
#Region "Structures"
Private Structure FlashVar : Implements IRegExCreator
@@ -98,11 +95,6 @@ Namespace API.PornHub
End Structure
#End Region
#Region "Enums"
Private Enum PhotoPageModels As Integer
Undefined = 0
PornHubPage = 1
ModelHubPage = 2
End Enum
Private Enum VideoTypes
Undefined
Uploaded
@@ -121,7 +113,6 @@ Namespace API.PornHub
#End Region
#Region "Person"
Friend Property PersonType As String
Friend Property NameTrue As String
Friend Overrides Property FriendlyName As String
Get
If _FriendlyName.IsEmptyString Then Return NameTrue Else Return _FriendlyName
@@ -137,21 +128,19 @@ Namespace API.PornHub
Return IsUser Or SiteMode = SiteModes.Playlists
End Get
End Property
Private Property PhotoPageModel As PhotoPageModels = PhotoPageModels.Undefined
Friend Property DownloadUHD As Boolean = False
Friend Property DownloadUploaded As Boolean = True
Friend Property DownloadTagged As Boolean = False
Friend Property DownloadPrivate As Boolean = False
Friend Property DownloadFavorite As Boolean = False
Friend Property DownloadGifs As Boolean
Friend Property DownloadPhotoOnlyFromModelHub As Boolean = True
Friend Overrides ReadOnly Property IsUser As Boolean
Get
Return SiteMode = SiteModes.User
End Get
End Property
Friend Property SiteMode As SiteModes = SiteModes.User
Friend Property QueryString As String
Friend Property QueryString As String Implements IPSite.QueryString
Get
If IsUser Then
Return String.Empty
@@ -174,18 +163,7 @@ Namespace API.PornHub
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)
DownloadUHD = .DownloadUHD
DownloadUploaded = .DownloadUploaded
DownloadTagged = .DownloadTagged
DownloadPrivate = .DownloadPrivate
DownloadFavorite = .DownloadFavorite
DownloadGifs = .DownloadGifs
DownloadPhotoOnlyFromModelHub = .DownloadPhotoOnlyFromModelHub
QueryString = .QueryString
End With
End If
If Not Obj Is Nothing AndAlso TypeOf Obj Is UserExchangeOptions Then DirectCast(Obj, UserExchangeOptions).Apply(Me)
End Sub
#End Region
Private ReadOnly Property MySettings As SiteSettings
@@ -207,7 +185,7 @@ Namespace API.PornHub
If Not Force OrElse (Not IsUser AndAlso Not SiteMode = SiteModes.Playlists AndAlso Not NewUrl.IsEmptyString AndAlso MyFileSettings.Exists) Then
Dim eObj As Plugin.ExchangeOptions = Nothing
If Force Then eObj = MySettings.IsMyUser(NewUrl)
If (Force And Not eObj.UserName.IsEmptyString) Or (Not Force And Not Name.IsEmptyString And NameTrue.IsEmptyString) Then
If (Force And Not eObj.UserName.IsEmptyString) Or (Not Force And Not Name.IsEmptyString And NameTrue(True).IsEmptyString) Then
If Not If(Force, eObj.Options, Options).IsEmptyString Then
If (IsUser Or SiteMode = SiteModes.Playlists) And Force Then
Return False
@@ -244,29 +222,23 @@ Namespace API.PornHub
With Container
If Loading Then
PersonType = .Value(Name_PersonType)
NameTrue = .Value(Name_NameTrue)
PhotoPageModel = .Value(Name_PhotoPageModel).FromXML(Of Integer)(PhotoPageModels.Undefined)
DownloadUHD = .Value(Name_DownloadUHD).FromXML(Of Boolean)(False)
DownloadUploaded = .Value(Name_DownloadUploaded).FromXML(Of Boolean)(True)
DownloadTagged = .Value(Name_DownloadTagged).FromXML(Of Boolean)(False)
DownloadPrivate = .Value(Name_DownloadPrivate).FromXML(Of Boolean)(False)
DownloadFavorite = .Value(Name_DownloadFavorite).FromXML(Of Boolean)(False)
DownloadGifs = .Value(Name_DownloadGifs).FromXML(Of Integer)(False)
DownloadPhotoOnlyFromModelHub = .Value(Name_DownloadPhotoOnlyFromModelHub).FromXML(Of Boolean)(True)
SiteMode = .Value(Name_SiteMode).FromXML(Of Integer)(SiteModes.User)
UpdateUserOptions()
Else
If UpdateUserOptions() Then .Value(Name_LabelsName) = LabelsString
If UpdateUserOptions() Then .Value(Name_LabelsName) = LabelsString : .Value(Name_TrueName) = NameTrue(True)
.Add(Name_PersonType, PersonType)
.Add(Name_NameTrue, NameTrue)
.Add(Name_PhotoPageModel, CInt(PhotoPageModel))
.Add(Name_DownloadUHD, DownloadUHD.BoolToInteger)
.Add(Name_DownloadUploaded, DownloadUploaded.BoolToInteger)
.Add(Name_DownloadTagged, DownloadTagged.BoolToInteger)
.Add(Name_DownloadPrivate, DownloadPrivate.BoolToInteger)
.Add(Name_DownloadFavorite, DownloadFavorite.BoolToInteger)
.Add(Name_DownloadGifs, DownloadGifs.BoolToInteger)
.Add(Name_DownloadPhotoOnlyFromModelHub, DownloadPhotoOnlyFromModelHub.BoolToInteger)
.Add(Name_SiteMode, CInt(SiteMode))
'Debug.WriteLine(GetNonUserUrl(0))
@@ -283,6 +255,7 @@ Namespace API.PornHub
Private _PageVideosRepeat As Integer = 0
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
Try
UpdateM3U8URLS = False
PlaylistToken = String.Empty
Responser.ResetStatus()
_PageVideosRepeat = 0
@@ -295,7 +268,6 @@ Namespace API.PornHub
Dim limit% = If(DownloadTopCount, -1)
If DownloadVideos Then
If SiteMode = SiteModes.Playlists Then
Responser.Mode = Responser.Modes.Default
GetPlaylistToken(Token)
@@ -519,25 +491,12 @@ Namespace API.PornHub
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)
Try
If IsSavedPosts Then
DownloadUserPhotos_SavedPosts(Token)
ElseIf PersonType = PersonTypeModel Then
If PhotoPageModel = PhotoPageModels.Undefined Then
If DownloadUserPhotos_ModelHub(Token) Then PhotoPageModel = PhotoPageModels.ModelHubPage
ThrowAny(Token)
If PhotoPageModel = PhotoPageModels.Undefined AndAlso Not DownloadPhotoOnlyFromModelHub AndAlso
DownloadUserPhotos_PornHub(Token) Then PhotoPageModel = PhotoPageModels.PornHubPage
Else
Select Case PhotoPageModel
Case PhotoPageModels.ModelHubPage : DownloadUserPhotos_ModelHub(Token)
Case PhotoPageModels.PornHubPage : If Not DownloadPhotoOnlyFromModelHub Then DownloadUserPhotos_PornHub(Token)
End Select
End If
ElseIf Not DownloadPhotoOnlyFromModelHub Then
Else
DownloadUserPhotos_PornHub(Token)
End If
ThrowAny(Token)
@@ -545,48 +504,6 @@ Namespace API.PornHub
ProcessException(ex, Token, "photos downloading error")
End Try
End Sub
Private Function DownloadUserPhotos_ModelHub(ByVal Token As CancellationToken) As Boolean
Dim URL$ = String.Empty
Try
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)
Dim r$ = Responser.GetResponse(URL)
If Not r.IsEmptyString Then
Dim l As List(Of PhotoBlock) = RegexFields(Of PhotoBlock)(r, {Regex_Photo_ModelHub_PhotoBlocks}, {1, 2}, EDP.ReturnValue)
If l.ListExists Then l.RemoveAll(Function(ll) ll.Data.IsEmptyString)
If l.ListExists Then
ProgressPre.ChangeMax(l.Count)
Dim albumRegex As RParams = RParams.DMS("", 1, EDP.ReturnValue)
For Each block As PhotoBlock In l
ProgressPre.Perform()
If Not _TempPostsList.Contains(block.AlbumID) Then _TempPostsList.Add(block.AlbumID) Else Continue For
albumRegex.Pattern = "<li id=""" & block.AlbumID & """ class=""modelBox"">[\r\n\s]*?<div class=""modelPhoto"">[\r\n\s]*?\<[^\>]*?alt=""([^""]*)"""
albumName = StringTrim(RegexReplace(r, albumRegex))
If albumName.IsEmptyString Then albumName = block.AlbumID
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
j.Dispose()
End If
Next
l.Clear()
End If
End If
End If
Return True
Catch ex As Exception
ThrowAny(Token)
Return False
End Try
End Function
Private Overloads Function DownloadUserPhotos_PornHub(ByVal Token As CancellationToken) As Boolean
Try
Dim albumName$
@@ -594,6 +511,7 @@ Namespace API.PornHub
Dim r$ = Responser.GetResponse(String.Format(PhotoUrlPattern_PornHub, PersonType, NameTrue))
If Not r.IsEmptyString Then
Dim l As List(Of PhotoBlock) = RegexFields(Of PhotoBlock)(r, {Regex_Photo_PornHub_PhotoBlocks}, {2, 1}, EDP.ReturnValue)
l.ListAddList(RegexFields(Of PhotoBlock)(r, {Regex_Photo_PornHub_PhotoBlocks2}, {1, 2}, EDP.ReturnValue))
If l.ListExists Then l.RemoveAll(Function(ll) ll.AlbumID.IsEmptyString)
If l.ListExists Then
ProgressPre.ChangeMax(l.Count)
@@ -618,6 +536,14 @@ Namespace API.PornHub
Return False
End Try
End Function
Private Function DownloadUserPhotos_PornHub_ParseSinglePhoto(ByVal r As String) As String
Dim url$ = String.Empty
With DirectCast(RegexReplace(r, Regex_Photo_PornHub_SinglePhoto), List(Of String))
If .ListExists(3) Then url = .Item(2).IfNullOrEmpty(.Item(1)).StringTrim
End With
If url.IsEmptyString Then url = RegexReplace(r, Regex_Photo_PornHub_SinglePhoto2)
Return url
End Function
Private Overloads Function DownloadUserPhotos_PornHub(ByVal Page As Integer, ByVal AlbumID As String, ByVal AlbumName As String,
ByVal Token As CancellationToken) As Boolean
Try
@@ -633,7 +559,7 @@ Namespace API.PornHub
Try
r = Responser.GetResponse(url)
If Not r.IsEmptyString Then
url = RegexReplace(r, Regex_Photo_PornHub_SinglePhoto)
url = DownloadUserPhotos_PornHub_ParseSinglePhoto(r)
If Not url.IsEmptyString Then _
_TempMediaList.ListAddValue(New UserMedia(url, UTypes.Picture) With {
.SpecialFolder = $"Albums\{AlbumName}\",
@@ -679,7 +605,7 @@ Namespace API.PornHub
Try
r = Responser.GetResponse(m.URL)
If Not r.IsEmptyString Then
NewUrl = RegexReplace(r, Regex_Photo_PornHub_SinglePhoto)
NewUrl = DownloadUserPhotos_PornHub_ParseSinglePhoto(r)
If Not NewUrl.IsEmptyString Then
m.URL = NewUrl
pFile = RegexReplace(NewUrl, Regex_Photo_File)
@@ -852,11 +778,34 @@ Namespace API.PornHub
End Sub
#End Region
#Region "Download content"
Private UpdateM3U8URLS As Boolean = False
Private UpdateM3U8URLS_Error As Boolean = False
Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken)
DownloadContentDefault(Token)
Try : DownloadContentDefault(Token) : Finally : UpdateM3U8URLS = False : End Try
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(URL, Responser, DestinationFile, DownloadUHD, Token, Progress, Not IsSingleObjectDownload)
Protected Overloads Overrides Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile,
ByVal Token As CancellationToken) As SFile
UpdateM3U8URLS_Error = False
Return DownloadM3U8(URL, Media, DestinationFile, Token, UpdateM3U8URLS)
End Function
Private Overloads Function DownloadM3U8(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile,
ByVal Token As CancellationToken, ByVal Second As Boolean) As SFile
Try
If Second Then
Dim r$ = Responser.Curl(Media.URL_BASE,, EDP.ReturnValue)
If Not r.IsEmptyString Then Media.URL = CreateVideoURL(r).IfNullOrEmpty(URL) : URL = Media.URL
End If
Dim f As SFile = M3U8.Download(URL, Responser, DestinationFile, DownloadUHD, Token, Progress, Not IsSingleObjectDownload)
If Not f.Exists And Not Second Then UpdateM3U8URLS = True : f = DownloadM3U8(URL, Media, DestinationFile, Token, True)
Return f
Catch ex As Exception
If Not UpdateM3U8URLS_Error Then
UpdateM3U8URLS_Error = True
Thread.Sleep(1000)
Return DownloadM3U8(URL, Media, DestinationFile, Token, True)
End If
Return Nothing
End Try
End Function
#End Region
#Region "CreateVideoURL"
@@ -953,6 +902,7 @@ Namespace API.PornHub
#End Region
#Region "DownloadSingleObject"
Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
UpdateM3U8URLS = False
_TempMediaList.Add(New UserMedia(Data.URL, UTypes.VideoPre))
ReparseVideo(Token, True, Data)
End Sub

View File

@@ -6,9 +6,10 @@
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports SCrawler.API.Base
Imports SCrawler.Plugin.Attributes
Namespace API.PornHub
Friend Class UserExchangeOptions : Inherits Xhamster.UserExchangeOptions
Friend Class UserExchangeOptions : Inherits EditorExchangeOptionsBase_P
<PSetting(NameOf(SiteSettings.DownloadUHD), NameOf(MySettings))>
Friend Property DownloadUHD As Boolean
<PSetting(NameOf(SiteSettings.DownloadUploaded), NameOf(MySettings))>
@@ -21,21 +22,19 @@ Namespace API.PornHub
Friend Property DownloadFavorite As Boolean
<PSetting(Caption:="Download gifs")>
Friend Property DownloadGifs As Boolean
<PSetting(NameOf(SiteSettings.DownloadPhotoOnlyFromModelHub), NameOf(MySettings), Caption:="Download photo only from ModelHub")>
Friend Property DownloadPhotoOnlyFromModelHub As Boolean
Private ReadOnly Property MySettings As SiteSettings
Friend Sub New(ByVal u As UserData)
MyBase.New(u)
DownloadUHD = u.DownloadUHD
DownloadUploaded = u.DownloadUploaded
DownloadTagged = u.DownloadTagged
DownloadPrivate = u.DownloadPrivate
DownloadFavorite = u.DownloadFavorite
DownloadGifs = u.DownloadGifs
DownloadPhotoOnlyFromModelHub = u.DownloadPhotoOnlyFromModelHub
QueryString = u.QueryString
MySettings = u.HOST.Source
End Sub
Friend Sub New(ByVal s As SiteSettings)
MyBase.New(s)
Dim v As CheckState = CInt(s.DownloadGifs.Value)
DownloadUHD = s.DownloadUHD.Value
DownloadUploaded = s.DownloadUploaded.Value
@@ -43,8 +42,18 @@ Namespace API.PornHub
DownloadPrivate = s.DownloadPrivate.Value
DownloadFavorite = s.DownloadFavorite.Value
DownloadGifs = Not v = CheckState.Unchecked
DownloadPhotoOnlyFromModelHub = s.DownloadPhotoOnlyFromModelHub.Value
MySettings = s
End Sub
Friend Overrides Sub Apply(ByRef u As IPSite)
MyBase.Apply(u)
With DirectCast(u, UserData)
.DownloadUHD = DownloadUHD
.DownloadUploaded = DownloadUploaded
.DownloadTagged = DownloadTagged
.DownloadPrivate = DownloadPrivate
.DownloadFavorite = DownloadFavorite
.DownloadGifs = DownloadGifs
End With
End Sub
End Class
End Namespace

View File

@@ -88,14 +88,22 @@ Namespace API.Reddit
End Property
Friend Property ViewMode As View = View.New Implements IRedditView.ViewMode
Friend Property ViewPeriod As Period = Period.All Implements IRedditView.ViewPeriod
Friend Property DownloadText As Boolean = False Implements IRedditView.DownloadText
Friend Property DownloadTextPosts As Boolean = False Implements IRedditView.DownloadTextPosts
Friend Property DownloadTextSpecialFolder As Boolean = False Implements IRedditView.DownloadTextSpecialFolder
Friend Property RedGifsAccount As String = String.Empty Implements IRedditView.RedGifsAccount
Friend Property RedditAccount As String = String.Empty Implements IRedditView.RedditAccount
Friend Sub SetView(ByVal Options As IRedditView) Implements IRedditView.SetView
If Not Options Is Nothing Then
ViewMode = Options.ViewMode
ViewPeriod = Options.ViewPeriod
RedditAccount = Options.RedditAccount
RedGifsAccount = Options.RedGifsAccount
With Options
ViewMode = .ViewMode
ViewPeriod = .ViewPeriod
DownloadText = .DownloadText
DownloadTextPosts = .DownloadTextPosts
DownloadTextSpecialFolder = .DownloadTextSpecialFolder
RedditAccount = .RedditAccount
RedGifsAccount = .RedGifsAccount
End With
End If
End Sub
#Region "Statistics support"

View File

@@ -21,6 +21,6 @@ Namespace API.Reddit
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 UnixDate32ProviderReddit As New CustomProvider(Function(v, d, p, n, e) ADateTime.ParseUnix32(AConvert(Of Double)(v, EUR_PROVIDER, v), n, e))
<Obsolete("Use 'UnixDate32Provider'", True)> Friend ReadOnly UnixDate32ProviderReddit As New CustomProvider(Function(v, d, p, n, e) ADateTime.ParseUnix32(AConvert(Of Double)(v, EUR_PROVIDER, v), n, e))
End Module
End Namespace

View File

@@ -12,6 +12,8 @@ Namespace API.Reddit
[New] = 0
Hot = 1
Top = 2
Best = 3
Rising = 4
End Enum
Enum Period As Integer
All = 0
@@ -23,25 +25,45 @@ Namespace API.Reddit
End Enum
Property ViewMode As View
Property ViewPeriod As Period
Property DownloadText As Boolean
Property DownloadTextPosts As Boolean
Property DownloadTextSpecialFolder As Boolean
Property RedGifsAccount As String
Property RedditAccount As String
Sub SetView(ByVal Options As IRedditView)
End Interface
Friend Class RedditViewExchange : Implements IRedditView
Friend Class RedditViewExchange : Inherits Base.EditorExchangeOptionsBase : Implements IRedditView
Friend Const Name_ViewMode As String = "ViewMode"
Friend Const Name_ViewPeriod As String = "ViewPeriod"
Friend Const Name_RedGifsAccount As String = "RedGifsAccount"
Friend Const Name_RedditAccount As String = "RedditAccount"
Friend Property ViewMode As IRedditView.View Implements IRedditView.ViewMode
Friend Property ViewPeriod As IRedditView.Period Implements IRedditView.ViewPeriod
Friend Overrides Property DownloadText As Boolean Implements IRedditView.DownloadText
Friend Overrides Property DownloadTextPosts As Boolean Implements IRedditView.DownloadTextPosts
Friend Overrides Property DownloadTextSpecialFolder As Boolean Implements IRedditView.DownloadTextSpecialFolder
Friend Property RedGifsAccount As String Implements IRedditView.RedGifsAccount
Friend Property RedditAccount As String Implements IRedditView.RedditAccount
Friend Sub New(ByVal Options As IRedditView)
MyBase.New(DirectCast(Options, UserData))
SetView(Options)
_ApplyBase_Name = False
End Sub
Friend Sub New(ByVal s As SiteSettings)
MyBase.New(s)
_ApplyBase_Name = False
End Sub
Friend Sub SetView(ByVal Options As IRedditView) Implements IRedditView.SetView
If Not Options Is Nothing Then
ViewMode = Options.ViewMode
ViewPeriod = Options.ViewPeriod
RedGifsAccount = Options.RedGifsAccount
RedditAccount = Options.RedditAccount
With Options
ViewMode = .ViewMode
ViewPeriod = .ViewPeriod
DownloadText = .DownloadText
DownloadTextPosts = .DownloadTextPosts
DownloadTextSpecialFolder = .DownloadTextSpecialFolder
RedGifsAccount = .RedGifsAccount
RedditAccount = .RedditAccount
End With
End If
End Sub
End Class

View File

@@ -22,17 +22,21 @@ Namespace API.Reddit
Private components As System.ComponentModel.IContainer
<System.Diagnostics.DebuggerStepThrough()>
Private Sub InitializeComponent()
Me.components = New System.ComponentModel.Container()
Dim CONTAINER_MAIN As System.Windows.Forms.ToolStripContainer
Dim TP_VIEW_MODE As System.Windows.Forms.TableLayoutPanel
Dim LBL_VIEW_MODE As System.Windows.Forms.Label
Dim LBL_PERIOD As System.Windows.Forms.Label
Dim ActionButton1 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 resources As System.ComponentModel.ComponentResourceManager = New System.ComponentModel.ComponentResourceManager(GetType(RedditViewSettingsForm))
Dim ActionButton2 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 TP_TEXT As System.Windows.Forms.TableLayoutPanel
Me.TP_MAIN = New System.Windows.Forms.TableLayoutPanel()
Me.OPT_VIEW_MODE_NEW = New System.Windows.Forms.RadioButton()
Me.OPT_VIEW_MODE_HOT = New System.Windows.Forms.RadioButton()
Me.OPT_VIEW_MODE_TOP = New System.Windows.Forms.RadioButton()
Me.OPT_VIEW_MODE_BEST = New System.Windows.Forms.RadioButton()
Me.OPT_VIEW_MODE_RISING = New System.Windows.Forms.RadioButton()
Me.TP_PERIOD = New System.Windows.Forms.TableLayoutPanel()
Me.OPT_PERIOD_ALL = New System.Windows.Forms.RadioButton()
Me.OPT_PERIOD_HOUR = New System.Windows.Forms.RadioButton()
@@ -42,10 +46,15 @@ Namespace API.Reddit
Me.OPT_PERIOD_YEAR = New System.Windows.Forms.RadioButton()
Me.CMB_REDGIFS_ACC = New PersonalUtilities.Forms.Controls.ComboBoxExtended()
Me.CMB_REDDIT_ACC = New PersonalUtilities.Forms.Controls.ComboBoxExtended()
Me.CH_TXT_DOWN_TXT = New System.Windows.Forms.CheckBox()
Me.CH_TXT_DOWN_POSTS = New System.Windows.Forms.CheckBox()
Me.TT_MAIN = New System.Windows.Forms.ToolTip(Me.components)
Me.CH_TXT_DOWN_SPEC_FOLDER = New System.Windows.Forms.CheckBox()
CONTAINER_MAIN = New System.Windows.Forms.ToolStripContainer()
TP_VIEW_MODE = New System.Windows.Forms.TableLayoutPanel()
LBL_VIEW_MODE = New System.Windows.Forms.Label()
LBL_PERIOD = New System.Windows.Forms.Label()
TP_TEXT = New System.Windows.Forms.TableLayoutPanel()
CONTAINER_MAIN.ContentPanel.SuspendLayout()
CONTAINER_MAIN.SuspendLayout()
Me.TP_MAIN.SuspendLayout()
@@ -53,6 +62,7 @@ Namespace API.Reddit
Me.TP_PERIOD.SuspendLayout()
CType(Me.CMB_REDGIFS_ACC, System.ComponentModel.ISupportInitialize).BeginInit()
CType(Me.CMB_REDDIT_ACC, System.ComponentModel.ISupportInitialize).BeginInit()
TP_TEXT.SuspendLayout()
Me.SuspendLayout()
'
'CONTAINER_MAIN
@@ -61,13 +71,13 @@ Namespace API.Reddit
'CONTAINER_MAIN.ContentPanel
'
CONTAINER_MAIN.ContentPanel.Controls.Add(Me.TP_MAIN)
CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(477, 169)
CONTAINER_MAIN.ContentPanel.Size = New System.Drawing.Size(477, 222)
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(477, 169)
CONTAINER_MAIN.Size = New System.Drawing.Size(477, 222)
CONTAINER_MAIN.TabIndex = 0
CONTAINER_MAIN.TopToolStripPanelVisible = False
'
@@ -78,18 +88,20 @@ Namespace API.Reddit
Me.TP_MAIN.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
Me.TP_MAIN.Controls.Add(TP_VIEW_MODE, 0, 0)
Me.TP_MAIN.Controls.Add(Me.TP_PERIOD, 0, 1)
Me.TP_MAIN.Controls.Add(Me.CMB_REDGIFS_ACC, 0, 3)
Me.TP_MAIN.Controls.Add(Me.CMB_REDDIT_ACC, 0, 2)
Me.TP_MAIN.Controls.Add(Me.CMB_REDGIFS_ACC, 0, 4)
Me.TP_MAIN.Controls.Add(Me.CMB_REDDIT_ACC, 0, 3)
Me.TP_MAIN.Controls.Add(TP_TEXT, 0, 2)
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 = 5
Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!))
Me.TP_MAIN.RowCount = 6
Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56.0!))
Me.TP_MAIN.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 56.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, 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.Percent, 100.0!))
Me.TP_MAIN.Size = New System.Drawing.Size(477, 169)
Me.TP_MAIN.Size = New System.Drawing.Size(477, 222)
Me.TP_MAIN.TabIndex = 0
'
'TP_VIEW_MODE
@@ -103,14 +115,16 @@ Namespace API.Reddit
TP_VIEW_MODE.Controls.Add(Me.OPT_VIEW_MODE_NEW, 1, 0)
TP_VIEW_MODE.Controls.Add(Me.OPT_VIEW_MODE_HOT, 2, 0)
TP_VIEW_MODE.Controls.Add(Me.OPT_VIEW_MODE_TOP, 3, 0)
TP_VIEW_MODE.Controls.Add(Me.OPT_VIEW_MODE_BEST, 1, 1)
TP_VIEW_MODE.Controls.Add(Me.OPT_VIEW_MODE_RISING, 2, 1)
TP_VIEW_MODE.Dock = System.Windows.Forms.DockStyle.Fill
TP_VIEW_MODE.Location = New System.Drawing.Point(1, 1)
TP_VIEW_MODE.Margin = New System.Windows.Forms.Padding(0)
TP_VIEW_MODE.Name = "TP_VIEW_MODE"
TP_VIEW_MODE.RowCount = 1
TP_VIEW_MODE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
TP_VIEW_MODE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 28.0!))
TP_VIEW_MODE.Size = New System.Drawing.Size(475, 28)
TP_VIEW_MODE.RowCount = 2
TP_VIEW_MODE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!))
TP_VIEW_MODE.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 50.0!))
TP_VIEW_MODE.Size = New System.Drawing.Size(475, 56)
TP_VIEW_MODE.TabIndex = 0
'
'LBL_VIEW_MODE
@@ -160,6 +174,30 @@ Namespace API.Reddit
Me.OPT_VIEW_MODE_TOP.Text = "Top"
Me.OPT_VIEW_MODE_TOP.UseVisualStyleBackColor = True
'
'OPT_VIEW_MODE_BEST
'
Me.OPT_VIEW_MODE_BEST.AutoSize = True
Me.OPT_VIEW_MODE_BEST.Dock = System.Windows.Forms.DockStyle.Fill
Me.OPT_VIEW_MODE_BEST.Location = New System.Drawing.Point(121, 31)
Me.OPT_VIEW_MODE_BEST.Name = "OPT_VIEW_MODE_BEST"
Me.OPT_VIEW_MODE_BEST.Size = New System.Drawing.Size(112, 22)
Me.OPT_VIEW_MODE_BEST.TabIndex = 4
Me.OPT_VIEW_MODE_BEST.TabStop = True
Me.OPT_VIEW_MODE_BEST.Text = "Best"
Me.OPT_VIEW_MODE_BEST.UseVisualStyleBackColor = True
'
'OPT_VIEW_MODE_RISING
'
Me.OPT_VIEW_MODE_RISING.AutoSize = True
Me.OPT_VIEW_MODE_RISING.Dock = System.Windows.Forms.DockStyle.Fill
Me.OPT_VIEW_MODE_RISING.Location = New System.Drawing.Point(239, 31)
Me.OPT_VIEW_MODE_RISING.Name = "OPT_VIEW_MODE_RISING"
Me.OPT_VIEW_MODE_RISING.Size = New System.Drawing.Size(112, 22)
Me.OPT_VIEW_MODE_RISING.TabIndex = 5
Me.OPT_VIEW_MODE_RISING.TabStop = True
Me.OPT_VIEW_MODE_RISING.Text = "Rising"
Me.OPT_VIEW_MODE_RISING.UseVisualStyleBackColor = True
'
'TP_PERIOD
'
Me.TP_PERIOD.ColumnCount = 4
@@ -175,7 +213,7 @@ Namespace API.Reddit
Me.TP_PERIOD.Controls.Add(Me.OPT_PERIOD_MONTH, 2, 1)
Me.TP_PERIOD.Controls.Add(Me.OPT_PERIOD_YEAR, 3, 1)
Me.TP_PERIOD.Dock = System.Windows.Forms.DockStyle.Fill
Me.TP_PERIOD.Location = New System.Drawing.Point(1, 30)
Me.TP_PERIOD.Location = New System.Drawing.Point(1, 58)
Me.TP_PERIOD.Margin = New System.Windows.Forms.Padding(0)
Me.TP_PERIOD.Name = "TP_PERIOD"
Me.TP_PERIOD.RowCount = 2
@@ -269,17 +307,17 @@ Namespace API.Reddit
'
'CMB_REDGIFS_ACC
'
ActionButton1.BackgroundImage = CType(resources.GetObject("ActionButton1.BackgroundImage"), System.Drawing.Image)
ActionButton1.Name = "ArrowDown"
ActionButton1.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.ArrowDown
Me.CMB_REDGIFS_ACC.Buttons.Add(ActionButton1)
ActionButton3.BackgroundImage = CType(resources.GetObject("ActionButton3.BackgroundImage"), System.Drawing.Image)
ActionButton3.Name = "ArrowDown"
ActionButton3.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.ArrowDown
Me.CMB_REDGIFS_ACC.Buttons.Add(ActionButton3)
Me.CMB_REDGIFS_ACC.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.Label
Me.CMB_REDGIFS_ACC.CaptionSizeType = System.Windows.Forms.SizeType.Percent
Me.CMB_REDGIFS_ACC.CaptionText = "RedGifs account"
Me.CMB_REDGIFS_ACC.CaptionVisible = True
Me.CMB_REDGIFS_ACC.CaptionWidth = 26.0R
Me.CMB_REDGIFS_ACC.Dock = System.Windows.Forms.DockStyle.Fill
Me.CMB_REDGIFS_ACC.Location = New System.Drawing.Point(4, 119)
Me.CMB_REDGIFS_ACC.Location = New System.Drawing.Point(4, 173)
Me.CMB_REDGIFS_ACC.Name = "CMB_REDGIFS_ACC"
Me.CMB_REDGIFS_ACC.Size = New System.Drawing.Size(469, 22)
Me.CMB_REDGIFS_ACC.TabIndex = 4
@@ -287,35 +325,89 @@ Namespace API.Reddit
'
'CMB_REDDIT_ACC
'
ActionButton2.BackgroundImage = CType(resources.GetObject("ActionButton2.BackgroundImage"), System.Drawing.Image)
ActionButton2.Name = "ArrowDown"
ActionButton2.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.ArrowDown
Me.CMB_REDDIT_ACC.Buttons.Add(ActionButton2)
ActionButton4.BackgroundImage = CType(resources.GetObject("ActionButton4.BackgroundImage"), System.Drawing.Image)
ActionButton4.Name = "ArrowDown"
ActionButton4.Tag = PersonalUtilities.Forms.Controls.Base.ActionButton.DefaultButtons.ArrowDown
Me.CMB_REDDIT_ACC.Buttons.Add(ActionButton4)
Me.CMB_REDDIT_ACC.CaptionMode = PersonalUtilities.Forms.Controls.Base.ICaptionControl.Modes.Label
Me.CMB_REDDIT_ACC.CaptionSizeType = System.Windows.Forms.SizeType.Percent
Me.CMB_REDDIT_ACC.CaptionText = "Reddit account"
Me.CMB_REDDIT_ACC.CaptionVisible = True
Me.CMB_REDDIT_ACC.CaptionWidth = 26.0R
Me.CMB_REDDIT_ACC.Dock = System.Windows.Forms.DockStyle.Fill
Me.CMB_REDDIT_ACC.Location = New System.Drawing.Point(4, 90)
Me.CMB_REDDIT_ACC.Location = New System.Drawing.Point(4, 144)
Me.CMB_REDDIT_ACC.Name = "CMB_REDDIT_ACC"
Me.CMB_REDDIT_ACC.Size = New System.Drawing.Size(469, 22)
Me.CMB_REDDIT_ACC.TabIndex = 3
Me.CMB_REDDIT_ACC.TextBoxBorderStyle = System.Windows.Forms.BorderStyle.FixedSingle
'
'TP_TEXT
'
TP_TEXT.ColumnCount = 3
TP_TEXT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
TP_TEXT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
TP_TEXT.ColumnStyles.Add(New System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333!))
TP_TEXT.Controls.Add(Me.CH_TXT_DOWN_TXT, 0, 0)
TP_TEXT.Controls.Add(Me.CH_TXT_DOWN_POSTS, 1, 0)
TP_TEXT.Controls.Add(Me.CH_TXT_DOWN_SPEC_FOLDER, 2, 0)
TP_TEXT.Dock = System.Windows.Forms.DockStyle.Fill
TP_TEXT.Location = New System.Drawing.Point(1, 115)
TP_TEXT.Margin = New System.Windows.Forms.Padding(0)
TP_TEXT.Name = "TP_TEXT"
TP_TEXT.RowCount = 1
TP_TEXT.RowStyles.Add(New System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100.0!))
TP_TEXT.Size = New System.Drawing.Size(475, 25)
TP_TEXT.TabIndex = 5
'
'CH_TXT_DOWN_TXT
'
Me.CH_TXT_DOWN_TXT.AutoSize = True
Me.CH_TXT_DOWN_TXT.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_TXT_DOWN_TXT.Location = New System.Drawing.Point(3, 3)
Me.CH_TXT_DOWN_TXT.Name = "CH_TXT_DOWN_TXT"
Me.CH_TXT_DOWN_TXT.Size = New System.Drawing.Size(152, 19)
Me.CH_TXT_DOWN_TXT.TabIndex = 0
Me.CH_TXT_DOWN_TXT.Text = "Download text"
Me.TT_MAIN.SetToolTip(Me.CH_TXT_DOWN_TXT, "Download text (if available) for posts with image and video")
Me.CH_TXT_DOWN_TXT.UseVisualStyleBackColor = True
'
'CH_TXT_DOWN_POSTS
'
Me.CH_TXT_DOWN_POSTS.AutoSize = True
Me.CH_TXT_DOWN_POSTS.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_TXT_DOWN_POSTS.Location = New System.Drawing.Point(161, 3)
Me.CH_TXT_DOWN_POSTS.Name = "CH_TXT_DOWN_POSTS"
Me.CH_TXT_DOWN_POSTS.Size = New System.Drawing.Size(152, 19)
Me.CH_TXT_DOWN_POSTS.TabIndex = 1
Me.CH_TXT_DOWN_POSTS.Text = "Download text posts"
Me.TT_MAIN.SetToolTip(Me.CH_TXT_DOWN_POSTS, "Download text (if available) for text posts (no image and video)")
Me.CH_TXT_DOWN_POSTS.UseVisualStyleBackColor = True
'
'CH_TXT_DOWN_SPEC_FOLDER
'
Me.CH_TXT_DOWN_SPEC_FOLDER.AutoSize = True
Me.CH_TXT_DOWN_SPEC_FOLDER.Dock = System.Windows.Forms.DockStyle.Fill
Me.CH_TXT_DOWN_SPEC_FOLDER.Location = New System.Drawing.Point(319, 3)
Me.CH_TXT_DOWN_SPEC_FOLDER.Name = "CH_TXT_DOWN_SPEC_FOLDER"
Me.CH_TXT_DOWN_SPEC_FOLDER.Size = New System.Drawing.Size(153, 19)
Me.CH_TXT_DOWN_SPEC_FOLDER.TabIndex = 2
Me.CH_TXT_DOWN_SPEC_FOLDER.Text = "Text special folder"
Me.TT_MAIN.SetToolTip(Me.CH_TXT_DOWN_SPEC_FOLDER, "If checked, text files will be saved to a separate folder")
Me.CH_TXT_DOWN_SPEC_FOLDER.UseVisualStyleBackColor = True
'
'RedditViewSettingsForm
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(477, 169)
Me.ClientSize = New System.Drawing.Size(477, 222)
Me.Controls.Add(CONTAINER_MAIN)
Me.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle
Me.Icon = Global.SCrawler.My.Resources.SiteResources.RedditIcon_128
Me.KeyPreview = True
Me.MaximizeBox = False
Me.MaximumSize = New System.Drawing.Size(493, 208)
Me.MaximumSize = New System.Drawing.Size(493, 261)
Me.MinimizeBox = False
Me.MinimumSize = New System.Drawing.Size(493, 208)
Me.MinimumSize = New System.Drawing.Size(493, 261)
Me.Name = "RedditViewSettingsForm"
Me.ShowInTaskbar = False
Me.SizeGripStyle = System.Windows.Forms.SizeGripStyle.Hide
@@ -330,6 +422,8 @@ Namespace API.Reddit
Me.TP_PERIOD.PerformLayout()
CType(Me.CMB_REDGIFS_ACC, System.ComponentModel.ISupportInitialize).EndInit()
CType(Me.CMB_REDDIT_ACC, System.ComponentModel.ISupportInitialize).EndInit()
TP_TEXT.ResumeLayout(False)
TP_TEXT.PerformLayout()
Me.ResumeLayout(False)
End Sub
@@ -346,5 +440,11 @@ Namespace API.Reddit
Private WithEvents CMB_REDGIFS_ACC As PersonalUtilities.Forms.Controls.ComboBoxExtended
Private WithEvents CMB_REDDIT_ACC As PersonalUtilities.Forms.Controls.ComboBoxExtended
Private WithEvents TP_MAIN As TableLayoutPanel
Private WithEvents OPT_VIEW_MODE_BEST As RadioButton
Private WithEvents OPT_VIEW_MODE_RISING As RadioButton
Private WithEvents CH_TXT_DOWN_TXT As CheckBox
Private WithEvents TT_MAIN As ToolTip
Private WithEvents CH_TXT_DOWN_POSTS As CheckBox
Private WithEvents CH_TXT_DOWN_SPEC_FOLDER As CheckBox
End Class
End Namespace

View File

@@ -130,184 +130,195 @@
<value>False</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="ActionButton1.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<data name="ActionButton3.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
U1wTEVFuCACwydqqfmZO8Z3yxXrc6T7f73o/AEyQiDgtp/QXG/EpgTbFF3JKv1D+TABgQnQfx2ur+POc
4kflC/kx5u6c4l+blF7YXWoofw4AMIG6W/G2i/Wzckp/lVP896EX9PJFvkh3M5/6o03E7+acF8vvCQBM
mQO/EFTVctfezxEXtlX9vLaqX9z9s6mqC5YWF+fLrwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6LEtW/4flgYiLD1qeX0A
AAAASUVORK5CYII=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAYIj+H5YGIizEb/aEAAAAAElFTkSuQmCC
</value>
</data>
<data name="ActionButton2.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<data name="ActionButton4.BackgroundImage" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
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
U1wTEVFuCACwydqqfmZO8Z3yxXrc6T7f73o/AEyQiDgtp/QXG/EpgTbFF3JKv1D+TABgQnQfx2ur+POc
4kflC/kx5u6c4l+blF7YXWoofw4AMIG6W/G2i/Wzckp/lVP896EX9PJFvkh3M5/6o03E7+acF8vvCQBM
mQO/EFTVctfezxEXtlX9vLaqX9z9s6mqC5YWF+fLrwEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA6LEtW/4flgYiLD1qeX0A
AAAASUVORK5CYII=
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAYIj+H5YGIizEb/aEAAAAAElFTkSuQmCC
</value>
</data>
<metadata name="TP_TEXT.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
<metadata name="TT_MAIN.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="TT_MAIN.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="LBL_VIEW_MODE.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
<metadata name="LBL_PERIOD.GenerateMember" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>False</value>
</metadata>
</root>

View File

@@ -38,6 +38,8 @@ Namespace API.Reddit
Select Case MyOptions.ViewMode
Case CView.Hot : OPT_VIEW_MODE_HOT.Checked = True
Case CView.Top : OPT_VIEW_MODE_TOP.Checked = True
Case CView.Best : OPT_VIEW_MODE_BEST.Checked = True
Case CView.Rising : OPT_VIEW_MODE_RISING.Checked = True
Case Else : OPT_VIEW_MODE_NEW.Checked = True
End Select
Select Case MyOptions.ViewPeriod
@@ -50,11 +52,15 @@ Namespace API.Reddit
End Select
ChangePeriodEnabled()
CH_TXT_DOWN_TXT.Checked = MyOptions.DownloadText
CH_TXT_DOWN_POSTS.Checked = MyOptions.DownloadTextPosts
CH_TXT_DOWN_SPEC_FOLDER.Checked = MyOptions.DownloadTextSpecialFolder
PopulateCMB(Settings(RedditSiteKey), CMB_REDDIT_ACC, MyOptions.RedditAccount)
PopulateCMB(Settings(RedGifs.RedGifsSiteKey), CMB_REDGIFS_ACC, MyOptions.RedGifsAccount)
If IsUserSettings Then
TP_MAIN.Controls.Remove(CMB_REDDIT_ACC)
TP_MAIN.RowStyles(2).Height = 0
TP_MAIN.RowStyles(3).Height = 0
TP_MAIN.Refresh()
Dim s As Size = Size
s.Height -= 28
@@ -100,6 +106,8 @@ Namespace API.Reddit
Select Case True
Case OPT_VIEW_MODE_HOT.Checked : .ViewMode = CView.Hot
Case OPT_VIEW_MODE_TOP.Checked : .ViewMode = CView.Top
Case OPT_VIEW_MODE_BEST.Checked : .ViewMode = CView.Best
Case OPT_VIEW_MODE_RISING.Checked : .ViewMode = CView.Rising
Case Else : .ViewMode = CView.New
End Select
Select Case True
@@ -110,6 +118,9 @@ Namespace API.Reddit
Case OPT_PERIOD_YEAR.Checked : .ViewPeriod = CPeriod.Year
Case Else : .ViewPeriod = CPeriod.All
End Select
.DownloadText = CH_TXT_DOWN_TXT.Checked
.DownloadTextPosts = CH_TXT_DOWN_POSTS.Checked
.DownloadTextSpecialFolder = CH_TXT_DOWN_SPEC_FOLDER.Checked
.RedGifsAccount = CMB_REDGIFS_ACC.Text
If Not IsUserSettings Then .RedditAccount = CMB_REDDIT_ACC.Text
End With

View File

@@ -9,6 +9,7 @@
Imports SCrawler.API.Base
Imports SCrawler.Plugin
Imports SCrawler.Plugin.Attributes
Imports System.Reflection
Imports PersonalUtilities.Tools.Web.Clients
Imports PersonalUtilities.Tools.Web.Clients.Base
Imports PersonalUtilities.Tools.Web.Documents.JSON
@@ -17,8 +18,8 @@ Imports PersonalUtilities.Functions.RegularExpressions
Imports DownDetector = SCrawler.API.Base.DownDetector
Imports Download = SCrawler.Plugin.ISiteSettings.Download
Namespace API.Reddit
<Manifest(RedditSiteKey), SavedPosts, SpecialForm(False)>
Friend Class SiteSettings : Inherits SiteSettingsBase
<Manifest(RedditSiteKey), SavedPosts, SpecialForm(False), UseDownDetector>
Friend Class SiteSettings : Inherits SiteSettingsBase : Implements DownDetector.IDownDetector
#Region "Declarations"
#Region "Authorization"
<PropertyOption(ControlText:="Login", ControlToolTip:="Your authorization username", IsAuth:=True), PXML, PClonable(Clone:=False)>
@@ -58,6 +59,48 @@ Namespace API.Reddit
Return {AuthUserName.Value, AuthPassword.Value, ApiClientID.Value, ApiClientSecret.Value}.All(Function(v$) Not v.IsEmptyString)
End Get
End Property
<PropertiesDataChecker({NameOf(AuthUserName), NameOf(AuthPassword), NameOf(ApiClientID), NameOf(ApiClientSecret),
NameOf(UseTokenForTimelines), NameOf(UseCookiesForTimelines)})>
Private Function OAuthCredentialsChecker(ByVal p As IEnumerable(Of PropertyData)) As Boolean
Const msgTitle$ = "OAuth credentials"
If p.ListExists Then
Dim useToken As Boolean = False, useCookies As Boolean = False
Dim d$ = String.Empty
Dim dCount As Byte = 0
Dim members As IEnumerable(Of MemberInfo) = GetObjectMembers(Me)
Dim getPropText As Func(Of String, String) = Function(name) members.First(Function(m) m.Name = name).GetCustomAttribute(Of PropertyOption).ControlText
Dim dataStr As Action(Of String, String) = Sub(dd, name) If dd.IsEmptyString Then d.StringAppendLine(getPropText(name)) : dCount += 1
For Each pp As PropertyData In p
Select Case pp.Name
Case NameOf(AuthUserName) : dataStr(pp.Value, NameOf(AuthUserName))
Case NameOf(AuthPassword) : dataStr(pp.Value, NameOf(AuthPassword))
Case NameOf(ApiClientID) : dataStr(pp.Value, NameOf(ApiClientID))
Case NameOf(ApiClientSecret) : dataStr(pp.Value, NameOf(ApiClientSecret))
Case NameOf(UseTokenForTimelines) : useToken = pp.Value
Case NameOf(UseCookiesForTimelines) : useCookies = pp.Value
Case Else : Throw New ArgumentException($"Property name '{pp.Name}' is not implemented", "Property Name")
End Select
Next
If d.IsEmptyString Then
If useToken And useCookies Then
Return True
Else
If Not useToken Then d.StringAppendLine(getPropText(NameOf(UseTokenForTimelines)))
If Not useCookies Then d.StringAppendLine(getPropText(NameOf(UseCookiesForTimelines)))
MsgBoxE({$"You need to check the following options:{vbCr}{d}", msgTitle}, vbCritical)
Return False
End If
ElseIf dCount = 4 Then
Return MsgBoxE({$"You haven't configured OAuth. It's highly recommended to use OAuth.{vbCr}Do you still want to continue?", msgTitle},
vbExclamation,,, {"Process", "Cancel"}) = 0
Else
MsgBoxE({$"You haven't filled in the following fields:{vbCr}{d}.{vbCr}{vbCr}" &
"To use OAuth authorization, you must fill in all authorization fields.", msgTitle}, vbCritical)
Return False
End If
End If
Return True
End Function
#End Region
#Region "Other"
<PropertyOption(ControlText:="Use M3U8", ControlToolTip:="Use M3U8 or mp4 for Reddit videos", IsAuth:=False), PXML, PClonable>
@@ -67,6 +110,26 @@ Namespace API.Reddit
<PropertyOption(ControlText:="Check image: get original", ControlToolTip:="Get the original image if it exists", IsAuth:=False), PXML, PClonable>
Friend ReadOnly Property CheckImageReturnOrig As PropertyValue
#End Region
#Region "IDownDetector Support"
Private ReadOnly Property IDownDetector_Value As Integer Implements DownDetector.IDownDetector.Value
Get
Return 100
End Get
End Property
Private ReadOnly Property IDownDetector_AddToLog As Boolean Implements DownDetector.IDownDetector.AddToLog
Get
Return False
End Get
End Property
Private ReadOnly Property IDownDetector_CheckSite As String Implements DownDetector.IDownDetector.CheckSite
Get
Return "reddit"
End Get
End Property
Private Function IDownDetector_Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean Implements DownDetector.IDownDetector.Available
Return MDD.Available(What, Silent)
End Function
#End Region
#End Region
#Region "Initializer"
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
@@ -76,6 +139,7 @@ Namespace API.Reddit
With Responser
Dim d% = .Decoders.Count
.Decoders.ListAddList({SymbolsConverter.Converters.Unicode, SymbolsConverter.Converters.HTML}, LAP.NotContainsOnly)
.Accept = "application/json"
token = .Headers.Value(DeclaredNames.Header_Authorization)
End With
@@ -84,7 +148,7 @@ Namespace API.Reddit
ApiClientID = New PropertyValue(String.Empty, GetType(String))
ApiClientSecret = New PropertyValue(String.Empty, GetType(String))
BearerToken = New PropertyValue(token, GetType(String), Sub(v) Responser.Headers.Add(DeclaredNames.Header_Authorization, v))
BearerTokenUseCurl = New PropertyValue(True)
BearerTokenUseCurl = New PropertyValue(False)
TokenUpdateInterval = New PropertyValue(360)
TokenUpdateIntervalProvider = New TokenRefreshIntervalProvider
BearerTokenDateUpdate = New PropertyValue(Now.AddYears(-1))
@@ -97,15 +161,17 @@ Namespace API.Reddit
CheckImage = New PropertyValue(False)
CheckImageReturnOrig = New PropertyValue(True)
MDD = New MyDownDetector(Me)
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
Private Const SettingsVersionCurrent As Integer = 1
Private Const SettingsVersionCurrent As Integer = 2
Friend Overrides Sub EndInit()
If CInt(SettingsVersion.Value) < SettingsVersionCurrent Then
SettingsVersion.Value = SettingsVersionCurrent
TokenUpdateInterval.Value = 360
BearerTokenUseCurl.Value = False
End If
MyBase.EndInit()
End Sub
@@ -116,81 +182,48 @@ Namespace API.Reddit
End Function
#End Region
#Region "DownloadStarted, ReadyToDownload, Available, DownloadDone, UpdateRedGifsToken"
Private ____DownloadStarted As Boolean = False
Friend Overrides Sub DownloadStarted(ByVal What As Download)
If What = Download.Main Then ____DownloadStarted = True
MyBase.DownloadStarted(What)
End Sub
Private ReadOnly MDD As MyDownDetector
Private Class MyDownDetector : Inherits DownDetector.Checker(Of SiteSettings)
Private __TrueValue As Boolean = False
Friend Sub New(ByRef _Source As SiteSettings)
MyBase.New(_Source)
End Sub
Protected Overrides Function AvailableImpl(ByVal What As Download, ByVal Silent As Boolean) As Boolean
__TrueValue = Source.AvailableTrueValue(What)
Return MyBase.AvailableImpl(What, Silent)
End Function
Protected Overrides Function AvailableImpl_TRUE() As Boolean
Return AvailableImpl_TrueValueReturn()
End Function
Protected Overrides Function AvailableImpl_FALSE_SILENT_NOT_MSG_YES() As Boolean
Return AvailableImpl_TrueValueReturn()
End Function
Private Function AvailableImpl_TrueValueReturn() As Boolean
If __TrueValue Then Source.UpdateRedGifsToken()
Return __TrueValue AndAlso Source.UpdateTokenIfRequired()
End Function
Friend Overrides Sub Reset()
__TrueValue = False
MyBase.Reset()
End Sub
End Class
Friend Property SessionInterrupted As Boolean = False
Friend Overrides Function ReadyToDownload(ByVal What As Download) As Boolean
If What = Download.Main Then
Dim result As Boolean = Not SessionInterrupted
If result Then
If ____DownloadStarted And ____AvailableRequested Then
____AvailableResult = AvailableImpl(What, ____AvailableSilent)
____AvailableChecked = True
____AvailableRequested = False
result = ____AvailableResult
ElseIf ____AvailableChecked Then
result = ____AvailableResult
End If
End If
Return result
Return Not SessionInterrupted
Else
Return True
End If
End Function
Private ____AvailableRequested As Boolean = False
Private ____AvailableSilent As Boolean = True
Private ____AvailableChecked As Boolean = False
Private ____AvailableResult As Boolean = False
Friend Overrides Function Available(ByVal What As Download, ByVal Silent As Boolean) As Boolean
If What = Download.Main And ____DownloadStarted Then
____AvailableRequested = True
____AvailableSilent = Silent
Return True
Else
Return AvailableImpl(What, Silent)
End If
Return AvailableTrueValue(What) AndAlso UpdateTokenIfRequired()
End Function
Private Function AvailableImpl(ByVal What As Download, ByVal Silent As Boolean) As Boolean
Try
AvailableText = String.Empty
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
Dim avg% = dl.Average(Function(d) d.Value)
If avg > 100 Then
AvailableText = "Over the past hour, Reddit has received an average of " &
avg.NumToString(New ANumbers With {.FormatOptions = ANumbers.Options.GroupIntegral}) & " outage reports:" & vbCr &
dl.ListToString(vbCr)
If Silent Then
Return False
Else
If MsgBoxE({$"{AvailableText}{vbCr}{vbCr}Do you want to continue parsing Reddit data?", "There are outage reports on Reddit"}, vbYesNo) = vbYes Then
If trueValue Then UpdateRedGifsToken()
Return trueValue AndAlso UpdateTokenIfRequired()
Else
Return False
End If
End If
End If
End If
If trueValue Then UpdateRedGifsToken()
Return trueValue AndAlso UpdateTokenIfRequired()
Catch ex As Exception
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, "[API.Reddit.SiteSettings.Available]", True)
End Try
Private Function AvailableTrueValue(ByVal What As Download) As Boolean
Return Not What = Download.SavedPosts OrElse (Responser.CookiesExists And ACheck(SavedPostsUserName.Value))
End Function
Friend Overrides Sub DownloadDone(ByVal What As Download)
SessionInterrupted = False
____DownloadStarted = False
____AvailableRequested = False
____AvailableChecked = False
____AvailableSilent = True
____AvailableResult = False
MDD.Reset()
MyBase.DownloadDone(What)
End Sub
Private Sub UpdateRedGifsToken()
@@ -210,7 +243,7 @@ Namespace API.Reddit
End If
End Function
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
With DirectCast(User, UserData) : Return String.Format(UrlPatternUser, IIf(.IsChannel, ChannelOption, "user"), .NameTrue) : 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
@@ -222,7 +255,7 @@ Namespace API.Reddit
#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 RedditViewExchange Then Options = New RedditViewExchange
If Options Is Nothing OrElse Not TypeOf Options Is RedditViewExchange Then Options = New RedditViewExchange(Me)
If OpenForm Then
Using f As New RedditViewSettingsForm(Options, True) : f.ShowDialog() : End Using
End If
@@ -243,24 +276,8 @@ Namespace API.Reddit
End Sub
#End Region
#Region "Token"
<PropertiesDataChecker({NameOf(AuthUserName), NameOf(AuthPassword), NameOf(ApiClientID), NameOf(ApiClientSecret)})>
Private Function TokenPropertiesChecker(ByVal p As IEnumerable(Of PropertyData)) As Boolean
If p.ListExists Then
Dim wrong As New List(Of String)
For i% = 0 To p.Count - 1
If CStr(p(i).Value).IsEmptyString Then wrong.Add(p(i).Name)
Next
If wrong.Count > 0 And wrong.Count <> 4 Then
MsgBoxE({$"You have not completed the following fields: {wrong.ListToString}." & vbCr &
"To use OAuth authorization, all authorization fields must be filled in.", "Validate token fields"}, vbCritical)
Return False
Else
Return True
End If
End If
Return False
End Function
Private Function UpdateTokenIfRequired() As Boolean
UpdateRedGifsToken()
If (CBool(UseTokenForTimelines.Value) Or CBool(UseTokenForSavedPosts.Value)) AndAlso CredentialsExists Then
If CDate(BearerTokenDateUpdate.Value).AddMinutes(TokenUpdateInterval.Value) <= Now Then Return UpdateToken()
End If

View File

@@ -33,7 +33,8 @@ Namespace API.Reddit
End Property
Private ReadOnly Property DateTrueProvider(ByVal IsChannel As Boolean) As IFormatProvider
Get
Return If(IsChannel, UnixDate32ProviderReddit, UnixDate64Provider)
Return UnixDate32Provider
'Return If(IsChannel, UnixDate32ProviderReddit, UnixDate64Provider)
End Get
End Property
Private ReadOnly Property UseM3U8 As Boolean
@@ -42,7 +43,6 @@ Namespace API.Reddit
End Get
End Property
Friend Property IsChannel As Boolean = False
Friend Property TrueName As String = String.Empty
Friend Overrides ReadOnly Property SpecialLabels As IEnumerable(Of String)
Get
Return {CannelsLabelName, CannelsLabelName_ChannelsForm, UserLabelName}
@@ -127,10 +127,16 @@ Namespace API.Reddit
Friend Property ViewPeriod As CPeriod Implements IRedditView.ViewPeriod
Friend Sub SetView(ByVal Options As IRedditView) Implements IRedditView.SetView
If Not Options Is Nothing Then
ViewMode = Options.ViewMode
ViewPeriod = Options.ViewPeriod
RedGifsAccount = Options.RedGifsAccount
RedditAccount = Options.RedditAccount
With Options
ViewMode = .ViewMode
ViewPeriod = .ViewPeriod
DownloadText = .DownloadText
DownloadTextPosts = .DownloadTextPosts
DownloadTextSpecialFolder = .DownloadTextSpecialFolder
RedGifsAccount = .RedGifsAccount
RedditAccount = .RedditAccount
If TypeOf Options Is RedditViewExchange Then DirectCast(Options, RedditViewExchange).ApplyBase(Me)
End With
End If
End Sub
Private ReadOnly Property View As String
@@ -138,6 +144,8 @@ Namespace API.Reddit
Select Case ViewMode
Case CView.Hot : Return "hot"
Case CView.Top : Return "top"
Case CView.Best : Return "best"
Case CView.Rising : Return "rising"
Case Else : Return "new"
End Select
End Get
@@ -158,6 +166,9 @@ Namespace API.Reddit
End If
End Get
End Property
Friend Overrides Property DownloadText As Boolean Implements IRedditView.DownloadText
Friend Overrides Property DownloadTextPosts As Boolean Implements IRedditView.DownloadTextPosts
Friend Overrides Property DownloadTextSpecialFolder As Boolean Implements IRedditView.DownloadTextSpecialFolder
#End Region
#Region "Initializer"
Friend Sub New()
@@ -173,16 +184,16 @@ Namespace API.Reddit
#End Region
#Region "Load and Update user info"
Private Function UpdateNames() As Boolean
If TrueName.IsEmptyString Then
If NameTrue(True).IsEmptyString Then
Dim n$() = Name.Split("@")
If n.ListExists Then
If n.Length = 2 Then
TrueName = n(0)
NameTrue = n(0)
IsChannel = True
ElseIf IsChannel Then
TrueName = Name
NameTrue = Name
Else
TrueName = n(0)
NameTrue = n(0)
End If
End If
If Not IsSavedPosts Then
@@ -201,7 +212,6 @@ Namespace API.Reddit
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)
RedGifsAccount = .Value(Name_RedGifsAccount)
RedditAccount = .Value(Name_RedditAccount)
UpdateNames()
@@ -210,14 +220,14 @@ Namespace API.Reddit
.Add(Name_ViewMode, CInt(ViewMode))
.Add(Name_ViewPeriod, CInt(ViewPeriod))
.Add(Name_IsChannel, IsChannel.BoolToInteger)
.Add(Name_TrueName, TrueName)
.Add(Name_TrueName, NameTrue(True))
.Add(Name_RedGifsAccount, RedGifsAccount)
.Add(Name_RedditAccount, RedditAccount)
End If
End With
End Sub
Friend Overrides Function ExchangeOptionsGet() As Object
Return New RedditViewExchange With {.ViewMode = ViewMode, .ViewPeriod = ViewPeriod, .RedGifsAccount = RedGifsAccount, .RedditAccount = RedditAccount}
Return New RedditViewExchange(Me)
End Function
Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
If Not Obj Is Nothing AndAlso TypeOf Obj Is IRedditView Then SetView(DirectCast(Obj, IRedditView))
@@ -230,7 +240,7 @@ Namespace API.Reddit
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
If IsSavedPosts Then NameTrue = MySiteSettings.SavedPostsUserName.Value
UpdateNames()
If IsChannelForm Then
UseMD5Comparison = False
@@ -276,8 +286,13 @@ Namespace API.Reddit
Else
GetUserInfo()
End If
If SaveToCache AndAlso Not Responser.Decoders.Contains(SymbolsConverter.Converters.HTML) Then _
Responser.Decoders.Add(SymbolsConverter.Converters.HTML)
If SaveToCache Then
DownloadText = False
DownloadTextPosts = False
DownloadTextSpecialFolder = False
If Not Responser.Decoders.Contains(SymbolsConverter.Converters.HTML) Then _
Responser.Decoders.Add(SymbolsConverter.Converters.HTML)
End If
DownloadDataChannel(String.Empty, Token)
If ChannelInfo Is Nothing Then _TempPostsList.ListAddList(_TempMediaList.Select(Function(m) m.Post.ID), LNC)
Else
@@ -310,55 +325,59 @@ Namespace API.Reddit
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 CheckNode As Predicate(Of EContainer) = Function(e) Not ParseUserMediaOnly OrElse If(e("author")?.Value, "/").ToLower.Equals(NameTrue.StringToLower)
Dim _PostID As Func(Of String) = Function() PostTmp.IfNullOrEmpty(PostID)
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"
'URL = $"https://gateway.reddit.com/desktopapi/v1/user/{NameTrue}/posts?rtj=only&allow_quarantined=true&allow_over18=1&include=identity&after={POST}&dist=25&sort={View}&t={Period}&layout=classic"
URL = $"https://oauth.reddit.com/user/{NameTrue}/submitted.json?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
'n = w.GetNode(JsonNodesJson)
n = w.GetNode(ChannelJsonNodes)
If n.ListExists Then
ProgressPre.ChangeMax(n.Count)
For Each nn In n
ProgressPre.Perform()
ThrowAny(Token)
If nn.Count > 0 Then
If CheckNode(nn) Then
With nn("data")
If .ListExists Then
If CheckNode(.Self) 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(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
'Obtain post ID
PostTmp = .Value("name") '.Name
If PostTmp.IsEmptyString Then PostTmp = .Value("id")
If PostTmp.IsEmptyString Then Continue For
'Check for CrossPost
If IsCrossPost(.Self) Then
_CrossPosts.ListAddList({ .Value(Node_CrosspostRootId),
.Value(Node_CrosspostParentId),
.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
PostDate = If(.Item("created")?.Value, String.Empty)
Select Case CheckDatesLimit(PostDate, DateTrueProvider(IsChannel))
Case DateResult.Skip : Continue For
Case DateResult.Exit : Exit Sub
End Select
ParseContainer(.Self, _PostID(), PostDate,,, GetTextDocument(.Self))
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 With
Next
End If
End If
@@ -394,10 +413,10 @@ Namespace API.Reddit
Dim lDate As Date?
If IsSavedPosts Then
URL = $"https://www.reddit.com/user/{TrueName}/saved.json?after={POST}"
URL = $"https://www.reddit.com/user/{NameTrue}/saved.json?after={POST}"
If Not POST.IsEmptyString Then Thread.Sleep(savedPostsSleepTimer)
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"
URL = $"https://reddit.com/r/{NameTrue}/{View}.json?allow_quarantined=true&allow_over18=1&include=identity&after={POST}&dist=25&sort={View}&t={Period}&layout=classic"
End If
ThrowAny(Token)
@@ -452,7 +471,7 @@ Namespace API.Reddit
Continue For
End If
ParseContainer(s, PostID, PostDate, _UserID)
ParseContainer(s, PostID, PostDate, _UserID,, If(Not SaveToCache, GetTextDocument(s), String.Empty))
End If
Next
End If
@@ -480,7 +499,7 @@ Namespace API.Reddit
Private Sub GetUserInfo()
Try
If Not IsSavedPosts And ChannelInfo Is Nothing Then
Dim r$ = Responser.GetResponse($"https://reddit.com/{IIf(IsChannel, "r", "user")}/{TrueName}/about.json",, EDP.ReturnValue)
Dim r$ = Responser.GetResponse($"https://reddit.com/{IIf(IsChannel, "r", "user")}/{NameTrue}/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
@@ -489,20 +508,10 @@ Namespace API.Reddit
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 = CreateFileFromUrl(img)
If Not f.Name.IsEmptyString Then
If f.Extension.IsEmptyString Then f.Extension = "jpg"
f.Path = dir.Path
If Not f.Exists Then GetWebFile(img, f, EDP.ReturnValue)
If f.Exists Then IconBannerDownloaded = True
End If
End If
End Sub
Dim fileCrFunc As Func(Of String, SFile) = Function(img) CreateFileFromUrl(img)
If DownloadIconBanner Then
__getFile.Invoke(.Value("icon_img"))
__getFile.Invoke(.Value("banner_img"))
SimpleDownloadAvatar(.Value("icon_img"), fileCrFunc)
SimpleDownloadAvatar(.Value("banner_img"), fileCrFunc)
End If
End With
End If
@@ -515,7 +524,7 @@ Namespace API.Reddit
#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
Optional ByVal AllowReparse As Boolean = True, Optional ByVal PostText As String = Nothing) 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
@@ -525,24 +534,24 @@ Namespace API.Reddit
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)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, UserID,, PostText), LNC)
_TotalPostsDownloaded += 1
Else
added = False
End If
Else
_TempMediaList.ListAddValue(MediaFromData(UTypes.VideoPre, tmpUrl, PostID, PostDate, UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.VideoPre, tmpUrl, PostID, PostDate, UserID,, PostText), LNC)
_TotalPostsDownloaded += 1
End If
ElseIf CreateImgurMedia(tmpUrl, PostID, PostDate, UserID, IsChannel) Then
ElseIf CreateImgurMedia(tmpUrl, PostID, PostDate, UserID, IsChannel, PostText) Then
_TotalPostsDownloaded += 1
ElseIf DownloadGallery(e, PostID, PostDate, UserID, SaveToCache) Then
ElseIf DownloadGallery(e, PostID, PostDate, UserID, SaveToCache, PostText) 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 "gallery" : If DownloadGallery(.Self, PostID, PostDate,,, PostText) Then _TotalPostsDownloaded += 1 Else added = False
Case "image", "gifvideo"
Dim resolution As Sizes = Nothing
@@ -577,17 +586,17 @@ Namespace API.Reddit
End If
If Not chosenVal.IsEmptyString Then
_TempMediaList.ListAddValue(MediaFromData(UPicType(t), chosenVal, PostID, PostDate, UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(UPicType(t), chosenVal, PostID, PostDate, UserID,, PostText), 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)
_TempMediaList.ListAddValue(MediaFromData(UTypes.m3u8, .Value("hlsUrl"), PostID, PostDate, UserID,, PostText), 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)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, .Value("fallback_url"), PostID, PostDate, UserID,, PostText), LNC)
_TotalPostsDownloaded += 1
Else
added = False
@@ -600,16 +609,16 @@ Namespace API.Reddit
If SaveToCache Then
tmpUrl = GetVideoRedditPreview(e)
If Not tmpUrl.IsEmptyString Then
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, UserID, False), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, tmpUrl, PostID, PostDate, UserID, False, PostText), 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)
_TempMediaList.ListAddValue(MediaFromData(UTypes.m3u8, e.Value({"media", "reddit_video"}, "hls_url"), PostID, PostDate, UserID,, PostText), LNC)
_TotalPostsDownloaded += 1
Else
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, tmpUrl, PostID, PostDate, UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, tmpUrl, PostID, PostDate, UserID,, PostText), LNC)
_TotalPostsDownloaded += 1
End If
Else
@@ -618,7 +627,7 @@ Namespace API.Reddit
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)
added = ParseContainer(e.ItemF({"crosspost_parent_list", 0}), PostID, PostDate, UserID, True, PostText)
Else
Dim tPostId$ = e.Value(Node_CrosspostParent).IfNullOrEmpty(e.Value(Node_CrosspostParentId)).IfNullOrEmpty(e.Value(Node_CrosspostRootId))
If Not PostID.IsEmptyString Then
@@ -627,7 +636,7 @@ Namespace API.Reddit
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)
If .ListExists Then added = ParseContainer(.Self, PostID, PostDate, UserID, False, PostText)
End With
End If
End Using
@@ -649,7 +658,7 @@ Namespace API.Reddit
End Select
End With
If Not tmpType = UTypes.Undefined Then
_TempMediaList.ListAddValue(MediaFromData(tmpType, node.Value, PostID, PostDate, UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(tmpType, node.Value, PostID, PostDate, UserID,, PostText), LNC)
added = True
End If
End If
@@ -672,7 +681,7 @@ Namespace API.Reddit
If Not tmpUrl.IsEmptyString Then tmpType = UTypes.Picture
End If
If Not tmpUrl.IsEmptyString And Not tmpType = UTypes.Undefined Then
Dim m As UserMedia = MediaFromData(tmpType, tmpUrl, PostID, PostDate, UserID)
Dim m As UserMedia = MediaFromData(tmpType, tmpUrl, PostID, PostDate, UserID,, PostText)
If tmpType = UTypes.Video Then m.File.Extension = "mp4"
_TempMediaList.ListAddValue(m, LNC)
_TotalPostsDownloaded += 1
@@ -683,6 +692,7 @@ Namespace API.Reddit
End If
End If
End If
If Not added And Not PostText.IsEmptyString Then _TempMediaList.ListAddValue(MediaFromData(UTypes.Text, String.Empty, PostID, PostDate, UserID,, PostText))
Return added
Else
Return False
@@ -705,28 +715,45 @@ Namespace API.Reddit
Return False
End Try
End Function
Private Function GetTextDocument(ByVal e As EContainer) As String
Dim t$ = String.Empty
Try
t = e.Value("title")
With e({"rtjson", "document"})
If .ListExists Then
For Each tt As EContainer In .Self
t.StringAppendLine(vbCrLf,, False)
t.StringAppendLine(If(tt.ItemF({"c", 0, "t"})?.Value, String.Empty))
Next
End If
End With
Catch
End Try
Return t
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
Optional ByVal _UserID As String = "", Optional ByVal IsChannel As Boolean = False,
Optional ByVal PostText As String = Nothing) 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), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL, PostID, PostDate, _UserID,, PostText), LNC)
ElseIf _URL.Contains(".gifv") Then
If SaveToCache Then
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL.Replace(".gifv", ".gif"), PostID, PostDate, _UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL.Replace(".gifv", ".gif"), PostID, PostDate, _UserID,, PostText), LNC)
Else
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL.Replace(".gifv", ".mp4"), PostID, PostDate, _UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL.Replace(".gifv", ".mp4"), PostID, PostDate, _UserID,, PostText), LNC)
End If
ElseIf _URL.Contains(".mp4") Then
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL, PostID, PostDate, _UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, _URL, PostID, PostDate, _UserID,, PostText), LNC)
ElseIf _URL.Contains(".gif") Then
_TempMediaList.ListAddValue(MediaFromData(UTypes.GIF, _URL, PostID, PostDate, _UserID), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.GIF, _URL, PostID, PostDate, _UserID,, PostText), 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), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, _URL, PostID, PostDate, _UserID,, PostText), LNC)
Else
Dim ut As UTypes
Dim m As UserMedia
@@ -741,7 +768,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)
m = MediaFromData(ut, _URL, PostID, PostDate, _UserID,, PostText)
m.URL = .URL
m.File = .File.File
_TempMediaList.ListAddValue(m, LNC)
@@ -757,7 +784,8 @@ Namespace API.Reddit
End If
End Function
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
Optional ByVal _UserID As String = Nothing, Optional ByVal FirstOnly As Boolean = False,
Optional ByVal PostText As String = Nothing) As Boolean
Try
Dim added As Boolean = False
Dim node As EContainer = Nothing
@@ -771,7 +799,7 @@ Namespace API.Reddit
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), LNC)
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, t.Value, PostID, PostDate, _UserID,, PostText), LNC)
added = True
If FirstOnly Then Exit For
End If
@@ -888,7 +916,7 @@ Namespace API.Reddit
If Not r.IsEmptyString Then
v = RegexReplace(r, VideoRegEx)
If Not v.IsEmptyString Then
_TempMediaList(i) = New UserMedia With {.Type = UTypes.Video, .URL = v, .File = v, .Post = m.Post}
_TempMediaList(i) = New UserMedia With {.Type = UTypes.Video, .URL = v, .File = v, .Post = m.Post, .PostText = m.PostText, .PostTextFile = m.PostTextFile}
Else
_TempMediaList.RemoveAt(i)
End If
@@ -936,7 +964,7 @@ Namespace API.Reddit
If j.Count > 0 Then
lastCount = _TempMediaList.Count
With j.GetNode(SingleJsonNodes)
If .ListExists AndAlso ParseContainer(.Self, m.Post.ID, String.Empty) Then
If .ListExists AndAlso ParseContainer(.Self, m.Post.ID, String.Empty,,, GetTextDocument(.Self)) Then
If lastCount <> _TempMediaList.Count Then
For li = IIf(lastCount < 0, 0, lastCount) To _TempMediaList.Count - 1
m2 = _TempMediaList(i)
@@ -983,13 +1011,15 @@ Namespace API.Reddit
#End Region
#Region "Structure creator"
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
Optional ByVal _UserID As String = "", Optional ByVal ReplacePreview As Boolean = True,
Optional ByVal PostText As String = Nothing) 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 = CreateFileFromUrl(m.URL) Else m.File = Nothing
If ReplacePreview And m.URL.Contains("preview") And Not t = UTypes.Picture Then m.URL = $"https://i.redd.it/{m.File.File}"
If Not PostDate.IsEmptyString Then m.Post.Date = AConvert(Of Date)(PostDate, DateTrueProvider(IsChannel Or IsSavedPosts), Nothing) Else m.Post.Date = Nothing
If Not PostText.IsEmptyString Then m.PostText = PostText
Return m
End Function
Private Function TryFile(ByVal URL As String) As Boolean
@@ -1060,25 +1090,28 @@ Namespace API.Reddit
ElseIf .StatusCode = HttpStatusCode.Forbidden Then '403
UserSuspended = True
ElseIf .StatusCode = HttpStatusCode.BadGateway Or .StatusCode = HttpStatusCode.ServiceUnavailable Then '502, 503
MyMainLOG = $"{ToStringForLog()}: [{CInt(Responser.StatusCode)}] Reddit is currently unavailable"
LogError(Nothing, $"[{CInt(Responser.StatusCode)}] Reddit is currently unavailable")
Throw New Plugin.ExitException With {.Silent = True}
ElseIf .StatusCode = HttpStatusCode.GatewayTimeout Then '504
Return 1
ElseIf .StatusCode = HttpStatusCode.Unauthorized Then '401
MyMainLOG = $"{ToStringForLog()}: [{CInt(Responser.StatusCode)}] Reddit credentials expired"
LogError(Nothing, $"[{CInt(Responser.StatusCode)}] Reddit credentials expired")
MySiteSettings.SessionInterrupted = True
Throw New Plugin.ExitException With {.Silent = True}
ElseIf .StatusCode = HttpStatusCode.InternalServerError Then '500
If Not IsNothing(EObj) AndAlso IsNumeric(EObj) AndAlso CInt(EObj) = HttpStatusCode.InternalServerError Then Return 1
Return HttpStatusCode.InternalServerError
ElseIf .StatusCode = 429 And IsSavedPosts And Err429Count = 0 Then
ElseIf .StatusCode = 429 And IsSavedPosts And Err429Count = 0 Then '429 (saved)
Err429Count += 1
Return 429
ElseIf .StatusCode = 429 AndAlso
((Not IsSavedPosts And CBool(MySiteSettings.UseTokenForTimelines.Value)) Or (IsSavedPosts And CBool(MySiteSettings.UseTokenForSavedPosts.Value))) AndAlso
Not MySiteSettings.CredentialsExists Then '429
MyMainLOG = $"{ToStringForLog()}: [{CInt(Responser.StatusCode)}] You should use OAuth authorization or disable " &
IIf(IsSavedPosts, "token usage for downloading saved posts", "the use of token and cookies for downloading timelines")
ElseIf .StatusCode = 429 Then '429 (all)
If ((Not IsSavedPosts And CBool(MySiteSettings.UseTokenForTimelines.Value)) Or (IsSavedPosts And CBool(MySiteSettings.UseTokenForSavedPosts.Value))) AndAlso
Not MySiteSettings.CredentialsExists Then
LogError(Nothing, $"[{CInt(Responser.StatusCode)}] You should use OAuth authorization or disable " &
IIf(IsSavedPosts, "token usage for downloading saved posts", "the use of token and cookies for downloading timelines"))
Else
LogError(Nothing, "Too many requests (429). Try again later!")
End If
MySiteSettings.SessionInterrupted = True
Throw New Plugin.ExitException With {.Silent = True}
Else

View File

@@ -31,6 +31,9 @@ Namespace API.RedGifs
<Provider(NameOf(TokenUpdateInterval), FieldsChecker:=True)>
Private ReadOnly Property TokenUpdateIntervalProvider As IFormatProvider
#End Region
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
#End Region
#Region "Initializer"
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)

View File

@@ -27,6 +27,9 @@ Namespace API.ThisVid
"If true, then public videos will be stored in the 'Public' folder, private - in the 'Private' folder." & vbCr &
"If false, all videos will be stored in the 'Video' folder."), PClonable>
Friend ReadOnly Property DifferentFolders As PropertyValue
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
#End Region
#Region "Initializer"
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)

View File

@@ -14,7 +14,7 @@ Imports PersonalUtilities.Functions.RegularExpressions
Imports PersonalUtilities.Tools
Imports PersonalUtilities.Tools.Web.Documents.JSON
Namespace API.ThisVid
Friend Class UserData : Inherits UserDataBase
Friend Class UserData : Inherits UserDataBase : Implements IPSite
#Region "XML names"
Private Const Name_DownloadPublic As String = "DownloadPublic"
Private Const Name_DownloadPrivate As String = "DownloadPrivate"
@@ -44,7 +44,6 @@ Namespace API.ThisVid
Friend Property DownloadPrivate As Boolean = True
Friend Property DownloadFavourite As Boolean = False
Friend Property DifferentFolders As Boolean = True
Friend Property TrueName As String = String.Empty
Friend Property SiteMode As SiteModes = SiteModes.User
Private Property Arguments As String = String.Empty
Friend Overrides ReadOnly Property SpecialLabels As IEnumerable(Of String)
@@ -52,7 +51,7 @@ Namespace API.ThisVid
Return {SearchRequestLabelName}
End Get
End Property
Friend Property QueryString As String
Friend Property QueryString As String Implements IPSite.QueryString
Get
If SiteMode = SiteModes.User Then
Return String.Empty
@@ -80,7 +79,7 @@ Namespace API.ThisVid
If Not Force OrElse (Not SiteMode = SiteModes.User AndAlso Not NewUrl.IsEmptyString AndAlso MyFileSettings.Exists) Then
Dim eObj As Plugin.ExchangeOptions = Nothing
If Force Then eObj = MySettings.IsMyUser(NewUrl)
If (Force And Not eObj.UserName.IsEmptyString) Or (Not Force And TrueName.IsEmptyString) Then
If (Force And Not eObj.UserName.IsEmptyString) Or (Not Force And NameTrue(True).IsEmptyString) Then
Dim n$() = If(Force, eObj.UserName, Name).Split("@")
If n.ListExists(2) Then
@@ -98,8 +97,8 @@ Namespace API.ThisVid
End If
__TrueName = n(1)
If Force AndAlso (Not TrueName = __TrueName Or Not SiteMode = __Mode) Then
If ValidateChangeSearchOptions(ToStringForLog, $"{__Mode}: {__TrueName}", $"{SiteMode}: {TrueName}") Then
If Force AndAlso (Not NameTrue(True) = __TrueName Or Not SiteMode = __Mode) Then
If ValidateChangeSearchOptions(ToStringForLog, $"{__Mode}: {__TrueName}", $"{SiteMode}: {NameTrue(True)}") Then
__ForceApply = True
Else
Return False
@@ -109,21 +108,21 @@ Namespace API.ThisVid
Arguments = __Arguments
Options = If(Force, eObj.Options, Options)
If Not Force Then
TrueName = __TrueName
NameTrue = __TrueName
SiteMode = __Mode
Settings.Labels.Add(SearchRequestLabelName)
Labels.ListAddValue(SearchRequestLabelName, LNC)
Labels.Sort()
UserSiteName = $"{SiteMode}: {TrueName}"
UserSiteName = $"{SiteMode}: {NameTrue}"
If FriendlyName.IsEmptyString Then FriendlyName = UserSiteName
ElseIf Force And __ForceApply Then
TrueName = __TrueName
NameTrue = __TrueName
SiteMode = __Mode
End If
Return True
Else
SiteMode = SiteModes.User
TrueName = Name
NameTrue = Name
End If
End If
End If
@@ -136,7 +135,6 @@ Namespace API.ThisVid
DownloadPrivate = .Value(Name_DownloadPrivate).FromXML(Of Boolean)(True)
DownloadFavourite = .Value(Name_DownloadFavourite).FromXML(Of Boolean)(False)
DifferentFolders = .Value(Name_DifferentFolders).FromXML(Of Boolean)(True)
TrueName = .Value(Name_TrueName)
SiteMode = .Value(Name_SiteMode).FromXML(Of Integer)(SiteModes.User)
Arguments = .Value(Name_Arguments)
UpdateUserOptions()
@@ -150,7 +148,7 @@ Namespace API.ThisVid
.Add(Name_DownloadPrivate, DownloadPrivate.BoolToInteger)
.Add(Name_DownloadFavourite, DownloadFavourite.BoolToInteger)
.Add(Name_DifferentFolders, DifferentFolders.BoolToInteger)
.Add(Name_TrueName, TrueName)
.Add(Name_TrueName, NameTrue(True))
.Add(Name_SiteMode, CInt(SiteMode))
.Add(Name_Arguments, Arguments)
@@ -163,15 +161,7 @@ Namespace API.ThisVid
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
DownloadFavourite = .DownloadFavourite
DifferentFolders = .DifferentFolders
QueryString = .QueryString
End With
End If
If Not Obj Is Nothing AndAlso TypeOf Obj Is UserExchangeOptions Then DirectCast(Obj, UserExchangeOptions).Apply(Me)
End Sub
#End Region
#Region "Initializer"
@@ -259,18 +249,18 @@ Namespace API.ThisVid
Dim url$ = String.Empty
Select Case SiteMode
Case SiteModes.Tags
url = $"https://thisvid.com/{SiteSettings.P_Tags}/{TrueName}/"
url = $"https://thisvid.com/{SiteSettings.P_Tags}/{NameTrue}/"
If Not Arguments.IsEmptyString Then url &= $"{Arguments}/"
If Page > 1 Then url &= $"{Page}/"
Case SiteModes.Categories
url = $"https://thisvid.com/{SiteSettings.P_Categories}/{TrueName}/"
url = $"https://thisvid.com/{SiteSettings.P_Categories}/{NameTrue}/"
If Not Arguments.IsEmptyString Then url &= $"{Arguments}/"
If Page > 1 Then url &= $"{Page}/"
Case SiteModes.Search
If Not Arguments.IsEmptyString Then
url = $"https://thisvid.com/{Arguments}/"
If Page > 1 Then url &= $"{Page}/"
url &= $"?q={TrueName}/"
url &= $"?q={NameTrue}/"
End If
End Select
Return url
@@ -473,35 +463,47 @@ Namespace API.ThisVid
Dim u As UserMedia
Dim n$, r$
Dim c% = 0
Dim ii As Byte
Dim repeat As Boolean
Progress.Maximum += _TempMediaList.Count
For i% = _TempMediaList.Count - 1 To 0 Step -1
Progress.Perform()
u = _TempMediaList(i)
If u.Type = UserMedia.Types.VideoPre Then
If Not DownloadTopCount.HasValue OrElse c <= DownloadTopCount.Value Then
ThrowAny(Token)
r = Responser.GetResponse(u.URL,, EDP.ReturnValue)
If Not r.IsEmptyString Then
n = TitleHtmlConverter(RegexReplace(r, RegExVideoTitle))
u.Post.ID = u.URL
If Not n.IsEmptyString Then n = n.Replace("ThisVid.com", String.Empty).StringTrim.StringTrimEnd("-").StringTrim
If n.IsEmptyString Then n = TitleHtmlConverter(u.URL.Replace("https://thisvid.com/videos/", String.Empty).StringTrim.StringTrimEnd("-").StringTrim)
If n.IsEmptyString Then n = "VideoFile"
u.File = $"{n}.mp4"
u.PictureOption = n
u.URL = RegexReplace(r, Regex_VideosThumb_OG_IMAGE)
If u.URL.IsEmptyString Then u.URL = RegexReplace(r, RegExVideosThumb1)
If u.URL.IsEmptyString Then u.URL = RegexReplace(r, RegExVideosThumb2)
If Not u.URL.IsEmptyString Then
u.URL = LinkFormatterSecure(u.URL)
u.Type = UserMedia.Types.Video
_TempPostsList.Add(u.Post.ID)
_TempMediaList(i) = u
c += 1
Else
_TempMediaList.RemoveAt(i)
repeat = False
For ii = 0 To 1
ThrowAny(Token)
r = Responser.GetResponse(u.URL,, EDP.ReturnValue)
If Not r.IsEmptyString Then
n = TitleHtmlConverter(RegexReplace(r, RegExVideoTitle))
u.Post.ID = u.URL
If Not n.IsEmptyString Then n = n.Replace("ThisVid.com", String.Empty).StringTrim.StringTrimEnd("-").StringTrim
If n.IsEmptyString Then n = TitleHtmlConverter(u.URL.Replace("https://thisvid.com/videos/", String.Empty).StringTrim.StringTrimEnd("-").StringTrim)
If n.IsEmptyString Then n = "VideoFile"
u.File = $"{n}.mp4"
u.PictureOption = n
u.URL = RegexReplace(r, Regex_VideosThumb_OG_IMAGE)
If u.URL.IsEmptyString And Not repeat And ii = 0 Then
Thread.Sleep(250)
u = _TempMediaList(i)
repeat = True
Continue For
End If
If u.URL.IsEmptyString Then u.URL = RegexReplace(r, RegExVideosThumb1)
If u.URL.IsEmptyString Then u.URL = RegexReplace(r, RegExVideosThumb2)
If Not u.URL.IsEmptyString Then
u.URL = LinkFormatterSecure(u.URL)
u.Type = UserMedia.Types.Video
_TempPostsList.Add(u.Post.ID)
_TempMediaList(i) = u
c += 1
Else
_TempMediaList.RemoveAt(i)
End If
End If
End If
If Not repeat Then Exit For
Next
Else
_TempMediaList.RemoveAt(i)
End If

View File

@@ -6,9 +6,10 @@
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports SCrawler.API.Base
Imports SCrawler.Plugin.Attributes
Namespace API.ThisVid
Friend Class UserExchangeOptions : Inherits Xhamster.UserExchangeOptions
Friend Class UserExchangeOptions : Inherits EditorExchangeOptionsBase_P
<PSetting(Caption:="Download public videos")>
Friend Property DownloadPublic As Boolean = True
<PSetting(Caption:="Download private videos")>
@@ -19,6 +20,7 @@ Namespace API.ThisVid
Friend Property DifferentFolders As Boolean = True
Private ReadOnly Property MySettings As SiteSettings
Friend Sub New(ByVal s As SiteSettings)
MyBase.New(s)
DownloadPublic = s.DownloadPublic.Value
DownloadPrivate = s.DownloadPrivate.Value
DownloadFavourite = s.DownloadFavourite.Value
@@ -26,12 +28,21 @@ Namespace API.ThisVid
MySettings = s
End Sub
Friend Sub New(ByVal u As UserData)
MyBase.New(u)
DownloadPublic = u.DownloadPublic
DownloadPrivate = u.DownloadPrivate
DownloadFavourite = u.DownloadFavourite
DifferentFolders = u.DifferentFolders
QueryString = u.QueryString
MySettings = u.HOST.Source
End Sub
Friend Overrides Sub Apply(ByRef u As IPSite)
MyBase.Apply(u)
With DirectCast(u, UserData)
.DownloadPublic = DownloadPublic
.DownloadPrivate = DownloadPrivate
.DownloadFavourite = DownloadFavourite
.DifferentFolders = DifferentFolders
End With
End Sub
End Class
End Namespace

View File

@@ -15,7 +15,7 @@ Imports PersonalUtilities.Functions.RegularExpressions
Imports IG = SCrawler.API.Instagram.SiteSettings
Imports DN = SCrawler.API.Base.DeclaredNames
Namespace API.ThreadsNet
<Manifest("AndyProgram_ThreadsNet"), SavedPosts, SeparatedTasks(1)>
<Manifest("AndyProgram_ThreadsNet"), SavedPosts, SeparatedTasks(1), SpecialForm(False)>
Friend Class SiteSettings : Inherits SiteSettingsBase
#Region "Declarations"
#Region "Authorization"
@@ -89,7 +89,7 @@ Namespace API.ThreadsNet
#End Region
#Region "Initializer"
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
Me.New("Threads", "threads.net", AccName, Temp, My.Resources.SiteResources.ThreadsIcon_192, My.Resources.SiteResources.ThreadsIcon_192.ToBitmap)
Me.New("Threads", "threads.com", AccName, Temp, My.Resources.SiteResources.ThreadsIcon_192, My.Resources.SiteResources.ThreadsIcon_192.ToBitmap)
End Sub
Protected Sub New(ByVal SiteName As String, ByVal CookiesDomain As String, ByVal AccName As String, ByVal Temp As Boolean,
Optional ByVal __Icon As Icon = Nothing, Optional ByVal __Image As Image = Nothing)
@@ -118,8 +118,8 @@ Namespace API.ThreadsNet
browserExt = .Value(IG.Header_BrowserExt)
platform = .Value(IG.Header_Platform_Verion)
End If
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Authority, "www.threads.net"))
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Origin, "https://www.threads.net"))
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Authority, "www.threads.com"))
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Origin, "https://www.threads.com"))
.Add("Upgrade-Insecure-Requests", 1)
.Add("Sec-Ch-Ua-Model", "")
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.SecChUaMobile, "?0"))
@@ -152,9 +152,10 @@ Namespace API.ThreadsNet
RequestsWaitTimer_AnyProvider = New IG.TimersChecker(0)
DownloadData_Impl = New PropertyValue(True)
UrlPatternUser = "https://www.threads.net/@{0}"
UserRegex = RParams.DMS(String.Format(UserRegexDefaultPattern, "threads.net/@"), 1)
ImageVideoContains = "threads.net"
UrlPatternUser = "https://www.threads.com/@{0}"
UserRegex = RParams.DMS(String.Format(UserRegexDefaultPattern, "threads.(net|com)/@"), 2)
ImageVideoContains = "threads.com"
UserOptionsType = GetType(EditorExchangeOptionsBase)
End Sub
#End Region
#Region "UpdateResponserData"
@@ -180,14 +181,11 @@ Namespace API.ThreadsNet
Friend Overrides Function BaseAuthExists() As Boolean
Return Responser.CookiesExists And {HH_CSRF_TOKEN, HH_IG_APP_ID}.All(Function(v) ACheck(Of String)(v.Value)) And CBool(DownloadData_Impl.Value)
End Function
Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String
Return String.Format(UrlPatternUser, DirectCast(User, UserData).NameTrue)
End Function
Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String
Try
Dim code$ = DirectCast(User, UserData).GetPostCodeById(Media.Post.ID)
Dim name$ = DirectCast(User, UserData).NameTrue
If Not code.IsEmptyString Then Return $"https://www.threads.net/@{name}/post/{code}/" Else Return String.Empty
If Not code.IsEmptyString Then Return $"https://www.threads.com/@{name}/post/{code}/" Else Return String.Empty
Catch ex As Exception
Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "Can't open user's post", String.Empty)
End Try

View File

@@ -7,6 +7,7 @@
' 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.Base
Imports SCrawler.API.YouTube.Objects
Imports PersonalUtilities.Functions.XML
@@ -51,9 +52,10 @@ Namespace API.ThreadsNet
#End Region
#Region "Exchange"
Friend Overrides Function ExchangeOptionsGet() As Object
Return Nothing
Return New EditorExchangeOptionsBase(Me)
End Function
Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
If Not Obj Is Nothing AndAlso TypeOf Obj Is EditorExchangeOptionsBase Then DirectCast(Obj, EditorExchangeOptionsBase).ApplyBase(Me)
End Sub
#End Region
#Region "Initializer"
@@ -61,7 +63,7 @@ Namespace API.ThreadsNet
ObtainMedia_SetReelsFunc()
ObtainMedia_AllowAbstract = True
DefaultParser_ElemNode = DefaultParser_ElemNode_Default
DefaultParser_PostUrlCreator = Function(post) $"https://www.threads.net/@{NameTrue}/post/{post.Code}"
DefaultParser_PostUrlCreator = Function(post) $"https://www.threads.com/@{NameTrue}/post/{post.Code}"
_ResponserAutoUpdateCookies = True
_ResponserAddResponseReceivedHandler = True
DefaultParser_Pinned = AddressOf IsPinnedPost
@@ -73,12 +75,13 @@ Namespace API.ThreadsNet
End Sub
Private Sub DisableDownload()
MySettings.DownloadData_Impl.Value = False
MyMainLOG = $"{Site} downloading is disabled until you update your credentials"
LogError(Nothing, $"{Site} downloading is disabled until you update your credentials")
End Sub
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
If CBool(MySettings.DownloadData_Impl.Value) Then
Dim errorFound As Boolean = False
Try
_IdChanged = False
Responser.Method = "POST"
LoadSavePostsKV(True)
ResetBaseTokens()
@@ -103,9 +106,9 @@ Namespace API.ThreadsNet
End If
If IsSavedPosts Then
DefaultParser_ElemNode = {"node", "thread_items", 0, "post"}
DownloadSavedPosts(String.Empty, Token)
DownloadSavedPosts(String.Empty, 0, Token)
Else
DownloadData(String.Empty, Token)
DownloadData(String.Empty, 0, Token)
End If
If _TempMediaList.Count > 0 Then FirstLoadingDone = True : setMaxPostDate.Invoke(_TempMediaList)
Catch ex As Exception
@@ -115,6 +118,7 @@ Namespace API.ThreadsNet
Responser.Method = "POST"
UpdateResponser()
MySettings.UpdateResponserData(Responser)
ValidateExtension()
If Not errorFound Then LoadSavePostsKV(False)
End Try
End If
@@ -124,10 +128,13 @@ Namespace API.ThreadsNet
If IsSavedPosts Then
Return False
Else
If MaxLastDownDate.HasValue Then
Dim d As Date? = AConvert(Of Date)(Items(Index).ItemF(DefaultParser_ElemNode_Default).Value("taken_at"), UnixDate32Provider, Nothing)
If d.HasValue Then Return d.Value < MaxLastDownDate.Value
End If
With Items(Index).ItemF(DefaultParser_ElemNode)
Return .Value({"text_post_app_info", "pinned_post_info"}, "is_pinned_to_profile").FromXML(Of Boolean)(False)
If MaxLastDownDate.HasValue Then
Dim d As Date? = AConvert(Of Date)(.Value("taken_at"), UnixDate32Provider, Nothing)
If d.HasValue Then Return d.Value <= MaxLastDownDate.Value
End If
End With
Return Not FirstLoadingDone
End If
Catch ex As Exception
@@ -150,12 +157,14 @@ Namespace API.ThreadsNet
Responser.Headers.Add(IGS.Header_CSRF_TOKEN, csrf)
End If
End Sub
Private Const GQL_Q As String = "https://www.threads.net/api/graphql?lsd={0}&fb_dtsg={1}&doc_id={2}&fb_api_req_friendly_name={3}&server_timestamps=true&variables={4}"
Private Const GQL_P_DOC_ID As String = "6371597506283707"
Private Const GQL_P_NAME As String = "BarcelonaProfileThreadsTabRefetchableQuery"
Private Const GQL_S_DOC_ID_1 As String = "7758166704280174"
Private Const GQL_S_NAME_1 As String = "BarcelonaSavedPageViewerQuery"
Private Const GQL_S_DOC_ID_2 As String = "8617275414954442"
'Private Const GQL_Q As String = "https://www.threads.com/api/graphql?lsd={0}&fb_dtsg={1}&doc_id={2}&fb_api_req_friendly_name={3}&server_timestamps=true&variables={4}"
Private Const GQL_Q2 As String = "https://www.threads.com/graphql/query"
Private Const PayloadData As String = "lsd={0}&fb_dtsg={1}&doc_id={2}&fb_api_req_friendly_name={3}&server_timestamps=true&variables={4}"
Private Const GQL_P_DOC_ID As String = "9039187972876777" '"8779269398849532" '"6371597506283707"
Private Const GQL_P_NAME As String = "BarcelonaProfileThreadsTabRefetchableDirectQuery" '"BarcelonaProfileThreadsTabRefetchableQuery"
'Private Const GQL_S_DOC_ID_1 As String = "9227844190587889" '"7758166704280174"
'Private Const GQL_S_NAME_1 As String = "BarcelonaSavedPageViewerQuery"
Private Const GQL_S_DOC_ID_2 As String = "9116629201788321" '"8617275414954442"
Private Const GQL_S_NAME_2 As String = "BarcelonaSavedPageRefetchableQuery"
Private Sub DownloadCheckCredentials()
If Not Valid Then
@@ -165,108 +174,191 @@ Namespace API.ThreadsNet
End If
If Not Valid Then DisableDownload() : Throw New Plugin.ExitException("Some credentials are missing")
End Sub
Private Overloads Sub DownloadData(ByVal Cursor As String, ByVal Token As CancellationToken)
Const var_init$ = """userID"":""{0}"""
Const var_cursor$ = """after"":""{1}"",""before"":null,""first"":25,""last"":null,""userID"":""{0}"",""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsFeedbackHubEnabledrelayprovider"":false"
Dim URL$ = String.Empty
Private Function CheckErrors(ByVal e As EContainer) As Boolean
Return e.ListExists AndAlso Not JsonErrorMessage(e).IsEmptyString
End Function
Private Function JsonErrorMessage(ByVal e As EContainer) As String
Return e.ItemF({"errors", 0, "summary"})?.Value
End Function
Private Sub ProcessJsonErrorException(ByVal uex As JsonErrorException, Optional ByVal ThrowEx As Boolean = True)
If uex.UserNotFound Then
UserExists = False
_ForceSaveUserInfo = True
_ForceSaveUserInfoOnException = True
ElseIf ThrowEx Then
Throw New ExitException(uex.ErrMessage) With {.SimpleLogLine = True}
Else
LogError(Nothing, uex.ErrMessage)
End If
End Sub
Private Class JsonErrorException : Inherits Exception
Friend Property UserNotFound As Boolean = False
Private _ErrMessage As String = String.Empty
Public Property ErrMessage As String
Get
Return _ErrMessage
End Get
Set(ByVal m As String)
_ErrMessage = m
UserNotFound = _ErrMessage.StringToLower = "not found"
End Set
End Property
Public Overrides ReadOnly Property Message As String
Get
Return _ErrMessage
End Get
End Property
Friend Sub New()
End Sub
Friend Sub New(ByVal Message As String)
ErrMessage = Message
End Sub
End Class
Private Overloads Sub DownloadData(ByVal Cursor As String, ByVal Round As Integer, ByVal Token As CancellationToken)
'Const var_init$ = """userID"":""{0}"""
'Const var_cursor$ = """after"":""{1}"",""before"":null,""first"":25,""last"":null,""userID"":""{0}"",""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsFeedbackHubEnabledrelayprovider"":false"
Const var_cursor2$ = """after"":{1},""before"":null,""first"":10,""last"":null,""userID"":""{0}"",""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaHasSelfReplyContextrelayprovider"":false,""__relay_internal__pv__BarcelonaShareableListsrelayprovider"":true,""__relay_internal__pv__BarcelonaIsSearchDiscoveryEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaOptionalCookiesEnabledrelayprovider"":true,""__relay_internal__pv__BarcelonaQuotedPostUFIEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaIsCrawlerrelayprovider"":false,""__relay_internal__pv__BarcelonaHasDisplayNamesrelayprovider"":false,""__relay_internal__pv__BarcelonaCanSeeSponsoredContentrelayprovider"":false,""__relay_internal__pv__BarcelonaShouldShowFediverseM075Featuresrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowTagRedesignrelayprovider"":false,""__relay_internal__pv__BarcelonaIsInternalUserrelayprovider"":false"
Try
DownloadCheckCredentials()
Responser.Method = "POST"
Responser.Referer = $"https://www.threads.net/@{NameTrue}"
Responser.Headers.Add(GQL_HEADER_FB_LSD, Token_lsd)
Responser.Headers.Add(GQL_HEADER_FB_FRINDLY_NAME, GQL_P_NAME)
With Responser
.Method = "POST"
.Referer = $"https://www.threads.com/@{NameTrue}"
.ContentType = "application/x-www-form-urlencoded"
With .Headers
.Add(GQL_HEADER_FB_LSD, Token_lsd)
.Add(GQL_HEADER_FB_FRINDLY_NAME, GQL_P_NAME)
End With
End With
Dim nextCursor$ = String.Empty
Dim dataFound As Boolean = False
Dim vars$
If Cursor.IsEmptyString Then
vars = String.Format(var_init, ID)
Else
vars = String.Format(var_cursor, ID, Cursor)
End If
vars = SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & vars & "}")
Dim vars$ = String.Format(var_cursor2, ID, IIf(Cursor.IsEmptyString, "null", $"""{Cursor}"""))
'If Cursor.IsEmptyString Then
' vars = String.Format(var_init, ID)
'Else
' vars = String.Format(var_cursor, ID, Cursor)
'End If
vars = String.Format(PayloadData, Token_lsd, Token_dtsg_Var, GQL_P_DOC_ID, GQL_P_NAME,
SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & vars & "}"))
URL = String.Format(GQL_Q, Token_lsd, Token_dtsg_Var, GQL_P_DOC_ID, GQL_P_NAME, vars)
'URL = String.Format(GQL_Q, Token_lsd, Token_dtsg_Var, GQL_P_DOC_ID, GQL_P_NAME, vars)
Using j As EContainer = GetDocument(URL, Token)
If j.ListExists Then
With j({"data", "mediaData"})
If .ListExists Then
nextCursor = .Value({"page_info"}, "end_cursor")
With .Item({"edges"})
If .ListExists Then dataFound = DefaultParser(.Self, Sections.Timeline, Token)
End With
End If
End With
Using j As EContainer = GetDocument(GQL_Q2, vars, Token)
If Not CheckErrors(j) Then
If j.ListExists Then
With j({"data", "mediaData"})
If .ListExists Then
nextCursor = .Value({"page_info"}, "end_cursor")
With .Item({"edges"})
If .ListExists Then dataFound = DefaultParser(.Self, Sections.Timeline, Token)
End With
End If
End With
End If
Else
Throw New JsonErrorException(JsonErrorMessage(j))
End If
End Using
If dataFound And Not nextCursor.IsEmptyString Then DownloadData(nextCursor, Token)
If dataFound And Not nextCursor.IsEmptyString Then DownloadData(nextCursor, 0, Token)
Catch uex As JsonErrorException
If Round > 0 Then
ProcessJsonErrorException(uex)
ElseIf Not _IdChanged AndAlso UpdateCredentials() Then
DownloadData(Cursor, Round + 1, Token)
Else
ProcessJsonErrorException(uex)
End If
Catch eex As ExitException
Throw eex
Catch ex As Exception
ProcessException(ex, Token, $"data downloading error [{URL}]")
ProcessException(ex, Token, "data downloading error")
End Try
End Sub
Private Sub DownloadSavedPosts(ByVal Cursor As String, ByVal Token As CancellationToken)
Const var_init$ = """__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsInlineReelsEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaUseCometVideoPlaybackEnginerelayprovider"":false,""__relay_internal__pv__BarcelonaOptionalCookiesEnabledrelayprovider"":true,""__relay_internal__pv__BarcelonaIsTextFragmentsEnabledForPostCaptionsrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowFediverseM075Featuresrelayprovider"":true"
Const var_cursor$ = """after"":""{0}"",""first"":25,""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsInlineReelsEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaUseCometVideoPlaybackEnginerelayprovider"":false,""__relay_internal__pv__BarcelonaOptionalCookiesEnabledrelayprovider"":true,""__relay_internal__pv__BarcelonaIsTextFragmentsEnabledForPostCaptionsrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowFediverseM075Featuresrelayprovider"":true"
Dim URL$ = String.Empty
Private Sub DownloadSavedPosts(ByVal Cursor As String, ByVal Round As Integer, ByVal Token As CancellationToken)
'Const var_init$ = """__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsInlineReelsEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaUseCometVideoPlaybackEnginerelayprovider"":false,""__relay_internal__pv__BarcelonaOptionalCookiesEnabledrelayprovider"":true,""__relay_internal__pv__BarcelonaIsTextFragmentsEnabledForPostCaptionsrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowFediverseM075Featuresrelayprovider"":true"
'Const var_cursor$ = """after"":""{0}"",""first"":25,""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsInlineReelsEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaUseCometVideoPlaybackEnginerelayprovider"":false,""__relay_internal__pv__BarcelonaOptionalCookiesEnabledrelayprovider"":true,""__relay_internal__pv__BarcelonaIsTextFragmentsEnabledForPostCaptionsrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowFediverseM075Featuresrelayprovider"":true"
Const var_cursor2$ = """after"":{0},""first"":25,""__relay_internal__pv__BarcelonaQuotedPostUFIEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaHasSelfReplyContextrelayprovider"":false,""__relay_internal__pv__BarcelonaShareableListsrelayprovider"":true,""__relay_internal__pv__BarcelonaIsSearchDiscoveryEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaOptionalCookiesEnabledrelayprovider"":true,""__relay_internal__pv__BarcelonaIsCrawlerrelayprovider"":false,""__relay_internal__pv__BarcelonaHasDisplayNamesrelayprovider"":false,""__relay_internal__pv__BarcelonaCanSeeSponsoredContentrelayprovider"":false,""__relay_internal__pv__BarcelonaShouldShowFediverseM075Featuresrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowTagRedesignrelayprovider"":false,""__relay_internal__pv__BarcelonaIsInternalUserrelayprovider"":false"
Try
DownloadCheckCredentials()
Responser.Method = "POST"
Responser.Referer = "https://www.threads.net/"
Responser.Headers.Add(GQL_HEADER_FB_LSD, Token_lsd)
Responser.Headers.Add(GQL_HEADER_FB_FRINDLY_NAME, If(Cursor.IsEmptyString, GQL_S_NAME_1, GQL_S_NAME_2))
With Responser
.Method = "POST"
.Referer = "https://www.threads.com/"
.ContentType = "application/x-www-form-urlencoded"
With .Headers
.Add(GQL_HEADER_FB_LSD, Token_lsd)
'.Add(GQL_HEADER_FB_FRINDLY_NAME, If(Cursor.IsEmptyString, GQL_S_NAME_1, GQL_S_NAME_2))
.Add(GQL_HEADER_FB_FRINDLY_NAME, GQL_S_NAME_2)
End With
End With
Dim nextCursor$ = String.Empty
Dim dataFound As Boolean = False
Dim vars$ = SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & If(Cursor.IsEmptyString, var_init, String.Format(var_cursor, Cursor)) & "}")
'Dim vars$ = SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & If(Cursor.IsEmptyString, var_init, String.Format(var_cursor, Cursor)) & "}")
Dim vars$ = String.Format(PayloadData, Token_lsd, Token_dtsg_Var, GQL_S_DOC_ID_2, GQL_S_NAME_2,
SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & String.Format(var_cursor2, IIf(Cursor.IsEmptyString, "null", $"""{Cursor}""")) & "}"))
If Cursor.IsEmptyString Then
URL = String.Format(GQL_Q, Token_lsd, Token_dtsg_Var, GQL_S_DOC_ID_1, GQL_S_NAME_1, vars)
Else
URL = String.Format(GQL_Q, Token_lsd, Token_dtsg_Var, GQL_S_DOC_ID_2, GQL_S_NAME_2, vars)
End If
'If Cursor.IsEmptyString Then
' URL = String.Format(GQL_Q, Token_lsd, Token_dtsg_Var, GQL_S_DOC_ID_1, GQL_S_NAME_1, vars)
'Else
' URL = String.Format(GQL_Q, Token_lsd, Token_dtsg_Var, GQL_S_DOC_ID_2, GQL_S_NAME_2, vars)
'End If
Using j As EContainer = GetDocument(URL, Token)
If j.ListExists Then
With j({"data", "xdt_viewer", "text_app_saved_media"})
If .ListExists Then
nextCursor = .Value({"page_info"}, "end_cursor")
With .Item({"edges"})
If .ListExists Then dataFound = DefaultParser(.Self, Sections.Timeline, Token)
End With
End If
End With
Using j As EContainer = GetDocument(GQL_Q2, vars, Token)
If Not CheckErrors(j) Then
If j.ListExists Then
With j({"data", "xdt_text_app_viewer", "saved_media"})
If .ListExists Then
nextCursor = .Value({"page_info"}, "end_cursor")
With .Item({"edges"})
If .ListExists Then dataFound = DefaultParser(.Self, Sections.Timeline, Token)
End With
End If
End With
End If
Else
Throw New JsonErrorException(JsonErrorMessage(j))
End If
End Using
If dataFound And Not nextCursor.IsEmptyString Then DownloadSavedPosts(nextCursor, Token)
If dataFound And Not nextCursor.IsEmptyString Then DownloadSavedPosts(nextCursor, 0, Token)
Catch uex As JsonErrorException
If Round > 0 Then
ProcessJsonErrorException(uex)
ElseIf Not _IdChanged AndAlso UpdateCredentials() Then
DownloadSavedPosts(Cursor, Round + 1, Token)
Else
ProcessJsonErrorException(uex)
End If
Catch eex As ExitException
Throw eex
Catch ex As Exception
ProcessException(ex, Token, $"saved posts downloading error [{URL}]")
ProcessException(ex, Token, "saved posts downloading error")
End Try
End Sub
Private Function GetDocument(ByVal URL As String, ByVal Token As CancellationToken, Optional ByVal Round As Integer = 0) As EContainer
Private Function GetDocument(ByVal URL As String, ByVal PayLoad As String, ByVal Token As CancellationToken, Optional ByVal Round As Integer = 0) As EContainer
Try
ThrowAny(Token)
If Round > 0 AndAlso Not UpdateCredentials() Then DisableDownload() : Throw New Exception("Failed to update credentials")
ThrowAny(Token)
WaitTimer()
Dim r$ = Responser.GetResponse(URL)
Dim r$ = Responser.GetResponse(URL, PayLoad)
If Not r.IsEmptyString Then Return JsonDocument.Parse(r) Else Throw New Exception("Failed to get a response")
Catch ex As Exception
If Round = 0 Then
Return GetDocument(URL, Token, Round + 1)
Return GetDocument(URL, PayLoad, Token, Round + 1)
Else
Throw ex
End If
End Try
End Function
Private _IdChanged As Boolean = False
Private Function UpdateCredentials(Optional ByVal e As ErrorsDescriber = Nothing) As Boolean
Dim URL$ = If(IsSavedPosts, "https://www.threads.net/", $"https://www.threads.net/@{NameTrue}")
Dim URL$ = If(IsSavedPosts, "https://www.threads.com/", $"https://www.threads.com/@{NameTrue}")
ResetBaseTokens()
Dim headers As New HttpHeaderCollection
headers.AddRange(Responser.Headers)
@@ -277,8 +369,8 @@ Namespace API.ThreadsNet
With .Headers
.Clear()
.Add("dnt", 1)
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Authority, "www.threads.net"))
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Origin, "https://www.threads.net"))
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Authority, "www.threads.com"))
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.Origin, "https://www.threads.com"))
.Add("Sec-Ch-Ua-Model", "")
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.SecChUaMobile, "?0"))
.Add(HttpHeaderCollection.GetSpecialHeader(MyHeaderTypes.SecChUaPlatform, """Windows"""))
@@ -293,9 +385,25 @@ Namespace API.ThreadsNet
End With
WaitTimer()
Dim r$ = Responser.GetResponse(URL,, EDP.ThrowException)
Dim newID$
Dim idStr$ = String.Empty
If Not r.IsEmptyString Then
ParseTokens(r, 0)
If ID.IsEmptyString Then ID = RegexReplace(r, RParams.DMS("""props"":\{""user_id"":""(\d+)""", 1, EDP.ReturnValue))
newID = RegexReplace(r, RParams.DMS("""props"":\{[^\{\}]*?""user_id"":""(\d+)""", 1, EDP.ReturnValue))
If ID.IsEmptyString OrElse ID = newID Then
_IdChanged = ID.IsEmptyString
ID = newID
Else
_IdChanged = True
idStr = $"user ID changed from {ID} to {newID}"
LogError(Nothing, idStr)
ID = newID
End If
If _IdChanged Then
If Not idStr.IsEmptyString Then UserDescriptionUpdate(idStr, True, True, True)
_ForceSaveUserInfo = True
_ForceSaveUserInfoOnException = True
End If
End If
Return Valid
Catch ex As Exception
@@ -321,20 +429,22 @@ Namespace API.ThreadsNet
#End Region
#Region "ReparseMissing"
Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken)
Const varsPattern$ = """postID"":""{0}"",""userID"":""{1}"",""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsFeedbackHubEnabledrelayprovider"":false"
'Const varsPattern$ = """postID"":""{0}"",""userID"":""{1}"",""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsFeedbackHubEnabledrelayprovider"":false"
Const varsPattern$ = """postID"":""{0}"",""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowFediverseM1Featuresrelayprovider"":true,""__relay_internal__pv__BarcelonaShareableListsrelayprovider"":true,""__relay_internal__pv__BarcelonaIsSearchDiscoveryEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaOptionalCookiesEnabledrelayprovider"":true,""__relay_internal__pv__BarcelonaQuotedPostUFIEnabledrelayprovider"":false,""__relay_internal__pv__BarcelonaIsCrawlerrelayprovider"":false,""__relay_internal__pv__BarcelonaHasDisplayNamesrelayprovider"":false,""__relay_internal__pv__BarcelonaCanSeeSponsoredContentrelayprovider"":false,""__relay_internal__pv__BarcelonaShouldShowFediverseM075Featuresrelayprovider"":true,""__relay_internal__pv__BarcelonaShouldShowTagRedesignrelayprovider"":false,""__relay_internal__pv__BarcelonaIsInternalUserrelayprovider"":false,""__relay_internal__pv__BarcelonaInlineComposerEnabledrelayprovider"":false"
'Const varsPattern$ = "{""postID"":""{0}"",""__relay_internal__pv__BarcelonaIsLoggedInrelayprovider"":true,""__relay_internal__pv__BarcelonaIsFeedbackHubEnabledrelayprovider"":false}"
Const urlPattern$ = "https://www.threads.net/api/graphql?lsd={0}&variables={1}&fb_api_req_friendly_name=BarcelonaPostPageQuery&server_timestamps=true&fb_dtsg={2}&doc_id=25460088156920903"
'Const urlPattern$ = "https://www.threads.com/api/graphql?lsd={0}&variables={1}&fb_api_req_friendly_name=BarcelonaPostPageQuery&server_timestamps=true&fb_dtsg={2}&doc_id=25460088156920903"
Dim rList As New List(Of Integer)
Dim URL$ = String.Empty
DefaultParser_ElemNode = Nothing
DefaultParser_IgnorePass = True
Try
If ContentMissingExists Then
Responser.Method = "POST"
Responser.Referer = $"https://www.threads.net/@{NameTrue}"
Responser.ContentType = "application/x-www-form-urlencoded"
Responser.Referer = $"https://www.threads.com/@{NameTrue}"
If Not IsSingleObjectDownload AndAlso Not UpdateCredentials() Then Throw New Exception("Failed to update credentials")
Dim m As UserMedia
Dim vars$
Dim r As Byte
Dim j As EContainer
ProgressPre.ChangeMax(_ContentList.Count)
For i% = 0 To _ContentList.Count - 1
@@ -342,21 +452,39 @@ Namespace API.ThreadsNet
m = _ContentList(i)
If m.State = UserMedia.States.Missing And Not m.Post.ID.IsEmptyString Then
ThrowAny(Token)
vars = SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & String.Format(varsPattern, m.Post.ID.Split("_").FirstOrDefault, ID) & "}")
URL = String.Format(urlPattern, Token_lsd, vars, Token_dtsg_Var)
'vars = SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & String.Format(varsPattern, m.Post.ID.Split("_").FirstOrDefault, ID) & "}")
'URL = String.Format(urlPattern, Token_lsd, vars, Token_dtsg_Var)
j = GetDocument(URL, Token)
If j.ListExists Then
With j.ItemF({"data", "data", "edges", 0, "node", "thread_items", 0, "post"})
If .ListExists AndAlso DefaultParser({ .Self}, Sections.Timeline, Token) Then rList.Add(i)
End With
j.Dispose()
End If
vars = String.Format(PayloadData, Token_lsd, Token_dtsg_Var, "9094233770675261", "BarcelonaPostPageDirectQuery",
SymbolsConverter.ASCII.EncodeSymbolsOnly("{" & String.Format(varsPattern, m.Post.ID) & "}"))
For r = 0 To 1
j = GetDocument(GQL_Q2, vars, Token)
If Not CheckErrors(j) Then
If j.ListExists Then
With j.ItemF({"data", "data", "edges", 0, "node", "thread_items", 0, "post"})
If .ListExists AndAlso DefaultParser({ .Self}, Sections.Timeline, Token) Then rList.Add(i)
End With
j.Dispose()
End If
Else
j.DisposeIfReady(False)
If r > 0 Then
ProcessJsonErrorException(New JsonErrorException(JsonErrorMessage(j)))
ElseIf Not _IdChanged AndAlso UpdateCredentials() Then
Continue For
Else
ProcessJsonErrorException(New JsonErrorException(JsonErrorMessage(j)))
End If
End If
Next
End If
Next
End If
Catch eex As ExitException
Throw eex
Catch ex As Exception
ProcessException(ex, Token, $"ReparseMissing error [{URL}]")
ProcessException(ex, Token, "reparseMissing error")
Finally
DefaultParser_ElemNode = DefaultParser_ElemNode_Default
DefaultParser_IgnorePass = False
@@ -374,9 +502,9 @@ Namespace API.ThreadsNet
If Not postCode.IsEmptyString Then
Dim postId$ = CodeToID(postCode)
If Not postId.IsEmptyString Then
_NameTrue = MySettings.IsMyUser(url).UserName
NameTrue = MySettings.IsMyUser(url).UserName
DefaultParser_PostUrlCreator = Function(post) url
If Not _NameTrue.IsEmptyString AndAlso UpdateCredentials(EDP.ReturnValue) Then
If Not NameTrue(True).IsEmptyString AndAlso UpdateCredentials(EDP.ReturnValue) Then
_ContentList.Add(New UserMedia(url) With {.State = UserMedia.States.Missing, .Post = postId})
ReparseMissing(Token)
End If

View File

@@ -6,11 +6,14 @@
'
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports System.Text.RegularExpressions
Imports PersonalUtilities.Functions.RegularExpressions
Namespace API.TikTok
Friend Module Declarations
Friend ReadOnly SimpleDateConverter As New ADateTime("yyyyMMdd")
Friend ReadOnly RegexTagsReplacer As RParams = RParams.DM("#\w+\s?", -1, RegexReturn.Replace,
CType(Function(input$) String.Empty, Func(Of String, String)), EDP.ReturnValue)
Friend ReadOnly RegexPhotoJson As RParams = RParams.DMS("UNIVERSAL_DATA_FOR_REHYDRATION__"" type=""application/json""\>([^\<]+)\<", 1,
RegexOptions.IgnoreCase, EDP.ReturnValue)
End Module
End Namespace

View File

@@ -13,6 +13,15 @@ Imports PersonalUtilities.Functions.RegularExpressions
Namespace API.TikTok
<Manifest("AndyProgram_TikTok"), SpecialForm(False), SeparatedTasks(1)>
Friend Class SiteSettings : Inherits SiteSettingsBase
#Region "Categories"
Private Const CAT_DOWN As String = "Download"
#End Region
#Region "Download"
<PropertyOption(ControlText:="Download videos", Category:=CAT_DOWN), PXML, PClonable>
Friend ReadOnly Property DownloadTTVideos As PropertyValue
<PropertyOption(ControlText:="Download photos", Category:=CAT_DOWN), PXML, PClonable>
Friend ReadOnly Property DownloadTTPhotos As PropertyValue
#End Region
<PropertyOption(ControlText:="Remove tags from title"), PXML, PClonable>
Friend ReadOnly Property RemoveTagsFromTitle As PropertyValue
<PropertyOption(ControlText:="Use native title", ControlToolTip:="Use a user-created video title for the filename instead of the video ID."), PXML, PClonable>
@@ -34,8 +43,15 @@ Namespace API.TikTok
<PropertyOption(ControlText:="Use video date as file date (standalone downloader)",
ControlToolTip:="Set the file date to the date the video was added (website) (if available)."), PXML, PClonable>
Friend ReadOnly Property UseParsedVideoDateSTD As PropertyValue
<DoNotUse> Friend Overrides Property DownloadText As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextPosts As PropertyValue
<DoNotUse> Friend Overrides Property DownloadTextSpecialFolder As PropertyValue
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
MyBase.New("TikTok", "www.tiktok.com", AccName, Temp, My.Resources.SiteResources.TikTokIcon_32, My.Resources.SiteResources.TikTokPic_192)
DownloadTTVideos = New PropertyValue(True)
DownloadTTPhotos = New PropertyValue(True)
RemoveTagsFromTitle = New PropertyValue(False)
TitleUseNative = New PropertyValue(True)
TitleUseNativeSTD = New PropertyValue(True)
@@ -45,6 +61,7 @@ Namespace API.TikTok
TitleUseRegexForTitle_Value = New PropertyValue(String.Empty, GetType(String))
UseParsedVideoDate = New PropertyValue(True)
UseParsedVideoDateSTD = New PropertyValue(False)
UseNetscapeCookies = True
UrlPatternUser = "https://www.tiktok.com/@{0}/"
UserRegex = RParams.DMS(String.Format(UserRegexDefaultPattern, "tiktok.com/@"), 1)
@@ -62,8 +79,5 @@ Namespace API.TikTok
Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using
End If
End Sub
Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String
Return String.Format(UrlPatternUser, DirectCast(User, UserData).TrueName)
End Function
End Class
End Namespace

View File

@@ -13,6 +13,7 @@ Imports PersonalUtilities.Functions.XML
Imports PersonalUtilities.Functions.RegularExpressions
Imports PersonalUtilities.Tools
Imports PersonalUtilities.Tools.Web.Documents.JSON
Imports UTypes = SCrawler.API.Base.UserMedia.Types
Namespace API.TikTok
Friend Class UserData : Inherits UserDataBase
#Region "XML names"
@@ -23,6 +24,7 @@ Namespace API.TikTok
Private Const Name_TitleUseRegexForTitle As String = "TitleUseRegexForTitle"
Private Const Name_TitleUseRegexForTitle_Value As String = "TitleUseRegexForTitle_Value"
Private Const Name_TitleUseGlobalRegexOptions As String = "TitleUseGlobalRegexOptions"
Private Const Name_PhotosDownloaded As String = "PhotosDownloaded"
#End Region
#Region "Declarations"
Private ReadOnly Property MySettings As SiteSettings
@@ -62,15 +64,7 @@ Namespace API.TikTok
Friend Property TitleUseRegexForTitle_Value As String = String.Empty
Friend Property TitleUseGlobalRegexOptions As Boolean = True
Private Property LastDownloadDate As Date? = Nothing
Private _TrueName As String = String.Empty
Friend Property TrueName As String
Get
Return _TrueName.IfNullOrEmpty(Name)
End Get
Set(ByVal NewName As String)
_TrueName = NewName
End Set
End Property
Private Property PhotosDownloaded As Boolean = False
#End Region
#Region "Exchange"
Friend Overrides Function ExchangeOptionsGet() As Object
@@ -98,19 +92,19 @@ Namespace API.TikTok
TitleAddVideoID = .Value(Name_TitleAddVideoID).FromXML(Of Boolean)(True)
LastDownloadDate = AConvert(Of Date)(.Value(Name_LastDownloadDate), ADateTime.Formats.BaseDateTime, Nothing)
If Not LastDownloadDate.HasValue Then LastDownloadDate = LastUpdated
_TrueName = .Value(Name_TrueName)
TitleUseRegexForTitle = .Value(Name_TitleUseRegexForTitle).FromXML(Of Boolean)(False)
TitleUseRegexForTitle_Value = .Value(Name_TitleUseRegexForTitle_Value)
TitleUseGlobalRegexOptions = .Value(Name_TitleUseGlobalRegexOptions).FromXML(Of Boolean)(True)
PhotosDownloaded = .Value(Name_PhotosDownloaded).FromXML(Of Boolean)(False)
Else
.Add(Name_RemoveTagsFromTitle, RemoveTagsFromTitle.BoolToInteger)
.Add(Name_TitleUseNative, TitleUseNative.BoolToInteger)
.Add(Name_TitleAddVideoID, TitleAddVideoID.BoolToInteger)
.Add(Name_LastDownloadDate, AConvert(Of String)(LastDownloadDate, AModes.XML, ADateTime.Formats.BaseDateTime, String.Empty))
.Add(Name_TrueName, _TrueName)
.Add(Name_TitleUseRegexForTitle, TitleUseRegexForTitle.BoolToInteger)
.Add(Name_TitleUseRegexForTitle_Value, TitleUseRegexForTitle_Value)
.Add(Name_TitleUseGlobalRegexOptions, TitleUseGlobalRegexOptions.BoolToInteger)
.Add(Name_PhotosDownloaded, PhotosDownloaded.BoolToInteger)
End If
End With
End Sub
@@ -153,7 +147,7 @@ Namespace API.TikTok
End Function
Private Function GetNewFileName(ByVal Title As String, ByVal Native As Boolean, ByVal RemoveTags As Boolean, ByVal AddVideoID As Boolean,
ByVal PostID As String, ByVal TitleRegex As RParams) As String
If Not Title.IsEmptyString Then Title = Left(Title, 150).StringTrim
If Not Title.IsEmptyString Then Title = TitleHtmlConverter(Left(Title, 150)).StringTrim
If Title.IsEmptyString Or Not Native Then
Title = PostID
Else
@@ -168,22 +162,33 @@ Namespace API.TikTok
End If
Return Title
End Function
Private Function GetPhotoNode() As Object()
Return {"imageURL", "urlList", 0, 0}
End Function
Friend Overrides Sub DownloadData(ByVal Token As CancellationToken)
MyBase.DownloadData(Token)
UserCache.DisposeIfReady(False)
UserCache = Nothing
End Sub
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
Dim URL$ = $"https://www.tiktok.com/@{TrueName}"
Dim URL$ = $"https://www.tiktok.com/@{NameTrue}"
UserCache = CreateCache()
Try
Dim postID$, title$, postUrl$, newName$
Const photoPrefix$ = "photo_"
Dim postID$, title$, postUrl$, newName$, t$, postID2$, imgUrl$
Dim postDate As Date?
Dim dateAfterC As Date? = Nothing
Dim dateBefore As Date? = DownloadDateTo
Dim dateAfter As Date? = DownloadDateFrom
Dim baseDataObtained As Boolean = False
Dim titleRegex As RParams = GetTitleRegex()
Dim vPath As SFile = Nothing, pPath As SFile = Nothing
Dim file As SFile
Dim j As EContainer, photo As EContainer
Dim photoNode As Object() = GetPhotoNode()
Dim c%, cc%, i%
Dim errDef As New ErrorsDescriber(EDP.ReturnValue)
Dim infoParsed As Boolean = False
If _ContentList.Count > 0 Then
With (From d In _ContentList Where d.Post.Date.HasValue Select d.Post.Date.Value)
@@ -209,60 +214,151 @@ Namespace API.TikTok
End If
End If
Using b As New YTDLP.YTDLPBatch(Token) With {.TempPostsList = _TempPostsList}
b.Commands.Clear()
b.ChangeDirectory(UserCache)
b.Encoding = BatchExecutor.UnicodeEncoding
b.Execute(CreateYTCommand(UserCache.RootDirectory, URL, False, dateBefore, dateAfter))
End Using
If DownloadVideos And Settings.YtdlpFile.Exists And CBool(MySettings.DownloadTTVideos.Value) Then
With UserCache.NewInstance : .Validate() : vPath = .RootDirectory : End With
Using b As New YTDLP.YTDLPBatch(Token) With {.TempPostsList = _TempPostsList}
b.Commands.Clear()
b.ChangeDirectory(vPath)
b.Encoding = BatchExecutor.UnicodeEncoding
b.Execute(CreateYTCommand(vPath, URL, False, dateBefore, dateAfter))
End Using
End If
If DownloadImages And Settings.GalleryDLFile.Exists And CBool(MySettings.DownloadTTPhotos.Value) Then
With UserCache.NewInstance : .Validate() : pPath = .RootDirectory : End With
Using b As New GDL.GDLBatch(Token)
With b
If PhotosDownloaded And _TempPostsList.Count > 0 Then
.TempPostsList = (From p As String In _TempPostsList
Where Not p.IsEmptyString AndAlso p.StartsWith(photoPrefix)
Select p.Replace(photoPrefix, String.Empty)).ListIfNothing
Else
.TempPostsList = New List(Of String)
End If
.ChangeDirectory(pPath)
.Encoding = BatchExecutor.UnicodeEncoding
.Execute(CreateGDLCommand(URL))
If Not PhotosDownloaded Then _ForceSaveUserInfo = True : _ForceSaveUserInfoOnException = True
PhotosDownloaded = True
End With
End Using
End If
ThrowAny(Token)
Dim files As List(Of SFile) = SFile.GetFiles(UserCache, "*.json",, EDP.ReturnValue)
If files.ListExists Then
Dim j As EContainer
For Each file As SFile In files
j = JsonDocument.Parse(file.GetText, EDP.ReturnValue)
If j.ListExists Then
If j.Value("_type").StringToLower = "video" Then
If Not baseDataObtained Then
baseDataObtained = True
If ID.IsEmptyString Then
ID = j.Value("uploader_id")
If Not ID.IsEmptyString Then _ForceSaveUserInfo = True
Dim files As List(Of SFile)
If Not vPath.IsEmptyString AndAlso vPath.Exists(SFO.Path, False) Then
files = SFile.GetFiles(vPath, "*.json",, errDef)
If files.ListExists Then
For Each file In files
j = JsonDocument.Parse(file.GetText, errDef)
If j.ListExists Then
If j.Value("_type").StringToLower = "video" Then
If Not baseDataObtained Then
baseDataObtained = True
If ID.IsEmptyString Then ID = j.Value("uploader_id")
newName = j.Value("uploader")
If Not newName.IsEmptyString Then NameTrue = newName
newName = j.Value("creator")
If Not newName.IsEmptyString Then UserSiteName = newName
End If
newName = j.Value("uploader")
If Not newName.IsEmptyString Then
If Not _TrueName = newName Then _ForceSaveUserInfo = True
_TrueName = newName
postID = j.Value("id")
If Not _TempPostsList.Contains(postID) Then
_TempPostsList.ListAddValue(postID, LNC)
Else
Exit For 'Exit Sub
End If
newName = j.Value("creator")
If Not newName.IsEmptyString Then UserSiteName = newName
End If
postID = j.Value("id")
If Not _TempPostsList.Contains(postID) Then
_TempPostsList.Add(postID)
Else
Exit Sub
End If
title = GetNewFileName(j.Value("title").StringRemoveWinForbiddenSymbols,
TitleUseNative, RemoveTagsFromTitle, TitleAddVideoID, postID, titleRegex)
postDate = AConvert(Of Date)(j.Value("timestamp"), UnixDate32Provider, Nothing)
If Not postDate.HasValue Then postDate = AConvert(Of Date)(j.Value("upload_date"), SimpleDateConverter, Nothing)
Select Case CheckDatesLimit(postDate, SimpleDateConverter)
Case DateResult.Skip : Continue For
Case DateResult.Exit : Exit Sub
End Select
title = GetNewFileName(j.Value("title").StringRemoveWinForbiddenSymbols,
TitleUseNative, RemoveTagsFromTitle, TitleAddVideoID, postID, titleRegex)
postDate = AConvert(Of Date)(j.Value("timestamp"), UnixDate32Provider, Nothing)
If Not postDate.HasValue Then postDate = AConvert(Of Date)(j.Value("upload_date"), SimpleDateConverter, Nothing)
Select Case CheckDatesLimit(postDate, SimpleDateConverter)
Case DateResult.Skip : Continue For
Case DateResult.Exit : Exit For 'Exit Sub
End Select
postUrl = j.Value("webpage_url")
If postUrl.IsEmptyString Then postUrl = $"https://www.tiktok.com/@{Name}/video/{postID}"
_TempMediaList.Add(New UserMedia(postUrl, UserMedia.Types.Video) With {
.File = $"{title}.mp4", .Post = New UserPost(postID, postDate)})
postUrl = j.Value("webpage_url")
If postUrl.IsEmptyString Then postUrl = $"https://www.tiktok.com/@{Name}/video/{postID}"
_TempMediaList.Add(New UserMedia(postUrl, UTypes.Video) With {
.File = $"{title}.mp4", .Post = New UserPost(postID, postDate)})
End If
j.Dispose()
End If
j.Dispose()
End If
Next
Next
End If
End If
If Not pPath.IsEmptyString AndAlso pPath.Exists(SFO.Path, False) Then
files = SFile.GetFiles(pPath, "*.txt",, errDef)
If files.ListExists Then
For Each file In files
t = file.GetText(errDef)
If Not t.IsEmptyString Then t = RegexReplace(t, RegexPhotoJson)
If Not t.IsEmptyString Then
j = JsonDocument.Parse(t, errDef)
If j.ListExists Then
With j.ItemF({0, "webapp.video-detail", "itemInfo", "itemStruct"})
If .ListExists Then
postID = .Value("id")
postID2 = $"{photoPrefix}{postID}"
If Not _TempPostsList.Contains(postID2) Then _TempPostsList.ListAddValue(postID2, LNC) Else Exit For 'Exit Sub
postDate = AConvert(Of Date)(j.Value("createTime"), UnixDate32Provider, Nothing)
Select Case CheckDatesLimit(postDate, SimpleDateConverter)
Case DateResult.Skip : Continue For
Case DateResult.Exit : Exit For 'Exit Sub
End Select
If Not infoParsed Then
With .Item("author")
If .ListExists Then
infoParsed = True
SimpleDownloadAvatar(.Value("avatarLarger").IfNullOrEmpty(.Value("avatarMedium")).IfNullOrEmpty(.Value("avatarThumb")),
Function(ByVal ____url As String) As SFile
Dim ____f As SFile = CreateFileFromUrl(____url)
If Not ____f.Name.IsEmptyString Then ____f.Name = ____f.Name.Replace(":", "_").Replace("~", "-")
If Not ____f.Extension.IsEmptyString Then
If Not (____f.Extension = "jpg" Or ____f.Extension = "jpeg") Then
____f.Extension = RegexReplace(____f.Extension, RParams.DMS("(.+)\?", 1, EDP.ReturnValue))
If Not ____f.Extension.IsEmptyString AndAlso Not (____f.Extension = "jpg" Or ____f.Extension = "jpeg") Then ____f.Extension = String.Empty
End If
End If
Return ____f
End Function)
UserSiteNameUpdate(.Value("nickname"))
UserDescriptionUpdate(.Value("signature"))
End If
End With
End If
title = GetNewFileName(j.Value({"imagePost"}, "title").StringRemoveWinForbiddenSymbols,
TitleUseNative, RemoveTagsFromTitle, TitleAddVideoID, postID, titleRegex)
postUrl = $"https://www.tiktok.com/@{Name}/photo/{postID}"
With .Item({"imagePost", "images"})
If .ListExists Then
i = 0
c = .Count
cc = Math.Max(c.ToString.Length, 3)
For Each photo In .Self
i += 1
imgUrl = photo.ItemF(photoNode).XmlIfNothingValue
If Not imgUrl.IsEmptyString Then _
_TempMediaList.Add(New UserMedia(imgUrl, UTypes.Picture) With {
.URL_BASE = postUrl,
.SpecialFolder = "Photo",
.File = $"{title}{IIf(c > 1, $"_{i.NumToString(ANumbers.Formats.NumberGroup, cc)}", String.Empty)}.jpg",
.Post = New UserPost(postID, postDate)})
Next
End If
End With
End If
End With
j.Dispose()
End If
End If
Next
End If
End If
If _TempMediaList.Count > 0 Then LastDownloadDate = Now
Catch ex As Exception
ProcessException(ex, Token, $"data downloading error [{URL}]")
@@ -273,16 +369,41 @@ Namespace API.TikTok
Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken)
If ContentMissingExists Then
Dim m As UserMedia
Dim d As IYouTubeMediaContainer = Nothing
Dim i%
Dim rList As New List(Of Integer)
Dim picIDs As New List(Of String)
Dim defDir As SFile = SFile.GetPath(DownloadContentDefault_GetRootDir())
Dim result As Boolean
For i = 0 To _ContentList.Count - 1
If _ContentList(i).State = UserMedia.States.Missing Then
m = _ContentList(i)
m.URL = m.URL_BASE
_TempMediaList.Add(m)
rList.Add(i)
result = False
Try
If m.Type = UTypes.Video Then
d = MySettings.GetSingleMediaInstance(m.URL_BASE, defDir)
result = False
If If(UserCache?.Disposed, True) Then UserCache = CreateCache()
DownloadSingleObject_GetPosts(d, Token, UserCache, result)
ElseIf m.Type = UTypes.Picture Then
If picIDs.Contains(m.Post.ID) Then
rList.Add(i)
Else
d = MySettings.GetSingleMediaInstance(m.URL_BASE, defDir)
If If(UserCache?.Disposed, True) Then UserCache = CreateCache()
DownloadSingleObject_GetPosts(d, Token, UserCache, result)
picIDs.Add(m.Post.ID)
End If
End If
Catch ex As Exception
result = False
ProcessException(ex, Token, "ReparseMissing")
End Try
If result Then rList.Add(i)
d.DisposeIfReady(False)
End If
Next
picIDs.Clear()
If rList.Count > 0 Then
For i% = rList.Count - 1 To 0 Step -1 : _ContentList.RemoveAt(rList(i)) : Next
End If
@@ -317,10 +438,18 @@ Namespace API.TikTok
Return command
End Function
#End Region
#Region "GDL Support"
Private Function CreateGDLCommand(ByVal URL As String) As String
Return $"""{Settings.GalleryDLFile}"" --verbose --no-download --no-skip --write-pages {URL}"
End Function
#End Region
#Region "DownloadContent, DownloadFile"
Protected Overrides Sub DownloadContent(ByVal Token As CancellationToken)
DownloadContentDefault(Token)
End Sub
Protected Overrides Function ValidateDownloadFile(ByVal URL As String, ByVal Media As UserMedia, ByRef Interrupt As Boolean) As Boolean
Return Not Media.Type = UTypes.Picture
End Function
Protected Overrides Function DownloadFile(ByVal URL As String, ByVal Media As UserMedia, ByVal DestinationFile As SFile, ByVal Token As CancellationToken) As SFile
Using b As New TokenBatch(Token) With {.FileExchanger = RootCacheTikTok}
b.Encoding = BatchExecutor.UnicodeEncoding
@@ -330,33 +459,94 @@ Namespace API.TikTok
End Function
#End Region
#Region "DownloadSingleObject"
Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
Protected Overloads Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
DownloadSingleObject_GetPosts(Data, Token, Nothing, Nothing)
End Sub
Private Overloads Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken,
ByRef Cache As CacheKeeper, ByRef Result As Boolean)
Dim f$ = String.Empty
If CBool(MySettings.TitleUseNativeSTD.Value) Then
Using b As New BatchExecutor(True) With {
.Encoding = BatchExecutor.UnicodeEncoding,
.CleanAutomaticallyViaRegEx = True,
.CleanAutomaticallyViaRegExRemoveAllCommands = True
}
b.Execute(CreateYTCommand(Nothing, Data.URL, True,,, True, False))
b.Clean()
With b.OutputData
If .Count > 0 Then
For Each vData$ In .Self
If Not vData.Contains($": {BatchExecutor.UnicodeEncoding}") Then f = vData : Exit For
Next
End If
End With
Dim urlsList As New List(Of String)
Dim t As UTypes
Dim defName$ = New SFile(Data.URL).Name
If Data.URL.ToLower.Contains("/video/") Then
urlsList.Add(Data.URL)
t = UTypes.Video
If CBool(MySettings.TitleUseNativeSTD.Value) Then
Using b As New BatchExecutor(True) With {
.Encoding = BatchExecutor.UnicodeEncoding,
.CleanAutomaticallyViaRegEx = True,
.CleanAutomaticallyViaRegExRemoveAllCommands = True
}
b.Execute(CreateYTCommand(Nothing, Data.URL, True,,, True, False))
b.Clean()
With b.OutputData
If .Count > 0 Then
For Each vData$ In .Self
If Not vData.Contains($": {BatchExecutor.UnicodeEncoding}") Then f = vData : Exit For
Next
End If
End With
End Using
End If
Else
t = UTypes.Picture
Data.ContentType = Plugin.UserMediaTypes.Picture
Data.Title = defName
Dim dir As SFile
With If(Cache, Settings.Cache).NewInstance() : .Validate() : dir = .RootDirectory : End With
Using b As New GDL.GDLBatch(Token)
b.ChangeDirectory(dir)
b.Encoding = BatchExecutor.UnicodeEncoding
b.Execute(CreateGDLCommand(Data.URL))
End Using
Dim file As SFile = SFile.GetFiles(dir, "*.txt",, EDP.ReturnValue).FirstOrDefault
If file.Exists Then
Dim r$ = file.GetText(EDP.ReturnValue)
If Not r.IsEmptyString Then r = RegexReplace(r, RegexPhotoJson)
If Not r.IsEmptyString Then
Using j As EContainer = JsonDocument.Parse(r, EDP.ReturnValue)
If j.ListExists Then
With j.ItemF({0, "webapp.video-detail", "itemInfo", "itemStruct"})
If CBool(MySettings.TitleUseNativeSTD.Value) Then f = j.Value({"imagePost"}, "title").StringRemoveWinForbiddenSymbols
With .Item({"imagePost", "images"})
If .ListExists Then
For Each photo As EContainer In .Self : urlsList.Add(photo.ItemF(GetPhotoNode()).XmlIfNothingValue) : Next
End If
End With
End With
End If
End Using
End If
End If
End If
Dim m As New UserMedia(Data.URL, UserMedia.Types.Video)
If Not f.IsEmptyString Then f = TitleHtmlConverter(f)
If Not f.IsEmptyString Then
f = GetNewFileName(f, MySettings.TitleUseNativeSTD.Value, MySettings.RemoveTagsFromTitle.Value, MySettings.TitleAddVideoIDSTD.Value,
m.File.Name, GetTitleRegex)
If Not f.IsEmptyString Then m.File.Name = f.StringTrim
Dim m As UserMedia
Dim i% = 0, c%, cc%
Dim ff As Boolean = False
If urlsList.Count > 0 Then
c = urlsList.Count
cc = Math.Max(c.ToString.Length, 3)
For Each url$ In urlsList
i += 1
m = New UserMedia(url, t) With {.URL_BASE = Data.URL}
If Not f.IsEmptyString Then f = TitleHtmlConverter(f)
If Not f.IsEmptyString Or t = UTypes.Picture Then
If Not ff Then f = GetNewFileName(f, MySettings.TitleUseNativeSTD.Value, MySettings.RemoveTagsFromTitle.Value, MySettings.TitleAddVideoIDSTD.Value,
defName, GetTitleRegex)
ff = True
If Not f.IsEmptyString Then
m.File.Name = $"{f.StringTrim}{IIf(c > 1, $"_{i.NumToString(ANumbers.Formats.NumberGroup, cc)}", String.Empty)}"
If t = UTypes.Picture Then m.File.Extension = "jpg"
End If
End If
_TempMediaList.Add(m)
Result = True
Next
End If
_TempMediaList.Add(m)
End Sub
Protected Overrides Sub DownloadSingleObject_PostProcessing(ByVal Data As IYouTubeMediaContainer, Optional ByVal ResetTitle As Boolean = True)
MyBase.DownloadSingleObject_PostProcessing(Data, Not Data.ContentType = Plugin.UserMediaTypes.Picture)
End Sub
#End Region
#Region "EraseData"

View File

@@ -7,7 +7,7 @@
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY
Imports System.Globalization
Imports PersonalUtilities.Functions.XML.Base
Imports System.Text.RegularExpressions
Imports PersonalUtilities.Functions.RegularExpressions
Namespace API.Twitter
Friend Module Declarations
@@ -16,6 +16,9 @@ Namespace API.Twitter
Friend ReadOnly DateProvider As ADateTime = GetDateProvider()
Friend ReadOnly VideoSizeRegEx As RParams = RParams.DMS("\d+x(\d+)", 1, EDP.ReturnValue)
Friend ReadOnly StatusRegEx As RParams = RParams.DM(".*?(twitter|x)\.com/\S+/status/\d+", 0, EDP.ReturnValue)
Friend ReadOnly BroadcastsUrls As Object() = {"entities", "urls", 0, "expanded_url"}
Friend ReadOnly GdlLimitRegEx As RParams = RParams.DM("Waiting until[\s\W\d\:]+\(rate limit\)", 0, RegexOptions.IgnoreCase, EDP.ReturnValue)
Friend ReadOnly GdlPostCoutNumberNodes As String() = {"data", "user", "result", "legacy", "statuses_count"}
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"

View File

@@ -8,11 +8,10 @@
' but WITHOUT ANY WARRANTY
Imports SCrawler.Plugin.Attributes
Imports DModels = SCrawler.API.Twitter.UserData.DownloadModels
Imports DN = SCrawler.API.Base.DeclaredNames
Namespace API.Twitter
Friend Class EditorExchangeOptions
Friend Class EditorExchangeOptions : Inherits Base.EditorExchangeOptionsBase
Private Const DefaultOffset As Integer = 100
Friend Property SiteKey As String = TwitterSiteKey
Friend Overrides Property SiteKey As String = TwitterSiteKey
<PSetting(NameOf(SiteSettings.GifsDownload), NameOf(MySettings), LeftOffset:=DefaultOffset)>
Friend Property GifsDownload As Boolean
<PSetting(NameOf(SiteSettings.GifsSpecialFolder), NameOf(MySettings), LeftOffset:=DefaultOffset)>
@@ -43,14 +42,17 @@ Namespace API.Twitter
Caption:="Download model 'Likes'",
ToolTip:="Download the data using the 'https://x.com/UserName/likes' command.", LeftOffset:=DefaultOffset)>
Friend Overridable Property DownloadModelLikes As Boolean = False
<PSetting(Address:=SettingAddress.User,
Caption:="Download 'Broadcasts'",
ToolTip:="Download broadcasts posted by the user using the 'https://x.com/i/broadcasts/abcdef1234567' URLs", LeftOffset:=DefaultOffset)>
Friend Overridable Property DownloadBroadcasts As Boolean = False
<PSetting(Address:=SettingAddress.User,
Caption:="Force apply",
ToolTip:="Force overrides the default parameters for the first download." & vbCr & "Applies to first download only.", LeftOffset:=DefaultOffset)>
Friend Overridable Property DownloadModelForceApply As Boolean = False
<PSetting(Address:=SettingAddress.User, Caption:=DN.UserNameChangeCaption, ToolTip:=DN.UserNameChangeToolTip, LeftOffset:=DefaultOffset)>
Friend Overridable Property UserName As String = String.Empty
Private ReadOnly Property MySettings As Object
Friend Sub New(ByVal s As SiteSettings)
MyBase.New(s)
GifsDownload = s.GifsDownload.Value
GifsSpecialFolder = s.GifsSpecialFolder.Value
GifsPrefix = s.GifsPrefix.Value
@@ -67,6 +69,7 @@ Namespace API.Twitter
MySettings = s
End Sub
Friend Sub New(ByVal u As UserData)
MyBase.New(u)
GifsDownload = u.GifsDownload
GifsSpecialFolder = u.GifsSpecialFolder
GifsPrefix = u.GifsPrefix
@@ -75,6 +78,7 @@ Namespace API.Twitter
MediaModelAllowNonUserTweets = u.MediaModelAllowNonUserTweets
If Not TypeOf u Is Mastodon.UserData Then
DownloadModelForceApply = u.DownloadModelForceApply
DownloadBroadcasts = u.DownloadBroadcasts
Dim dm As DModels() = EnumExtract(Of DModels)(u.DownloadModel)
If dm.ListExists Then
DownloadModelMedia = dm.Contains(DModels.Media)
@@ -83,7 +87,6 @@ Namespace API.Twitter
DownloadModelLikes = dm.Contains(DModels.Likes)
End If
End If
UserName = u.NameTrue(True)
MySettings = u.HOST.Source
End Sub
End Class

View File

@@ -11,14 +11,50 @@ Imports SCrawler.Plugin
Imports SCrawler.Plugin.Attributes
Imports PersonalUtilities.Functions.RegularExpressions
Imports PersonalUtilities.Tools.Web.Clients
Imports PersonalUtilities.Tools.Web.Cookies
Imports DN = SCrawler.API.Base.DeclaredNames
Imports IG = SCrawler.API.Instagram.SiteSettings
Namespace API.Twitter
<Manifest(TwitterSiteKey), SavedPosts, SeparatedTasks, SpecialForm(False)>
Friend Class SiteSettings : Inherits SiteSettingsBase
#Region "Declarations"
#Region "Icon"
<PXML("UseNewIcon")> Private ReadOnly Property UseNewIconXML As PropertyValue
<PropertyOption(ControlText:="Use the new Twitter icon (X)", ControlToolTip:="Restart SCrawler to take effect")>
Private ReadOnly Property UseNewIcon As PropertyValue
Get
If Not DefaultInstance Is Nothing Then
Return DirectCast(DefaultInstance, SiteSettings).UseNewIconXML
Else
Return UseNewIconXML
End If
End Get
End Property
Private Sub UpdateIcon()
If CBool(UseNewIcon.Value) Then _Icon = My.Resources.SiteResources.TwitterIconNew_32 : _Image = _Icon.ToBitmap
End Sub
#End Region
#Region "Categories"
Private Const CAT_DOWN As String = "Downloading"
#End Region
#Region "Auth"
<PropertyOption(ControlText:="Update cookies", ControlToolTip:="Update cookies during requests", IsAuth:=True), PXML, PClonable, HiddenControl>
Friend ReadOnly Property CookiesUpdate As PropertyValue
<PropertyOption(ControlText:="Use UserAgent", ControlToolTip:="Use UserAgent in requests", IsAuth:=True), PXML, PClonable>
Friend ReadOnly Property UserAgentUse As PropertyValue
<PropertyOption(ControlText:="UserAgent", IsAuth:=True, AllowNull:=True, InheritanceName:=SettingsCLS.HEADER_DEF_UserAgent),
PXML("UserAgent", OnlyForChecked:=True), PClonable>
Private ReadOnly Property UserAgentXML As PropertyValue
Friend ReadOnly Property UserAgent As String
Get
If CBool(UserAgentUse.Value) AndAlso Not CStr(UserAgentXML.Value).IsEmptyString Then
Return UserAgentXML.Value
Else
Return String.Empty
End If
End Get
End Property
#End Region
#Region "Other properties"
<PropertyOption(ControlText:="Use the appropriate model",
ControlToolTip:="Use the appropriate model for new users." & vbCr &
@@ -35,10 +71,30 @@ Namespace API.Twitter
Friend Property UseNewEndPointProfiles As PropertyValue
#End Region
#Region "Limits"
Friend Const TimerDisabled As Integer = -1
Friend Const TimerFirstUseTheSame As Integer = -2
<PropertyOption(ControlText:="Abort on limit", ControlToolTip:="Abort twitter downloading when limit is reached", Category:=CAT_DOWN), PXML, PClonable>
Friend Property AbortOnLimit As PropertyValue
<PropertyOption(ControlText:="Download already parsed", ControlToolTip:="Download already parsed content on abort", Category:=CAT_DOWN), PXML, PClonable>
Friend Property DownloadAlreadyParsed As PropertyValue
#End Region
#Region "Timers"
<PropertyOption(ControlText:="Sleep timer",
ControlToolTip:="Use sleep timer in requests." & vbCr &
"You can set a timer value (in seconds) to wait before each subsequent request." & vbCr &
"-1 to disable and use the default algorithm." & vbCr &
"Default: 20", Category:=CAT_DOWN), PXML, PClonable>
Friend ReadOnly Property SleepTimer As PropertyValue
<Provider(NameOf(SleepTimer), FieldsChecker:=True)>
Private ReadOnly Property SleepTimerProvider As IFormatProvider
<PropertyOption(ControlText:="Sleep timer at start",
ControlToolTip:="Set a sleep timer (in seconds) before the first request." & vbCr &
"-1 to disable" & vbCr &
"-2 to use the 'Sleep timer' value" & vbCr &
"Default: -2", Category:=CAT_DOWN), PXML, PClonable>
Friend ReadOnly Property SleepTimerBeforeFirst As PropertyValue
<Provider(NameOf(SleepTimerBeforeFirst), FieldsChecker:=True)>
Private ReadOnly Property SleepTimerBeforeFirstProvider As IFormatProvider
#End Region
<PropertyOption(ControlText:="Media Model: allow non-user tweets", ControlToolTip:="Allow downloading non-user tweets in the media-model.", Category:=DN.CAT_UserDefs), PXML, PClonable>
Friend ReadOnly Property MediaModelAllowNonUserTweets As PropertyValue
@@ -76,7 +132,17 @@ Namespace API.Twitter
<Provider(NameOf(ConcurrentDownloads), FieldsChecker:=True)>
Private ReadOnly Property MyConcurrentDownloadsProvider As IFormatProvider
#End Region
Friend Overrides Property DefaultInstance As ISiteSettings
Get
Return MyBase.DefaultInstance
End Get
Set(ByVal Instance As ISiteSettings)
MyBase.DefaultInstance = Instance
If Not Instance Is Nothing Then UpdateIcon()
End Set
End Property
#End Region
#Region "Initializer"
Friend Sub New(ByVal AccName As String, ByVal Temp As Boolean)
MyBase.New(TwitterSite, "x.com", AccName, Temp, My.Resources.SiteResources.TwitterIcon_32, My.Resources.SiteResources.TwitterIcon_32.ToBitmap)
@@ -87,11 +153,28 @@ Namespace API.Twitter
.Cookies.Changed = False
End With
UseNewIconXML = New PropertyValue(False)
CookiesUpdate = New PropertyValue(False)
UserAgentUse = New PropertyValue(True)
UserAgentXML = New PropertyValue(If(Responser.UserAgentExists, Responser.UserAgent, String.Empty), GetType(String),
Sub(ByVal Value As Object)
Responser.UserAgent = CStr(Value)
Responser.SaveSettings(, EDP.ReturnValue)
End Sub)
UseAppropriateModel = New PropertyValue(True)
UseNewEndPointSearch = New PropertyValue(True)
UseNewEndPointProfiles = New PropertyValue(True)
AbortOnLimit = New PropertyValue(True)
DownloadAlreadyParsed = New PropertyValue(True)
SleepTimer = New PropertyValue(TimerDisabled)
SleepTimerProvider = New IG.TimersChecker(TimerDisabled)
SleepTimerBeforeFirst = New PropertyValue(TimerFirstUseTheSame)
SleepTimerBeforeFirstProvider = New IG.TimersChecker(TimerFirstUseTheSame)
MediaModelAllowNonUserTweets = New PropertyValue(False)
GifsDownload = New PropertyValue(True)
GifsSpecialFolder = New PropertyValue(String.Empty, GetType(String))
@@ -101,28 +184,60 @@ Namespace API.Twitter
ConcurrentDownloads = New PropertyValue(1)
MyConcurrentDownloadsProvider = New ConcurrentDownloadsProvider
_AllowUserAgentUpdate = False
UserRegex = RParams.DMS(String.Format(UserRegexDefaultPattern, $"/(twitter|x).com({CommunitiesUser}|)/"), 3)
UrlPatternUser = "https://x.com/{0}"
ImageVideoContains = "twitter"
CheckNetscapeCookiesOnEndInit = True
UseNetscapeCookies = True
End Sub
Friend Overrides Sub EndInit()
UpdateIcon()
MyBase.EndInit()
End Sub
#End Region
#Region "GetInstance"
Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider
Return New UserData
End Function
Friend Const SinglePostPattern As String = "https://x.com/i/web/status/{0}"
Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String
Return String.Format(SinglePostPattern, Media.Post.ID)
End Function
#End Region
#Region "BaseAuthExists, Available"
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
Return Settings.GalleryDLFile.Exists And BaseAuthExists()
End Function
#End Region
#Region "Download"
Friend Property LIMIT_ABORT As Boolean = False
Friend ReadOnly Property LimitSkippedUsers As List(Of UserDataBase)
Friend Property UserNumber As Integer = -1
Friend Overrides Sub DownloadStarted(ByVal What As ISiteSettings.Download)
UserNumber = 0
MyBase.DownloadStarted(What)
End Sub
Friend Overrides Sub AfterDownload(ByVal User As Object, ByVal What As ISiteSettings.Download)
If Not User Is Nothing AndAlso DirectCast(User, UserData).GDL_REQUESTS_COUNT > 0 Then UserNumber += 1
MyBase.AfterDownload(User, What)
End Sub
Friend Overrides Sub DownloadDone(ByVal What As ISiteSettings.Download)
If UserNumber > 0 Then
If CBool(CookiesUpdate.Value) Then
With CookieKeeper.ParseNetscapeText(CookiesNetscapeFile.GetText(EDP.ReturnValue), EDP.ReturnValue)
If .ListExists Then
Responser.Cookies.Clear()
Responser.Cookies.AddRange(.Self,, EDP.ReturnValue)
Responser.SaveCookies(EDP.ReturnValue)
Else
Update_SaveCookiesNetscape(True)
End If
End With
Else
Update_SaveCookiesNetscape(True)
End If
End If
UserNumber = -1
If LimitSkippedUsers.Count > 0 Then
With LimitSkippedUsers
If .Count = 1 Then
@@ -137,6 +252,8 @@ Namespace API.Twitter
LIMIT_ABORT = False
MyBase.DownloadDone(What)
End Sub
#End Region
#Region "UserOptions, Update"
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 _
@@ -152,6 +269,8 @@ Namespace API.Twitter
End If
MyBase.Update()
End Sub
#End Region
#Region "IsMyUser, IsMyImageVideo, GetUserPostUrl, GetUserUrl"
Friend Const CommunitiesUser As String = "/i/communities"
Friend Overrides Function IsMyUser(ByVal UserURL As String) As ExchangeOptions
Dim e As ExchangeOptions = MyBase.IsMyUser(UserURL)
@@ -162,8 +281,20 @@ Namespace API.Twitter
Return Nothing
End If
End Function
Friend Overrides Function IsMyImageVideo(ByVal URL As String) As ExchangeOptions
If Not URL.IsEmptyString AndAlso (URL.Contains("twitter") Or URL.Contains("x.com")) Then
Return New ExchangeOptions(Site, String.Empty) With {.Exists = True}
Else
Return Nothing
End If
End Function
Friend Const SinglePostPattern As String = "https://x.com/i/web/status/{0}"
Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String
Return String.Format(SinglePostPattern, Media.Post.ID)
End Function
Friend Overrides Function GetUserUrl(ByVal User As IPluginContentProvider) As String
Return DirectCast(User, UserData).GetUserUrl
End Function
#End Region
End Class
End Namespace

Some files were not shown because too many files have changed in this diff Show More