diff --git a/package-lock.json b/package-lock.json index 15b608d..9ec1de8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1499,6 +1499,11 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "node-stream-zip": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.11.3.tgz", + "integrity": "sha512-GY+9LxkQuIT3O7K8BTdHVGKFcBYBy2vAVcTBtkKpu+OlBef/NSb6VuIWSyLiVDfmLMkggHeRJZN0F3W0GWU/uw==" + }, "on-finished": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", diff --git a/package.json b/package.json index 74fb601..b3188f9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "express-fileupload": "^1.2.0", "mongoose": "^5.10.2", "mongoose-int32": "^0.4.1", + "node-stream-zip": "^1.11.3", "typescript": "^4.0.2", "winston": "^3.3.3" }, diff --git a/src/index.ts b/src/index.ts index 16d87d1..cc1f8e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -58,15 +58,13 @@ db.then(() => { app.get('/list', async (req, res) => { // only for testing const docs = await File.find({}); - const map = docs.map(d => { - const keys = Object.keys(d.toObject()).filter(k => !k.startsWith('_')); - const obj = {}; - for (const key of keys) obj[key] = d[key]; - - return obj; - }); - - res.send(JSON.stringify(map)); + + res.send(docs.map((doc: any) => + `${doc.artist} - ${doc.title} by ${doc.credit}
` + + doc.charts.map(ch => + `- ${ch.difficulty} ${ch.rating}: ${ch.name}` + ).join('
') + ).join('

')); }); app.get('/', (req, res) => { diff --git a/src/lib/smparse.ts b/src/lib/smparse.ts new file mode 100644 index 0000000..6736c6e --- /dev/null +++ b/src/lib/smparse.ts @@ -0,0 +1,57 @@ +export function parseSM(data: string) { + data = data.replace(/[\n\r]/g,''); + + // steps + const difficulties = []; + const steps = data.split('#NOTES:'); + steps.slice(1); + + for (const step of steps) { + if (step.includes(';')) { + const diff: any = {}; + const stepsSplit = step.split(';')[0].split(':').map(seg => seg.trim()); + + if (stepsSplit.length === 6) { + diff.type = stepsSplit[0]; + diff.name = stepsSplit[1]; + diff.difficulty = stepsSplit[2]; + diff.rating = Number(stepsSplit[3]); + diff.radarvalues = stepsSplit[4].split(',').map(v => Number(v)); + + difficulties.push(diff); + } + } + } + + // metadata + const lines = data.split(';').filter(l => l.startsWith('#')); + const obj: any = {}; + + for (const l of lines) { + const key = l.split(':')[0].slice(1); + let value: any = l.split(':')[1]; + + if (value !== '' && !(key === 'FGCHANGES' || key === 'BGCHANGES' || key === 'BETTERBGCHANGES' || key === 'SPELLCARDS')) { // no + if (!isNaN(value)) value = Number(value); + if (value === 'YES') value = true; + if (value === 'NO') value = false; + if (typeof value === 'string' && value.includes('=')) { // likely a map + const keys = value.split(',').map(v => v.split('=')[0]); + const values = value.split(',').map(v => v.split('=')[1]); + const map = {}; + + for (const i in keys) { + map[Number(keys[i])] = Number(values[i]); // afaik maps are only numbers? + } + + value = map; + } + + obj[key.toLowerCase()] = value; + } + } + + obj.charts = difficulties; + + return obj; +} \ No newline at end of file diff --git a/src/schema.ts b/src/schema.ts index df9bec1..2bf463c 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -1,17 +1,18 @@ import * as mongoose from 'mongoose'; -import * as Int32 from 'mongoose-int32'; const Schema = mongoose.Schema; const Sample = new Schema({ - start: {type: Int32, default: new Int32(0)}, - length: {type: Int32, default: new Int32(0)} + start: {type: Number, default: 0}, + length: {type: Number, default: 0} }); const Chart = new Schema({ + type: {type: String, default: 'dance-single'}, name: {type: String, default: ''}, - rating: {type: Int32, default: new Int32(0)}, - type: {type: String, default: 'Challenge'} + difficulty: {type: String, default: 'Challenge'}, + rating: {type: Number, default: 0}, + radarvalues: [Number] }); const FileSchema = new Schema({ @@ -24,7 +25,7 @@ const FileSchema = new Schema({ credit: String, uploader: {type: String, default: '00000000-0000-4000-a000-000000000000'}, sample: Sample, - bpms: {type: [Number], default: 0}, + bpms: {type: Object, default: {'0': 0}}, charts: [Chart] }); diff --git a/src/upload.ts b/src/upload.ts index b6a686a..d8b94c6 100644 --- a/src/upload.ts +++ b/src/upload.ts @@ -1,11 +1,53 @@ +import { tmpdir } from 'os'; +import * as fs from 'fs'; +const StreamZip = require('node-stream-zip'); + import { returnStatic } from './lib/util'; +import { parseSM } from './lib/smparse'; +import { File } from './schema'; export function run(app) { + const logger = app.get('logger'); + app.get('/upload', returnStatic('upload.html')); app.post('/upload', async (req, res) => { // only for testing, very abusable - console.log(req.files); - - res.send('ok, i got something'); + const file = req.files.file; + if (file.mimetype !== 'application/zip') return res.status(400).send('Invalid filetype'); + + const dir = tmpdir() + '/' + file.md5; + fs.writeFile(dir, file.data, (err) => { + if (err) throw err; + + const zip = new StreamZip({ + file: dir, + storeEntries: true + }); + + zip.on('ready', () => { + const smFile = Object.values(zip.entries()).find((f: any) => + !f.isDirectory && (f.name.endsWith('.sm')) + ); + + if (!smFile) { + res.status(400).send('No .sm found'); + } else { + const data = zip.entryDataSync((smFile as any).name); + const chart = parseSM(data.toString()); + + logger.info(`${chart.artist} - ${chart.title} was just uploaded`); + + const file = new File(chart); + file.save(); + + res.send(`your file "${chart.artist} - ${chart.title}" has been uploaded! check the listing`); // todo: change to actual url + } + + zip.close(); + fs.unlink(dir, (err) => { + if (err) throw err; + }); + }); + }); }); } \ No newline at end of file