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:
LoveEevee
2020-10-29 08:07:56 +03:00
parent 224bf25fc0
commit 3fea149353
17 changed files with 940 additions and 406 deletions

View File

@@ -49,7 +49,7 @@ class SongSelect{
border: ["#dec4fd", "#a543ef"],
outline: "#a741ef"
},
"browse": {
"customSongs": {
sort: 0,
background: "#fab5d3",
border: ["#ffe7ef", "#d36aa2"],
@@ -80,42 +80,7 @@ class SongSelect{
this.songs = []
for(let song of assets.songs){
var title = this.getLocalTitle(song.title, song.title_lang)
var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
var skin = null
var categoryName = ""
var originalCategory = ""
if(song.category_id !== null && song.category_id !== undefined){
var category = assets.categories.find(cat => cat.id === song.category_id)
var categoryName = this.getLocalTitle(category.title, category.title_lang)
var originalCategory = category.title
var skin = this.songSkin[category.title]
}else if(song.category){
var categoryName = song.category
var originalCategory = song.category
}
this.songs.push({
id: song.id,
title: title,
originalTitle: song.title,
subtitle: subtitle,
skin: skin || this.songSkin.default,
courses: song.courses,
originalCategory: originalCategory,
category: categoryName,
category_id: song.category_id,
preview: song.preview || 0,
type: song.type,
offset: song.offset,
songSkin: song.song_skin || {},
music: song.music,
volume: song.volume,
maker: song.maker,
canJump: true,
hash: song.hash || song.title,
order: song.order,
lyrics: song.lyrics
})
this.songs.push(this.addSong(song))
}
this.songs.sort((a, b) => {
var catA = a.originalCategory in this.songSkin ? this.songSkin[a.originalCategory] : this.songSkin.default
@@ -170,17 +135,26 @@ class SongSelect{
action: "settings",
category: strings.random
})
if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){
this.browse = document.getElementById("browse")
pageEvents.add(this.browse, "change", this.browseChange.bind(this))
var showCustom = false
if(gameConfig.google_credentials.gdrive_enabled){
if(!(/iPhone|iPad/.test(navigator.userAgent))){
showCustom = true
}
}else{
if("webkitdirectory" in HTMLInputElement.prototype && !(/Android|iPhone|iPad/.test(navigator.userAgent))){
showCustom = true
}
}
if(showCustom){
this.songs.push({
title: assets.customSongs ? strings.defaultSongList : strings.browse,
skin: this.songSkin.browse,
action: "browse",
title: assets.customSongs ? strings.customSongs.default : strings.customSongs.title,
skin: this.songSkin.customSongs,
action: "customSongs",
category: strings.random
})
}
this.songs.push({
title: strings.back,
skin: this.songSkin.back,
@@ -501,11 +475,11 @@ class SongSelect{
event.preventDefault()
if(this.state.screen === "song" && this.redrawRunning){
var currentSong = this.songs[this.selectedSong]
if(currentSong.action === "browse"){
if(currentSong.action === "customSongs"){
var mouse = this.mouseOffset(event.changedTouches[0].pageX, event.changedTouches[0].pageY)
var moveBy = this.songSelMouse(mouse.x, mouse.y)
if(moveBy === 0){
this.toBrowse()
this.toCustomSongs()
}
}
}
@@ -668,10 +642,6 @@ class SongSelect{
}
}
browseChange(event){
new ImportSongs(this, event)
}
toSelectDifficulty(fromP2){
var currentSong = this.songs[this.selectedSong]
if(p2.session && !fromP2 && currentSong.action !== "random"){
@@ -686,6 +656,9 @@ class SongSelect{
}
}else if(this.state.locked === 0 || fromP2){
if(currentSong.courses){
if(currentSong.unloaded){
return
}
this.state.screen = "difficulty"
this.state.screenMS = this.getMS()
this.state.locked = true
@@ -721,8 +694,8 @@ class SongSelect{
this.toAbout()
}else if(currentSong.action === "settings"){
this.toSettings()
}else if(currentSong.action === "browse"){
this.toBrowse()
}else if(currentSong.action === "customSongs"){
this.toCustomSongs()
}
}
this.pointer(false)
@@ -857,18 +830,25 @@ class SongSelect{
}, 500)
}
}
toBrowse(){
toCustomSongs(){
if(assets.customSongs){
assets.customSongs = false
assets.songs = assets.songsDefault
delete assets.otherFiles
this.playSound("se_don")
this.clean()
setTimeout(() => {
new SongSelect("browse", false, this.touchEnabled)
new SongSelect("customSongs", false, this.touchEnabled)
}, 500)
pageEvents.send("import-songs-default")
}else{
this.browse.click()
localStorage["selectedSong"] = this.selectedSong
this.playSound("se_don")
this.clean()
setTimeout(() => {
new CustomSongs(this.touchEnabled)
}, 500)
}
}
@@ -2420,30 +2400,31 @@ class SongSelect{
}
}else{
songObj = {id: id}
var previewFilename = prvTime > 0 ? "/preview.mp3" : "/main.mp3"
var loadPreview = previewFilename => {
return snd.previewGain.load(gameConfig.songs_baseurl + id + previewFilename)
}
new Promise((resolve, reject) => {
if(!currentSong.music){
songObj.preview_time = 0
loadPreview(previewFilename).catch(() => {
songObj.preview_time = prvTime
return loadPreview("/main.mp3")
}).then(resolve, reject)
}else if(currentSong.music !== "muted"){
if(currentSong.previewMusic){
songObj.preview_time = 0
var promise = snd.previewGain.load(currentSong.previewMusic).catch(() => {
songObj.preview_time = prvTime
snd.previewGain.load(currentSong.music, true).then(resolve, reject)
}
}).then(sound => {
if(currentId === this.previewId){
return snd.previewGain.load(currentSong.music)
})
}else if(currentSong.unloaded){
var promise = this.getUnloaded(this.selectedSong, songObj)
}else if(currentSong.sound){
songObj.preview_time = prvTime
currentSong.sound.gain = snd.previewGain
var promise = Promise.resolve(currentSong.sound)
}else if(currentSong.music !== "muted"){
songObj.preview_time = prvTime
var promise = snd.previewGain.load(currentSong.music)
}else{
return
}
promise.then(sound => {
if(currentId === this.previewId || loadOnly){
songObj.preview_sound = sound
this.preview = sound
this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
if(!loadOnly){
this.preview = sound
this.previewLoaded(startLoad, songObj.preview_time, currentSong.volume)
}
var oldPreview = this.previewList.shift()
if(oldPreview){
oldPreview.preview_sound.clean()
@@ -2452,6 +2433,10 @@ class SongSelect{
}else{
sound.clean()
}
}).catch(e => {
if(e !== "cancel"){
return Promise.reject(e)
}
})
}
}
@@ -2483,6 +2468,72 @@ class SongSelect{
snd.musicGain.fadeOut(0.4)
}
}
getUnloaded(selectedSong, songObj){
var currentSong = this.songs[selectedSong]
var file = currentSong.chart
var importSongs = new ImportSongs(false, assets.otherFiles)
return file.read(currentSong.type === "tja" ? "sjis" : "").then(data => {
currentSong.chart = new CachedFile(data, file)
return importSongs.addTja({
file: currentSong.chart,
index: 0
})
}).then(() => {
var imported = importSongs.songs[0]
importSongs.clean()
imported.id = currentSong.id
imported.order = currentSong.order
delete imported.song_skin
songObj.preview_time = imported.preview
if(imported.music){
return snd.previewGain.load(imported.music).then(sound => {
imported.sound = sound
var index = assets.songs.findIndex(song => song.id === currentSong.id)
if(index !== -1){
assets.songs[index] = imported
}
this.songs[selectedSong] = this.addSong(imported)
return sound.copy()
})
}else{
return Promise.reject("cancel")
}
})
}
addSong(song){
var title = this.getLocalTitle(song.title, song.title_lang)
var subtitle = this.getLocalTitle(title === song.title ? song.subtitle : "", song.subtitle_lang)
var skin = null
var categoryName = ""
var originalCategory = ""
if(song.category_id !== null && song.category_id !== undefined){
var category = assets.categories.find(cat => cat.id === song.category_id)
var categoryName = this.getLocalTitle(category.title, category.title_lang)
var originalCategory = category.title
var skin = this.songSkin[category.title]
}else if(song.category){
var categoryName = song.category
var originalCategory = song.category
}
var addedSong = {
title: title,
originalTitle: song.title,
subtitle: subtitle,
skin: skin || this.songSkin.default,
originalCategory: originalCategory,
category: categoryName,
preview: song.preview || 0,
songSkin: song.song_skin || {},
canJump: true,
hash: song.hash || song.title
}
for(var i in song){
if(!(i in addedSong)){
addedSong[i] = song[i]
}
}
return addedSong
}
onusers(response){
this.songs.forEach(song => {
@@ -2657,8 +2708,6 @@ class SongSelect{
pageEvents.remove(this.touchFullBtn, "click")
delete this.touchFullBtn
}
pageEvents.remove(this.browse, "change")
delete this.browse
delete this.selectable
delete this.ctx
delete this.canvas