mirror of
https://github.com/AAndyProgram/SCrawler.git
synced 2026-11-10 05:35:25 +00:00
API.EditorExchangeOptionsBase: move the 'Apply' function API.EditorExchangeOptionsBase_P: remove the 'Apply' function API.UserDataBase: update the 'UserSiteNameUpdate' function; remove the '_ForceSaveUserInfoOnException' property; update the 'DownloadContentDefault_ConvertWebp' function; add the 'DownloadContentDefault_ConvertWebp_TestImg' and 'DownloadContentDefault_ConvertWebp_Impl' functions API.Bluesky: add the 'EditorExchangeOptions' class; add 'did:' handle for recognizing API.PornHub: update M3U8 download functions API.TikTok: fix where gallery-dl would hang when downloading stories API.Twitter: fix downloading a single media file (standalone downloader) fails API.Xhamster: add 'webm' to 'jpg' correct conversion FeedMedia: move the 'ImageRenderer2' class into the 'UserImage' class UserImage: add the 'ImageRenderer2' class
1408 lines
82 KiB
VB.net
1408 lines
82 KiB
VB.net
' Copyright (C) 2023 Andy https://github.com/AAndyProgram
|
|
' This program is free software: you can redistribute it and/or modify
|
|
' it under the terms of the GNU General Public License as published by
|
|
' the Free Software Foundation, either version 3 of the License, or
|
|
' (at your option) any later version.
|
|
'
|
|
' This program is distributed in the hope that it will be useful,
|
|
' but WITHOUT ANY WARRANTY
|
|
Imports System.Threading
|
|
Imports System.Text.RegularExpressions
|
|
Imports SCrawler.API.Base
|
|
Imports SCrawler.API.YouTube.Objects
|
|
Imports PersonalUtilities.Functions.XML
|
|
Imports PersonalUtilities.Functions.RegularExpressions
|
|
Imports PersonalUtilities.Tools
|
|
Imports PersonalUtilities.Tools.Web.Documents
|
|
Imports PersonalUtilities.Tools.Web.Documents.JSON
|
|
Imports UStates = SCrawler.API.Base.UserMedia.States
|
|
Imports UTypes = SCrawler.API.Base.UserMedia.Types
|
|
Imports PKV = SCrawler.API.Instagram.UserData.PostKV
|
|
Namespace API.Twitter
|
|
Friend Class UserData : Inherits UserDataBase
|
|
#Region "XML names"
|
|
Private Const Name_FirstDownloadComplete As String = "FirstDownloadComplete"
|
|
Private Const Name_DownloadModel As String = "DownloadModel"
|
|
Private Const Name_DownloadModelForceApply As String = "DownloadModelForceApply"
|
|
Private Const Name_MediaModelAllowNonUserTweets As String = "MediaModelAllowNonUserTweets"
|
|
Private Const Name_DownloadBroadcasts As String = "DownloadBroadcasts"
|
|
Private Const Name_GifsDownload As String = "GifsDownload"
|
|
Private Const Name_GifsSpecialFolder As String = "GifsSpecialFolder"
|
|
Private Const Name_GifsPrefix As String = "GifsPrefix"
|
|
Private Const Name_IsCommunity As String = "IsCommunity"
|
|
Private Const Name_LargeProfile As String = "LargeProfile"
|
|
Private Const Name_DownloadModelChanged As String = "DownloadModelChanged"
|
|
#End Region
|
|
#Region "Declarations"
|
|
Private Const BroadCastPartUrl As String = "i/broadcasts"
|
|
Private Const Label_Community As String = "Community"
|
|
Friend Overrides ReadOnly Property SpecialLabels As IEnumerable(Of String)
|
|
Get
|
|
Return {Label_Community}
|
|
End Get
|
|
End Property
|
|
Friend Enum DownloadModels As Integer
|
|
Undefined = 0
|
|
Media = 1
|
|
Profile = 2
|
|
Search = 5
|
|
Likes = 10
|
|
End Enum
|
|
Private FirstDownloadComplete As Boolean = False
|
|
Friend Property DownloadModelForceApply As Boolean = False
|
|
Friend Property DownloadModel As DownloadModels = DownloadModels.Undefined
|
|
Private ReadOnly Property IsMultiMode As Boolean
|
|
Get
|
|
Return EnumExtract(Of DownloadModels)(DownloadModel).ListIfNothing.Count > 1
|
|
End Get
|
|
End Property
|
|
Private Property DownloadModelChanged As Boolean = False
|
|
Friend Property MediaModelAllowNonUserTweets As Boolean = False
|
|
Friend Property DownloadBroadcasts As Boolean = False
|
|
Friend Property GifsDownload As Boolean = True
|
|
Friend Property GifsSpecialFolder As String = String.Empty
|
|
Friend Property GifsPrefix As String = String.Empty
|
|
Friend Property IsCommunity As Boolean = False
|
|
#Region "LargeProfile"
|
|
Friend Property LargeProfile As Boolean = False
|
|
Private ReadOnly Property LargeProfileOverride As Boolean
|
|
Get
|
|
Return LargeProfile And Not FirstDownloadComplete
|
|
End Get
|
|
End Property
|
|
Private ReadOnly Property CookiesUpdate As Boolean
|
|
Get
|
|
If LargeProfileOverride Then
|
|
MySettings.CookiesUpdateForce = True
|
|
Return True
|
|
Else
|
|
Return MySettings.CookiesUpdate.Value
|
|
End If
|
|
End Get
|
|
End Property
|
|
Private ReadOnly Property UserAgent As String
|
|
Get
|
|
If LargeProfileOverride Then
|
|
Return MySettings.UserAgent(True).IfNullOrEmpty(Settings.UserAgent)
|
|
Else
|
|
Return MySettings.UserAgent
|
|
End If
|
|
End Get
|
|
End Property
|
|
Private ReadOnly Property SleepTimerBeforeFirst As Integer
|
|
Get
|
|
Dim v% = MySettings.SleepTimerBeforeFirst.Value
|
|
If LargeProfileOverride And v <= 0 And v <> SiteSettings.TimerFirstUseTheSame Then v = SiteSettings.TimerFirstUseTheSame
|
|
Return v
|
|
End Get
|
|
End Property
|
|
Private ReadOnly Property SleepTimer As Integer
|
|
Get
|
|
Dim v% = MySettings.SleepTimer.Value
|
|
If LargeProfileOverride And v <= 0 Then v = SiteSettings.TimerDefault
|
|
Return v
|
|
End Get
|
|
End Property
|
|
#End Region
|
|
Private ReadOnly LikesPosts As List(Of String)
|
|
Private ReadOnly PostsKV As List(Of PKV)
|
|
Private ReadOnly _DataNames As List(Of String)
|
|
Private ReadOnly Property MySettings As SiteSettings
|
|
Get
|
|
Return HOST.Source
|
|
End Get
|
|
End Property
|
|
Friend Function GetUserUrl() As String
|
|
Return $"https://x.com{IIf(IsCommunity, SiteSettings.CommunitiesUser, String.Empty)}/{NameTrue}"
|
|
End Function
|
|
#End Region
|
|
#Region "Exchange options"
|
|
Friend Overrides Function ExchangeOptionsGet() As Object
|
|
Return New EditorExchangeOptions(Me) With {.SiteKey = HOST.Key}
|
|
End Function
|
|
Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
|
|
If Not Obj Is Nothing AndAlso TypeOf Obj Is EditorExchangeOptions Then
|
|
With DirectCast(Obj, EditorExchangeOptions)
|
|
.ApplyBase(Me)
|
|
GifsDownload = .GifsDownload
|
|
GifsSpecialFolder = .GifsSpecialFolder
|
|
GifsPrefix = .GifsPrefix
|
|
UseMD5Comparison = .UseMD5Comparison
|
|
RemoveExistingDuplicates = .RemoveExistingDuplicates
|
|
If RemoveExistingDuplicates Then StartMD5Checked = False
|
|
DownloadModel = DownloadModels.Undefined
|
|
DownloadModelForceApply = .DownloadModelForceApply
|
|
MediaModelAllowNonUserTweets = .MediaModelAllowNonUserTweets
|
|
DownloadBroadcasts = .DownloadBroadcasts
|
|
LargeProfile = .LargeProfile
|
|
Dim dModel As DownloadModels = DownloadModel
|
|
If .DownloadModelMedia Then DownloadModel += DownloadModels.Media
|
|
If .DownloadModelProfile Or .DownloadBroadcasts Then DownloadModel += DownloadModels.Profile
|
|
If .DownloadModelSearch Then DownloadModel += DownloadModels.Search
|
|
If .DownloadModelLikes Then DownloadModel += DownloadModels.Likes
|
|
If Not dModel = DownloadModel Then DownloadModelChanged = True
|
|
End With
|
|
End If
|
|
End Sub
|
|
#End Region
|
|
#Region "Initializer, loader"
|
|
Friend Sub New()
|
|
_DataNames = New List(Of String)
|
|
LikesPosts = New List(Of String)
|
|
PostsKV = New List(Of PKV)
|
|
UseInternalM3U8Function = True
|
|
End Sub
|
|
Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean)
|
|
With Container
|
|
If Loading Then
|
|
DownloadBroadcasts = .Value(Name_DownloadBroadcasts).FromXML(Of Boolean)(False)
|
|
DownloadModelForceApply = .Value(Name_DownloadModelForceApply).FromXML(Of Boolean)(False)
|
|
DownloadModelChanged = .Value(Name_DownloadModelChanged).FromXML(Of Boolean)(False)
|
|
If .Contains(Name_FirstDownloadComplete) Then
|
|
FirstDownloadComplete = .Value(Name_FirstDownloadComplete).FromXML(Of Boolean)(False)
|
|
DownloadModel = .Value(Name_DownloadModel).FromXML(Of Integer)(DownloadModels.Undefined)
|
|
Else
|
|
FirstDownloadComplete = DownloadedVideos(True) + DownloadedPictures(True) > 0
|
|
If .Contains(Name_DownloadModel) Then
|
|
DownloadModel = .Value(Name_DownloadModel).FromXML(Of Integer)(DownloadModels.Undefined)
|
|
Else
|
|
If FirstDownloadComplete Then
|
|
DownloadModelForceApply = False
|
|
If ParseUserMediaOnly Then
|
|
DownloadModel = DownloadModels.Media
|
|
Else
|
|
DownloadModel = DownloadModels.Media + DownloadModels.Profile + DownloadModels.Search
|
|
End If
|
|
Else
|
|
DownloadModel = DownloadModels.Undefined
|
|
End If
|
|
End If
|
|
End If
|
|
GifsDownload = .Value(Name_GifsDownload).FromXML(Of Boolean)(True)
|
|
GifsSpecialFolder = .Value(Name_GifsSpecialFolder)
|
|
If Not .Contains(Name_GifsPrefix) Then
|
|
GifsPrefix = "GIF_"
|
|
Else
|
|
GifsPrefix = .Value(Name_GifsPrefix)
|
|
End If
|
|
UseMD5Comparison = .Value(Name_UseMD5Comparison).FromXML(Of Boolean)(False)
|
|
RemoveExistingDuplicates = .Value(Name_RemoveExistingDuplicates).FromXML(Of Boolean)(False)
|
|
StartMD5Checked = .Value(Name_StartMD5Checked).FromXML(Of Boolean)(False)
|
|
MediaModelAllowNonUserTweets = .Value(Name_MediaModelAllowNonUserTweets).FromXML(Of Boolean)(False)
|
|
IsCommunity = .Value(Name_IsCommunity).FromXML(Of Boolean)(False)
|
|
LargeProfile = .Value(Name_LargeProfile).FromXML(Of Boolean)(False)
|
|
Else
|
|
If Name.Contains("@") And Not IsCommunity Then
|
|
IsCommunity = True
|
|
NameTrue = Name.Split("@")(0)
|
|
ID = NameTrue
|
|
ParseUserMediaOnly = False
|
|
Labels.ListAddValue(Label_Community, LNC)
|
|
Labels.Sort()
|
|
.Add(Name_UserID, ID)
|
|
.Add(Name_LabelsName, LabelsString)
|
|
.Add(Name_ParseUserMediaOnly, ParseUserMediaOnly.BoolToInteger)
|
|
End If
|
|
.Add(Name_FirstDownloadComplete, FirstDownloadComplete.BoolToInteger)
|
|
.Add(Name_DownloadModelForceApply, DownloadModelForceApply.BoolToInteger)
|
|
.Add(Name_DownloadModelChanged, DownloadModelChanged.BoolToInteger)
|
|
.Add(Name_DownloadModel, CInt(DownloadModel))
|
|
.Add(Name_DownloadBroadcasts, DownloadBroadcasts.BoolToInteger)
|
|
.Add(Name_GifsDownload, GifsDownload.BoolToInteger)
|
|
.Add(Name_GifsSpecialFolder, GifsSpecialFolder)
|
|
.Add(Name_GifsPrefix, GifsPrefix)
|
|
.Add(Name_UseMD5Comparison, UseMD5Comparison.BoolToInteger)
|
|
.Add(Name_RemoveExistingDuplicates, RemoveExistingDuplicates.BoolToInteger)
|
|
.Add(Name_StartMD5Checked, StartMD5Checked.BoolToInteger)
|
|
.Add(Name_MediaModelAllowNonUserTweets, MediaModelAllowNonUserTweets.BoolToInteger)
|
|
.Add(Name_IsCommunity, IsCommunity.BoolToInteger)
|
|
.Add(Name_LargeProfile, LargeProfile.BoolToInteger)
|
|
.Add(Name_TrueName, NameTrue(True))
|
|
End If
|
|
End With
|
|
End Sub
|
|
#End Region
|
|
#Region "Download functions"
|
|
Private Function GetContainerSubnodes() As List(Of String())
|
|
Return New List(Of String()) From {
|
|
{{"content", "itemContent", "tweet_results", "result", "legacy"}},
|
|
{{"content", "itemContent", "tweet_results", "result", "tweet", "legacy"}},
|
|
{{"item", "itemContent", "tweet_results", "result", "legacy"}},
|
|
{{"item", "itemContent", "tweet_results", "result", "tweet", "legacy"}}
|
|
}
|
|
End Function
|
|
Private Function ExtractBroadcast(ByVal e As EContainer, Optional ByVal PostID As String = Nothing, Optional ByVal PostDate As String = Nothing,
|
|
Optional ByVal Nodes As List(Of String()) = Nothing,
|
|
Optional ByVal IgnoreNodes As Boolean = False) As UserMedia
|
|
If e.ListExists Then
|
|
Dim __nodes As List(Of String()) = If(Nodes, GetContainerSubnodes())
|
|
Dim urlValue$
|
|
Dim m As UserMedia = Nothing
|
|
Dim __parseContainer As Func(Of EContainer, Boolean) =
|
|
Function(ByVal ee As EContainer) As Boolean
|
|
With ee
|
|
If .ListExists Then
|
|
urlValue = .ItemF(BroadcastsUrls, EDP.ReturnValue).XmlIfNothingValue
|
|
If Not urlValue.IsEmptyString AndAlso urlValue.Contains(BroadCastPartUrl) Then
|
|
m = MediaFromData(urlValue, PostID, PostDate,,, UTypes.m3u8)
|
|
If Not IsSingleObjectDownload Then m.SpecialFolder = "Broadcasts*"
|
|
Return True
|
|
End If
|
|
End If
|
|
End With
|
|
Return False
|
|
End Function
|
|
If IgnoreNodes Then
|
|
If __parseContainer(e) Then Return m
|
|
Else
|
|
For Each n As String() In __nodes
|
|
If __parseContainer(e(n)) Then Return m
|
|
Next
|
|
End If
|
|
m = ExtractBroadcast(e.ItemF(Of Object)({0}), PostID, PostDate, Nodes)
|
|
If Not m.URL.IsEmptyString Then Return m
|
|
End If
|
|
Return Nothing
|
|
End Function
|
|
Private ReadOnly Property MyFilePostsKV As SFile
|
|
Get
|
|
Dim f As SFile = MyFilePosts
|
|
If Not f.IsEmptyString Then
|
|
f.Name &= "_KV"
|
|
f.Extension = "xml"
|
|
Return f
|
|
Else
|
|
Return Nothing
|
|
End If
|
|
End Get
|
|
End Property
|
|
Protected Sub LoadSavePostsKV(ByVal Load As Boolean)
|
|
Instagram.UserData.LoadSavePostsKV(Load, MyFilePostsKV, PostsKV)
|
|
End Sub
|
|
Private Function PostKVExists(ByVal PID As String, ByVal Model As DownloadModels,
|
|
ByVal MultiMode As Boolean, ByVal IgnorePKV As Boolean, ByVal AutoAdd As Boolean) As Boolean
|
|
Dim result As Boolean
|
|
If IgnorePKV Or PostsKV.Count = 0 Then
|
|
result = _TempPostsList.Contains(PID)
|
|
Else
|
|
result = PostsKV.Contains(New PKV(PID, PID, Model)) Or (Not MultiMode AndAlso _TempPostsList.Contains(PID))
|
|
End If
|
|
If Not result And AutoAdd Then
|
|
PostsKV.ListAddValue(New PKV(PID, PID, Model), LNC)
|
|
_TempPostsList.ListAddValue(PID, LNC)
|
|
End If
|
|
Return result
|
|
End Function
|
|
Friend Property GDL_REQUESTS_COUNT As Integer = 0
|
|
Private Const DEBUG_PROFILE As Boolean = False
|
|
Private Const DEBUG_LEAVE_CACHE As Boolean = False
|
|
Private JsonNullErr As Boolean = False
|
|
Private ____UserExists As Boolean = True
|
|
Private NotUserExistsAttempts As Integer = 0
|
|
Friend Overrides Sub DownloadData(ByVal Token As CancellationToken)
|
|
____UserExists = UserExists
|
|
MyBase.DownloadData(Token)
|
|
End Sub
|
|
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
|
|
Try
|
|
GDL_REQUESTS_COUNT = 0
|
|
JsonNullErr = False
|
|
NotUserExistsAttempts = 0
|
|
If MySettings.LIMIT_ABORT Then
|
|
Throw New TwitterLimitException(Me)
|
|
Else
|
|
If IsSavedPosts Then
|
|
If _ContentList.Count > 0 Then _DataNames.ListAddList(_ContentList.Select(Function(c) c.Post.ID), LAP.ClearBeforeAdd, LAP.NotContainsOnly)
|
|
DownloadData_SavedPosts(Token)
|
|
Else
|
|
LoadSavePostsKV(True)
|
|
If PostsKV.Count = 0 And (_ContentList.Count > 0 Or _TempPostsList.Count > 0) Then
|
|
Dim m As DownloadModels = IIf(IsMultiMode, DownloadModels.Media, DownloadModel)
|
|
PostsKV.ListAddList(_TempPostsList.Select(Function(p) New PKV(p, p, m)), LNC)
|
|
PostsKV.ListAddList(_ContentList.Select(Function(p) New PKV(p.Post.ID, p.Post.ID, m)), LNC)
|
|
_ForceSaveUserData = True
|
|
End If
|
|
LikesPosts.Clear()
|
|
If _ContentList.Count > 0 Then _DataNames.ListAddList(_ContentList.Select(Function(c) c.File.File), LAP.ClearBeforeAdd, LAP.NotContainsOnly)
|
|
If Not ____UserExists Then
|
|
For i% = 0 To 1
|
|
NotUserExistsAttempts += 1
|
|
DownloadData_Timeline(Token)
|
|
If UserExists Then ____UserExists = True : Exit For
|
|
Next
|
|
End If
|
|
If Not UserExists Then Exit Sub
|
|
DownloadData_Timeline(Token)
|
|
If _TempMediaList.Count = 0 And LikesPosts.Count = 0 And JsonNullErr Then Throw New Plugin.ExitException("No deserialized data found")
|
|
LoadSavePostsKV(False)
|
|
If LikesPosts.Count > 0 Then
|
|
_ReparseLikes = True
|
|
ReparseMissing(Token)
|
|
_ReparseLikes = False
|
|
End If
|
|
End If
|
|
End If
|
|
Finally
|
|
_ReparseLikes = False
|
|
End Try
|
|
End Sub
|
|
Private Class WebDocumentEventArgsT : Inherits WebDocumentEventArgs
|
|
Public Overloads Overrides Sub Reset()
|
|
State = States.None
|
|
End Sub
|
|
Public Overloads Sub Reset(ByVal Token As CancellationToken)
|
|
Reset()
|
|
Me.Token = Token
|
|
End Sub
|
|
End Class
|
|
Private Sub DownloadData_Timeline(ByVal Token As CancellationToken)
|
|
Dim URL$ = String.Empty
|
|
Dim tCache As CacheKeeper = Nothing
|
|
Dim likesDetected As Boolean = False
|
|
Dim jsonArgs As New WebDocumentEventArgsT With {.DeclaredError = New ErrorsDescriber(EDP.ReturnValue)}
|
|
Try
|
|
Const entry$ = "entry"
|
|
Dim PostID$ = String.Empty
|
|
Dim PostDate$, tmpUserId$
|
|
Dim i%, nodeIndx%
|
|
Dim dirIndx% = -1
|
|
Dim nodes As List(Of String()) = GetContainerSubnodes()
|
|
Dim node$()
|
|
Dim timelineNode As Predicate(Of EContainer) = Function(ee) ee.Value("type").StringToLower = "timelineaddentries"
|
|
Dim pinNode As Predicate(Of EContainer) = Function(ee) ee.Value("type").StringToLower = "timelinepinentry"
|
|
Dim entriesNode As Predicate(Of EContainer) = Function(ee) ee.Name = "entries" Or ee.Name = entry
|
|
Dim sourceIdPredicate As Predicate(Of EContainer) = Function(ee) ee.Name = "source_user_id_str" Or ee.Name = "source_user_id"
|
|
Dim moduleItemsPredicate As Predicate(Of EContainer) = Function(ee) ee.Name.StringToLower = "moduleitems"
|
|
Dim newTwitterNodes() As Object = {0, "content", "items"}
|
|
Dim p As Predicate(Of EContainer)
|
|
Dim pIndx%
|
|
Dim indxChanged As Boolean = False
|
|
Dim isOneNode As Boolean, isPins As Boolean, ExistsDetected As Boolean, userInfoParsed As Boolean = False
|
|
Dim j As EContainer, rootNode As EContainer, optionalNode As EContainer, workingNode As EContainer, tmpNode As EContainer, nn As EContainer = Nothing
|
|
Dim multiMode As Boolean = IsMultiMode
|
|
Dim currentModel As DownloadModels = DownloadModels.Undefined
|
|
Dim onlyUpdateUser As Boolean = Not ____UserExists
|
|
|
|
Dim __parseContainer As Func(Of EContainer, Boolean) =
|
|
Function(ByVal ee As EContainer) As Boolean
|
|
nn = Nothing
|
|
If dirIndx > 1 Or IsCommunity Then nn = ee
|
|
If Not nn.ListExists Or IsCommunity Then
|
|
For Each node In nodes
|
|
nn = ee(node)
|
|
If nn.ListExists Then Exit For
|
|
Next
|
|
End If
|
|
|
|
If nn.ListExists Then
|
|
PostID = nn.Value("id_str").IfNullOrEmpty(nn.Value("id"))
|
|
|
|
'Date Pattern:
|
|
'Sat Jan 01 01:10:15 +0000 2000
|
|
If nn.Contains("created_at") Then PostDate = nn("created_at").Value Else PostDate = String.Empty
|
|
Select Case CheckDatesLimit(PostDate, Declarations.DateProvider)
|
|
Case DateResult.Skip, DateResult.Exit : Return False
|
|
End Select
|
|
|
|
If Not PostKVExists(PostID, currentModel, multiMode, False, True) Then
|
|
ElseIf dirIndx = 3 Then
|
|
ElseIf isPins Then
|
|
Return False
|
|
ElseIf Not FirstDownloadComplete Then
|
|
Return True
|
|
Else
|
|
ExistsDetected = Not multiMode
|
|
Return multiMode
|
|
End If
|
|
|
|
tmpUserId = nn({"retweeted_status_result", "result", "legacy", "user_id_str"}).XmlIfNothingValue
|
|
|
|
If tmpUserId.IsEmptyString Then tmpUserId = nn.ItemF({"extended_entities", "media", 0, sourceIdPredicate}).XmlIfNothingValue.
|
|
IfNullOrEmpty(nn.Value("user_id")).IfNullOrEmpty(nn.Value("user_id_str")).IfNullOrEmpty("/")
|
|
|
|
If (Not ParseUserMediaOnly Or dirIndx = 3) OrElse
|
|
(dirIndx = 0 AndAlso MediaModelAllowNonUserTweets) OrElse
|
|
(Not ID.IsEmptyString AndAlso tmpUserId = ID) Then
|
|
If dirIndx = 1 And DownloadBroadcasts Then
|
|
Dim m As UserMedia = ExtractBroadcast(nn, PostID, PostDate, nodes)
|
|
If Not m.URL.IsEmptyString Then
|
|
_TempMediaList.ListAddValue(m, LNC)
|
|
Else
|
|
m = ExtractBroadcast(ee, PostID, PostDate, nodes)
|
|
If Not m.URL.IsEmptyString Then _TempMediaList.ListAddValue(m, LNC)
|
|
End If
|
|
End If
|
|
If dirIndx = 3 Then
|
|
Dim lUrl$ = nn.ItemF({"content", "itemContent", "tweet_results", "result", "legacy", "entities", "media", 0}, "expanded_url").XmlIfNothingValue
|
|
If Not lUrl.IsEmptyString Then
|
|
lUrl = RegexReplace(lUrl, StatusRegEx)
|
|
If Not lUrl.IsEmptyString Then
|
|
If PostKVExists(lUrl, currentModel, multiMode, False, True) Then Return multiMode
|
|
LikesPosts.ListAddValue(lUrl, LNC)
|
|
End If
|
|
End If
|
|
Else
|
|
ObtainMedia(nn, PostID, PostDate)
|
|
End If
|
|
End If
|
|
End If
|
|
Return True
|
|
End Function
|
|
|
|
If Not DEBUG_PROFILE Then tCache = CreateCache()
|
|
|
|
'0 - media
|
|
'1 - profile
|
|
'2 - search
|
|
'3 - likes
|
|
Dim dirs As List(Of SFile)
|
|
If Not DEBUG_PROFILE Then
|
|
dirs = GetTimelineFromGalleryDL(tCache, Token)
|
|
Else
|
|
dirs = SFile.GetDirectories($"{DownloadContentDefault_GetRootDir()}\_tCache\".CSFileP)
|
|
If dirs.ListExists(2) Then
|
|
dirs = SFile.GetDirectories(dirs(1))
|
|
Else
|
|
dirs = New List(Of SFile)
|
|
End If
|
|
End If
|
|
|
|
If dirs.ListExists Then
|
|
For Each dir As SFile In dirs
|
|
dirIndx += 1
|
|
|
|
Select Case dirIndx
|
|
Case 0 : currentModel = DownloadModels.Media
|
|
Case 1 : currentModel = DownloadModels.Profile
|
|
Case 2 : currentModel = DownloadModels.Search
|
|
Case 3 : currentModel = DownloadModels.Likes
|
|
Case Else : currentModel = DownloadModels.Undefined
|
|
End Select
|
|
|
|
If dirIndx = 3 Then likesDetected = True
|
|
|
|
ExistsDetected = False
|
|
|
|
If Not dir.IsEmptyString Then
|
|
ThrowAny(Token)
|
|
Dim timelineFiles As List(Of SFile) = SFile.GetFiles(dir, "*.txt",, EDP.ReturnValue)
|
|
If timelineFiles.ListExists Then
|
|
GDLResetFileNameProvider(Math.Max(timelineFiles.Count.ToString.Length, 2))
|
|
'rename files
|
|
If Not DEBUG_PROFILE Then
|
|
For i = 0 To timelineFiles.Count - 1 : timelineFiles(i) = GDLRenameFile(timelineFiles(i), i) : Next
|
|
End If
|
|
'parse files
|
|
For i = 0 To timelineFiles.Count - 1
|
|
If userInfoParsed And onlyUpdateUser Then Exit Sub
|
|
j = JsonDocument.Parse(timelineFiles(i).GetText, jsonArgs)
|
|
If jsonArgs.State = WebDocumentEventArgs.States.Error Then
|
|
jsonArgs.Reset(Token)
|
|
Continue For
|
|
End If
|
|
If Not j Is Nothing Then
|
|
If Not userInfoParsed Or (i = 0 And Not indxChanged) Then
|
|
If Not userInfoParsed Then
|
|
userInfoParsed = True
|
|
Dim resValue$ = j.Value({"data", IIf(IsCommunity, "communityResults", "user"), "result"}, "__typename").StringTrim.StringToLower
|
|
Dim icon$
|
|
Dim fileCrFunc As Func(Of String, SFile) = Function(img) UrlFile(img, True)
|
|
If resValue.IsEmptyString Then
|
|
UserExists = False
|
|
j.Dispose()
|
|
Exit Sub
|
|
ElseIf resValue = "userunavailable" Then
|
|
UserSuspended = True
|
|
j.Dispose()
|
|
Exit Sub
|
|
ElseIf IsCommunity Then
|
|
With j({"data", "communityResults", "result", "community_media_timeline", "timeline", "instructions"})
|
|
If .ListExists Then
|
|
With .Find(entriesNode, True)
|
|
If .ListExists Then
|
|
With .ItemF({0, "content", "items", 0, "item", "itemContent", "tweet_results", "result", "tweet", "community_results", "result"})
|
|
If .ListExists Then
|
|
If ID = .Value("id_str") Then
|
|
UserSiteNameUpdate(.Value("name"), True)
|
|
UserDescriptionUpdate(.Value("description"))
|
|
|
|
icon = .Value({"custom_banner_media", "media_info"}, "original_img_url").
|
|
IfNullOrEmpty(.Value({"default_banner_media", "media_info"}, "original_img_url"))
|
|
If Not icon.IsEmptyString And DownloadIconBanner Then SimpleDownloadAvatar(icon, fileCrFunc)
|
|
End If
|
|
End If
|
|
End With
|
|
End If
|
|
End With
|
|
End If
|
|
End With
|
|
i = -1
|
|
indxChanged = True
|
|
Else
|
|
With j({"data", "user", "result"})
|
|
If .ListExists Then
|
|
If ID.IsEmptyString Then ID = .Value("rest_id")
|
|
icon = .Value({"avatar"}, "image_url")
|
|
UserSiteNameUpdate(.Value({"core"}, "name"), True)
|
|
Dim tScreenName$ = .Value({"core"}, "screen_name")
|
|
With .Item({"legacy"})
|
|
If .ListExists Then
|
|
If onlyUpdateUser Then
|
|
If Not NameTrue = tScreenName Then
|
|
Dim uStr$ = $"username changed from '{NameTrue}' to '{tScreenName}'"
|
|
LogError(Nothing, uStr)
|
|
UserDescriptionUpdate(uStr, True, True, True)
|
|
End If
|
|
NameTrue = tScreenName
|
|
End If
|
|
If .Value("screen_name").IfNullOrEmpty(tScreenName).StringToLower = NameTrue.ToLower Then
|
|
UserSiteNameUpdate(.Value("name"), True)
|
|
UserDescriptionUpdate(.Value("description"))
|
|
|
|
If icon.IsEmptyString Then icon = .Value("profile_image_url_https")
|
|
If Not icon.IsEmptyString Then icon = icon.Replace("_normal", String.Empty)
|
|
If DownloadIconBanner Then
|
|
SimpleDownloadAvatar(.Value("profile_banner_url").IfNullOrEmpty(.Value({"legacy"}, "profile_banner_url")), fileCrFunc)
|
|
SimpleDownloadAvatar(icon, fileCrFunc)
|
|
End If
|
|
End If
|
|
End If
|
|
End With
|
|
End If
|
|
End With
|
|
End If
|
|
ElseIf IsCommunity Then
|
|
i = -1
|
|
indxChanged = True
|
|
End If
|
|
Else
|
|
For pIndx = 0 To IIf(dirIndx < 2 Or dirIndx = 3, 1, 0)
|
|
optionalNode = Nothing
|
|
rootNode = Nothing
|
|
If IsCommunity Then
|
|
With j({"data", "communityResults", "result", "community_media_timeline", "timeline", "instructions"})
|
|
If .ListExists Then
|
|
If i = 0 Then
|
|
rootNode = .Find(entriesNode, True)
|
|
Else
|
|
rootNode = .Find(moduleItemsPredicate, True)
|
|
End If
|
|
optionalNode = rootNode
|
|
End If
|
|
End With
|
|
Else
|
|
Select Case dirIndx
|
|
Case 0, 1, 3
|
|
rootNode = j({"data", "user", "result", "timeline_v2", "timeline", "instructions"})
|
|
If Not rootNode.ListExists Then rootNode = j({"data", "user", "result", "timeline", "timeline", "instructions"})
|
|
If Not rootNode.ListExists Then rootNode = j({"data", "search_by_raw_query", "search_timeline", "timeline", "instructions"})
|
|
If rootNode.ListExists Then
|
|
If dirIndx = 3 Then
|
|
p = entriesNode
|
|
isPins = False
|
|
Else
|
|
p = If(pIndx = 0, pinNode, timelineNode)
|
|
isPins = pIndx = 0
|
|
End If
|
|
optionalNode = rootNode
|
|
rootNode = rootNode.Find(p, dirIndx = 3)
|
|
If dirIndx <> 3 And rootNode.ListExists Then rootNode = rootNode.Find(entriesNode, dirIndx = 3)
|
|
End If
|
|
Case Else
|
|
isPins = False
|
|
rootNode = j({"globalObjects", "tweets"})
|
|
optionalNode = rootNode
|
|
End Select
|
|
End If
|
|
|
|
If rootNode.ListExists Then
|
|
With rootNode
|
|
If IsCommunity Then
|
|
isOneNode = pIndx = 0
|
|
Else
|
|
isOneNode = dirIndx < 2 AndAlso .Name = entry
|
|
End If
|
|
ProgressPre.ChangeMax(If(isOneNode, 1, .Count))
|
|
If isOneNode Then
|
|
ProgressPre.Perform()
|
|
If Not __parseContainer(.Self) Then Continue For 'Exit For
|
|
Else
|
|
For nodeIndx = 0 To 1
|
|
If nodeIndx = 0 Then
|
|
workingNode = rootNode
|
|
Else
|
|
workingNode = optionalNode
|
|
If workingNode.ListExists Then workingNode = workingNode.Find(moduleItemsPredicate, True)
|
|
End If
|
|
If workingNode.ListExists Then
|
|
With workingNode
|
|
For Each tmpNode In If(If(.ItemF(newTwitterNodes)?.Count, 0) > 0,
|
|
.ItemF(newTwitterNodes),
|
|
.Self)
|
|
ProgressPre.Perform()
|
|
If Not __parseContainer(tmpNode) Then
|
|
If isPins Then GoTo nextpIndx
|
|
Exit For
|
|
End If
|
|
Next
|
|
End With
|
|
End If
|
|
nextNodeIndx:
|
|
Next
|
|
End If
|
|
End With
|
|
End If
|
|
nextpIndx:
|
|
Next
|
|
|
|
If ExistsDetected And i = 1 Then Exit For Else ExistsDetected = False
|
|
End If
|
|
j.Dispose()
|
|
End If
|
|
Next
|
|
timelineFiles.Clear()
|
|
End If
|
|
End If
|
|
Next
|
|
dirs.Clear()
|
|
End If
|
|
ThrowAny(Token)
|
|
If Not FirstDownloadComplete Then
|
|
_ForceSaveUserInfo = True
|
|
If DownloadModel = DownloadModels.Undefined Then
|
|
If ParseUserMediaOnly Then
|
|
DownloadModel = DownloadModels.Media
|
|
If DownloadBroadcasts Then DownloadModel += DownloadModels.Profile
|
|
Else
|
|
DownloadModel = DownloadModels.Media + DownloadModels.Profile + DownloadModels.Search
|
|
End If
|
|
End If
|
|
End If
|
|
DownloadModelForceApply = False
|
|
FirstDownloadComplete = True
|
|
LargeProfile = False
|
|
Catch jsonNull_ex As JsonDocumentException When jsonNull_ex.State = WebDocumentEventArgs.States.Error
|
|
Throw New Plugin.ExitException("No deserialized data found")
|
|
Catch limit_ex As TwitterLimitException
|
|
Throw limit_ex
|
|
Catch ex As Exception
|
|
ProcessException(ex, Token, $"data downloading error [{URL}]")
|
|
Finally
|
|
If Not tCache Is Nothing And Not DEBUG_PROFILE And Not DEBUG_LEAVE_CACHE Then tCache.Dispose()
|
|
If _TempPostsList.Count > 0 And Not likesDetected Then _TempPostsList.Sort()
|
|
jsonArgs.DisposeIfReady(False)
|
|
End Try
|
|
End Sub
|
|
Private Sub DownloadData_SavedPosts(ByVal Token As CancellationToken)
|
|
Try
|
|
Dim f As SFile = GetDataFromGalleryDL("https://x.com/i/bookmarks", Settings.Cache, True, Token)
|
|
Dim files As List(Of SFile) = SFile.GetFiles(f, "*.txt")
|
|
If files.ListExists Then
|
|
GDLResetFileNameProvider(Math.Max(files.Count.ToString.Length, 3))
|
|
Dim id$
|
|
Dim nodes As List(Of String()) = GetContainerSubnodes()
|
|
Dim node$()
|
|
Dim j As EContainer, jj As EContainer
|
|
Dim jErr As New ErrorsDescriber(EDP.ReturnValue)
|
|
For i% = 0 To files.Count - 1
|
|
f = GDLRenameFile(files(i), i)
|
|
j = JsonDocument.Parse(f.GetText, jErr)
|
|
If Not j Is Nothing Then
|
|
With j.ItemF({"data", 0, "timeline", "instructions", 0, "entries"})
|
|
If .ListExists Then
|
|
ProgressPre.ChangeMax(.Count)
|
|
For Each jj In .Self
|
|
ProgressPre.Perform()
|
|
For Each node In nodes
|
|
With jj(node)
|
|
If .ListExists Then
|
|
id = .Value("id_str")
|
|
If _TempPostsList.Contains(id) Then j.Dispose() : Exit Sub Else ObtainMedia(.Self, id, .Value("created_at"))
|
|
Exit For
|
|
End If
|
|
End With
|
|
Next
|
|
Next
|
|
End If
|
|
End With
|
|
j.Dispose()
|
|
End If
|
|
Next
|
|
nodes.Clear()
|
|
End If
|
|
Catch limit_ex As TwitterLimitException
|
|
Catch ex As Exception
|
|
ProcessException(ex, Token, "data downloading error (Saved Posts)")
|
|
End Try
|
|
End Sub
|
|
#End Region
|
|
#Region "Obtain media"
|
|
Private Sub ObtainMedia(ByVal e As EContainer, ByVal PostID As String, ByVal PostDate As String, Optional ByVal State As UStates = UStates.Unknown,
|
|
Optional ByVal Attempts As Integer = 0, Optional ByVal SpecialFolder As String = Nothing)
|
|
Dim s As EContainer = e({"extended_entities", "media"})
|
|
If If(s?.Count, 0) = 0 Then s = e({"retweeted_status", "extended_entities", "media"})
|
|
If If(s?.Count, 0) = 0 Then s = e({"retweeted_status_result", "result", "legacy", "extended_entities", "media"})
|
|
Dim txt$ = If(DownloadText, e.Value("full_text"), String.Empty)
|
|
|
|
Dim mUrl$
|
|
Dim media As UserMedia
|
|
Dim setMediaDef As Action = Sub()
|
|
If Not SpecialFolder.IsEmptyString Then media.SpecialFolder = SpecialFolder
|
|
media.PostText = txt
|
|
media.PostTextFileSpecialFolder = DownloadTextSpecialFolder
|
|
End Sub
|
|
If If(s?.Count, 0) > 0 Then
|
|
For Each m As EContainer In s
|
|
If Not CheckVideoNode(m, PostID, PostDate, State, SpecialFolder, txt) Then
|
|
mUrl = m.Value("media_url").IfNullOrEmpty(m.Value("media_url_https"))
|
|
If Not mUrl.IsEmptyString Then
|
|
Dim dName$ = UrlFile(mUrl)
|
|
If Not dName.IsEmptyString AndAlso Not _DataNames.Contains(dName) Then
|
|
_DataNames.Add(dName)
|
|
media = MediaFromData(mUrl, PostID, PostDate, GetPictureOption(m), State, UTypes.Picture, Attempts)
|
|
setMediaDef()
|
|
_TempMediaList.ListAddValue(media, LNC)
|
|
End If
|
|
End If
|
|
End If
|
|
Next
|
|
ElseIf Not txt.IsEmptyString And DownloadTextPosts Then
|
|
media = MediaFromData(PostID, PostID, PostDate, String.Empty, State, UTypes.Text, Attempts)
|
|
setMediaDef()
|
|
media.PostTextFile = $"{PostID}.txt"
|
|
_TempMediaList.ListAddValue(media, LNC)
|
|
End If
|
|
End Sub
|
|
Private Function CheckVideoNode(ByVal w As EContainer, ByVal PostID As String, ByVal PostDate As String,
|
|
Optional ByVal State As UStates = UStates.Unknown, Optional ByVal SpecialFolder As String = Nothing,
|
|
Optional ByVal PostText As String = Nothing) As Boolean
|
|
Try
|
|
If CheckForGif(w, PostID, PostDate, State, SpecialFolder, PostText) Then Return True
|
|
Dim URL$ = GetVideoNodeURL(w)
|
|
If Not URL.IsEmptyString Then
|
|
Dim f$ = UrlFile(URL)
|
|
If Not f.IsEmptyString AndAlso Not _DataNames.Contains(f) Then
|
|
_DataNames.Add(f)
|
|
Dim m As UserMedia = MediaFromData(URL, PostID, PostDate,, State, UTypes.Video)
|
|
If Not SpecialFolder.IsEmptyString Then m.SpecialFolder = SpecialFolder
|
|
m.PostText = PostText
|
|
_TempMediaList.ListAddValue(m, LNC)
|
|
End If
|
|
Return True
|
|
End If
|
|
Return False
|
|
Catch ex As Exception
|
|
LogError(ex, "[API.Twitter.UserData.CheckVideoNode]")
|
|
Return False
|
|
End Try
|
|
End Function
|
|
Private Function CheckForGif(ByVal w As EContainer, ByVal PostID As String, ByVal PostDate As String,
|
|
Optional ByVal State As UStates = UStates.Unknown, Optional ByVal SpecialFolder As String = Nothing,
|
|
Optional ByVal PostText As String = Nothing) As Boolean
|
|
Try
|
|
Dim gifUrl As Predicate(Of EContainer) = Function(e) Not e.Value("content_type").IsEmptyString AndAlso
|
|
e.Value("content_type").Contains("mp4") AndAlso
|
|
Not e.Value("url").IsEmptyString
|
|
Dim url$, ff$
|
|
Dim f As SFile
|
|
Dim m As UserMedia
|
|
If w.ListExists Then
|
|
If w.Value("type") = "animated_gif" Then
|
|
With w({"video_info", "variants"})
|
|
If .ListExists Then
|
|
With .ItemF({gifUrl})
|
|
If .ListExists Then
|
|
url = .Value("url")
|
|
ff = UrlFile(url)
|
|
If Not ff.IsEmptyString Then
|
|
If GifsDownload And Not _DataNames.Contains(ff) Then
|
|
m = MediaFromData(url, PostID, PostDate,, State, UTypes.Video)
|
|
If Not SpecialFolder.IsEmptyString Then m.SpecialFolder = SpecialFolder
|
|
f = m.File
|
|
If Not f.IsEmptyString And Not GifsPrefix.IsEmptyString Then f.Name = $"{GifsPrefix}{f.Name}" : m.File = f
|
|
If Not GifsSpecialFolder.IsEmptyString Then
|
|
If Not m.SpecialFolder.IsEmptyString Then m.SpecialFolder &= "\"
|
|
m.SpecialFolder &= $"{GifsSpecialFolder}*"
|
|
End If
|
|
m.PostText = PostText
|
|
_TempMediaList.ListAddValue(m, LNC)
|
|
End If
|
|
Return True
|
|
End If
|
|
End If
|
|
End With
|
|
End If
|
|
End With
|
|
End If
|
|
End If
|
|
Return False
|
|
Catch ex As Exception
|
|
LogError(ex, "[API.Twitter.UserData.CheckForGif]")
|
|
Return False
|
|
End Try
|
|
End Function
|
|
Private Function GetVideoNodeURL(ByVal w As EContainer) As String
|
|
With w({"video_info", "variants"})
|
|
If .ListExists Then
|
|
Dim l As New List(Of Sizes)
|
|
Dim u$
|
|
For Each n As EContainer In .Self
|
|
If n.Count > 0 Then
|
|
If n("content_type").XmlIfNothingValue("none").Contains("mp4") AndAlso n.Contains("url") Then
|
|
u = n.Value("url")
|
|
l.Add(New Sizes(RegexReplace(u, VideoSizeRegEx), u))
|
|
End If
|
|
End If
|
|
Next
|
|
If l.Count > 0 Then l.RemoveAll(Function(s) s.HasError)
|
|
If l.Count > 0 Then l.Sort() : Return l(0).Data
|
|
End If
|
|
End With
|
|
Return String.Empty
|
|
End Function
|
|
#End Region
|
|
#Region "Gallery-DL Support"
|
|
Private Class TwitterLimitException : Inherits Plugin.ExitException
|
|
Friend Sub New(ByVal User As UserData)
|
|
Silent = True
|
|
User.MySettings.LimitSkippedUsers.Add(User)
|
|
End Sub
|
|
End Class
|
|
Private Class TwitterGDL : Inherits GDL.GDLBatch
|
|
Private ReadOnly KillOnLimit As Boolean
|
|
Friend LimitReached As Boolean = False
|
|
Private _GetOnlyUserInfo As Boolean = False
|
|
Friend Overrides Property MyWorkingDirectory As SFile
|
|
Get
|
|
Return If(MyBase.MyWorkingDirectory.IsEmptyString, If(FileExchanger?.RootDirectory, MyBase.MyWorkingDirectory), MyBase.MyWorkingDirectory)
|
|
End Get
|
|
Set(ByVal dir As SFile)
|
|
MyBase.MyWorkingDirectory = dir
|
|
End Set
|
|
End Property
|
|
Friend Property GetOnlyUserInfo As Boolean
|
|
Get
|
|
Return _GetOnlyUserInfo And Not MyWorkingDirectory.IsEmptyString
|
|
End Get
|
|
Set(ByVal __GetOnlyUserInfo As Boolean)
|
|
_GetOnlyUserInfo = __GetOnlyUserInfo
|
|
End Set
|
|
End Property
|
|
Friend Sub New(ByVal Dir As SFile, ByVal _Token As CancellationToken, ByVal _KillOnLimit As Boolean)
|
|
MyBase.New(_Token,, Dir)
|
|
KillOnLimit = _KillOnLimit
|
|
End Sub
|
|
Protected Overrides Async Function Validate(ByVal Value As String) As Task
|
|
If Not ProcessKilled AndAlso Await Task.Run(Function() Token.IsCancellationRequested OrElse IdExists(Value)) Then Kill()
|
|
End Function
|
|
Private Function IdExists(ByVal Value As String) As Boolean
|
|
Try
|
|
If GetOnlyUserInfo Then
|
|
Return CheckForData()
|
|
Else
|
|
Value = Value.StringTrim
|
|
If Not Value.IsEmptyString AndAlso (Value.StartsWith("*") Or Value.StartsWith(".\gallery-dl\")) Then
|
|
Dim id$ = Value.Split("\").Last.Split(".").First.Split("_").First
|
|
If Not id.IsEmptyString Then Return TempPostsList.Contains(id)
|
|
End If
|
|
End If
|
|
Catch ex As Exception
|
|
End Try
|
|
Return False
|
|
End Function
|
|
Protected Overrides Async Sub ErrorDataReceiver(ByVal Sender As Object, ByVal e As DataReceivedEventArgs)
|
|
Await Task.Run(Sub() CheckForLimit(e.Data))
|
|
End Sub
|
|
Private Function CheckForData()
|
|
If GetOnlyUserInfo Then
|
|
If SFile.GetFiles(MyWorkingDirectory, "*.txt",, EDP.ReturnValue).Count > 2 Then Return True
|
|
End If
|
|
Return False
|
|
End Function
|
|
Private Sub CheckForLimit(ByVal Value As String)
|
|
If CheckForData() Or Token.IsCancellationRequested Or (KillOnLimit AndAlso Not ProcessKilled AndAlso
|
|
Not Value.IsEmptyString AndAlso (Value.ToLower.Contains("for rate limit reset") OrElse
|
|
Not CStr(RegexReplace(Value, GdlLimitRegEx)).IsEmptyString)) Then
|
|
LimitReached = True
|
|
Kill()
|
|
End If
|
|
End Sub
|
|
End Class
|
|
Private ReadOnly Property SleepTimerValue(ByVal First As Boolean) As Integer
|
|
Get
|
|
Dim fTimer% = If(First, SleepTimerBeforeFirst, SleepTimer)
|
|
If First And fTimer = SiteSettings.TimerFirstUseTheSame Then fTimer = SleepTimer
|
|
Return fTimer
|
|
End Get
|
|
End Property
|
|
Private ReadOnly Property SleepRequest As String
|
|
Get
|
|
Dim s% = SleepTimerValue(False)
|
|
Return If(s = SiteSettings.TimerDisabled, String.Empty, $" --sleep-request {s}")
|
|
End Get
|
|
End Property
|
|
Private Sub GdlWaitFirstTimer(ByVal fTimer As Integer)
|
|
If GDL_REQUESTS_COUNT = 0 And Not fTimer = SiteSettings.TimerDisabled And MySettings.UserNumber > 0 Then Thread.Sleep(fTimer * 1000)
|
|
GDL_REQUESTS_COUNT += 1
|
|
End Sub
|
|
Private _GdlPreProgressPerformEnabled As Boolean = False
|
|
Private Sub GdlPreProgressPerform(ByVal Path As SFile, ByVal UpDir As SFile)
|
|
Dim f As SFile = Nothing
|
|
Try
|
|
Dim c% = -1, lb% = -1, cc%
|
|
Dim e As New ErrorsDescriber(EDP.ReturnValue)
|
|
While _GdlPreProgressPerformEnabled
|
|
Thread.Sleep(100)
|
|
If c > 0 Then
|
|
cc = If(SFile.GetFiles(Path,,, e)?.Count, 0)
|
|
If cc > c Then
|
|
Exit Sub
|
|
ElseIf cc > 0 And (lb = -1 Or cc > lb) Then
|
|
ProgressPre.Perform(cc - IIf(lb = -1, 0, lb))
|
|
lb = cc
|
|
End If
|
|
ElseIf Path.Exists(SFO.Path, False) Then
|
|
Dim files As List(Of SFile) = SFile.GetFiles(Path,,, e)
|
|
If files.ListExists Then
|
|
f = files.FirstOrDefault(Function(ff) Not ff.Name.IsEmptyString AndAlso ff.Name.StartsWith("01_"))
|
|
If UpDir.Exists(SFO.Path, False) Then
|
|
Dim fNew As SFile = $"{UpDir.PathWithSeparator}00.txt"
|
|
If f.Copy(fNew,, True, SFODelete.DeletePermanently,, e) Then
|
|
f = fNew
|
|
Using j As EContainer = JsonDocument.Parse(f.GetText(e), e)
|
|
If j.ListExists Then
|
|
c = j(GdlPostCoutNumberNodes).XmlIfNothingValue.FromXML(Of Integer)(-1)
|
|
If c > 0 Then c /= 30 : ProgressPre.ChangeMax(c) : Continue While
|
|
End If
|
|
End Using
|
|
End If
|
|
End If
|
|
Exit Sub
|
|
End If
|
|
End If
|
|
End While
|
|
Catch ex As Exception
|
|
ErrorsDescriber.Execute(EDP.SendToLog, ex, $"{ToStringForLog()}:{vbCr}Path: '{Path.Path}'{vbCr}Path 2: '{UpDir.Path}'")
|
|
Finally
|
|
If f.Exists Then f.Delete(SFO.File, SFODelete.DeletePermanently, EDP.ReturnValue)
|
|
End Try
|
|
End Sub
|
|
Private Sub GdlPreProgressPerformWait(ByVal t As Task)
|
|
_GdlPreProgressPerformEnabled = False
|
|
Try
|
|
While t.Status = TaskStatus.Running Or t.Status = TaskStatus.WaitingToRun : Thread.Sleep(100) : End While
|
|
Catch
|
|
End Try
|
|
End Sub
|
|
Private Function GetDataFromGalleryDL(ByVal URL As String, ByVal Cache As CacheKeeper, ByVal UseTempPostList As Boolean,
|
|
Optional ByVal Token As CancellationToken = Nothing) As SFile
|
|
Dim command$ = String.Empty
|
|
Try
|
|
Dim conf As SFile = GdlCreateConf(Cache.NewPath)
|
|
Dim fTimer% = SleepTimerValue(True)
|
|
command = $"""{Settings.GalleryDLFile}"" --verbose --no-download --no-skip{SleepRequest} --config ""{conf}"" --write-pages "
|
|
'command &= GdlGetIdFilterString()
|
|
Dim dir As SFile = Cache.NewPath
|
|
If dir.Exists(SFO.Path,, EDP.ThrowException) Then
|
|
Using batch As New TwitterGDL(dir, Token, MySettings.AbortOnLimit.Value)
|
|
If UseTempPostList Then
|
|
batch.TempPostsList = _TempPostsList
|
|
command &= GdlGetIdFilterString()
|
|
End If
|
|
command &= URL
|
|
'#If DEBUG Then
|
|
'Debug.WriteLine(command)
|
|
'#End If
|
|
GdlWaitFirstTimer(fTimer)
|
|
batch.Execute(command)
|
|
If batch.LimitReached Then
|
|
If CBool(MySettings.DownloadAlreadyParsed.Value) And
|
|
SFile.GetFiles(dir, "*.txt", IO.SearchOption.AllDirectories, EDP.ReturnValue).Count > 0 Then
|
|
MySettings.LIMIT_ABORT = True
|
|
Return dir
|
|
Else
|
|
Throw New TwitterLimitException(Me)
|
|
End If
|
|
End If
|
|
End Using
|
|
Return dir
|
|
End If
|
|
Return Nothing
|
|
Catch limit_ex As TwitterLimitException
|
|
MySettings.LIMIT_ABORT = True
|
|
Throw limit_ex
|
|
Catch ex As Exception
|
|
Return ErrorsDescriber.Execute(EDP.SendToLog, ex, $"{ToStringForLog()}: GetDataFromGalleryDL({command})")
|
|
End Try
|
|
End Function
|
|
Private Function GetTimelineFromGalleryDL(ByVal Cache As CacheKeeper, ByVal Token As CancellationToken) As List(Of SFile)
|
|
Dim command$ = String.Empty
|
|
Try
|
|
Dim confCache As CacheKeeper = Cache.NewInstance(Of BatchFileExchanger)
|
|
Dim conf As SFile = GdlCreateConf(confCache.RootDirectory)
|
|
|
|
If DownloadModel = DownloadModels.Undefined And Not FirstDownloadComplete And DownloadModelForceApply Then
|
|
If ParseUserMediaOnly And DownloadBroadcasts Then
|
|
DownloadModel = DownloadModels.Media + DownloadModels.Profile
|
|
ElseIf ParseUserMediaOnly Then
|
|
DownloadModel = DownloadModels.Media
|
|
Else
|
|
DownloadModel = DownloadModels.Media + DownloadModels.Profile + DownloadModels.Search
|
|
End If
|
|
End If
|
|
|
|
Dim outList As New List(Of SFile)
|
|
Dim rootDir As CacheKeeper = Cache.NewInstance
|
|
Dim dir As SFile
|
|
Dim dm As List(Of DownloadModels) = EnumExtract(Of DownloadModels)(DownloadModel).ListIfNothing
|
|
Dim process As Boolean, multiMode As Boolean
|
|
Dim currentModel As DownloadModels
|
|
Dim urlPrePattern$ = $"https://x.com{IIf(IsCommunity, SiteSettings.CommunitiesUser, String.Empty)}/"
|
|
Dim fTimer% = SleepTimerValue(True)
|
|
Dim t As Task
|
|
|
|
If DownloadBroadcasts AndAlso Not dm.Contains(DownloadModels.Profile) Then dm.Add(DownloadModels.Profile)
|
|
|
|
multiMode = dm.Count > 1
|
|
|
|
Using tgdl As New TwitterGDL(Nothing, Token, MySettings.AbortOnLimit.Value) With {
|
|
.AutoClear = True,
|
|
.AutoReset = True,
|
|
.CommandPermanent = $"chcp {BatchExecutor.UnicodeEncoding}",
|
|
.FileExchanger = confCache,
|
|
.GetOnlyUserInfo = NotUserExistsAttempts > 0
|
|
}
|
|
tgdl.FileExchanger.DeleteCacheOnDispose = False
|
|
tgdl.FileExchanger.DeleteRootOnDispose = False
|
|
For i As Byte = 0 To IIf(IsCommunity Or NotUserExistsAttempts > 0, 0, 3)
|
|
dir = rootDir.NewPath
|
|
dir.Exists(SFO.Path, True, EDP.ThrowException)
|
|
outList.Add(dir)
|
|
tgdl.ChangeDirectory(dir)
|
|
command = $"""{Settings.GalleryDLFile}"" --verbose --no-download --no-skip{SleepRequest} --config ""{conf}"" --write-pages "
|
|
If multiMode Then
|
|
command &= "{0}"
|
|
Else
|
|
command &= GdlGetIdFilterString()
|
|
End If
|
|
If NotUserExistsAttempts > 0 Then
|
|
Select Case NotUserExistsAttempts
|
|
Case 1 : command &= $"{urlPrePattern}{NameTrue}/media" : currentModel = DownloadModels.Media : process = True
|
|
Case 2
|
|
If ID.IsEmptyString Then
|
|
process = False
|
|
Else
|
|
command &= $"https://twitter.com/intent/user?user_id={ID}"
|
|
currentModel = DownloadModels.Media
|
|
process = True
|
|
End If
|
|
Case Else : process = False
|
|
End Select
|
|
Else
|
|
Select Case i
|
|
Case 0 : command &= $"{urlPrePattern}{NameTrue}/media" : currentModel = DownloadModels.Media : process = dm.Contains(currentModel) Or IsCommunity
|
|
Case 1 : command &= $"{urlPrePattern}{NameTrue}" : currentModel = DownloadModels.Profile : process = dm.Contains(currentModel)
|
|
Case 2 : command &= $"-o search-endpoint=graphql https://x.com/search?q=from:{NameTrue}+include:nativeretweets" : currentModel = DownloadModels.Search : process = dm.Contains(currentModel) And Not IsCommunity
|
|
Case 3 : command &= $"{urlPrePattern}{NameTrue}/likes" : currentModel = DownloadModels.Likes : process = dm.Contains(currentModel)
|
|
Case Else : process = False
|
|
End Select
|
|
End If
|
|
'#If DEBUG Then
|
|
'Debug.WriteLine(command)
|
|
'#End If
|
|
ThrowAny(Token)
|
|
If process Then
|
|
If multiMode Then
|
|
If PostsKV.Count = 0 Then
|
|
tgdl.TempPostsList = New List(Of String)
|
|
Else
|
|
tgdl.TempPostsList = (From p As PKV In PostsKV Where p.Section = currentModel Select p.ID).ListIfNothing
|
|
End If
|
|
command = String.Format(command, GdlGetIdFilterString(tgdl.TempPostsList))
|
|
Else
|
|
tgdl.TempPostsList = _TempPostsList
|
|
End If
|
|
|
|
GdlWaitFirstTimer(fTimer)
|
|
|
|
_GdlPreProgressPerformEnabled = True
|
|
t = New Task(Sub() GdlPreProgressPerform(dir, conf))
|
|
t.Start()
|
|
|
|
tgdl.Execute(command)
|
|
|
|
_GdlPreProgressPerformEnabled = False
|
|
GdlPreProgressPerformWait(t)
|
|
|
|
If tgdl.LimitReached Then
|
|
If CBool(MySettings.DownloadAlreadyParsed.Value) And
|
|
SFile.GetFiles(rootDir, "*.txt", IO.SearchOption.AllDirectories, EDP.ReturnValue).Count > 0 Then
|
|
MySettings.LIMIT_ABORT = True
|
|
Exit For
|
|
Else
|
|
Throw New TwitterLimitException(Me)
|
|
End If
|
|
End If
|
|
End If
|
|
ThrowAny(Token)
|
|
Next
|
|
End Using
|
|
dm.Clear()
|
|
|
|
Return outList
|
|
Catch limit_ex As TwitterLimitException
|
|
MySettings.LIMIT_ABORT = True
|
|
Throw limit_ex
|
|
Catch ex As Exception
|
|
ProcessException(ex, Token, $"{ToStringForLog()}: GetTimelineFromGalleryDL({command})")
|
|
Return Nothing
|
|
Finally
|
|
_GdlPreProgressPerformEnabled = False
|
|
End Try
|
|
End Function
|
|
Private Function GdlGetIdFilterString(Optional ByVal TL As List(Of String) = Nothing) As String
|
|
If TL.ListExists Then TL.Sort()
|
|
With If(TL, _TempPostsList) : Return If(.Count > 0, $"--filter ""int(tweet_id) > { .Last} or abort()"" ", String.Empty) : End With
|
|
End Function
|
|
Private Function GdlCreateConf(ByVal Path As SFile) As SFile
|
|
Try
|
|
Dim conf As SFile = $"{Path.PathWithSeparator}TwitterGdlConfig.conf"
|
|
Dim __userAgent$ = UserAgent
|
|
If Not __userAgent.IsEmptyString Then __userAgent = $"""user-agent"": ""{__userAgent}"","
|
|
Dim confText$ = "{""extractor"":{""cookies"": """ & MySettings.CookiesNetscapeFile.ToString.Replace("\", "/") &
|
|
$""",""cookies-update"": {IIf(CookiesUpdate, "true", "false")}," & __userAgent &
|
|
"""twitter"":{""tweet-endpoint"": ""detail"",""cards"": false,""conversations"": true,""pinned"": false,""quoted"": false,""replies"": true,""retweets"": true,""strategy"": null,""text-tweets"": false,""twitpic"": false,""unique"": true,""users"": ""timeline"",""videos"": true}}}"
|
|
If conf.Exists(SFO.Path, True, EDP.ThrowException) Then TextSaver.SaveTextToFile(confText, conf)
|
|
If Not conf.Exists Then Throw New IO.FileNotFoundException("Can't find Twitter GDL config file", conf)
|
|
Return conf
|
|
Catch file_ex As IO.FileNotFoundException
|
|
Throw file_ex
|
|
Catch ex As Exception
|
|
Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "gallery-dl configuration file creating error", New SFile)
|
|
End Try
|
|
End Function
|
|
#End Region
|
|
#Region "ReparseMissing"
|
|
Private _ReparseLikes As Boolean = False
|
|
Protected Overrides Sub ReparseMissing(ByVal Token As CancellationToken)
|
|
Dim rList As New List(Of Integer)
|
|
Dim URL$ = String.Empty
|
|
Dim cache As CacheKeeper = Nothing
|
|
Try
|
|
If ContentMissingExists Or (_ReparseLikes And LikesPosts.Count > 0) Then
|
|
Const __entries$ = "entries"
|
|
Dim m As UserMedia, mTmp As UserMedia
|
|
Dim PostDate$
|
|
Dim nodes As List(Of String()) = GetContainerSubnodes()
|
|
Dim node$()
|
|
Dim entriesNode As Predicate(Of EContainer) = Function(ee) ee.Contains(__entries)
|
|
Dim j As EContainer, n As EContainer
|
|
Dim f As SFile
|
|
Dim i%, ii%
|
|
Dim files As List(Of SFile)
|
|
Dim lim%
|
|
Dim specFolder$ = IIf(_ReparseLikes, "Likes", String.Empty)
|
|
GDLResetFileNameProvider()
|
|
cache = If(IsSingleObjectDownload, Settings.Cache, CreateCache())
|
|
If _ReparseLikes Then lim = LikesPosts.Count Else lim = _ContentList.Count
|
|
ProgressPre.ChangeMax(lim)
|
|
For i = 0 To lim - 1
|
|
ProgressPre.Perform()
|
|
If _ReparseLikes OrElse _ContentList(i).State = UStates.Missing Then
|
|
m = If(_ReparseLikes, Nothing, _ContentList(i))
|
|
If Not m.Post.ID.IsEmptyString Or (IsSingleObjectDownload And Not m.URL_BASE.IsEmptyString) Or _ReparseLikes Then
|
|
ThrowAny(Token)
|
|
If m.Type = UTypes.m3u8 Then
|
|
_TempMediaList.Add(m)
|
|
rList.ListAddValue(i, LNC)
|
|
Continue For
|
|
ElseIf IsSingleObjectDownload Then
|
|
URL = m.URL_BASE
|
|
ElseIf _ReparseLikes Then
|
|
URL = LikesPosts(i)
|
|
Else
|
|
URL = String.Format(SiteSettings.SinglePostPattern, m.Post.ID)
|
|
End If
|
|
f = GetDataFromGalleryDL(URL, cache, False, Token)
|
|
If Not f.IsEmptyString Then
|
|
files = SFile.GetFiles(f, "*.txt")
|
|
If files.ListExists Then
|
|
For ii = 0 To files.Count - 1
|
|
f = GDLRenameFile(files(ii), ii)
|
|
j = JsonDocument.Parse(f.GetText)
|
|
If Not j Is Nothing Then
|
|
With j.ItemF({"data", 0, "instructions", entriesNode, __entries})
|
|
If .ListExists Then
|
|
If IsSingleObjectDownload Or DownloadBroadcasts Then
|
|
mTmp = ExtractBroadcast(.Self, m.Post.ID, String.Empty, nodes)
|
|
If Not mTmp.URL.IsEmptyString Then
|
|
_TempMediaList.ListAddValue(mTmp, LNC)
|
|
rList.ListAddValue(i, LNC)
|
|
End If
|
|
End If
|
|
For Each n In .Self
|
|
For Each node In nodes
|
|
With n(node)
|
|
If .ListExists Then
|
|
PostDate = String.Empty
|
|
If .Contains("created_at") Then PostDate = .Value("created_at") Else PostDate = String.Empty
|
|
ObtainMedia(.Self, m.Post.ID, PostDate, UStates.Missing, m.Attempts, specFolder)
|
|
rList.ListAddValue(i, LNC)
|
|
End If
|
|
End With
|
|
Next
|
|
Next
|
|
End If
|
|
End With
|
|
j.Dispose()
|
|
End If
|
|
Next
|
|
files.Clear()
|
|
End If
|
|
End If
|
|
End If
|
|
End If
|
|
Next
|
|
End If
|
|
Catch ex As Exception
|
|
ProcessException(ex, Token, $"ReparseMissing error [{URL}]")
|
|
Finally
|
|
If Not cache Is Nothing And Not IsSingleObjectDownload Then cache.Dispose()
|
|
If rList.Count > 0 And Not _ReparseLikes 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 "DownloadSingleObject"
|
|
Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
|
|
GDL_REQUESTS_COUNT = 0
|
|
Dim um As New UserMedia(Data.URL) With {.State = UStates.Missing}
|
|
If Not Data.URL.IsEmptyString AndAlso Data.URL.Contains(BroadCastPartUrl) Then um.Type = UTypes.m3u8
|
|
_ContentList.Add(um)
|
|
ReparseMissing(Token)
|
|
End Sub
|
|
#End Region
|
|
#Region "Picture options"
|
|
Private Function GetPictureOption(ByVal w As EContainer) As String
|
|
Const P4K As String = "4096x4096"
|
|
Try
|
|
Dim ww As EContainer = w("sizes")
|
|
If ww.ListExists Then
|
|
Dim l As New List(Of Sizes)
|
|
Dim Orig As Sizes? = New Sizes(w.Value({"original_info"}, "height").FromXML(Of Integer)(-1), P4K)
|
|
If Orig.Value.Value = -1 Then Orig = Nothing
|
|
Dim LargeContained As Boolean = ww.Contains("large")
|
|
For Each v As EContainer In ww
|
|
If v.Count > 0 AndAlso v.Contains("h") Then l.Add(New Sizes(v.Value("h"), v.Name))
|
|
Next
|
|
If l.Count > 0 Then
|
|
l.Sort()
|
|
If Orig.HasValue AndAlso l(0).Value < Orig.Value.Value Then
|
|
Return P4K
|
|
ElseIf l(0).Data.IsEmptyString Then
|
|
Return P4K
|
|
Else
|
|
Return l(0).Data
|
|
End If
|
|
Else
|
|
Return P4K
|
|
End If
|
|
ElseIf Not w.Value({"original_info"}, "height").IsEmptyString Then
|
|
Return P4K
|
|
Else
|
|
Return String.Empty
|
|
End If
|
|
Catch ex As Exception
|
|
LogError(ex, "[API.Twitter.UserData.GetPictureOption]")
|
|
Return String.Empty
|
|
End Try
|
|
End Function
|
|
#End Region
|
|
#Region "UrlFile"
|
|
Private Function UrlFile(ByVal URL As String, Optional ByVal GetWithoutExtension As Boolean = False) As String
|
|
Try
|
|
If Not URL.IsEmptyString Then
|
|
Dim f As SFile = CStr(RegexReplace(LinkFormatterSecure(RegexReplace(URL.Replace("\", String.Empty), LinkPattern)), FilesPattern))
|
|
If f.IsEmptyString And GetWithoutExtension Then
|
|
URL = LinkFormatterSecure(RegexReplace(URL.Replace("\", String.Empty), LinkPattern))
|
|
If Not URL.IsEmptyString Then f = New SFile With {.Name = URL.Split("/").LastOrDefault}
|
|
End If
|
|
If Not f.IsEmptyString Then Return f.File
|
|
End If
|
|
Return String.Empty
|
|
Catch ex As Exception
|
|
Return String.Empty
|
|
End Try
|
|
End Function
|
|
#End Region
|
|
#Region "Clear"
|
|
Protected Overrides Sub EraseData_AdditionalDataFiles()
|
|
MyFilePostsKV.Delete(SFO.File, SFODelete.DeleteToRecycleBin, EDP.SendToLog + EDP.ReturnValue)
|
|
_DataNames.Clear()
|
|
MyBase.EraseData_AdditionalDataFiles()
|
|
End Sub
|
|
#End Region
|
|
#Region "Create media"
|
|
Private Function MediaFromData(ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String,
|
|
Optional ByVal _PictureOption As String = Nothing,
|
|
Optional ByVal State As UStates = UStates.Unknown,
|
|
Optional ByVal Type As UTypes = UTypes.Undefined,
|
|
Optional ByVal Attempts As Integer = 0) As UserMedia
|
|
_URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern))
|
|
Dim m As New UserMedia(_URL) With {.PictureOption = _PictureOption, .Post = New UserPost With {.ID = PostID}, .Type = Type}
|
|
If Not m.URL.IsEmptyString Then m.File = CStr(RegexReplace(m.URL, FilesPattern))
|
|
If Not m.PictureOption.IsEmptyString And Not m.File.IsEmptyString And Not m.URL.IsEmptyString Then
|
|
m.URL = $"{m.URL.Replace($".{m.File.Extension}", String.Empty)}?format={m.File.Extension}&name={m.PictureOption}"
|
|
End If
|
|
If Not PostDate.IsEmptyString Then m.Post.Date = AConvert(Of Date)(PostDate, Declarations.DateProvider, Nothing) Else m.Post.Date = Nothing
|
|
m.State = State
|
|
m.Attempts = Attempts
|
|
Return m
|
|
End Function
|
|
#End Region
|
|
#Region "Downloader"
|
|
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
|
|
Const ytDest$ = "[download] destination"
|
|
Dim f As SFile = Nothing
|
|
If MySettings.CookiesNetscapeFile.Exists And Settings.YtdlpFile.Exists And (Not URL.IsEmptyString AndAlso URL.Contains(BroadCastPartUrl)) Then
|
|
Dim destPath$ = DestinationFile.PathWithSeparator.Replace("\", "\\")
|
|
Dim rr As RParams = RParams.DM($"{destPath}.+mp4", 0, RegexOptions.IgnoreCase, EDP.ReturnValue)
|
|
Dim cmd$ = $"""{Settings.YtdlpFile.File}"" --no-cookies-from-browser --cookies ""{MySettings.CookiesNetscapeFile}"" "
|
|
cmd &= $"{URL} -P ""{destPath}"" --no-mtime"
|
|
Using ytdlp As New YTDLP.YTDLPBatch(Token)
|
|
With ytdlp
|
|
.Execute(cmd)
|
|
If .OutputData.Count > 0 Then
|
|
For Each outStr$ In .OutputData
|
|
If Not outStr.IsEmptyString AndAlso outStr.ToLower.Trim.StartsWith(ytDest) Then
|
|
f = CStr(RegexReplace(outStr, rr))
|
|
If Not f.Exists Then f = Nothing
|
|
Exit For
|
|
End If
|
|
Next
|
|
End If
|
|
End With
|
|
End Using
|
|
End If
|
|
Return f
|
|
End Function
|
|
#End Region
|
|
#Region "Exception"
|
|
Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False,
|
|
Optional ByVal EObj As Object = Nothing) As Integer
|
|
Return 0
|
|
End Function
|
|
#End Region
|
|
#Region "IDisposable support"
|
|
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
|
|
If Not disposedValue And disposing Then _DataNames.Clear() : LikesPosts.Clear() : PostsKV.Clear()
|
|
MyBase.Dispose(disposing)
|
|
End Sub
|
|
#End Region
|
|
End Class
|
|
End Namespace |