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

@@ -1,32 +1,10 @@
class ImportSongs{
constructor(songSelect, event){
this.songSelect = songSelect
this.songSelect.redrawRunning = false
this.songSelect.pointer(false)
this.loaderDiv = document.createElement("div")
this.loaderDiv.innerHTML = assets.pages["loadsong"]
loader.screen.appendChild(this.loaderDiv)
var loadingText = document.getElementById("loading-text")
loadingText.appendChild(document.createTextNode(strings.loading))
loadingText.setAttribute("alt", strings.loading)
var files = []
for(var i = 0; i < event.target.files.length; i++){
files.push(event.target.files[i])
}
var extensionRegex = /\.[^\/]+$/
files.sort((a, b) => {
var path1 = a.webkitRelativePath.replace(extensionRegex, "")
var path2 = b.webkitRelativePath.replace(extensionRegex, "")
return path1 > path2 ? 1 : -1
})
constructor(limited, otherFiles){
this.limited = limited
this.tjaFiles = []
this.osuFiles = []
this.assetFiles = {}
var metaFiles = []
this.otherFiles = {}
this.otherFiles = otherFiles || {}
this.songs = []
this.stylesheet = []
this.songTitle = {}
@@ -38,7 +16,6 @@
"oni": 3,
"ura": 4
}
this.categoryAliases = {}
assets.categories.forEach(cat => {
this.categoryAliases[cat.title.toLowerCase()] = cat.id
@@ -53,7 +30,6 @@
}
}
})
this.assetSelectors = {
"bg-pattern-1": ".pattern-bg",
"bg_genre_0": "#song-select",
@@ -66,11 +42,20 @@
"bg_stage_2": ".song-stage-2",
"bg_stage_3": ".song-stage-3"
}
}
load(files){
var extensionRegex = /\.[^\/]+$/
files.sort((a, b) => {
var path1 = a.path.replace(extensionRegex, "")
var path2 = b.path.replace(extensionRegex, "")
return path1 > path2 ? 1 : -1
})
var metaFiles = []
for(var i = 0; i < files.length; i++){
var file = files[i]
var name = file.name.toLowerCase()
var path = file.webkitRelativePath.toLowerCase()
var path = file.path.toLowerCase()
if(name.endsWith(".tja")){
this.tjaFiles.push({
file: file,
@@ -81,13 +66,13 @@
file: file,
index: i
})
}else if(name === "genre.ini" || name === "box.def" || name === "songtitle.txt"){
var level = (file.webkitRelativePath.match(/\//g) || []).length
}else if(!this.limited && (name === "genre.ini" || name === "box.def" || name === "songtitle.txt")){
var level = (file.path.match(/\//g) || []).length
metaFiles.push({
file: file,
level: (level * 2) + (name === "genre.ini" ? 1 : 0)
})
}else if(path.indexOf("/taiko-web assets/") !== -1){
}else if(!this.limited && path.indexOf("/taiko-web assets/") !== -1){
if(!(name in this.assetFiles)){
this.assetFiles[name] = file
}
@@ -97,32 +82,30 @@
}
var metaPromises = []
metaFiles.forEach(fileObj => {
metaPromises.push(this.addMeta(fileObj))
})
Promise.all(metaPromises).then(() => {
return Promise.all(metaPromises).then(() => {
var songPromises = []
this.tjaFiles.forEach(fileObj => {
songPromises.push(this.addTja(fileObj))
songPromises.push(this.addTja(fileObj).catch(e => console.warn(e)))
})
this.osuFiles.forEach(fileObj => {
songPromises.push(this.addOsu(fileObj))
songPromises.push(this.addOsu(fileObj).catch(e => console.warn(e)))
})
songPromises.push(this.addAssets())
Promise.all(songPromises).then(this.loaded.bind(this))
})
return Promise.all(songPromises)
}).then(this.loaded.bind(this))
}
addMeta(fileObj){
var file = fileObj.file
var level = fileObj.level
var name = file.name.toLowerCase()
var reader = new FileReader()
var promise = pageEvents.load(reader).then(event => {
var data = event.target.result.replace(/\0/g, "").split("\n")
return file.read(name === "songtitle.txt" ? undefined : "sjis").then(data => {
var data = data.replace(/\0/g, "").split("\n")
var category
if(name === "genre.ini"){
var key
@@ -177,9 +160,9 @@
}
}
if(category){
var metaPath = file.webkitRelativePath.toLowerCase().slice(0, file.name.length * -1)
var metaPath = file.path.toLowerCase().slice(0, file.name.length * -1)
var filesLoop = fileObj => {
var tjaPath = fileObj.file.webkitRelativePath.toLowerCase().slice(0, fileObj.file.name.length * -1)
var tjaPath = fileObj.file.path.toLowerCase().slice(0, fileObj.file.name.length * -1)
if(tjaPath.startsWith(metaPath) && (!("categoryLevel" in fileObj) || fileObj.categoryLevel < level)){
if(category.toLowerCase() in this.categoryAliases){
fileObj.category_id = this.categoryAliases[category.toLowerCase()]
@@ -192,13 +175,9 @@
this.tjaFiles.forEach(filesLoop)
this.osuFiles.forEach(filesLoop)
}
}).catch(() => {})
if(name === "songtitle.txt"){
reader.readAsText(file)
}else{
reader.readAsText(file, "sjis")
}
return promise
}).catch(e => {
console.warn(e)
})
}
addTja(fileObj){
@@ -206,24 +185,33 @@
var index = fileObj.index
var category = fileObj.category
var category_id = fileObj.category_id
var reader = new FileReader()
var promise = pageEvents.load(reader).then(event => {
var data = event.target.result.replace(/\0/g, "").split("\n")
if(!this.limited){
var filePromise = file.read("sjis")
}else{
var filePromise = Promise.resolve()
}
return filePromise.then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var tja = new ParseTja(data, "oni", 0, 0, true)
var songObj = {
id: index + 1,
order: index + 1,
title: file.name.slice(0, file.name.lastIndexOf(".")),
type: "tja",
chart: file,
courses: {},
music: "muted"
music: "muted",
custom: true
}
if(this.limited){
songObj.unloaded = true
}
var coursesAdded = false
var titleLang = {}
var titleLangAdded = false
var subtitleLangAdded = false
var subtitleLang = {}
var dir = file.webkitRelativePath.toLowerCase()
var dir = file.path.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
for(var diff in tja.metadata){
var meta = tja.metadata[diff]
@@ -321,19 +309,19 @@
songObj.category_id = category_id
}
}
if(coursesAdded){
if(coursesAdded || songObj.unloaded){
this.songs[index] = songObj
}
var hash = md5.base64(event.target.result).slice(0, -2)
songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true)
if(score){
score.title = songObj.title
if(!this.limited){
var hash = md5.base64(dataRaw).slice(0, -2)
songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true)
if(score){
score.title = songObj.title
}
}
}).catch(() => {})
reader.readAsText(file, "sjis")
return promise
})
}
addOsu(fileObj){
@@ -341,11 +329,15 @@
var index = fileObj.index
var category = fileObj.category
var category_id = fileObj.category_id
var reader = new FileReader()
var promise = pageEvents.load(reader).then(event => {
var data = event.target.result.replace(/\0/g, "").split("\n")
var osu = new ParseOsu(data, "oni", 0, 0, true);
var dir = file.webkitRelativePath.toLowerCase()
if(!this.limited){
var filePromise = file.read()
}else{
var filePromise = Promise.resolve()
}
return filePromise.then(dataRaw => {
var data = dataRaw ? dataRaw.replace(/\0/g, "").split("\n") : []
var osu = new ParseOsu(data, "oni", 0, 0, true)
var dir = file.path.toLowerCase()
dir = dir.slice(0, dir.lastIndexOf("/") + 1)
var songObj = {
id: index + 1,
@@ -356,14 +348,17 @@
subtitle_lang: {
en: osu.metadata.Artist || osu.metadata.ArtistUnicode
},
preview: osu.generalInfo.PreviewTime / 1000,
courses: {
oni:{
stars: parseInt(osu.difficulty.overallDifficulty) || 0,
branch: false
}
},
music: this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] || "muted"
preview: osu.generalInfo.PreviewTime ? osu.generalInfo.PreviewTime / 1000 : 0,
courses: {},
music: (osu.generalInfo.AudioFilename ? this.otherFiles[dir + osu.generalInfo.AudioFilename.toLowerCase()] : "") || "muted"
}
if(!this.limited){
songObj.courses.oni = {
stars: parseInt(osu.difficulty.overallDifficulty) || 0,
branch: false
}
}else{
songObj.unloaded = true
}
var filename = file.name.slice(0, file.name.lastIndexOf("."))
var title = osu.metadata.TitleUnicode || osu.metadata.Title || file.name.slice(0, file.name.lastIndexOf("."))
@@ -375,7 +370,7 @@
}
songObj.title = title + suffix
songObj.title_lang = {
en: (osu.metadata.Title || osu.metadata.TitleUnicode) + suffix
en: (osu.metadata.Title || osu.metadata.TitleUnicode || title) + suffix
}
}else{
songObj.title = filename
@@ -389,72 +384,68 @@
}else{
songObj.category_id = category_id
}
var hash = md5.base64(event.target.result).slice(0, -2)
songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true)
if(score){
score.title = songObj.title
if(!this.limited){
var hash = md5.base64(dataRaw).slice(0, -2)
songObj.hash = hash
scoreStorage.songTitles[songObj.title] = hash
var score = scoreStorage.get(hash, false, true)
if(score){
score.title = songObj.title
}
}
}).catch(() => {})
reader.readAsText(file)
return promise
})
}
addAssets(){
return new Promise((resolve, reject) => {
var promises = []
for(let name in this.assetFiles){
let id = this.getFilename(name)
var file = this.assetFiles[name]
if(name === "vectors.json"){
var reader = new FileReader()
promises.push(pageEvents.load(reader).then(() => response => {
vectors = JSON.parse(response)
}))
reader.readAsText(file)
}
if(assets.img.indexOf(name) !== -1){
let image = document.createElement("img")
promises.push(pageEvents.load(image).then(() => {
if(id in this.assetSelectors){
var selector = this.assetSelectors[id]
this.stylesheet.push(selector + '{background-image:url("' + image.src + '")}')
}
}))
image.id = name
image.src = URL.createObjectURL(file)
loader.assetsDiv.appendChild(image)
assets.image[id].parentNode.removeChild(assets.image[id])
assets.image[id] = image
}
if(assets.audioSfx.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.sfxGain))
}
if(assets.audioMusic.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.musicGain))
}
if(assets.audioSfxLR.indexOf(name) !== -1){
assets.sounds[id + "_p1"].clean()
assets.sounds[id + "_p2"].clean()
promises.push(this.loadSound(file, name, snd.sfxGain).then(sound => {
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
}))
}
if(assets.audioSfxLoud.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.sfxLoudGain))
}
var promises = []
for(let name in this.assetFiles){
let id = this.getFilename(name)
var file = this.assetFiles[name]
if(name === "vectors.json"){
promises.push(file.read().then(() => response => {
vectors = JSON.parse(response)
}))
}
Promise.all(promises).then(resolve, reject)
})
if(assets.img.indexOf(name) !== -1){
let image = document.createElement("img")
promises.push(pageEvents.load(image).then(() => {
if(id in this.assetSelectors){
var selector = this.assetSelectors[id]
this.stylesheet.push(selector + '{background-image:url("' + image.src + '")}')
}
}))
image.id = name
image.src = URL.createObjectURL(file)
loader.assetsDiv.appendChild(image)
assets.image[id].parentNode.removeChild(assets.image[id])
assets.image[id] = image
}
if(assets.audioSfx.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.sfxGain))
}
if(assets.audioMusic.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.musicGain))
}
if(assets.audioSfxLR.indexOf(name) !== -1){
assets.sounds[id + "_p1"].clean()
assets.sounds[id + "_p2"].clean()
promises.push(this.loadSound(file, name, snd.sfxGain).then(sound => {
assets.sounds[id + "_p1"] = assets.sounds[id].copy(snd.sfxGainL)
assets.sounds[id + "_p2"] = assets.sounds[id].copy(snd.sfxGainR)
}))
}
if(assets.audioSfxLoud.indexOf(name) !== -1){
assets.sounds[id].clean()
promises.push(this.loadSound(file, name, snd.sfxLoudGain))
}
}
return Promise.all(promises)
}
loadSound(file, name, gain){
var id = this.getFilename(name)
return gain.load(file, true).then(sound => {
return gain.load(file).then(sound => {
assets.sounds[id] = sound
})
}
@@ -463,7 +454,7 @@
}
getCategory(file, exclude){
var path = file.webkitRelativePath.toLowerCase().split("/")
var path = file.path.toLowerCase().split("/")
for(var i = path.length - 2; i >= 0; i--){
var hasTitle = false
for(var j in exclude){
@@ -532,24 +523,14 @@
document.head.appendChild(style)
}
if(this.songs.length){
var length = this.songs.length
assets.songs = this.songs
assets.customSongs = true
assets.customSelected = 0
assets.sounds["se_don"].play()
this.songSelect.clean()
setTimeout(() => {
loader.screen.removeChild(this.loaderDiv)
this.clean()
new SongSelect("browse", false, this.songSelect.touchEnabled)
pageEvents.send("import-songs", length)
}, 500)
if(this.limited){
assets.otherFiles = this.otherFiles
}
return Promise.resolve(this.songs)
}else{
loader.screen.removeChild(this.loaderDiv)
this.songSelect.browse.parentNode.reset()
this.songSelect.redrawRunning = true
this.clean()
return Promise.reject("cancel")
}
this.clean()
}
joinPath(){
@@ -587,7 +568,6 @@
}
clean(){
delete this.loaderDiv
delete this.songs
delete this.tjaFiles
delete this.osuFiles