Skip to content

Commit

Permalink
More OAuth work (#551)
Browse files Browse the repository at this point in the history
* More OAuth work

* Lint fix

* tweaks

---------

Co-authored-by: github-action linter <[email protected]>
  • Loading branch information
iBicha and github-action linter authored Jan 29, 2025
1 parent 12231fe commit 29a3206
Show file tree
Hide file tree
Showing 11 changed files with 382 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
<field id="accessToken" type="string" />

<!-- Innertube fields -->
<field id="thumbnail" type="uri" />
<field id="refreshToken" type="string" />
<field id="scope" type="string" />
<field id="tokenType" type="string" />
Expand Down
51 changes: 51 additions & 0 deletions playlet-lib/src/components/Dialog/YouTubeLoginDialog.bs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import "pkg:/source/AsyncTask/AsyncTask.bs"
import "pkg:/source/AsyncTask/Tasks.bs"
import "pkg:/source/utils/Locale.bs"
import "pkg:/source/utils/Types.bs"

function Init()
m.qrCode = m.top.findNode("QrCodePoster")

codeLabel = m.top.findNode("codeLabel")
if codeLabel <> invalid
codeLabel = codeLabel.getChild(0)
if codeLabel <> invalid
codeFont = m.top.findNode("codeFont")
codeLabel.font = codeFont
end if
end if

m.top.width = "920"
m.top.observeFieldScoped("buttonSelected", FuncName(Close))
m.top.observeFieldScoped("wasClosed", FuncName(OnWasClosed))
scanLabel = m.top.findNode("scanLabel")
' TODO:P2 localize all dialog items
scanLabel.text = Tr(Locale.Dialogs.ScanTheQrCode)
end function

function OnNodeReady()
m.task = AsyncTask.Start(Tasks.YouTubeLoginTask, {
dialog: m.top
profilesService: m.top.profilesService
})
end function

function OnCodeSet(event as object)
code = event.getData()
m.qrCode.text = code
end function

function Close()
m.top.close = true
end function

function OnWasClosed()
if m.task <> invalid
m.task.cancel = true
m.task = invalid
end if
end function

function OnUrlSet()
m.qrCode.text = m.top.url
end function
49 changes: 49 additions & 0 deletions playlet-lib/src/components/Dialog/YouTubeLoginDialog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<component name="YouTubeLoginDialog" extends="StandardDialog" initialFocus="buttonArea" includes="AutoBind">
<interface>
<field id="profilesService" type="node" bind="/ProfilesService" />
<field id="code" type="string" alias="codeLabel.text" />
<field id="url" type="string" onChange="OnUrlSet" />
<field id="alwaysOnTop" type="boolean" />
</interface>
<children>
<Font id="codeFont" uri="font:MediumSystemFontFile" size="36" />

<StdDlgTitleArea primaryTitle="Login to YouTube" />
<StdDlgContentArea>
<StdDlgTextItem text="Open the link 'https://yt.be/activate' and enter the following code:" />
<StdDlgTextItem id="codeLabel" text="Loading..." />
</StdDlgContentArea>
<StdDlgButtonArea id="buttonArea">
<StdDlgButton text="OK" />
</StdDlgButtonArea>
<StdDlgSideCardArea
id="sideCarArea"
horizAlign="right"
width="400"
extendToDialogEdge="false"
showDivider="false">
<Label
id="scanLabel"
text="Scan the QR Code"
horizAlign="center"
wrap="true" width="400">
<Font role="font" uri="font:SystemFontFile" size="24" />
</Label>
<QrCodePoster
id="QrCodePoster"
width="300"
height="300"
loadWidth="300"
loadHeight="300"
padding="10"
translation="[50, 50]" />
<Label
horizAlign="center"
width="400"
translation="[0, 380]"
text="https://yt.be/activate">
<Font role="font" uri="font:MediumSystemFontFile" size="18" />
</Label>
</StdDlgSideCardArea>
</children>
</component>
118 changes: 118 additions & 0 deletions playlet-lib/src/components/Dialog/YouTubeLoginTask.bs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import "pkg:/components/Dialog/DialogUtils.bs"
import "pkg:/components/Services/Innertube/InnertubeService.bs"
import "pkg:/source/utils/CancellationUtils.bs"
import "pkg:/source/utils/Logging.bs"

@asynctask
function YouTubeLoginTask(input as object) as object
profilesNode = input.profilesService
dialogNode = input.dialog

cancellation = m.top.cancellation
' TODO:P2 cache client identity
authCode = InnertubeService.AuthGetCode(cancellation)

if CancellationUtils.IsCancelled(cancellation)
return invalid
end if

if authCode.error <> invalid
LogError(authCode.error)
DialogUtils.ShowDialogEx({
title: "Error"
message: authCode.error
large: true
})
return invalid
end if

url = InnertubeService.AuthGetActivationUrl(authCode)

LogInfo("Login url:", url)

dialogNode.url = url
dialogNode.code = authCode.userCode

accessToken = InnertubeService.AuthPollForAccessToken(authCode, cancellation)

if CancellationUtils.IsCancelled(cancellation)
return invalid
end if

if accessToken.error <> invalid
LogError(accessToken.error)
DialogUtils.ShowDialogEx({
title: "Error"
message: accessToken.error
large: true
})
return invalid
end if

accounts = InnertubeService.AuthListAccounts(accessToken.accessToken, cancellation)

if CancellationUtils.IsCancelled(cancellation)
return invalid
end if

if IsAssociativeArray(accounts) and accounts.error <> invalid
LogError(accounts.error)
DialogUtils.ShowDialogEx({
title: "Error"
message: accounts.error
large: true
})
return invalid
end if

if not IsArray(accounts) or accounts.Count() = 0
LogError("No accounts found")
DialogUtils.ShowDialogEx({
title: "Error"
message: "No accounts found"
large: true
})
return invalid
end if

selectedAccount = invalid
for each account in accounts
if ValidBool(account.isSelected)
selectedAccount = account
exit for
end if
end for

if selectedAccount = invalid
selectedAccount = accounts[0]
end if

profile = CreateProfileContentNode(selectedAccount, accessToken)
profilesNode@.LoginWithProfile(profile)

dialogNode.close = true

return invalid
end function

function CreateProfileContentNode(account as object, accessToken as object) as object
profile = CreateObject("roSGNode", "ProfileContentNode")
profile.type = "youtube"
profile.serverUrl = "http://127.0.0.1:8888/playlet-invidious-backend"
username = account.channelHandle
if StringUtils.IsNullOrEmpty(username)
username = account.accountByline
end if
profile.username = username
profile.thumbnail = account.accountPhoto
profile.accessToken = accessToken.accessToken
profile.refreshToken = accessToken.refreshToken
profile.scope = accessToken.scope
profile.tokenType = accessToken.tokenType
profile.expiresIn = accessToken.expiresIn
profile.expiresTimestamp = accessToken.expiresTimestamp
profile.clientId = accessToken.clientId
profile.clientSecret = accessToken.clientSecret

return profile
end function
Original file line number Diff line number Diff line change
Expand Up @@ -24,24 +24,37 @@ function OnContentSet() as void
return
end if

m.top.circlePosterInnerUri = "pkg:/images/white-circle.png"
m.top.circlePosterInnerBlendColor = content.color
username = content.username
m.top.username = username
if not StringUtils.IsNullOrEmpty(username)
m.top.letter = UCase(username.Left(1))
else

thumbnail = content.thumbnail
if not StringUtils.IsNullOrEmpty(thumbnail)
m.top.circlePosterInnerUri = thumbnail
' bs:disable-next-line LINT3023
m.top.circlePosterInnerBlendColor = "#FFFFFFFF"
m.top.letter = ""
else
m.top.circlePosterInnerUri = "pkg:/images/white-circle.png"
m.top.circlePosterInnerBlendColor = content.color

if not StringUtils.IsNullOrEmpty(username)
m.top.letter = UCase(username.Left(1))
else
m.top.letter = ""
end if
end if
m.top.serverUrl = content.serverUrl

m.top.crownVisible = content.isSelected

backendType = content.type
if backendType = "invidious"
m.top.serverUrl = content.serverUrl
m.top.backendTypePosterUri = "pkg:/images/invidious-logo.png"
else if backendType = "youtube"
m.top.serverUrl = "YouTube"
m.top.backendTypePosterUri = "pkg:/images/youtube-logo.png"
else
m.top.serverUrl = ""
m.top.backendTypePosterUri = ""
end if
end function
Expand Down
31 changes: 26 additions & 5 deletions playlet-lib/src/components/Screens/ProfileScreen/ProfileScreen.bs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ function OnBackEndSelected(event as object) as void
if selectedBackendType = "BackendTypeInvidious"
LoginWithInvidious()
else if selectedBackendType = "BackendTypeYouTube"
LoginWithYouTube()
ShowLoginWithYouTubeDialog()
else
LogError("Unknown backend type:", selectedBackendType)
end if
Expand All @@ -85,11 +85,32 @@ function LoginWithInvidious() as void
m.top.getScene().dialog = dialog
end function

function LoginWithYouTube() as void
DialogUtils.ShowDialogEx({
title: "YouTube"
message: ["Not implemented yet."]
function ShowLoginWithYouTubeDialog() as void
' TODO:P2 localize
dialog = DialogUtils.ShowDialogEx({
title: "Disclaimer"
message: "Playlet authors acknowledge that all trademarks and registered trademarks mentioned in this application and related pages are the property of their respective owners. The use of these trademarks or trade names is for identification purposes only and does not imply any endorsement, affiliation, or sponsorship by the trademark owner."
buttons: [
Tr(Locale.Buttons.OK)
Tr(Locale.Buttons.Cancel)
]
})
dialog.ObserveField("buttonSelected", FuncName(OnLoginWithYouTubeDialogButtonSelected))
end function

function OnLoginWithYouTubeDialogButtonSelected(event as object) as void
buttonSelected = event.getData()
if buttonSelected <> 0
return
end if

LoginWithYouTube()
end function

function LoginWithYouTube() as void
dialog = CreateObject("roSGNode", "YouTubeLoginDialog")
dialog@.BindNode()
m.top.getScene().dialog = dialog
end function

function OnCurrentProfileChanged() as void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ function OnContentSet() as void
end if
m.top.serverUrl = content.serverUrl
m.top.crownVisible = content.isSelected

backendType = content.type
if backendType = "invidious"
m.top.backendTypePosterUri = "pkg:/images/invidious-logo.png"
else if backendType = "youtube"
m.top.backendTypePosterUri = "pkg:/images/youtube-logo.png"
else
m.top.backendTypePosterUri = ""
end if

UpdateActivateButton()
end function

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<field id="username" type="string" alias="UsernameLabel.text" />
<field id="serverUrl" type="string" alias="ServerLabel.text" />
<field id="circlePosterInnerUri" type="uri" alias="circlePosterInner.uri" />
<field id="backendTypePosterUri" type="uri" alias="backendTypePoster.uri" />
<field id="circlePosterInnerBlendColor" type="uri" alias="circlePosterInner.blendColor" />
<field id="crownVisible" type="boolean" alias="CrownPoster.visible" />

Expand Down Expand Up @@ -47,6 +48,12 @@
horizAlign="center">
<Font role="font" uri="font:SystemFontFile" size="120" />
</Label>
<Poster
id="backendTypePoster"
width="60"
height="60"
translation="[110,110]"
/>
</Poster>
<Poster
id="CrownPoster"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -455,14 +455,14 @@ namespace InnertubeService
function AuthGetCode(cancellation = invalid as object) as object
result = Innertube.GetClientIdentity(cancellation)

if CancellationUtils.IsCancelled(cancellation)
return invalid
if CancellationUtils.IsCancelled(cancellation) or result.error <> invalid
return result
end if

result2 = Innertube.GetDeviceAndUserCode(result.clientId, cancellation)

if CancellationUtils.IsCancelled(cancellation)
return invalid
if CancellationUtils.IsCancelled(cancellation) or result2.error <> invalid
return result2
end if

result.Append(result2)
Expand Down Expand Up @@ -508,4 +508,7 @@ namespace InnertubeService
return Innertube.RevokeAccessToken(accessToken["accessToken"], cancellation)
end function

function AuthListAccounts(accessToken as string, cancellation = invalid as object) as object
return Innertube.ListAccounts(accessToken, cancellation)
end function
end namespace
Loading

0 comments on commit 29a3206

Please sign in to comment.