ImportSongs: Add Google Drive support
- Adds a new page for importing custom songs, where it is possible to pick a local folder (desktop only) or a Google Drive folder (desktop and Android)
- This feature is disabled on iOS due to the lack of OGG audio support in the browser
- In order to not get rate limited, a TJA file is parsed for metadata only when the song is clicked in the song selection, rather than all at once at import time
- The instance maintainer will need to provide the API credentials in the config.py file to enable this feature
- This requires a new project to be created at console.cloud.google.com
- Drive API will have to be enabled
- API and OAuth keys should be created
- API key can be restricted to only have Google Drive and Google Picker APIs
- OAuth Client ID should have Web Application type and JavaScript origins set
- Editing the OAuth consent screen to have a name and icon is recommended
- It is semi-required to submit the consent screen for verification as the permission to download all of the Drive files will be asked.
- Note that the email of the maintainer is publicly visible on the consent screen
- The project number can be found in the IAM & Admin settings page
This commit is contained in:
185
public/src/js/gpicker.js
Normal file
185
public/src/js/gpicker.js
Normal file
@@ -0,0 +1,185 @@
|
||||
class Gpicker{
|
||||
constructor(){
|
||||
this.apiKey = gameConfig.google_credentials.api_key
|
||||
this.oauthClientId = gameConfig.google_credentials.oauth_client_id
|
||||
this.projectNumber = gameConfig.google_credentials.project_number
|
||||
this.scope = "https://www.googleapis.com/auth/drive.readonly"
|
||||
this.folder = "application/vnd.google-apps.folder"
|
||||
this.filesUrl = "https://www.googleapis.com/drive/v3/files/"
|
||||
this.resolveQueue = []
|
||||
this.queueActive = false
|
||||
}
|
||||
browse(lockedCallback){
|
||||
return this.loadApi()
|
||||
.then(() => this.getToken(lockedCallback))
|
||||
.then(() => new Promise((resolve, reject) => {
|
||||
this.displayPicker(data => {
|
||||
if(data.action === "picked"){
|
||||
var file = data.docs[0]
|
||||
var walk = (files, output=[]) => {
|
||||
var batch = null
|
||||
for(var i = 0; i < files.length; i++){
|
||||
var path = files[i].path ? files[i].path + "/" : ""
|
||||
var list = files[i].list
|
||||
for(var j = 0; j < list.length; j++){
|
||||
var file = list[j]
|
||||
if(file.mimeType === this.folder){
|
||||
if(!batch){
|
||||
batch = gapi.client.newBatch()
|
||||
}
|
||||
batch.add(gapi.client.drive.files.list({
|
||||
q: "'" + file.id + "' in parents",
|
||||
orderBy: "name_natural"
|
||||
}), {
|
||||
id: path + file.name
|
||||
})
|
||||
}else{
|
||||
output.push(new GdriveFile({
|
||||
path: path + file.name,
|
||||
name: file.name,
|
||||
id: file.id
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
if(batch){
|
||||
return this.queue()
|
||||
.then(() => batch.then(responses => {
|
||||
var files = []
|
||||
for(var path in responses.result){
|
||||
files.push({path: path, list: responses.result[path].result.files})
|
||||
}
|
||||
return walk(files, output)
|
||||
}))
|
||||
}else{
|
||||
return output
|
||||
}
|
||||
}
|
||||
if(file.mimeType === this.folder){
|
||||
return walk([{list: [file]}]).then(resolve, reject)
|
||||
}else{
|
||||
return reject("cancel")
|
||||
}
|
||||
}else if(data.action === "cancel"){
|
||||
return reject("cancel")
|
||||
}
|
||||
})
|
||||
}))
|
||||
}
|
||||
loadApi(){
|
||||
if(window.gapi && gapi.client && gapi.client.drive){
|
||||
return
|
||||
}
|
||||
return loader.loadScript("https://apis.google.com/js/api.js")
|
||||
.then(() => new Promise((resolve, reject) =>
|
||||
gapi.load("auth2:picker:client", {
|
||||
callback: resolve,
|
||||
onerror: reject
|
||||
})
|
||||
))
|
||||
.then(() => new Promise((resolve, reject) =>
|
||||
gapi.client.load("drive", "v3").then(resolve, reject)
|
||||
))
|
||||
}
|
||||
getToken(lockedCallback){
|
||||
if(this.oauthToken){
|
||||
return
|
||||
}
|
||||
if(!this.auth){
|
||||
var authPromise = gapi.auth2.init({
|
||||
clientId: this.oauthClientId,
|
||||
fetch_basic_profile: false,
|
||||
scope: this.scope
|
||||
}).then(() => {
|
||||
this.auth = gapi.auth2.getAuthInstance()
|
||||
}, e => {
|
||||
if(e.details){
|
||||
alert(strings.gpicker.authError.replace("%s", e.details))
|
||||
}
|
||||
return Promise.reject(e)
|
||||
})
|
||||
}else{
|
||||
var authPromise = Promise.resolve()
|
||||
}
|
||||
return authPromise.then(() => {
|
||||
var user = this.auth.currentUser.get()
|
||||
if(!this.checkScope(user)){
|
||||
lockedCallback(false)
|
||||
this.auth.signIn().then(user => {
|
||||
if(this.checkScope(user)){
|
||||
lockedCallback(true)
|
||||
}else{
|
||||
return Promise.reject("cancel")
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
checkScope(user){
|
||||
if(user.hasGrantedScopes(this.scope)){
|
||||
this.oauthToken = user.getAuthResponse(true).access_token
|
||||
return this.oauthToken
|
||||
}else{
|
||||
return false
|
||||
}
|
||||
}
|
||||
displayPicker(callback){
|
||||
var picker = gapi.picker.api
|
||||
new picker.PickerBuilder()
|
||||
.setDeveloperKey(this.apiKey)
|
||||
.setAppId(this.projectNumber)
|
||||
.setOAuthToken(this.oauthToken)
|
||||
.hideTitleBar()
|
||||
.addView(new picker.DocsView("folders")
|
||||
.setLabel(strings.gpicker.myDrive)
|
||||
.setParent("root")
|
||||
.setSelectFolderEnabled(true)
|
||||
.setMode("grid")
|
||||
)
|
||||
.addView(new picker.DocsView("folders")
|
||||
.setLabel(strings.gpicker.starred)
|
||||
.setStarred(true)
|
||||
.setSelectFolderEnabled(true)
|
||||
.setMode("grid")
|
||||
)
|
||||
.addView(new picker.DocsView("folders")
|
||||
.setLabel(strings.gpicker.sharedWithMe)
|
||||
.setOwnedByMe(false)
|
||||
.setSelectFolderEnabled(true)
|
||||
.setMode("list")
|
||||
)
|
||||
.setCallback(callback)
|
||||
.setSize(Infinity, Infinity)
|
||||
.build()
|
||||
.setVisible(true)
|
||||
}
|
||||
downloadFile(id, arrayBuffer){
|
||||
return this.queue().then(() =>
|
||||
loader.ajax(this.filesUrl + id + "?alt=media", request => {
|
||||
if(arrayBuffer){
|
||||
request.responseType = "arraybuffer"
|
||||
}
|
||||
request.setRequestHeader("Authorization", "Bearer " + this.oauthToken)
|
||||
})
|
||||
)
|
||||
}
|
||||
queue(){
|
||||
return new Promise(resolve => {
|
||||
this.resolveQueue.push(resolve)
|
||||
if(!this.queueActive){
|
||||
this.queueActive = true
|
||||
this.queueTimer = setInterval(this.parseQueue.bind(this), 100)
|
||||
this.parseQueue()
|
||||
}
|
||||
})
|
||||
}
|
||||
parseQueue(){
|
||||
if(this.resolveQueue.length){
|
||||
var resolve = this.resolveQueue.shift()
|
||||
resolve()
|
||||
}else{
|
||||
this.queueActive = false
|
||||
clearInterval(this.queueTimer)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user