mirror of
https://github.com/jiojciojsioe3/a3cjroijsiojiorj.git
synced 2024-11-15 07:21:50 +08:00
Merge pull request #303 from bui/gdrive-beta
ImportSongs: Implement Google Drive beta testing
This commit is contained in:
commit
1fceaadc7d
70
app.py
70
app.py
@ -9,9 +9,10 @@ import re
|
||||
import requests
|
||||
import schema
|
||||
import os
|
||||
import time
|
||||
|
||||
from functools import wraps
|
||||
from flask import Flask, g, jsonify, render_template, request, abort, redirect, session, flash
|
||||
from flask import Flask, g, jsonify, render_template, request, abort, redirect, session, flash, make_response
|
||||
from flask_caching import Cache
|
||||
from flask_session import Session
|
||||
from flask_wtf.csrf import CSRFProtect, generate_csrf, CSRFError
|
||||
@ -113,15 +114,27 @@ def before_request_func():
|
||||
session.clear()
|
||||
|
||||
|
||||
def get_config():
|
||||
def get_config(credentials=False):
|
||||
config_out = {
|
||||
'songs_baseurl': config.SONGS_BASEURL,
|
||||
'assets_baseurl': config.ASSETS_BASEURL,
|
||||
'email': config.EMAIL,
|
||||
'accounts': config.ACCOUNTS,
|
||||
'custom_js': config.CUSTOM_JS,
|
||||
'google_credentials': config.GOOGLE_CREDENTIALS
|
||||
'custom_js': config.CUSTOM_JS
|
||||
}
|
||||
if credentials:
|
||||
min_level = config.GOOGLE_CREDENTIALS['min_level'] or 0
|
||||
if not session.get('username'):
|
||||
user_level = 0
|
||||
else:
|
||||
user = db.users.find_one({'username': session.get('username')})
|
||||
user_level = user['user_level']
|
||||
if user_level >= min_level:
|
||||
config_out['google_credentials'] = config.GOOGLE_CREDENTIALS
|
||||
else:
|
||||
config_out['google_credentials'] = {
|
||||
'gdrive_enabled': False
|
||||
}
|
||||
|
||||
if not config_out.get('songs_baseurl'):
|
||||
config_out['songs_baseurl'] = ''.join([request.host_url, 'songs']) + '/'
|
||||
@ -342,6 +355,43 @@ def route_admin_songs_id_delete(id):
|
||||
return redirect('/admin/songs')
|
||||
|
||||
|
||||
@app.route('/admin/users')
|
||||
@admin_required(level=50)
|
||||
def route_admin_users():
|
||||
user = db.users.find_one({'username': session.get('username')})
|
||||
max_level = user['user_level'] - 1
|
||||
return render_template('admin_users.html', config=get_config(), max_level=max_level, username='', level='')
|
||||
|
||||
|
||||
@app.route('/admin/users', methods=['POST'])
|
||||
@admin_required(level=50)
|
||||
def route_admin_users_post():
|
||||
admin_name = session.get('username')
|
||||
admin = db.users.find_one({'username': admin_name})
|
||||
max_level = admin['user_level'] - 1
|
||||
|
||||
username = request.form.get('username')
|
||||
level = int(request.form.get('level')) or 0
|
||||
|
||||
user = db.users.find_one({'username': username})
|
||||
if not user:
|
||||
flash('Error: User was not found.')
|
||||
elif admin_name == username:
|
||||
flash('Error: You cannot modify your own level.')
|
||||
else:
|
||||
user_level = user['user_level']
|
||||
if level < 0 or level > max_level:
|
||||
flash('Error: Invalid level.')
|
||||
elif user_level > max_level:
|
||||
flash('Error: This user has higher level than you.')
|
||||
else:
|
||||
output = {'user_level': level}
|
||||
db.users.update_one({'username': username}, {'$set': output})
|
||||
flash('User updated.')
|
||||
|
||||
return render_template('admin_users.html', config=get_config(), max_level=max_level, username=username, level=level)
|
||||
|
||||
|
||||
@app.route('/api/preview')
|
||||
@app.cache.cached(timeout=15, query_string=True)
|
||||
def route_api_preview():
|
||||
@ -400,7 +450,7 @@ def route_api_categories():
|
||||
@app.route('/api/config')
|
||||
@app.cache.cached(timeout=15)
|
||||
def route_api_config():
|
||||
config = get_config()
|
||||
config = get_config(credentials=True)
|
||||
return jsonify(config)
|
||||
|
||||
|
||||
@ -611,6 +661,16 @@ def route_api_scores_get():
|
||||
return jsonify({'status': 'ok', 'scores': scores, 'username': user['username'], 'display_name': user['display_name'], 'don': don})
|
||||
|
||||
|
||||
@app.route('/privacy')
|
||||
def route_api_privacy():
|
||||
last_modified = time.strftime('%d %B %Y', time.gmtime(os.path.getmtime('templates/privacy.txt')))
|
||||
integration = config.GOOGLE_CREDENTIALS['gdrive_enabled']
|
||||
|
||||
response = make_response(render_template('privacy.txt', last_modified=last_modified, config=get_config(), integration=integration))
|
||||
response.headers['Content-type'] = 'text/plain; charset=utf-8'
|
||||
return response
|
||||
|
||||
|
||||
def make_preview(song_id, song_type, song_ext, preview):
|
||||
song_path = 'public/songs/%s/main.%s' % (song_id, song_ext)
|
||||
prev_path = 'public/songs/%s/preview.mp3' % song_id
|
||||
|
@ -1,11 +1,11 @@
|
||||
# The full URL base asset URL, with trailing slash.
|
||||
ASSETS_BASEURL = ''
|
||||
ASSETS_BASEURL = '/assets/'
|
||||
|
||||
# The full URL base song URL, with trailing slash.
|
||||
SONGS_BASEURL = ''
|
||||
SONGS_BASEURL = '/songs/'
|
||||
|
||||
# The email address to display in the "About Simulator" menu.
|
||||
EMAIL = 'taiko@example.com'
|
||||
EMAIL = None
|
||||
|
||||
# Whether to use the user account system.
|
||||
ACCOUNTS = True
|
||||
@ -39,5 +39,6 @@ GOOGLE_CREDENTIALS = {
|
||||
'gdrive_enabled': False,
|
||||
'api_key': '',
|
||||
'oauth_client_id': '',
|
||||
'project_number': ''
|
||||
'project_number': '',
|
||||
'min_level': None
|
||||
}
|
||||
|
@ -112,10 +112,15 @@ kbd{
|
||||
margin-right: 0.4em;
|
||||
}
|
||||
.center-buttons{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
.account-view .center-buttons{
|
||||
margin: 0.3em 0;
|
||||
}
|
||||
.center-buttons>div{
|
||||
text-align: center;
|
||||
margin: 0.2em 0;
|
||||
}
|
||||
.center-buttons .taibtn{
|
||||
margin: 0 0.2em;
|
||||
}
|
||||
|
@ -93,6 +93,11 @@ class Account{
|
||||
this.inputForms.push(this.accountDel.password)
|
||||
this.accountDelDiv = this.getElement("accountdel-div")
|
||||
|
||||
this.linkPrivacy = this.getElement("privacy-btn")
|
||||
this.setAltText(this.linkPrivacy, strings.account.privacy)
|
||||
pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this))
|
||||
this.items.push(this.linkPrivacy)
|
||||
|
||||
this.logoutButton = this.getElement("logout-btn")
|
||||
this.setAltText(this.logoutButton, strings.account.logout)
|
||||
pageEvents.add(this.logoutButton, ["mousedown", "touchstart"], this.onLogout.bind(this))
|
||||
@ -245,6 +250,12 @@ class Account{
|
||||
|
||||
pageEvents.add(this.registerButton, ["mousedown", "touchstart"], this.onSwitchMode.bind(this))
|
||||
this.items.push(this.registerButton)
|
||||
|
||||
this.linkPrivacy = this.getElement("privacy-btn")
|
||||
this.setAltText(this.linkPrivacy, strings.account.privacy)
|
||||
pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this))
|
||||
this.items.push(this.linkPrivacy)
|
||||
|
||||
if(!register){
|
||||
this.items.push(this.loginButton)
|
||||
}
|
||||
@ -282,11 +293,17 @@ class Account{
|
||||
this.onSwitchMode()
|
||||
}else if(selected === this.loginButton){
|
||||
this.onLogin()
|
||||
}else if(selected === this.linkPrivacy){
|
||||
assets.sounds["se_don"].play()
|
||||
this.openPrivacy()
|
||||
}
|
||||
}else if(name === "previous" || name === "next"){
|
||||
selected.classList.remove("selected")
|
||||
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
|
||||
this.items[this.selected].classList.add("selected")
|
||||
if(this.items[this.selected] === this.linkPrivacy){
|
||||
this.items[this.selected].scrollIntoView()
|
||||
}
|
||||
assets.sounds["se_ka"].play()
|
||||
}else if(name === "back"){
|
||||
this.onEnd()
|
||||
@ -380,6 +397,19 @@ class Account{
|
||||
}
|
||||
})
|
||||
}
|
||||
openPrivacy(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked){
|
||||
return
|
||||
}
|
||||
open("privacy")
|
||||
}
|
||||
onLogout(){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
@ -583,6 +613,7 @@ class Account{
|
||||
pageEvents.remove(this.customdonResetBtn, ["click", "touchstart"])
|
||||
pageEvents.remove(this.accounPassButton, ["click", "touchstart"])
|
||||
pageEvents.remove(this.accountDelButton, ["click", "touchstart"])
|
||||
pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.logoutButton, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.saveButton, ["mousedown", "touchstart"])
|
||||
for(var i = 0; i < this.inputForms.length; i++){
|
||||
@ -602,6 +633,7 @@ class Account{
|
||||
delete this.accountDelButton
|
||||
delete this.accountDel
|
||||
delete this.accountDelDiv
|
||||
delete this.linkPrivacy
|
||||
delete this.logoutButton
|
||||
delete this.saveButton
|
||||
delete this.inputForms
|
||||
@ -615,12 +647,14 @@ class Account{
|
||||
for(var i = 0; i < this.form.length; i++){
|
||||
pageEvents.remove(this.registerButton, ["keydown", "keyup", "keypress"])
|
||||
}
|
||||
pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"])
|
||||
delete this.errorDiv
|
||||
delete this.form
|
||||
delete this.password2
|
||||
delete this.remember
|
||||
delete this.loginButton
|
||||
delete this.registerButton
|
||||
delete this.linkPrivacy
|
||||
}
|
||||
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
|
||||
delete this.endButton
|
||||
|
@ -36,7 +36,10 @@ class CustomSongs{
|
||||
this.linkLocalFolder.parentNode.removeChild(this.linkLocalFolder)
|
||||
}
|
||||
|
||||
var groupGdrive = document.getElementById("group-gdrive")
|
||||
this.linkGdriveFolder = document.getElementById("link-gdrivefolder")
|
||||
this.linkGdriveAccount = document.getElementById("link-gdriveaccount")
|
||||
this.linkPrivacy = document.getElementById("link-privacy")
|
||||
if(gameConfig.google_credentials.gdrive_enabled){
|
||||
this.setAltText(this.linkGdriveFolder, strings.customSongs.gdriveFolder)
|
||||
pageEvents.add(this.linkGdriveFolder, ["mousedown", "touchstart"], this.gdriveFolder.bind(this))
|
||||
@ -45,8 +48,15 @@ class CustomSongs{
|
||||
this.linkGdriveFolder.classList.add("selected")
|
||||
this.selected = this.items.length - 1
|
||||
}
|
||||
this.setAltText(this.linkGdriveAccount, strings.customSongs.gdriveAccount)
|
||||
pageEvents.add(this.linkGdriveAccount, ["mousedown", "touchstart"], this.gdriveAccount.bind(this))
|
||||
this.items.push(this.linkGdriveAccount)
|
||||
this.setAltText(this.linkPrivacy, strings.account.privacy)
|
||||
pageEvents.add(this.linkPrivacy, ["mousedown", "touchstart"], this.openPrivacy.bind(this))
|
||||
this.items.push(this.linkPrivacy)
|
||||
}else{
|
||||
this.linkGdriveFolder.parentNode.removeChild(this.linkGdriveFolder)
|
||||
groupGdrive.style.display = "none"
|
||||
this.linkPrivacy.parentNode.removeChild(this.linkPrivacy)
|
||||
}
|
||||
|
||||
this.endButton = this.getElement("view-end-button")
|
||||
@ -237,13 +247,67 @@ class CustomSongs{
|
||||
}else if(e !== "cancel"){
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}).finally(() => {
|
||||
var addRemove = !gpicker || !gpicker.oauthToken ? "add" : "remove"
|
||||
this.linkGdriveAccount.classList[addRemove]("hiddenbtn")
|
||||
})
|
||||
}
|
||||
gdriveAccount(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked || this.mode !== "main"){
|
||||
return
|
||||
}
|
||||
this.changeSelected(this.linkGdriveAccount)
|
||||
this.locked = true
|
||||
this.loading(true)
|
||||
if(!gpicker){
|
||||
var gpickerPromise = loader.loadScript("/src/js/gpicker.js").then(() => {
|
||||
gpicker = new Gpicker()
|
||||
})
|
||||
}else{
|
||||
var gpickerPromise = Promise.resolve()
|
||||
}
|
||||
gpickerPromise.then(() => {
|
||||
return gpicker.switchAccounts(locked => {
|
||||
this.locked = locked
|
||||
this.loading(locked)
|
||||
}, error => {
|
||||
this.showError(error)
|
||||
})
|
||||
}).then(() => {
|
||||
this.locked = false
|
||||
this.loading(false)
|
||||
}).catch(error => {
|
||||
if(error !== "cancel"){
|
||||
this.showError(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
openPrivacy(event){
|
||||
if(event){
|
||||
if(event.type === "touchstart"){
|
||||
event.preventDefault()
|
||||
}else if(event.which !== 1){
|
||||
return
|
||||
}
|
||||
}
|
||||
if(this.locked || this.mode !== "main"){
|
||||
return
|
||||
}
|
||||
this.changeSelected(this.linkPrivacy)
|
||||
open("privacy")
|
||||
}
|
||||
loading(show){
|
||||
if(show){
|
||||
loader.screen.appendChild(this.loaderDiv)
|
||||
}else{
|
||||
loader.screen.removeChild(this.loaderDiv)
|
||||
}else if(this.loaderDiv.parentNode){
|
||||
this.loaderDiv.parentNode.removeChild(this.loaderDiv)
|
||||
}
|
||||
}
|
||||
songsLoaded(songs){
|
||||
@ -276,11 +340,19 @@ class CustomSongs{
|
||||
}else if(selected === this.linkGdriveFolder){
|
||||
assets.sounds["se_don"].play()
|
||||
this.gdriveFolder()
|
||||
}else if(selected === this.linkGdriveAccount){
|
||||
assets.sounds["se_don"].play()
|
||||
this.gdriveAccount()
|
||||
}else if(selected === this.linkPrivacy){
|
||||
assets.sounds["se_don"].play()
|
||||
this.openPrivacy()
|
||||
}
|
||||
}
|
||||
}else if(name === "previous" || name === "next"){
|
||||
selected.classList.remove("selected")
|
||||
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
|
||||
do{
|
||||
this.selected = this.mod(this.items.length, this.selected + (name === "next" ? 1 : -1))
|
||||
}while(this.items[this.selected] === this.linkPrivacy && name !== "previous")
|
||||
this.items[this.selected].classList.add("selected")
|
||||
assets.sounds["se_ka"].play()
|
||||
}else if(name === "back" || name === "backEsc"){
|
||||
@ -327,6 +399,8 @@ class CustomSongs{
|
||||
}, 500)
|
||||
}
|
||||
showError(text){
|
||||
this.locked = false
|
||||
this.loading(false)
|
||||
if(this.mode === "error"){
|
||||
return
|
||||
}
|
||||
@ -352,6 +426,8 @@ class CustomSongs{
|
||||
}
|
||||
if(gameConfig.google_credentials.gdrive_enabled){
|
||||
pageEvents.remove(this.linkGdriveFolder, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.linkGdriveAccount, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.linkPrivacy, ["mousedown", "touchstart"])
|
||||
}
|
||||
pageEvents.remove(this.endButton, ["mousedown", "touchstart"])
|
||||
pageEvents.remove(this.errorDiv, ["mousedown", "touchstart"])
|
||||
@ -363,6 +439,8 @@ class CustomSongs{
|
||||
delete this.browse
|
||||
delete this.linkLocalFolder
|
||||
delete this.linkGdriveFolder
|
||||
delete this.linkGdriveAccount
|
||||
delete this.linkPrivacy
|
||||
delete this.endButton
|
||||
delete this.items
|
||||
delete this.loaderDiv
|
||||
|
@ -131,37 +131,48 @@ class Gpicker{
|
||||
gapi.client.load("drive", "v3").then(resolve, reject)
|
||||
))
|
||||
}
|
||||
getToken(lockedCallback=()=>{}, errorCallback=()=>{}){
|
||||
if(this.oauthToken){
|
||||
return Promise.resolve()
|
||||
}
|
||||
getAuth(errorCallback=()=>{}){
|
||||
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){
|
||||
errorCallback(strings.gpicker.authError.replace("%s", e.details))
|
||||
}
|
||||
return Promise.reject(e)
|
||||
return new Promise((resolve, reject) => {
|
||||
gapi.auth2.init({
|
||||
clientId: this.oauthClientId,
|
||||
fetch_basic_profile: false,
|
||||
scope: this.scope
|
||||
}).then(() => {
|
||||
this.auth = gapi.auth2.getAuthInstance()
|
||||
resolve(this.auth)
|
||||
}, e => {
|
||||
if(e.details){
|
||||
var errorStr = strings.gpicker.authError.replace("%s", e.details)
|
||||
if(/cookie/i.test(e.details)){
|
||||
errorStr += "\n\n" + strings.gpicker.cookieError
|
||||
}
|
||||
errorCallback(errorStr)
|
||||
}
|
||||
reject(e)
|
||||
})
|
||||
})
|
||||
}else{
|
||||
var authPromise = Promise.resolve()
|
||||
return Promise.resolve(this.auth)
|
||||
}
|
||||
return authPromise.then(() => {
|
||||
var user = this.auth.currentUser.get()
|
||||
if(!this.checkScope(user)){
|
||||
}
|
||||
getToken(lockedCallback=()=>{}, errorCallback=()=>{}, force){
|
||||
if(this.oauthToken && !force){
|
||||
return Promise.resolve()
|
||||
}
|
||||
return this.getAuth(errorCallback).then(auth => {
|
||||
var user = force || auth.currentUser.get()
|
||||
if(force || !this.checkScope(user)){
|
||||
lockedCallback(false)
|
||||
this.auth.signIn().then(user => {
|
||||
return auth.signIn(force ? {
|
||||
prompt: "select_account"
|
||||
} : undefined).then(user => {
|
||||
if(this.checkScope(user)){
|
||||
lockedCallback(true)
|
||||
}else{
|
||||
return Promise.reject("cancel")
|
||||
}
|
||||
})
|
||||
}, () => Promise.reject("cancel"))
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -173,6 +184,9 @@ class Gpicker{
|
||||
return false
|
||||
}
|
||||
}
|
||||
switchAccounts(lockedCallback, errorCallback){
|
||||
return this.loadApi().then(() => this.getToken(lockedCallback, errorCallback, true))
|
||||
}
|
||||
displayPicker(callback){
|
||||
var picker = gapi.picker.api
|
||||
new picker.PickerBuilder()
|
||||
|
@ -1,6 +1,6 @@
|
||||
addEventListener("error", function(err){
|
||||
var stack
|
||||
if("error" in err){
|
||||
if("error" in err && err.error){
|
||||
stack = err.error.stack
|
||||
}else{
|
||||
stack = err.message + "\n at " + err.filename + ":" + err.lineno + ":" + err.colno
|
||||
|
@ -931,6 +931,13 @@ var translations = {
|
||||
tw: "註冊",
|
||||
ko: "가입하기"
|
||||
},
|
||||
privacy: {
|
||||
ja: "プライバシー",
|
||||
en: "Privacy",
|
||||
cn: "隐私权",
|
||||
tw: "隱私權",
|
||||
ko: "개인정보처리방침"
|
||||
},
|
||||
registerAccount: {
|
||||
ja: "アカウントを登録",
|
||||
en: "Register account",
|
||||
@ -1148,6 +1155,13 @@ var translations = {
|
||||
tw: "Google雲端硬碟...",
|
||||
ko: "구글 드라이브..."
|
||||
},
|
||||
gdriveAccount: {
|
||||
ja: "アカウントの切り替え",
|
||||
en: "Switch Accounts",
|
||||
cn: "切换帐户",
|
||||
tw: "切換帳戶",
|
||||
ko: "계정 전환"
|
||||
},
|
||||
dropzone: {
|
||||
ja: "ここにファイルをドロップ",
|
||||
en: "Drop files here",
|
||||
@ -1193,6 +1207,9 @@ var translations = {
|
||||
},
|
||||
authError: {
|
||||
en: "Auth error: %s"
|
||||
},
|
||||
cookieError: {
|
||||
en: "This function requires third party cookies."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,11 @@
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
</form>
|
||||
<div class="center-buttons">
|
||||
<div>
|
||||
<div class="privacy-btn taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="diag-txt"></div>
|
||||
<div class="left-buttons">
|
||||
|
@ -3,8 +3,16 @@
|
||||
<div class="view-title stroke-sub"></div>
|
||||
<div class="view-content"></div>
|
||||
<div class="center-buttons">
|
||||
<div id="link-localfolder" class="taibtn stroke-sub link-btn"></div>
|
||||
<div id="link-gdrivefolder" class="taibtn stroke-sub link-btn"></div>
|
||||
<div>
|
||||
<div id="link-localfolder" class="taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
<div id="group-gdrive">
|
||||
<div id="link-gdrivefolder" class="taibtn stroke-sub link-btn"></div>
|
||||
<div id="link-gdriveaccount" class="taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="left-buttons">
|
||||
<div id="link-privacy" class="taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub"></div>
|
||||
<div class="view-outer shadow-outer" id="dropzone">
|
||||
|
@ -19,6 +19,7 @@
|
||||
</div>
|
||||
<div class="left-buttons">
|
||||
<div class="register-btn taibtn stroke-sub link-btn"></div>
|
||||
<div class="privacy-btn taibtn stroke-sub link-btn"></div>
|
||||
</div>
|
||||
<div class="view-end-button taibtn stroke-sub selected"></div>
|
||||
</div>
|
||||
|
@ -1,12 +1,12 @@
|
||||
bcrypt==3.1.7
|
||||
ffmpy==0.2.2
|
||||
Flask==1.1.1
|
||||
Flask-Caching==1.8.0
|
||||
Flask-Session==0.3.1
|
||||
bcrypt==3.2.0
|
||||
ffmpy==0.2.3
|
||||
Flask==1.1.2
|
||||
Flask-Caching==1.9.0
|
||||
Flask-Session==0.3.2
|
||||
Flask-WTF==0.14.3
|
||||
gunicorn==20.0.4
|
||||
jsonschema==3.2.0
|
||||
pymongo==3.10.1
|
||||
redis==3.4.1
|
||||
requests==2.23.0
|
||||
websockets==7.0
|
||||
pymongo==3.11.2
|
||||
redis==3.5.3
|
||||
requests==2.25.1
|
||||
websockets==8.1
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import asyncio
|
||||
import websockets
|
||||
|
@ -13,6 +13,7 @@
|
||||
<header>
|
||||
<div class="nav">
|
||||
<a href="/admin/songs">Songs</a>
|
||||
<a href="/admin/users">Users</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
21
templates/admin_users.html
Normal file
21
templates/admin_users.html
Normal file
@ -0,0 +1,21 @@
|
||||
{% extends 'admin.html' %}
|
||||
{% block content %}
|
||||
<h1>Users</h1>
|
||||
{% for message in get_flashed_messages() %}
|
||||
<div class="message">{{ message }}</div>
|
||||
{% endfor %}
|
||||
<div class="song-form">
|
||||
<form method="post">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="form-field">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" id="username" name="username" value="{{username}}" required>
|
||||
<label for="level">Level</label>
|
||||
<input type="number" id="level" name="level" min="0" max="{{max_level}}" value="{{level}}" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="save-song">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
38
templates/privacy.txt
Normal file
38
templates/privacy.txt
Normal file
@ -0,0 +1,38 @@
|
||||
Taiko Web is committed to safeguarding and preserving the privacy of our website visitors and players. This Privacy Policy explains what happens to any personal data that you provide to us or that we collect from you while you play our game or visit our site.
|
||||
|
||||
1. The Types and Sources of Data We Collect
|
||||
|
||||
1.1 Basic Account Data
|
||||
When setting up an account, we will collect your user name and password.
|
||||
|
||||
1.2 Game High Score Data
|
||||
When setting a high score in-game, we will collect and store your score. This information may become available to other users when you start a multiplayer game with them.
|
||||
|
||||
1.3 Cookies
|
||||
We use "Cookies", which are text files placed on your computer, and similar technologies to improve the services we are offering and to improve website functionality.
|
||||
|
||||
2. Who Has Access to Data
|
||||
|
||||
Taiko Web does not sell Personal Data. However, we may share or provide access to each of the categories of Personal Data we collect as necessary for the following business purposes.
|
||||
|
||||
2.1 Taiko Web includes multiplayer features, where users can play the game with each other. When playing with other users, please be aware that the information is being made available to them; therefore, you are doing so at your own risk.
|
||||
{% if integration %}
|
||||
3. Google Drive Integration
|
||||
|
||||
You can use the Google Drive integration to let Taiko Web make your Taiko chart files and other related files available in-game.
|
||||
|
||||
Applications that integrate with a Google account must declare their intent by requesting permissions. These permissions to your account must be granted in order for Taiko Web to integrate with Google accounts. Below is a list of these permissions and why they are required. At no time will Taiko Web request or have access to your Google account password.
|
||||
|
||||
3.1 "Associate you with your personal info on Google" Permission
|
||||
Required for Google Sign-In to provide Taiko Web with a non-identifiable user authentication token. No other information provided by this permission is used.
|
||||
|
||||
3.2 "See and download all your Google Drive files" Permission
|
||||
When selecting a folder with the Google Drive file picker, Taiko Web instructs your Browser to recursively download all the files of that folder directly into your computer's memory. Limitation of Google Drive's permission model requires us to request access to all your Google Drive files, however, Taiko Web will only access the selected folder and its children, and only when requested. File parsing is handled locally; none of your Google Drive files is ever sent to our servers or third parties.
|
||||
{% endif %}{% if config.email %}
|
||||
{% if integration %}4{% else %}3{% endif %}. Contact Info
|
||||
|
||||
You can contact the Taiko Web operator at the email address below.
|
||||
|
||||
{{config.email}}
|
||||
{% endif %}
|
||||
Revision Date: {{last_modified}}
|
146
tools/categories.json
Normal file
146
tools/categories.json
Normal file
@ -0,0 +1,146 @@
|
||||
[{
|
||||
"id": 1,
|
||||
"title": "Pop",
|
||||
"title_lang": {
|
||||
"ja": "J-POP",
|
||||
"en": "Pop",
|
||||
"cn": "流行音乐",
|
||||
"tw": "流行音樂",
|
||||
"ko": "POP"
|
||||
},
|
||||
"song_skin": {
|
||||
"sort": 1,
|
||||
"background": "#219fbb",
|
||||
"border": ["#7ec3d3", "#0b6773"],
|
||||
"outline": "#005058",
|
||||
"info_fill": "#004d68",
|
||||
"bg_img": "bg_genre_0.png"
|
||||
},
|
||||
"aliases": null
|
||||
},{
|
||||
"id": 2,
|
||||
"title": "Anime",
|
||||
"title_lang": {
|
||||
"ja": "アニメ",
|
||||
"en": "Anime",
|
||||
"cn": "卡通动画音乐",
|
||||
"tw": "卡通動畫音樂",
|
||||
"ko": "애니메이션"
|
||||
},
|
||||
"song_skin": {
|
||||
"sort": 2,
|
||||
"background": "#ff9700",
|
||||
"border": ["#ffdb8c", "#e75500"],
|
||||
"outline": "#9c4100",
|
||||
"info_fill": "#9c4002",
|
||||
"bg_img": "bg_genre_1.png"
|
||||
},
|
||||
"aliases": null
|
||||
},{
|
||||
"id": 3,
|
||||
"title": "VOCALOID™ Music",
|
||||
"title_lang": {
|
||||
"ja": "ボーカロイド™曲",
|
||||
"en": "VOCALOID™ Music"
|
||||
},
|
||||
"song_skin": {
|
||||
"sort": 3,
|
||||
"background": "#def2ef",
|
||||
"border": ["#f7fbff", "#79919f"],
|
||||
"outline": "#5a6584",
|
||||
"info_fill": "#546184",
|
||||
"bg_img": "bg_genre_2.png"
|
||||
},
|
||||
"aliases": [
|
||||
"ボーカロイド曲",
|
||||
"ボーカロイド",
|
||||
"vocaloid music",
|
||||
"vocaloid"
|
||||
]
|
||||
},{
|
||||
"id": 4,
|
||||
"title": "Variety",
|
||||
"title_lang": {
|
||||
"ja": "バラエティ",
|
||||
"en": "Variety",
|
||||
"cn": "综合音乐",
|
||||
"tw": "綜合音樂",
|
||||
"ko": "버라이어티"
|
||||
},
|
||||
"song_skin": {
|
||||
"sort": 4,
|
||||
"background": "#8fd321",
|
||||
"border": ["#f7fbff", "#587d0b"],
|
||||
"outline": "#374c00",
|
||||
"info_fill": "#3c6800",
|
||||
"bg_img": "bg_genre_3.png"
|
||||
},
|
||||
"aliases": [
|
||||
"バラエティー",
|
||||
"どうよう",
|
||||
"童謡・民謡",
|
||||
"children",
|
||||
"children/folk",
|
||||
"children-folk"
|
||||
]
|
||||
},{
|
||||
"id": 5,
|
||||
"title": "Classical",
|
||||
"title_lang": {
|
||||
"ja": "クラシック",
|
||||
"en": "Classical",
|
||||
"cn": "古典音乐",
|
||||
"tw": "古典音樂",
|
||||
"ko": "클래식"
|
||||
},
|
||||
"song_skin": {
|
||||
"sort": 5,
|
||||
"background": "#d1a016",
|
||||
"border": ["#e7cf6b", "#9a6b00"],
|
||||
"outline": "#734d00",
|
||||
"info_fill": "#865800",
|
||||
"bg_img": "bg_genre_4.png"
|
||||
},
|
||||
"aliases": [
|
||||
"クラッシック",
|
||||
"classic"
|
||||
]
|
||||
},{
|
||||
"id": 6,
|
||||
"title": "Game Music",
|
||||
"title_lang": {
|
||||
"ja": "ゲームミュージック",
|
||||
"en": "Game Music",
|
||||
"cn": "游戏音乐",
|
||||
"tw": "遊戲音樂",
|
||||
"ko": "게임"
|
||||
},
|
||||
"song_skin": {
|
||||
"sort": 6,
|
||||
"background": "#9c72c0",
|
||||
"border": ["#bda2ce", "#63407e"],
|
||||
"outline": "#4b1c74",
|
||||
"info_fill": "#4f2886",
|
||||
"bg_img": "bg_genre_5.png"
|
||||
},
|
||||
"aliases": null
|
||||
},{
|
||||
"id": 7,
|
||||
"title": "NAMCO Original",
|
||||
"title_lang": {
|
||||
"ja": "ナムコオリジナル",
|
||||
"en": "NAMCO Original",
|
||||
"cn": "NAMCO原创音乐",
|
||||
"tw": "NAMCO原創音樂",
|
||||
"ko": "남코 오리지널"
|
||||
},
|
||||
"song_skin": {
|
||||
"sort": 7,
|
||||
"background": "#ff5716",
|
||||
"border": ["#ffa66b", "#b53000"],
|
||||
"outline": "#941c00",
|
||||
"info_fill": "#961e00",
|
||||
"bg_img": "bg_genre_6.png"
|
||||
},
|
||||
"aliases": null
|
||||
}]
|
25
tools/nginx.conf
Normal file
25
tools/nginx.conf
Normal file
@ -0,0 +1,25 @@
|
||||
server {
|
||||
listen 80;
|
||||
#server_name taiko.example.com;
|
||||
|
||||
location / {
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header Host $server_name;
|
||||
proxy_pass http://127.0.0.1:34801;
|
||||
}
|
||||
|
||||
location ~ ^/(assets|songs|src)/ {
|
||||
root /srv/taiko-web/public;
|
||||
location ~ ^/songs/(\d+)/preview\.mp3$ {
|
||||
try_files $uri /api/preview?id=$1;
|
||||
}
|
||||
}
|
||||
|
||||
location /p2 {
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "Upgrade";
|
||||
proxy_pass http://127.0.0.1:34802;
|
||||
}
|
||||
}
|
15
tools/supervisor.conf
Normal file
15
tools/supervisor.conf
Normal file
@ -0,0 +1,15 @@
|
||||
[program:taiko_app]
|
||||
directory=/srv/taiko-web
|
||||
command=/srv/taiko-web/.venv/bin/gunicorn -b 127.0.0.1:34801 app:app
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stdout_logfile=/var/log/taiko-web/app.out.log
|
||||
stderr_logfile=/var/log/taiko-web/app.err.log
|
||||
|
||||
[program:taiko_server]
|
||||
directory=/srv/taiko-web
|
||||
command=/srv/taiko-web/.venv/bin/python server.py 34802
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stdout_logfile=/var/log/taiko-web/server.out.log
|
||||
stderr_logfile=/var/log/taiko-web/server.err.log
|
Loading…
Reference in New Issue
Block a user