I tried to get OAuth and API working, but it was still too difficult.
However, I had another idea. Using modified code from
, I think I've developed a working solution.
. Afterwards, you should be able to load your subscription list without being asked to login.
Please try the code below, and let me know if you have any issues. If it works, I'll add additional code to get the tags and other info.
Code: Select all
#SingleInstance, Force
#Persistent
#NoEnv
SetBatchLines, -1
Gui, +LastFound -Resize +HWNDhYouTube
Gui, +LabelYouTube
Gui, Margin, 10, 10
Gui, Add, Edit, w480 r18 +ReadOnly +HScroll vEDT
Gui, Add, Button, w80 h24 gGetSubscriptions, Get Subs
Gui, Show, AutoSize, YouTube Subscriptions Parser
neutron := new NeutronWindow()
neutron.Gui("+LabelNeutron")
neutron.wb.silent := true
neutron.Show("w640 h480 Hide")
; Allow page scrolling
SetTimer, HTMLOverflow, 250
return
GetSubscriptions() {
Global ; Assume-global mode
; Load YouTube Mobile (Persistent) website
neutron.doc.location.href := "https://m.youtube.com/feed/channels?app=m&persist_app=1"
; Wait for Subscription List loading to complete
While (!InStr(neutron.doc.location.href, "app=m")) {
; Show window if prompted for login
If (InStr(neutron.doc.location.href, "accounts.google")) {
If (DllCall("IsWindowVisible", "UInt", WinExist("ahk_id " neutron.HWnd)) = 0) {
neutron.Show()
}
}
Sleep, 250
}
; Wait for JSON to be populated
While (!RegExMatch(neutron.doc.getElementsByTagName("html")[0].innerHTML, "{""responseContext"":.*?""trackingParams"":"".*""}", Match)) {
Sleep, 250
}
; Store JSON to variable
JSON := Match
; Logout of YouTube
; neutron.doc.location.href := "https://m.youtube.com/logout?app=m&persist_app=1"
; Set URL to "about:blank"
neutron.doc.location.href := "about:blank"
; Hide window
Gui, % neutron.hWnd ": Hide"
; RegExMatch Patterns
PatChannel := "{""channelListItemRenderer"":.*?""timestampMs"":""\d+""}}"
PatChannelName := """text"":""(.*?)"""
PatChannelURL := """canonicalBaseUrl"":""(.*?)"""
Pos := 0 ; RegExMatch Position
; Find all matching patterns
While (Pos := RegExMatch(JSON, PatChannel, Channel, Pos + 1)) {
RegExMatch(Channel, PatChannelName, ChannelName)
RegExMatch(Channel, PatChannelURL, ChannelURL)
Subscriptions .= StrReplace(ChannelName1, "\u0026", "&") " - https://www.youtube.com" ChannelURL1 "`n"
}
GuiControl,, EDT, % RegExReplace(Subscriptions, "^\s+|\s+$")
}
HTMLOverflow:
Overflow := neutron.doc.getElementsByTagName("html")[0].style.overflow
If (Overflow = "") {
neutron.doc.getElementsByTagName("html")[0].style.overflow := "auto"
}
return
YouTubeClose:
NeutronClose:
ExitApp
return
Ctrl & F12:: ; Logout (Debug)
neutron.doc.location.href := "https://m.youtube.com/logout?app=m&persist_app=1"
return
;
; Neutron.ahk v1.0.0
; Copyright (c) 2020 Philip Taylor (known also as GeekDude, G33kDude)
; https://github.com/G33kDude/Neutron.ahk
;
; MIT License
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in all
; copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE.
; ==========================================================
; Neutron.ahk v1.0.0 - Modified & Reduced by TheDewd
; https://www.autohotkey.com/boards/viewtopic.php?t=76865
; https://github.com/G33kDude/Neutron.ahk
; ==========================================================
Class NeutronWindow {
Static WM_DESTROY := 0x02
, WM_SIZE := 0x05
, WM_NCLBUTTONDOWN := 0xA1
, WM_KEYDOWN := 0x100
, WM_MOUSEMOVE := 0x200
, WM_LBUTTONDOWN := 0x201
, KEY_FBE := "HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION"
, EXE_NAME := A_IsCompiled ? A_ScriptName : StrSplit(A_AhkPath, "\").Pop()
w := 800
h := 600
doc[] {
get {
return this.wb.Document
}
}
wnd[] {
get {
return this.wb.Document.parentWindow
}
}
__New() {
Static wb
this.LISTENERS := [this.WM_DESTROY, this.WM_SIZE, this.WM_KEYDOWN, this.WM_LBUTTONDOWN]
this.bound := {}
this.bound._OnMessage := this._OnMessage.Bind(this)
For i, message In this.LISTENERS {
OnMessage(message, this.bound._OnMessage)
}
Gui, New, +hWndhWnd +Resize -DPIScale
this.hWnd := hWnd
RegRead, fbe, % this.KEY_FBE, % this.EXE_NAME
RegWrite, REG_DWORD, % this.KEY_FBE, % this.EXE_NAME, 0
Gui, Add, ActiveX, vwb hWndhWB x0 y0 w800 h600, about:blank
If (fbe = "") {
RegDelete, % this.KEY_FBE, % this.EXE_NAME
} Else {
RegWrite, REG_DWORD, % this.KEY_FBE, % this.EXE_NAME, % fbe
}
this.wb := wb
this.hWB := hWB
ComObjConnect(this.wb, new this.WBEvents(this))
this.wnd.neutron := this
this.wnd.ahk := new this.Dispatch(this)
While (wb.readyState < 4) {
Sleep, 50
}
dhw := A_DetectHiddenWindows
DetectHiddenWindows, On
ControlGet, hWnd, hWnd,, Internet Explorer_Server1, % "ahk_id" this.hWnd
this.hIES := hWnd
DetectHiddenWindows, %dhw%
this.pWndProc := RegisterCallback(this._WindowProc, "", 4, &this)
this.pWndProcOld := DllCall("SetWindowLong" (A_PtrSize == 8 ? "Ptr" : ""), "Ptr", hWnd, "Int", -4, "Ptr", this.pWndProc, "Ptr")
this.wb.RegisterAsDropTarget := False
DllCall("ole32\RevokeDragDrop", "UPtr", this.hIES)
}
_OnMessage(wParam, lParam, Msg, hWnd) {
If (hWnd == this.hWnd) {
If (Msg == this.WM_SIZE) {
this.w := w := lParam<<48>>48
this.h := h := lParam<<32>>48
DllCall("MoveWindow", "UPtr", this.hWB, "Int", 0, "Int", 0, "Int", w, "Int", h, "UInt", 0)
return 0
} Else If (Msg == this.WM_DESTROY) {
For i, message In this.LISTENERS {
OnMessage(message, this.bound._OnMessage, 0)
}
this.bound := []
}
} Else If (hWnd == this.hIES) {
If (Msg == this.WM_KEYDOWN) {
If (Chr(wParam) ~= "[A-Z]" || wParam = 0x74) {
return
}
Gui +OwnDialogs
pipa := ComObjQuery(this.wb, "{00000117-0000-0000-C000-000000000046}")
VarSetCapacity(kMsg, 48), NumPut(A_GuiY, NumPut(A_GuiX, NumPut(A_EventInfo, NumPut(lParam, NumPut(wParam, NumPut(Msg, NumPut(hWnd, kMsg)))), "uint"), "int"), "int")
Loop, 2 {
r := DllCall(NumGet(NumGet(1*pipa)+5*A_PtrSize), "ptr", pipa, "ptr", &kMsg)
}
Until (wParam != 9 || this.wb.document.activeElement != "")
ObjRelease(pipa)
If (r = 0) {
return 0
}
return
}
}
}
Show(options := "") {
w := RegExMatch(options, "w\s*\K\d+", match) ? match : this.w
h := RegExMatch(options, "h\s*\K\d+", match) ? match : this.h
Gui, % this.hWnd ":Show", %options% w%w% h%h%, YouTube
}
Gui(subCommand, value1 := "", value2 := "", value3 := "") {
Gui, % this.hWnd ":" subCommand, % value1, % value2, % value3
}
Class WBEvents {
__New(parent) {
this.parent := parent
}
DocumentComplete(wb) {
wb.document.parentWindow.neutron := this.parent
wb.document.parentWindow.ahk := new this.parent.Dispatch(this.parent)
}
}
}