mirror of
https://github.com/AAndyProgram/SCrawler.git
synced 2026-03-14 15:52:18 +00:00
2023.9.30.0
Add Threads.net API.UserDataBase: add 'EraseData_AdditionalDataFiles' function; add 'ThrowAnyImpl' function; add 'e' arg to 'LogError' function API.Instagram: make classes compatible with threads.net; add top limits; update container parsers; add an override to the 'EraseData_AdditionalDataFiles' function
This commit is contained in:
@@ -1829,6 +1829,7 @@ BlockNullPicture:
|
|||||||
If m.Contains(IUserData.EraseMode.History) Then
|
If m.Contains(IUserData.EraseMode.History) Then
|
||||||
If MyFilePosts.Delete(SFO.File, SFODelete.DeleteToRecycleBin, e) Then result = True
|
If MyFilePosts.Delete(SFO.File, SFODelete.DeleteToRecycleBin, e) Then result = True
|
||||||
If MyFileData.Delete(SFO.File, SFODelete.DeleteToRecycleBin, e) Then result = True
|
If MyFileData.Delete(SFO.File, SFODelete.DeleteToRecycleBin, e) Then result = True
|
||||||
|
EraseData_AdditionalDataFiles()
|
||||||
End If
|
End If
|
||||||
If m.Contains(IUserData.EraseMode.Data) Then
|
If m.Contains(IUserData.EraseMode.Data) Then
|
||||||
Dim files As List(Of SFile) = SFile.GetFiles(DownloadContentDefault_GetRootDir.CSFileP,, SearchOption.AllDirectories, e)
|
Dim files As List(Of SFile) = SFile.GetFiles(DownloadContentDefault_GetRootDir.CSFileP,, SearchOption.AllDirectories, e)
|
||||||
@@ -1850,6 +1851,8 @@ BlockNullPicture:
|
|||||||
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, $"EraseData({CInt(Mode)}): {ToStringForLog()}", False)
|
Return ErrorsDescriber.Execute(EDP.SendToLog + EDP.ReturnValue, ex, $"EraseData({CInt(Mode)}): {ToStringForLog()}", False)
|
||||||
End Try
|
End Try
|
||||||
End Function
|
End Function
|
||||||
|
Protected Overridable Sub EraseData_AdditionalDataFiles()
|
||||||
|
End Sub
|
||||||
Friend Overridable Function Delete(Optional ByVal Multiple As Boolean = False, Optional ByVal CollectionValue As Integer = -1) As Integer Implements IUserData.Delete
|
Friend Overridable Function Delete(Optional ByVal Multiple As Boolean = False, Optional ByVal CollectionValue As Integer = -1) As Integer Implements IUserData.Delete
|
||||||
Dim f As SFile = SFile.GetPath(MyFile.CutPath.Path)
|
Dim f As SFile = SFile.GetPath(MyFile.CutPath.Path)
|
||||||
If f.Exists(SFO.Path, False) AndAlso (User.Merged OrElse f.Delete(SFO.Path, Settings.DeleteMode)) Then
|
If f.Exists(SFO.Path, False) AndAlso (User.Merged OrElse f.Delete(SFO.Path, Settings.DeleteMode)) Then
|
||||||
@@ -2026,8 +2029,8 @@ BlockNullPicture:
|
|||||||
End Function
|
End Function
|
||||||
#End Region
|
#End Region
|
||||||
#Region "Errors functions"
|
#Region "Errors functions"
|
||||||
Protected Sub LogError(ByVal ex As Exception, ByVal Message As String)
|
Protected Sub LogError(ByVal ex As Exception, ByVal Message As String, Optional ByVal e As ErrorsDescriber = Nothing)
|
||||||
ErrorsDescriber.Execute(EDP.SendToLog, ex, $"{ToStringForLog()}: {Message}")
|
ErrorsDescriber.Execute(If(e.Exists, e, New ErrorsDescriber(EDP.SendToLog)), ex, $"{ToStringForLog()}: {Message}")
|
||||||
End Sub
|
End Sub
|
||||||
Protected Sub ErrorDownloading(ByVal f As SFile, ByVal URL As String)
|
Protected Sub ErrorDownloading(ByVal f As SFile, ByVal URL As String)
|
||||||
If Not f.Exists Then MyMainLOG = $"Error downloading from [{URL}] to [{f}]"
|
If Not f.Exists Then MyMainLOG = $"Error downloading from [{URL}] to [{f}]"
|
||||||
@@ -2040,9 +2043,13 @@ BlockNullPicture:
|
|||||||
Private Overloads Sub ThrowAny() Implements IThrower.ThrowAny
|
Private Overloads Sub ThrowAny() Implements IThrower.ThrowAny
|
||||||
ThrowAny(TokenQueue)
|
ThrowAny(TokenQueue)
|
||||||
End Sub
|
End Sub
|
||||||
|
''' <summary><c>ThrowAnyImpl(Token)</c></summary>
|
||||||
''' <exception cref="OperationCanceledException"></exception>
|
''' <exception cref="OperationCanceledException"></exception>
|
||||||
''' <exception cref="ObjectDisposedException"></exception>
|
''' <exception cref="ObjectDisposedException"></exception>
|
||||||
Friend Overridable Overloads Sub ThrowAny(ByVal Token As CancellationToken)
|
Friend Overridable Overloads Sub ThrowAny(ByVal Token As CancellationToken)
|
||||||
|
ThrowAnyImpl(Token)
|
||||||
|
End Sub
|
||||||
|
Protected Sub ThrowAnyImpl(ByVal Token As CancellationToken)
|
||||||
Token.ThrowIfCancellationRequested()
|
Token.ThrowIfCancellationRequested()
|
||||||
TokenQueue.ThrowIfCancellationRequested()
|
TokenQueue.ThrowIfCancellationRequested()
|
||||||
TokenPersonal.ThrowIfCancellationRequested()
|
TokenPersonal.ThrowIfCancellationRequested()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Namespace API.Instagram
|
|||||||
Friend ReadOnly FilesPattern As RParams = RParams.DMS(".+?([^/\?]+?\.[\w\d]{3,4})(?=(\?|\Z))", 1, EDP.ReturnValue)
|
Friend ReadOnly FilesPattern As RParams = RParams.DMS(".+?([^/\?]+?\.[\w\d]{3,4})(?=(\?|\Z))", 1, EDP.ReturnValue)
|
||||||
Friend Sub UpdateResponser(ByVal Source As IResponse, ByRef Destination As Responser)
|
Friend Sub UpdateResponser(ByVal Source As IResponse, ByRef Destination As Responser)
|
||||||
Const r_wwwClaimName$ = "x-ig-set-www-claim"
|
Const r_wwwClaimName$ = "x-ig-set-www-claim"
|
||||||
Const r_tokenName$ = "csrftoken"
|
Const r_tokenName$ = SiteSettings.Header_CSRF_TOKEN_COOKIE
|
||||||
If Not Source Is Nothing Then
|
If Not Source Is Nothing Then
|
||||||
Dim isInternal As Boolean = TypeOf Source Is WebDataResponse
|
Dim isInternal As Boolean = TypeOf Source Is WebDataResponse
|
||||||
Dim wwwClaimName$, tokenName$
|
Dim wwwClaimName$, tokenName$
|
||||||
|
|||||||
@@ -70,13 +70,14 @@ Namespace API.Instagram
|
|||||||
End Class
|
End Class
|
||||||
#End Region
|
#End Region
|
||||||
#Region "Authorization properties"
|
#Region "Authorization properties"
|
||||||
Private Const Header_IG_APP_ID As String = "x-ig-app-id"
|
Friend Const Header_IG_APP_ID As String = "x-ig-app-id"
|
||||||
Friend Const Header_IG_WWW_CLAIM As String = "x-ig-www-claim"
|
Friend Const Header_IG_WWW_CLAIM As String = "x-ig-www-claim"
|
||||||
Friend Const Header_CSRF_TOKEN As String = "x-csrftoken"
|
Friend Const Header_CSRF_TOKEN As String = "x-csrftoken"
|
||||||
Private Const Header_ASBD_ID As String = "X-Asbd-Id"
|
Friend Const Header_CSRF_TOKEN_COOKIE As String = "csrftoken"
|
||||||
Private Const Header_Browser As String = "Sec-Ch-Ua"
|
Friend Const Header_ASBD_ID As String = "X-Asbd-Id"
|
||||||
Private Const Header_BrowserExt As String = "Sec-Ch-Ua-Full-Version-List"
|
Friend Const Header_Browser As String = "Sec-Ch-Ua"
|
||||||
Private Const Header_Platform As String = "Sec-Ch-Ua-Platform-Version"
|
Friend Const Header_BrowserExt As String = "Sec-Ch-Ua-Full-Version-List"
|
||||||
|
Friend Const Header_Platform As String = "Sec-Ch-Ua-Platform-Version"
|
||||||
<PropertyOption(ControlText:="Hash", ControlToolTip:="Instagram session hash for tagged posts", IsAuth:=True), PXML("InstaHash"), ControlNumber(0)>
|
<PropertyOption(ControlText:="Hash", ControlToolTip:="Instagram session hash for tagged posts", IsAuth:=True), PXML("InstaHash"), ControlNumber(0)>
|
||||||
Friend ReadOnly Property HashTagged As PropertyValue
|
Friend ReadOnly Property HashTagged As PropertyValue
|
||||||
<PropertyOption(ControlText:="x-csrftoken", IsAuth:=True, AllowNull:=False), ControlNumber(2)>
|
<PropertyOption(ControlText:="x-csrftoken", IsAuth:=True, AllowNull:=False), ControlNumber(2)>
|
||||||
@@ -365,13 +366,16 @@ Namespace API.Instagram
|
|||||||
SkipUntilNextSession = False
|
SkipUntilNextSession = False
|
||||||
End Sub
|
End Sub
|
||||||
#End Region
|
#End Region
|
||||||
#Region "UserOptions, GetUserPostUrl"
|
#Region "UserOptions, GetUserUrl, GetUserPostUrl"
|
||||||
Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean)
|
Friend Overrides Sub UserOptions(ByRef Options As Object, ByVal OpenForm As Boolean)
|
||||||
If Options Is Nothing OrElse Not TypeOf Options Is EditorExchangeOptions Then Options = New EditorExchangeOptions(Me)
|
If Options Is Nothing OrElse Not TypeOf Options Is EditorExchangeOptions Then Options = New EditorExchangeOptions(Me)
|
||||||
If OpenForm Then
|
If OpenForm Then
|
||||||
Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using
|
Using f As New InternalSettingsForm(Options, Me, False) : f.ShowDialog() : End Using
|
||||||
End If
|
End If
|
||||||
End Sub
|
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
|
Friend Overrides Function GetUserPostUrl(ByVal User As UserDataBase, ByVal Media As UserMedia) As String
|
||||||
Try
|
Try
|
||||||
Dim code$ = DirectCast(User, UserData).GetPostCodeById(Media.Post.ID)
|
Dim code$ = DirectCast(User, UserData).GetPostCodeById(Media.Post.ID)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Namespace API.Instagram
|
|||||||
Private Const Name_NameTrue As String = "NameTrue"
|
Private Const Name_NameTrue As String = "NameTrue"
|
||||||
#End Region
|
#End Region
|
||||||
#Region "Declarations"
|
#Region "Declarations"
|
||||||
Private Structure PostKV : Implements IEContainerProvider
|
Protected Structure PostKV : Implements IEContainerProvider
|
||||||
Private Const Name_Code As String = "Code"
|
Private Const Name_Code As String = "Code"
|
||||||
Private Const Name_Section As String = "Section"
|
Private Const Name_Section As String = "Section"
|
||||||
Friend Code As String
|
Friend Code As String
|
||||||
@@ -78,8 +78,8 @@ Namespace API.Instagram
|
|||||||
Friend Property GetStories As Boolean
|
Friend Property GetStories As Boolean
|
||||||
Friend Property GetStoriesUser As Boolean
|
Friend Property GetStoriesUser As Boolean
|
||||||
Friend Property GetTaggedData As Boolean
|
Friend Property GetTaggedData As Boolean
|
||||||
Private _NameTrue As String = String.Empty
|
Protected _NameTrue As String = String.Empty
|
||||||
Private ReadOnly Property NameTrue As String
|
Friend ReadOnly Property NameTrue As String
|
||||||
Get
|
Get
|
||||||
Return _NameTrue.IfNullOrEmpty(Name)
|
Return _NameTrue.IfNullOrEmpty(Name)
|
||||||
End Get
|
End Get
|
||||||
@@ -143,12 +143,22 @@ Namespace API.Instagram
|
|||||||
Throw New ExitException
|
Throw New ExitException
|
||||||
End Sub
|
End Sub
|
||||||
End Class
|
End Class
|
||||||
Private Sub LoadSavePostsKV(ByVal Load As Boolean)
|
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)
|
||||||
Dim x As XmlFile
|
Dim x As XmlFile
|
||||||
Dim f As SFile = MyFilePosts
|
Dim f As SFile = MyFilePostsKV
|
||||||
If Not f.IsEmptyString Then
|
If Not f.IsEmptyString Then
|
||||||
f.Name &= "_KV"
|
|
||||||
f.Extension = "xml"
|
|
||||||
If Load Then
|
If Load Then
|
||||||
PostsKVIDs.Clear()
|
PostsKVIDs.Clear()
|
||||||
x = New XmlFile(f, Protector.Modes.All, False) With {.AllowSameNames = True, .XmlReadOnly = True}
|
x = New XmlFile(f, Protector.Modes.All, False) With {.AllowSameNames = True, .XmlReadOnly = True}
|
||||||
@@ -182,10 +192,8 @@ Namespace API.Instagram
|
|||||||
Friend Function GetPostCodeById(ByVal PostID As String) As String
|
Friend Function GetPostCodeById(ByVal PostID As String) As String
|
||||||
Try
|
Try
|
||||||
If Not PostID.IsEmptyString Then
|
If Not PostID.IsEmptyString Then
|
||||||
Dim f As SFile = MyFilePosts
|
Dim f As SFile = MyFilePostsKV
|
||||||
If Not f.IsEmptyString Then
|
If Not f.IsEmptyString Then
|
||||||
f.Name &= "_KV"
|
|
||||||
f.Extension = "xml"
|
|
||||||
Dim l As List(Of PostKV) = Nothing
|
Dim l As List(Of PostKV) = Nothing
|
||||||
Using x As New XmlFile(f, Protector.Modes.All, False) With {.AllowSameNames = True, .XmlReadOnly = True}
|
Using x As New XmlFile(f, Protector.Modes.All, False) With {.AllowSameNames = True, .XmlReadOnly = True}
|
||||||
x.LoadData()
|
x.LoadData()
|
||||||
@@ -213,11 +221,15 @@ Namespace API.Instagram
|
|||||||
End If
|
End If
|
||||||
End Function
|
End Function
|
||||||
Private _DownloadingInProgress As Boolean = False
|
Private _DownloadingInProgress As Boolean = False
|
||||||
|
Private _Limit As Integer = -1
|
||||||
|
Private _TotalPostsParsed As Integer = 0
|
||||||
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
|
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
|
||||||
UserNameRequested = False
|
UserNameRequested = False
|
||||||
Dim s As Sections = Sections.Timeline
|
Dim s As Sections = Sections.Timeline
|
||||||
Dim errorFound As Boolean = False
|
Dim errorFound As Boolean = False
|
||||||
Try
|
Try
|
||||||
|
_Limit = If(DownloadTopCount, -1)
|
||||||
|
_TotalPostsParsed = 0
|
||||||
LoadSavePostsKV(True)
|
LoadSavePostsKV(True)
|
||||||
_DownloadingInProgress = True
|
_DownloadingInProgress = True
|
||||||
AddHandler Responser.ResponseReceived, AddressOf Responser_ResponseReceived
|
AddHandler Responser.ResponseReceived, AddressOf Responser_ResponseReceived
|
||||||
@@ -270,7 +282,7 @@ Namespace API.Instagram
|
|||||||
Catch ex As Exception
|
Catch ex As Exception
|
||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
Private Sub UpdateResponser()
|
Protected Overridable Sub UpdateResponser()
|
||||||
Try
|
Try
|
||||||
If _DownloadingInProgress AndAlso Not Responser Is Nothing AndAlso Not Responser.Disposed Then
|
If _DownloadingInProgress AndAlso Not Responser Is Nothing AndAlso Not Responser.Disposed Then
|
||||||
_DownloadingInProgress = False
|
_DownloadingInProgress = False
|
||||||
@@ -280,10 +292,10 @@ Namespace API.Instagram
|
|||||||
Catch
|
Catch
|
||||||
End Try
|
End Try
|
||||||
End Sub
|
End Sub
|
||||||
Private Sub Responser_ResponseReceived(ByVal Sender As Object, ByVal e As EventArguments.WebDataResponse)
|
Protected Overridable Sub Responser_ResponseReceived(ByVal Sender As Object, ByVal e As EventArguments.WebDataResponse)
|
||||||
Declarations.UpdateResponser(e, Responser)
|
Declarations.UpdateResponser(e, Responser)
|
||||||
End Sub
|
End Sub
|
||||||
Private Enum Sections : Timeline : Tagged : Stories : UserStories : SavedPosts : End Enum
|
Protected Enum Sections : Timeline : Tagged : Stories : UserStories : SavedPosts : End Enum
|
||||||
Private Const StoriesFolder As String = "Stories"
|
Private Const StoriesFolder As String = "Stories"
|
||||||
Private Const TaggedFolder As String = "Tagged"
|
Private Const TaggedFolder As String = "Tagged"
|
||||||
#Region "429 bypass"
|
#Region "429 bypass"
|
||||||
@@ -572,6 +584,7 @@ Namespace API.Instagram
|
|||||||
Dim URL$ = String.Empty
|
Dim URL$ = String.Empty
|
||||||
Dim dValue% = 1
|
Dim dValue% = 1
|
||||||
Dim _Index% = 0
|
Dim _Index% = 0
|
||||||
|
Dim before%
|
||||||
If PostsToReparse.Count > 0 Then ProgressPre.ChangeMax(PostsToReparse.Count)
|
If PostsToReparse.Count > 0 Then ProgressPre.ChangeMax(PostsToReparse.Count)
|
||||||
Try
|
Try
|
||||||
Do While dValue = 1
|
Do While dValue = 1
|
||||||
@@ -600,7 +613,12 @@ Namespace API.Instagram
|
|||||||
If Not j Is Nothing Then
|
If Not j Is Nothing Then
|
||||||
If If(j("items")?.Count, 0) > 0 Then
|
If If(j("items")?.Count, 0) > 0 Then
|
||||||
With j("items")
|
With j("items")
|
||||||
For Each jj In .Self : ObtainMedia(jj, PostsToReparse(i).ID) : Next
|
For Each jj In .Self
|
||||||
|
before = _TempMediaList.Count
|
||||||
|
ObtainMedia(jj, PostsToReparse(i).ID)
|
||||||
|
If Not before = _TempMediaList.Count Then _TotalPostsParsed += 1
|
||||||
|
If _Limit > 0 And _TotalPostsParsed >= _Limit Then Throw New ExitException
|
||||||
|
Next
|
||||||
End With
|
End With
|
||||||
End If
|
End If
|
||||||
j.Dispose()
|
j.Dispose()
|
||||||
@@ -643,13 +661,17 @@ Namespace API.Instagram
|
|||||||
End Using
|
End Using
|
||||||
End If
|
End If
|
||||||
End Sub
|
End Sub
|
||||||
Private Function DefaultParser(ByVal Items As IEnumerable(Of EContainer), ByVal Section As Sections, ByVal Token As CancellationToken,
|
Protected DefaultParser_ElemNode() As Object = Nothing
|
||||||
Optional ByVal SpecFolder As String = Nothing) As Boolean
|
Protected DefaultParser_IgnorePass As Boolean = False
|
||||||
|
Protected DefaultParser_PostUrlCreator As Func(Of PostKV, String) = Function(post) $"https://www.instagram.com/p/{post.Code}/"
|
||||||
|
Protected Function DefaultParser(ByVal Items As IEnumerable(Of EContainer), ByVal Section As Sections, ByVal Token As CancellationToken,
|
||||||
|
Optional ByVal SpecFolder As String = Nothing) As Boolean
|
||||||
ThrowAny(Token)
|
ThrowAny(Token)
|
||||||
If Items.Count > 0 Then
|
If Items.Count > 0 Then
|
||||||
Dim PostIDKV As PostKV
|
Dim PostIDKV As PostKV
|
||||||
Dim Pinned As Boolean
|
Dim Pinned As Boolean
|
||||||
Dim PostDate$
|
Dim PostDate$, PostOriginUrl$
|
||||||
|
Dim before%
|
||||||
If SpecFolder.IsEmptyString Then
|
If SpecFolder.IsEmptyString Then
|
||||||
Select Case Section
|
Select Case Section
|
||||||
Case Sections.Tagged : SpecFolder = TaggedFolder
|
Case Sections.Tagged : SpecFolder = TaggedFolder
|
||||||
@@ -660,22 +682,26 @@ Namespace API.Instagram
|
|||||||
ProgressPre.ChangeMax(Items.Count)
|
ProgressPre.ChangeMax(Items.Count)
|
||||||
For Each nn In Items
|
For Each nn In Items
|
||||||
ProgressPre.Perform()
|
ProgressPre.Perform()
|
||||||
With nn
|
With If(Not DefaultParser_ElemNode Is Nothing, nn.ItemF(DefaultParser_ElemNode), nn)
|
||||||
PostIDKV = New PostKV(.Value("code"), .Value("id"), Section)
|
PostIDKV = New PostKV(.Value("code"), .Value("id"), Section)
|
||||||
|
PostOriginUrl = DefaultParser_PostUrlCreator(PostIDKV)
|
||||||
Pinned = .Contains("timeline_pinned_user_ids")
|
Pinned = .Contains("timeline_pinned_user_ids")
|
||||||
If PostKvExists(PostIDKV) Then
|
If Not DefaultParser_IgnorePass AndAlso PostKvExists(PostIDKV) Then
|
||||||
If Not Pinned Then Return False
|
If Not Pinned Then Return False
|
||||||
Else
|
Else
|
||||||
_TempPostsList.Add(PostIDKV.ID)
|
_TempPostsList.Add(PostIDKV.ID)
|
||||||
PostsKVIDs.ListAddValue(PostIDKV, LNC)
|
PostsKVIDs.ListAddValue(PostIDKV, LNC)
|
||||||
PostDate = .Value("taken_at")
|
PostDate = .Value("taken_at")
|
||||||
If Not IsSavedPosts Then
|
If Not DefaultParser_IgnorePass And Not IsSavedPosts Then
|
||||||
Select Case CheckDatesLimit(PostDate, UnixDate32Provider)
|
Select Case CheckDatesLimit(PostDate, UnixDate32Provider)
|
||||||
Case DateResult.Skip : Continue For
|
Case DateResult.Skip : Continue For
|
||||||
Case DateResult.Exit : If Not Pinned Then Return False
|
Case DateResult.Exit : If Not Pinned Then Return False
|
||||||
End Select
|
End Select
|
||||||
End If
|
End If
|
||||||
ObtainMedia(.Self, PostIDKV.ID, SpecFolder, PostDate)
|
before = _TempMediaList.Count
|
||||||
|
ObtainMedia(.Self, PostIDKV.ID, SpecFolder, PostDate,, PostOriginUrl)
|
||||||
|
If Not before = _TempMediaList.Count Then _TotalPostsParsed += 1
|
||||||
|
If _Limit > 0 And _TotalPostsParsed >= _Limit Then Return False
|
||||||
End If
|
End If
|
||||||
End With
|
End With
|
||||||
Next
|
Next
|
||||||
@@ -686,7 +712,7 @@ Namespace API.Instagram
|
|||||||
End Function
|
End Function
|
||||||
#End Region
|
#End Region
|
||||||
#Region "Code ID converters"
|
#Region "Code ID converters"
|
||||||
Private Function CodeToID(ByVal Code As String) As String
|
Protected Function CodeToID(ByVal Code As String) As String
|
||||||
Const CodeSymbols$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
Const CodeSymbols$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||||
Try
|
Try
|
||||||
If Not Code.IsEmptyString Then
|
If Not Code.IsEmptyString Then
|
||||||
@@ -706,12 +732,19 @@ Namespace API.Instagram
|
|||||||
End Function
|
End Function
|
||||||
#End Region
|
#End Region
|
||||||
#Region "Obtain Media"
|
#Region "Obtain Media"
|
||||||
Private Sub ObtainMedia(ByVal n As EContainer, ByVal PostID As String, Optional ByVal SpecialFolder As String = Nothing,
|
Protected ObtainMedia_SizeFuncVid As Func(Of EContainer, Sizes) = Nothing
|
||||||
Optional ByVal DateObj As String = Nothing)
|
Protected ObtainMedia_SizeFuncPic As Func(Of EContainer, Sizes) = Nothing
|
||||||
|
Protected ObtainMedia_AllowAbstract As Boolean = False
|
||||||
|
Protected Sub ObtainMedia(ByVal n As EContainer, ByVal PostID As String, Optional ByVal SpecialFolder As String = Nothing,
|
||||||
|
Optional ByVal DateObj As String = Nothing, Optional ByVal InitialType As Integer = -1,
|
||||||
|
Optional ByVal PostOriginUrl As String = Nothing)
|
||||||
Try
|
Try
|
||||||
|
Dim wrongData As Predicate(Of Sizes) = Function(_ss) _ss.HasError Or _ss.Data.IsEmptyString
|
||||||
Dim img As Predicate(Of EContainer) = Function(_img) Not _img.Name.IsEmptyString AndAlso _img.Name.StartsWith("image_versions") AndAlso _img.Count > 0
|
Dim img As Predicate(Of EContainer) = Function(_img) Not _img.Name.IsEmptyString AndAlso _img.Name.StartsWith("image_versions") AndAlso _img.Count > 0
|
||||||
Dim vid As Predicate(Of EContainer) = Function(_vid) Not _vid.Name.IsEmptyString AndAlso _vid.Name.StartsWith("video_versions") AndAlso _vid.Count > 0
|
Dim vid As Predicate(Of EContainer) = Function(_vid) Not _vid.Name.IsEmptyString AndAlso _vid.Name.StartsWith("video_versions") AndAlso _vid.Count > 0
|
||||||
Dim ss As Func(Of EContainer, Sizes) = Function(_ss) New Sizes(_ss.Value("width"), _ss.Value("url"))
|
Dim ss As Func(Of EContainer, Sizes) = Function(_ss) New Sizes(_ss.Value("width"), _ss.Value("url"))
|
||||||
|
Dim ssVid As Func(Of EContainer, Sizes) = ss
|
||||||
|
Dim ssPic As Func(Of EContainer, Sizes) = ss
|
||||||
Dim mDate As Func(Of EContainer, String) = Function(ByVal elem As EContainer) As String
|
Dim mDate As Func(Of EContainer, String) = Function(ByVal elem As EContainer) As String
|
||||||
If Not DateObj.IsEmptyString Then Return DateObj
|
If Not DateObj.IsEmptyString Then Return DateObj
|
||||||
If elem.Contains("taken_at") Then
|
If elem.Contains("taken_at") Then
|
||||||
@@ -731,28 +764,41 @@ Namespace API.Instagram
|
|||||||
End If
|
End If
|
||||||
End If
|
End If
|
||||||
End Function
|
End Function
|
||||||
|
If Not ObtainMedia_SizeFuncVid Is Nothing Then ssVid = ObtainMedia_SizeFuncVid
|
||||||
|
If Not ObtainMedia_SizeFuncPic Is Nothing Then ssPic = ObtainMedia_SizeFuncPic
|
||||||
If n.Count > 0 Then
|
If n.Count > 0 Then
|
||||||
Dim l As New List(Of Sizes)
|
Dim l As New List(Of Sizes)
|
||||||
Dim d As EContainer
|
Dim d As EContainer
|
||||||
Dim t%
|
Dim t%
|
||||||
|
Dim abstractDecision As Boolean = False
|
||||||
'8 - gallery
|
'8 - gallery
|
||||||
'2 - one video
|
'2 - one video
|
||||||
'1 - one picture
|
'1 - one picture
|
||||||
t = n.Value("media_type").FromXML(Of Integer)(-1)
|
t = n.Value("media_type").FromXML(Of Integer)(-1)
|
||||||
|
If t = -1 And InitialType = 8 And ObtainMedia_AllowAbstract Then
|
||||||
|
If n.Contains(vid) Then
|
||||||
|
t = 2
|
||||||
|
abstractDecision = True
|
||||||
|
ElseIf n.Contains(img) Then
|
||||||
|
t = 1
|
||||||
|
abstractDecision = True
|
||||||
|
End If
|
||||||
|
End If
|
||||||
If t >= 0 Then
|
If t >= 0 Then
|
||||||
Select Case t
|
Select Case t
|
||||||
Case 1
|
Case 1
|
||||||
If n.Contains(img) Then
|
If n.Contains(img) Then
|
||||||
t = n.Value("media_type").FromXML(Of Integer)(-1)
|
If Not abstractDecision Then t = n.Value("media_type").FromXML(Of Integer)(-1)
|
||||||
DateObj = mDate(n)
|
DateObj = mDate(n)
|
||||||
If t >= 0 Then
|
If t >= 0 Then
|
||||||
With n.ItemF({img, "candidates"}).XmlIfNothing
|
With n.ItemF({img, "candidates"}).XmlIfNothing
|
||||||
If .Count > 0 Then
|
If .Count > 0 Then
|
||||||
l.Clear()
|
l.Clear()
|
||||||
l.ListAddList(.Select(ss), LNC)
|
l.ListAddList(.Select(ssPic), LNC)
|
||||||
|
If l.Count > 0 Then l.RemoveAll(wrongData)
|
||||||
If l.Count > 0 Then
|
If l.Count > 0 Then
|
||||||
l.Sort()
|
l.Sort()
|
||||||
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, l.First.Data, PostID, DateObj, SpecialFolder), LNC)
|
_TempMediaList.ListAddValue(MediaFromData(UTypes.Picture, l.First.Data, PostID, DateObj, SpecialFolder, PostOriginUrl), LNC)
|
||||||
l.Clear()
|
l.Clear()
|
||||||
End If
|
End If
|
||||||
End If
|
End If
|
||||||
@@ -765,10 +811,11 @@ Namespace API.Instagram
|
|||||||
With n.ItemF({vid}).XmlIfNothing
|
With n.ItemF({vid}).XmlIfNothing
|
||||||
If .Count > 0 Then
|
If .Count > 0 Then
|
||||||
l.Clear()
|
l.Clear()
|
||||||
l.ListAddList(.Select(ss), LNC)
|
l.ListAddList(.Select(ssVid), LNC)
|
||||||
|
If l.Count > 0 Then l.RemoveAll(wrongData)
|
||||||
If l.Count > 0 Then
|
If l.Count > 0 Then
|
||||||
l.Sort()
|
l.Sort()
|
||||||
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, l.First.Data, PostID, DateObj, SpecialFolder), LNC)
|
_TempMediaList.ListAddValue(MediaFromData(UTypes.Video, l.First.Data, PostID, DateObj, SpecialFolder, PostOriginUrl), LNC)
|
||||||
l.Clear()
|
l.Clear()
|
||||||
End If
|
End If
|
||||||
End If
|
End If
|
||||||
@@ -778,7 +825,7 @@ Namespace API.Instagram
|
|||||||
DateObj = mDate(n)
|
DateObj = mDate(n)
|
||||||
With n("carousel_media").XmlIfNothing
|
With n("carousel_media").XmlIfNothing
|
||||||
If .Count > 0 Then
|
If .Count > 0 Then
|
||||||
For Each d In .Self : ObtainMedia(d, PostID, SpecialFolder, DateObj) : Next
|
For Each d In .Self : ObtainMedia(d, PostID, SpecialFolder, DateObj, 8, PostOriginUrl) : Next
|
||||||
End If
|
End If
|
||||||
End With
|
End With
|
||||||
End Select
|
End Select
|
||||||
@@ -939,6 +986,12 @@ Namespace API.Instagram
|
|||||||
DownloadContentDefault(Token)
|
DownloadContentDefault(Token)
|
||||||
End Sub
|
End Sub
|
||||||
#End Region
|
#End Region
|
||||||
|
#Region "Erase"
|
||||||
|
Protected Overrides Sub EraseData_AdditionalDataFiles()
|
||||||
|
Dim f As SFile = MyFilePostsKV
|
||||||
|
If f.Exists Then f.Delete(SFO.File, SFODelete.DeleteToRecycleBin, EDP.ReturnValue)
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
#Region "Exceptions"
|
#Region "Exceptions"
|
||||||
''' <exception cref="ExitException"></exception>
|
''' <exception cref="ExitException"></exception>
|
||||||
''' <inheritdoc cref="UserDataBase.ThrowAny(CancellationToken)"/>
|
''' <inheritdoc cref="UserDataBase.ThrowAny(CancellationToken)"/>
|
||||||
@@ -996,9 +1049,9 @@ Namespace API.Instagram
|
|||||||
#End Region
|
#End Region
|
||||||
#Region "Create media"
|
#Region "Create media"
|
||||||
Private Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String,
|
Private Function MediaFromData(ByVal t As UTypes, ByVal _URL As String, ByVal PostID As String, ByVal PostDate As String,
|
||||||
Optional ByVal SpecialFolder As String = Nothing) As UserMedia
|
Optional ByVal SpecialFolder As String = Nothing, Optional ByVal PostOriginUrl As String = Nothing) As UserMedia
|
||||||
_URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern))
|
_URL = LinkFormatterSecure(RegexReplace(_URL.Replace("\", String.Empty), LinkPattern))
|
||||||
Dim m As New UserMedia(_URL, t) With {.Post = New UserPost With {.ID = PostID}}
|
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 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
|
If Not PostDate.IsEmptyString Then m.Post.Date = AConvert(Of Date)(PostDate, UnixDate32Provider, Nothing) Else m.Post.Date = Nothing
|
||||||
m.SpecialFolder = SpecialFolder
|
m.SpecialFolder = SpecialFolder
|
||||||
|
|||||||
166
SCrawler/API/ThreadsNet/SiteSettings.vb
Normal file
166
SCrawler/API/ThreadsNet/SiteSettings.vb
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
' Copyright (C) 2023 Andy https://github.com/AAndyProgram
|
||||||
|
' This program is free software: you can redistribute it and/or modify
|
||||||
|
' it under the terms of the GNU General Public License as published by
|
||||||
|
' the Free Software Foundation, either version 3 of the License, or
|
||||||
|
' (at your option) any later version.
|
||||||
|
'
|
||||||
|
' This program is distributed in the hope that it will be useful,
|
||||||
|
' but WITHOUT ANY WARRANTY
|
||||||
|
Imports SCrawler.API.Base
|
||||||
|
Imports SCrawler.Plugin
|
||||||
|
Imports SCrawler.Plugin.Attributes
|
||||||
|
Imports PersonalUtilities.Tools.Web.Clients
|
||||||
|
Imports PersonalUtilities.Tools.Web.Cookies
|
||||||
|
Imports PersonalUtilities.Functions.RegularExpressions
|
||||||
|
Imports IG = SCrawler.API.Instagram.SiteSettings
|
||||||
|
Namespace API.ThreadsNet
|
||||||
|
<Manifest("AndyProgram_ThreadsNet"), SeparatedTasks(1)>
|
||||||
|
Friend Class SiteSettings : Inherits SiteSettingsBase
|
||||||
|
#Region "Declarations"
|
||||||
|
Friend Overrides ReadOnly Property Icon As Icon
|
||||||
|
Get
|
||||||
|
Return My.Resources.SiteResources.ThreadsIcon_192
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
Private ReadOnly _Image As Image
|
||||||
|
Friend Overrides ReadOnly Property Image As Image
|
||||||
|
Get
|
||||||
|
Return _Image
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
#Region "Authorization"
|
||||||
|
<PropertyOption(ControlText:="x-csrftoken", AllowNull:=False)>
|
||||||
|
Friend ReadOnly Property HH_CSRF_TOKEN As PropertyValue
|
||||||
|
<PropertyOption(ControlText:="x-ig-app-id", AllowNull:=False)>
|
||||||
|
Friend Property HH_IG_APP_ID As PropertyValue
|
||||||
|
<PropertyOption(ControlText:="x-asbd-id", AllowNull:=True)>
|
||||||
|
Friend Property HH_ASBD_ID As PropertyValue
|
||||||
|
<PropertyOption(ControlText:="sec-ch-ua", AllowNull:=True)>
|
||||||
|
Private Property HH_BROWSER As PropertyValue
|
||||||
|
<PropertyOption(ControlText:="sec-ch-ua-full", ControlToolTip:="sec-ch-ua-full-version-list", AllowNull:=True)>
|
||||||
|
Private Property HH_BROWSER_EXT As PropertyValue
|
||||||
|
<PropertyOption(ControlText:="sec-ch-ua-platform-ver", ControlToolTip:="sec-ch-ua-platform-version", AllowNull:=True, LeftOffset:=120)>
|
||||||
|
Private Property HH_PLATFORM As PropertyValue
|
||||||
|
<PropertyOption(ControlText:="UserAgent")>
|
||||||
|
Private ReadOnly Property HH_USER_AGENT As PropertyValue
|
||||||
|
Private Sub ChangeResponserFields(ByVal PropName As String, ByVal Value As Object)
|
||||||
|
If Not PropName.IsEmptyString Then
|
||||||
|
Dim f$ = String.Empty
|
||||||
|
Dim isUserAgent As Boolean = False
|
||||||
|
Select Case PropName
|
||||||
|
Case NameOf(HH_IG_APP_ID) : f = IG.Header_IG_APP_ID
|
||||||
|
Case NameOf(HH_ASBD_ID) : f = IG.Header_ASBD_ID
|
||||||
|
Case NameOf(HH_CSRF_TOKEN) : f = IG.Header_CSRF_TOKEN
|
||||||
|
Case NameOf(HH_BROWSER) : f = IG.Header_Browser
|
||||||
|
Case NameOf(HH_BROWSER_EXT) : f = IG.Header_BrowserExt
|
||||||
|
Case NameOf(HH_PLATFORM) : f = IG.Header_Platform
|
||||||
|
Case NameOf(HH_USER_AGENT) : isUserAgent = True
|
||||||
|
End Select
|
||||||
|
If Not f.IsEmptyString Then
|
||||||
|
Responser.Headers.Remove(f)
|
||||||
|
If Not CStr(Value).IsEmptyString Then Responser.Headers.Add(f, CStr(Value))
|
||||||
|
ElseIf isUserAgent Then
|
||||||
|
Responser.UserAgent = CStr(Value)
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#End Region
|
||||||
|
#Region "Initializer"
|
||||||
|
Friend Sub New()
|
||||||
|
MyBase.New("Threads", "threads.net")
|
||||||
|
_AllowUserAgentUpdate = False
|
||||||
|
_Image = My.Resources.SiteResources.ThreadsIcon_192.ToBitmap
|
||||||
|
|
||||||
|
Dim app_id$ = String.Empty
|
||||||
|
Dim token$ = String.Empty
|
||||||
|
Dim asbd$ = String.Empty
|
||||||
|
Dim browser$ = String.Empty
|
||||||
|
Dim browserExt$ = String.Empty
|
||||||
|
Dim platform$ = String.Empty
|
||||||
|
Dim useragent$ = String.Empty
|
||||||
|
|
||||||
|
With Responser
|
||||||
|
.Accept = "*/*"
|
||||||
|
'URGENT: remove after debug
|
||||||
|
.DeclaredError = EDP.SendToLog + EDP.ThrowException
|
||||||
|
If .UserAgentExists Then useragent = .UserAgent
|
||||||
|
With .Headers
|
||||||
|
If .Count > 0 Then
|
||||||
|
token = .Value(IG.Header_CSRF_TOKEN)
|
||||||
|
app_id = .Value(IG.Header_IG_APP_ID)
|
||||||
|
asbd = .Value(IG.Header_ASBD_ID)
|
||||||
|
browser = .Value(IG.Header_Browser)
|
||||||
|
browserExt = .Value(IG.Header_BrowserExt)
|
||||||
|
platform = .Value(IG.Header_Platform)
|
||||||
|
End If
|
||||||
|
.Add("Authority", "www.threads.net")
|
||||||
|
.Add("Origin", "https://www.threads.net")
|
||||||
|
.Add("Upgrade-Insecure-Requests", 1)
|
||||||
|
.Add("Sec-Ch-Ua-Model", "")
|
||||||
|
.Add("Sec-Ch-Ua-Mobile", "?0")
|
||||||
|
.Add("Sec-Ch-Ua-Platform", """Windows""")
|
||||||
|
.Add("Sec-Fetch-Dest", "empty")
|
||||||
|
.Add("Sec-Fetch-Mode", "cors")
|
||||||
|
.Add("Sec-Fetch-Site", "same-origin")
|
||||||
|
.Add("Sec-Fetch-User", "?1")
|
||||||
|
.Add("x-fb-friendly-name", "BarcelonaProfileThreadsTabRefetchableQuery")
|
||||||
|
End With
|
||||||
|
.CookiesExtractMode = Responser.CookiesExtractModes.Any
|
||||||
|
.CookiesUpdateMode = CookieKeeper.UpdateModes.ReplaceByNameAll
|
||||||
|
.CookiesExtractedAutoSave = False
|
||||||
|
.Cookies.ChangedAllowInternalDrop = False
|
||||||
|
.Cookies.Changed = False
|
||||||
|
End With
|
||||||
|
|
||||||
|
HH_CSRF_TOKEN = New PropertyValue(token, GetType(String), Sub(v) ChangeResponserFields(NameOf(HH_CSRF_TOKEN), v))
|
||||||
|
HH_IG_APP_ID = New PropertyValue(app_id, GetType(String), Sub(v) ChangeResponserFields(NameOf(HH_IG_APP_ID), v))
|
||||||
|
HH_ASBD_ID = New PropertyValue(asbd, GetType(String), Sub(v) ChangeResponserFields(NameOf(HH_ASBD_ID), v))
|
||||||
|
HH_BROWSER = New PropertyValue(browser, GetType(String), Sub(v) ChangeResponserFields(NameOf(HH_BROWSER), v))
|
||||||
|
HH_BROWSER_EXT = New PropertyValue(browserExt, GetType(String), Sub(v) ChangeResponserFields(NameOf(HH_BROWSER_EXT), v))
|
||||||
|
HH_PLATFORM = New PropertyValue(platform, GetType(String), Sub(v) ChangeResponserFields(NameOf(HH_PLATFORM), v))
|
||||||
|
HH_USER_AGENT = New PropertyValue(useragent, GetType(String), Sub(v) ChangeResponserFields(NameOf(HH_USER_AGENT), v))
|
||||||
|
|
||||||
|
UrlPatternUser = "https://www.threads.net/@{0}"
|
||||||
|
UserRegex = RParams.DMS("threads.net/@([^/\?&]+)", 1)
|
||||||
|
ImageVideoContains = "threads.net"
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#Region "UpdateResponserData"
|
||||||
|
Friend Sub UpdateResponserData(ByVal Resp As Responser)
|
||||||
|
With Responser.Cookies
|
||||||
|
Dim csrf$ = String.Empty
|
||||||
|
.Update(Resp.Cookies)
|
||||||
|
If .Changed Then
|
||||||
|
Responser.SaveCookies()
|
||||||
|
.Changed = False
|
||||||
|
csrf = If(.FirstOrDefault(Function(c) c.Name.StringToLower = IG.Header_CSRF_TOKEN_COOKIE)?.Value, String.Empty)
|
||||||
|
End If
|
||||||
|
If Not csrf.IsEmptyString AndAlso Not AEquals(Of String)(csrf, HH_CSRF_TOKEN.Value) Then HH_CSRF_TOKEN.Value = csrf
|
||||||
|
End With
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#Region "GetInstance"
|
||||||
|
Friend Overrides Function GetInstance(ByVal What As ISiteSettings.Download) As IPluginContentProvider
|
||||||
|
Return New UserData
|
||||||
|
End Function
|
||||||
|
#End Region
|
||||||
|
#Region "BaseAuthExists, GetUserUrl, GetUserPostUrl"
|
||||||
|
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))
|
||||||
|
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
|
||||||
|
Catch ex As Exception
|
||||||
|
Return ErrorsDescriber.Execute(EDP.SendToLog, ex, "Can't open user's post", String.Empty)
|
||||||
|
End Try
|
||||||
|
End Function
|
||||||
|
#End Region
|
||||||
|
End Class
|
||||||
|
End Namespace
|
||||||
290
SCrawler/API/ThreadsNet/UserData.vb
Normal file
290
SCrawler/API/ThreadsNet/UserData.vb
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
' Copyright (C) 2023 Andy https://github.com/AAndyProgram
|
||||||
|
' This program is free software: you can redistribute it and/or modify
|
||||||
|
' it under the terms of the GNU General Public License as published by
|
||||||
|
' the Free Software Foundation, either version 3 of the License, or
|
||||||
|
' (at your option) any later version.
|
||||||
|
'
|
||||||
|
' This program is distributed in the hope that it will be useful,
|
||||||
|
' but WITHOUT ANY WARRANTY
|
||||||
|
Imports System.Threading
|
||||||
|
Imports SCrawler.API.Base
|
||||||
|
Imports SCrawler.API.YouTube.Objects
|
||||||
|
Imports PersonalUtilities.Functions.XML
|
||||||
|
Imports PersonalUtilities.Functions.RegularExpressions
|
||||||
|
Imports PersonalUtilities.Tools.Web.Documents.JSON
|
||||||
|
Imports PersonalUtilities.Tools.Web.Clients
|
||||||
|
Imports PersonalUtilities.Tools.Web.Clients.EventArguments
|
||||||
|
Imports IGS = SCrawler.API.Instagram.SiteSettings
|
||||||
|
Namespace API.ThreadsNet
|
||||||
|
Friend Class UserData : Inherits Instagram.UserData
|
||||||
|
#Region "Declarations"
|
||||||
|
Private Const Header_FB_LSD As String = "x-fb-lsd"
|
||||||
|
Private ReadOnly Property MySettings As SiteSettings
|
||||||
|
Get
|
||||||
|
Return HOST.Source
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
Private ReadOnly ObtainMedia_SizeFuncPic_RegexP As RParams = RParams.DMS("_p(\d+)x(\d+)", 1, EDP.ReturnValue)
|
||||||
|
Private ReadOnly ObtainMedia_SizeFuncPic_RegexS As RParams = RParams.DMS("_s(\d+)x(\d+)", 1, EDP.ReturnValue)
|
||||||
|
Private ReadOnly DefaultParser_ElemNode_Default() As Object = {"node", "thread_items", 0, "post"}
|
||||||
|
Private OPT_LSD As String = String.Empty
|
||||||
|
Private OPT_FB_DTSG As String = String.Empty
|
||||||
|
Private ReadOnly Property Valid As Boolean
|
||||||
|
Get
|
||||||
|
Return Not OPT_LSD.IsEmptyString And Not OPT_FB_DTSG.IsEmptyString And Not ID.IsEmptyString
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
#End Region
|
||||||
|
#Region "Loader"
|
||||||
|
Protected Overrides Sub LoadUserInformation_OptionalFields(ByRef Container As XmlFile, ByVal Loading As Boolean)
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#Region "Exchange"
|
||||||
|
Friend Overrides Function ExchangeOptionsGet() As Object
|
||||||
|
Return Nothing
|
||||||
|
End Function
|
||||||
|
Friend Overrides Sub ExchangeOptionsSet(ByVal Obj As Object)
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#Region "Initializer"
|
||||||
|
Friend Sub New()
|
||||||
|
ObtainMedia_SizeFuncPic = Function(ByVal ss As EContainer) As Sizes
|
||||||
|
If ss.Value("url").IsEmptyString Then
|
||||||
|
Return New Sizes("----", "")
|
||||||
|
ElseIf Not ss.Value("width").IsEmptyString Then
|
||||||
|
Return New Sizes(ss.Value("height").IfNullOrEmpty(ss.Value("width")), ss.Value("url"))
|
||||||
|
Else
|
||||||
|
Dim rval$ = RegexReplace(ss.Value("url"), ObtainMedia_SizeFuncPic_RegexP)
|
||||||
|
If Not rval.IsEmptyString Then Return New Sizes(rval, ss.Value("url"))
|
||||||
|
rval = RegexReplace(ss.Value("url"), ObtainMedia_SizeFuncPic_RegexS)
|
||||||
|
If Not rval.IsEmptyString Then Return New Sizes(AConvert(Of Integer)(rval, 1) * -1, ss.Value("url"))
|
||||||
|
Return New Sizes(10000, ss.Value("url"))
|
||||||
|
End If
|
||||||
|
End Function
|
||||||
|
ObtainMedia_SizeFuncVid = Function(ss) If(ss.Value("url").IsEmptyString, New Sizes("----", ""), New Sizes(10000, ss.Value("url")))
|
||||||
|
ObtainMedia_AllowAbstract = True
|
||||||
|
DefaultParser_ElemNode = DefaultParser_ElemNode_Default
|
||||||
|
DefaultParser_PostUrlCreator = Function(post) $"https://www.threads.net/@{NameTrue}/post/{post.Code}"
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#Region "Download functions"
|
||||||
|
Protected Overrides Sub DownloadDataF(ByVal Token As CancellationToken)
|
||||||
|
Dim errorFound As Boolean = False
|
||||||
|
Try
|
||||||
|
Responser.Method = "POST"
|
||||||
|
AddHandler Responser.ResponseReceived, AddressOf Responser_ResponseReceived
|
||||||
|
LoadSavePostsKV(True)
|
||||||
|
OPT_LSD = String.Empty
|
||||||
|
OPT_FB_DTSG = String.Empty
|
||||||
|
DownloadData(String.Empty, Token)
|
||||||
|
Catch ex As Exception
|
||||||
|
errorFound = True
|
||||||
|
Throw ex
|
||||||
|
Finally
|
||||||
|
Responser.Method = "POST"
|
||||||
|
UpdateResponser()
|
||||||
|
MySettings.UpdateResponserData(Responser)
|
||||||
|
If Not errorFound Then LoadSavePostsKV(False)
|
||||||
|
End Try
|
||||||
|
End Sub
|
||||||
|
Protected Overrides Sub UpdateResponser()
|
||||||
|
If Not Responser Is Nothing AndAlso Not Responser.Disposed Then
|
||||||
|
RemoveHandler Responser.ResponseReceived, AddressOf Responser_ResponseReceived
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
Protected Overrides Sub Responser_ResponseReceived(ByVal Sender As Object, ByVal e As WebDataResponse)
|
||||||
|
If e.CookiesExists Then
|
||||||
|
Dim csrf$ = If(e.Cookies.FirstOrDefault(Function(v) v.Name.StringToLower = IGS.Header_CSRF_TOKEN_COOKIE)?.Value, String.Empty)
|
||||||
|
If Not csrf.IsEmptyString AndAlso Not AEquals(Of String)(csrf, Responser.Headers.Value(IGS.Header_CSRF_TOKEN)) Then _
|
||||||
|
Responser.Headers.Add(IGS.Header_CSRF_TOKEN, csrf)
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
Private Overloads Sub DownloadData(ByVal Cursor As String, ByVal Token As CancellationToken)
|
||||||
|
Const urlPattern$ = "https://www.threads.net/api/graphql?lsd={0}&variables={1}&doc_id=6371597506283707&fb_api_req_friendly_name=BarcelonaProfileThreadsTabRefetchableQuery&server_timestamps=true&fb_dtsg={2}"
|
||||||
|
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
|
||||||
|
Try
|
||||||
|
If Not Valid Then
|
||||||
|
Dim idIsNull As Boolean = ID.IsEmptyString
|
||||||
|
UpdateCredentials()
|
||||||
|
If idIsNull And Not ID.IsEmptyString Then _ForceSaveUserInfo = True
|
||||||
|
End If
|
||||||
|
If Not Valid Then Throw New Plugin.ExitException("Some credentials are missing")
|
||||||
|
|
||||||
|
Responser.Method = "POST"
|
||||||
|
Responser.Referer = $"https://www.threads.net/@{NameTrue}"
|
||||||
|
Responser.Headers.Add(Header_FB_LSD, OPT_LSD)
|
||||||
|
|
||||||
|
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 & "}")
|
||||||
|
|
||||||
|
URL = String.Format(urlPattern, OPT_LSD, vars, SymbolsConverter.ASCII.EncodeSymbolsOnly(OPT_FB_DTSG))
|
||||||
|
|
||||||
|
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
|
||||||
|
End If
|
||||||
|
End Using
|
||||||
|
|
||||||
|
If dataFound And Not nextCursor.IsEmptyString Then DownloadData(nextCursor, Token)
|
||||||
|
Catch ex As Exception
|
||||||
|
ProcessException(ex, Token, $"data downloading error [{URL}]")
|
||||||
|
End Try
|
||||||
|
End Sub
|
||||||
|
Private Function GetDocument(ByVal URL As String, ByVal Token As CancellationToken, Optional ByVal Round As Integer = 0) As EContainer
|
||||||
|
Try
|
||||||
|
ThrowAny(Token)
|
||||||
|
If Round > 0 AndAlso Not UpdateCredentials() Then Throw New Exception("Failed to update credentials")
|
||||||
|
ThrowAny(Token)
|
||||||
|
Dim r$ = Responser.GetResponse(URL)
|
||||||
|
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)
|
||||||
|
Else
|
||||||
|
Throw ex
|
||||||
|
End If
|
||||||
|
End Try
|
||||||
|
End Function
|
||||||
|
Private Function UpdateCredentials(Optional ByVal e As ErrorsDescriber = Nothing) As Boolean
|
||||||
|
Dim URL$ = $"https://www.threads.net/@{NameTrue}"
|
||||||
|
OPT_LSD = String.Empty
|
||||||
|
OPT_FB_DTSG = String.Empty
|
||||||
|
Try
|
||||||
|
Responser.Method = "GET"
|
||||||
|
Responser.Referer = URL
|
||||||
|
Responser.Headers.Remove(Header_FB_LSD)
|
||||||
|
Dim r$ = Responser.GetResponse(URL,, EDP.SendToLog + EDP.ThrowException)
|
||||||
|
Dim rr As RParams
|
||||||
|
Dim tt$, ttVal$
|
||||||
|
If Not r.IsEmptyString Then
|
||||||
|
rr = RParams.DM("\[\],{""token"":""(.*?)""},\d+\]", 0, RegexReturn.List, EDP.ReturnValue)
|
||||||
|
Dim tokens As List(Of String) = RegexReplace(r, rr)
|
||||||
|
If tokens.ListExists Then
|
||||||
|
With rr
|
||||||
|
.Match = Nothing
|
||||||
|
.MatchSub = 1
|
||||||
|
.WhatGet = RegexReturn.Value
|
||||||
|
End With
|
||||||
|
For Each tt In tokens
|
||||||
|
If Not OPT_FB_DTSG.IsEmptyString And Not OPT_LSD.IsEmptyString Then
|
||||||
|
Exit For
|
||||||
|
Else
|
||||||
|
ttVal = RegexReplace(tt, rr)
|
||||||
|
If Not ttVal.IsEmptyString Then
|
||||||
|
If ttVal.Contains(":") Then
|
||||||
|
If OPT_FB_DTSG.IsEmptyString Then OPT_FB_DTSG = ttVal
|
||||||
|
Else
|
||||||
|
If OPT_LSD.IsEmptyString Then OPT_LSD = ttVal
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
End If
|
||||||
|
If ID.IsEmptyString Then ID = RegexReplace(r, RParams.DMS("""props"":\{""user_id"":""(\d+)""\},", 1, EDP.ReturnValue))
|
||||||
|
End If
|
||||||
|
Return Valid
|
||||||
|
Catch ex As Exception
|
||||||
|
Dim notFound$ = String.Empty
|
||||||
|
If OPT_FB_DTSG.IsEmptyString Then notFound.StringAppend(Header_FB_LSD)
|
||||||
|
If OPT_LSD.IsEmptyString Then notFound.StringAppend("lsd")
|
||||||
|
If ID.IsEmptyString Then notFound.StringAppend("User ID")
|
||||||
|
LogError(ex, $"failed to update some{IIf(notFound.IsEmptyString, String.Empty, $" ({notFound})")} credentials", e)
|
||||||
|
Return False
|
||||||
|
End Try
|
||||||
|
End Function
|
||||||
|
#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}"",""__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"
|
||||||
|
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}"
|
||||||
|
If Not IsSingleObjectDownload AndAlso Not UpdateCredentials() Then Throw New Exception("Failed to update credentials")
|
||||||
|
Dim m As UserMedia
|
||||||
|
Dim vars$
|
||||||
|
Dim j As EContainer
|
||||||
|
ProgressPre.ChangeMax(_ContentList.Count)
|
||||||
|
For i% = 0 To _ContentList.Count - 1
|
||||||
|
ProgressPre.Perform()
|
||||||
|
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, OPT_LSD, vars, SymbolsConverter.ASCII.EncodeSymbolsOnly(OPT_FB_DTSG))
|
||||||
|
|
||||||
|
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
|
||||||
|
End If
|
||||||
|
Next
|
||||||
|
End If
|
||||||
|
Catch ex As Exception
|
||||||
|
ProcessException(ex, Token, $"ReparseMissing error [{URL}]")
|
||||||
|
Finally
|
||||||
|
DefaultParser_ElemNode = DefaultParser_ElemNode_Default
|
||||||
|
DefaultParser_IgnorePass = False
|
||||||
|
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 "DownloadSingleObject"
|
||||||
|
Protected Overrides Sub DownloadSingleObject_GetPosts(ByVal Data As IYouTubeMediaContainer, ByVal Token As CancellationToken)
|
||||||
|
Dim url$ = Data.URL_BASE.IfNullOrEmpty(Data.URL)
|
||||||
|
Dim postCode$ = RegexReplace(url, RParams.DMS("post/([^/\?&]+)", 1, EDP.ReturnValue))
|
||||||
|
If Not postCode.IsEmptyString Then
|
||||||
|
Dim postId$ = CodeToID(postCode)
|
||||||
|
If Not postId.IsEmptyString Then
|
||||||
|
_NameTrue = MySettings.IsMyUser(url).UserName
|
||||||
|
DefaultParser_PostUrlCreator = Function(post) url
|
||||||
|
If Not _NameTrue.IsEmptyString AndAlso UpdateCredentials(EDP.ReturnValue) Then
|
||||||
|
_ContentList.Add(New UserMedia(url) With {.State = UserMedia.States.Missing, .Post = postId})
|
||||||
|
ReparseMissing(Token)
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
End If
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#Region "ThrowAny"
|
||||||
|
Friend Overrides Sub ThrowAny(ByVal Token As CancellationToken)
|
||||||
|
ThrowAnyImpl(Token)
|
||||||
|
End Sub
|
||||||
|
#End Region
|
||||||
|
#Region "DownloadingException"
|
||||||
|
Protected Overrides Function DownloadingException(ByVal ex As Exception, ByVal Message As String, Optional ByVal FromPE As Boolean = False,
|
||||||
|
Optional ByVal EObj As Object = Nothing) As Integer
|
||||||
|
Return 0
|
||||||
|
End Function
|
||||||
|
#End Region
|
||||||
|
End Class
|
||||||
|
End Namespace
|
||||||
BIN
SCrawler/Content/Icons/SiteIcons/ThreadsIcon_192.ico
Normal file
BIN
SCrawler/Content/Icons/SiteIcons/ThreadsIcon_192.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 166 KiB |
@@ -79,6 +79,7 @@ Namespace Plugin.Hosts
|
|||||||
New PluginHost(New API.Twitter.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
New PluginHost(New API.Twitter.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
||||||
New PluginHost(New API.Mastodon.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
New PluginHost(New API.Mastodon.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
||||||
New PluginHost(New API.Instagram.SiteSettings(_XML, GlobalPath), _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
New PluginHost(New API.Instagram.SiteSettings(_XML, GlobalPath), _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
||||||
|
New PluginHost(New API.ThreadsNet.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
||||||
New PluginHost(New API.RedGifs.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
New PluginHost(New API.RedGifs.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
||||||
New PluginHost(New API.YouTube.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
New PluginHost(New API.YouTube.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
||||||
New PluginHost(New API.Pinterest.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
New PluginHost(New API.Pinterest.SiteSettings, _XML, GlobalPath, _Temp, _Imgs, _Vids),
|
||||||
|
|||||||
@@ -223,6 +223,8 @@
|
|||||||
<Compile Include="API\ThisVid\SiteSettings.vb" />
|
<Compile Include="API\ThisVid\SiteSettings.vb" />
|
||||||
<Compile Include="API\ThisVid\UserData.vb" />
|
<Compile Include="API\ThisVid\UserData.vb" />
|
||||||
<Compile Include="API\ThisVid\UserExchangeOptions.vb" />
|
<Compile Include="API\ThisVid\UserExchangeOptions.vb" />
|
||||||
|
<Compile Include="API\ThreadsNet\SiteSettings.vb" />
|
||||||
|
<Compile Include="API\ThreadsNet\UserData.vb" />
|
||||||
<Compile Include="API\TikTok\Declarations.vb" />
|
<Compile Include="API\TikTok\Declarations.vb" />
|
||||||
<Compile Include="API\TikTok\SiteSettings.vb" />
|
<Compile Include="API\TikTok\SiteSettings.vb" />
|
||||||
<Compile Include="API\TikTok\UserData.vb" />
|
<Compile Include="API\TikTok\UserData.vb" />
|
||||||
@@ -709,6 +711,9 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Content\Icons\SiteIcons\JFFIcon_64.ico" />
|
<None Include="Content\Icons\SiteIcons\JFFIcon_64.ico" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Content\Icons\SiteIcons\ThreadsIcon_192.ico" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
|
<Import Project="$(MSBuildToolsPath)\Microsoft.VisualBasic.targets" />
|
||||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
10
SCrawler/SiteResources.Designer.vb
generated
10
SCrawler/SiteResources.Designer.vb
generated
@@ -264,6 +264,16 @@ Namespace My.Resources
|
|||||||
End Get
|
End Get
|
||||||
End Property
|
End Property
|
||||||
|
|
||||||
|
'''<summary>
|
||||||
|
''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||||
|
'''</summary>
|
||||||
|
Friend Shared ReadOnly Property ThreadsIcon_192() As System.Drawing.Icon
|
||||||
|
Get
|
||||||
|
Dim obj As Object = ResourceManager.GetObject("ThreadsIcon_192", resourceCulture)
|
||||||
|
Return CType(obj,System.Drawing.Icon)
|
||||||
|
End Get
|
||||||
|
End Property
|
||||||
|
|
||||||
'''<summary>
|
'''<summary>
|
||||||
''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
''' Looks up a localized resource of type System.Drawing.Icon similar to (Icon).
|
||||||
'''</summary>
|
'''</summary>
|
||||||
|
|||||||
@@ -178,6 +178,9 @@
|
|||||||
<data name="ThisVidPic_16" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="ThisVidPic_16" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>Content\Pictures\SitePictures\ThisVidPic_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
<value>Content\Pictures\SitePictures\ThisVidPic_16.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ThreadsIcon_192" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
|
<value>Content\Icons\SiteIcons\ThreadsIcon_192.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
|
</data>
|
||||||
<data name="TikTokIcon_32" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
<data name="TikTokIcon_32" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||||
<value>Content\Icons\SiteIcons\TikTokIcon_32.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
<value>Content\Icons\SiteIcons\TikTokIcon_32.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
Reference in New Issue
Block a user