Compare commits
3 Commits
96ee81f77d
...
dc83dcb903
Author | SHA1 | Date |
---|---|---|
Jill | dc83dcb903 | |
Jill | 30ff75bf34 | |
Jill | 77946f0ed7 |
47
README.md
47
README.md
|
@ -1 +1,46 @@
|
||||||
todo document this LOLL
|
# nlw-api
|
||||||
|
|
||||||
|
A simple API that caches and allows you to fetch data from the [Non-Listworthy Extreme Demons spreadsheet](https://docs.google.com/spreadsheets/d/1YxUE2kkvhT2E6AjnkvTf-o8iu_shSLbuFkEFcZOvieA/edit) (and [Insane Demon Spreadsheet](https://docs.google.com/spreadsheets/d/15ehtAIpCR8s04qIb8zij9sTpUdGJbmAE_LDcfVA3tcU/edit)!) running on `https://nlw.oat.zone/`. Originally made for the [NLW Integration](https://geode-sdk.org/mods/oatmealine.nlw_integration/) Geode mod, but free for anyone to use.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
### Endpoints
|
||||||
|
|
||||||
|
#### `/list?type={type}`
|
||||||
|
|
||||||
|
Fetches the entire list as a JSON, including levels of type `type`. `type` can be `regular`, `pending`, `platformer` or `all` and defaults to `regular`.
|
||||||
|
|
||||||
|
**Returns**: An array of [`NLWLevel`](#nlwlevel--level)s.
|
||||||
|
|
||||||
|
#### `/ids`
|
||||||
|
|
||||||
|
Fetches the IDS list as a JSON. **Experimental.**
|
||||||
|
|
||||||
|
**Returns**: An array of [`IDSLevel`](#idslevel-level)s.
|
||||||
|
|
||||||
|
### Objects
|
||||||
|
|
||||||
|
#### `Level`
|
||||||
|
|
||||||
|
Represents a generic level.
|
||||||
|
|
||||||
|
- `sheetIndex`: The row index of the level on the associated spreadsheet. 0-indexed.
|
||||||
|
- `name`: The name of the level. Standardized to the in-game level name.
|
||||||
|
- `creator`: The creator(s) of the level, as listed on the sheet.
|
||||||
|
- `description`: Descriptions and notes as listed on the sheet.
|
||||||
|
|
||||||
|
#### `NLWLevel` : [`Level`](#level)
|
||||||
|
|
||||||
|
Represents an NLW level.
|
||||||
|
|
||||||
|
- `tier`: `"Fuck"`, `"Beginner"`, `"Easy"`, `"Medium"`, `"Hard"`, `"Very Hard"`, `"Insane"`, `"Extreme"`, `"Remorseless"`, `"Relentless"` or `"Terrifying"`.
|
||||||
|
- `skillset`: Level skillset, as listed on the sheet.
|
||||||
|
- `enjoyment`: Level enjoyment sampled from EDEL as a string. Can be `"N/A"`, look out!
|
||||||
|
|
||||||
|
#### `IDSLevel`: [`Level`](#level)
|
||||||
|
|
||||||
|
Represents an IDS level.
|
||||||
|
|
||||||
|
- `tier`: `"Fuck"`, `"Beginner"`, `"Easy"`, `"Medium"`, `"Hard"`, `"Very Hard"`, `"Insane"` or `"Extreme"`.
|
||||||
|
- `skillset`: Level skillset, as listed on the sheet.
|
||||||
|
- `broken`: If the level is broken in 2.2. `"no"`, `"yes"`, or rarely `null` if unknown.
|
|
@ -0,0 +1,102 @@
|
||||||
|
import { GoogleSpreadsheet } from 'google-spreadsheet';
|
||||||
|
|
||||||
|
const IDS_ID = '15ehtAIpCR8s04qIb8zij9sTpUdGJbmAE_LDcfVA3tcU';
|
||||||
|
const IDS_REGULAR_LEVELS_ID = 1309758655;
|
||||||
|
|
||||||
|
const fruityLevels = {
|
||||||
|
'The Place': 'The Place',
|
||||||
|
'\'10': '10',
|
||||||
|
'(m)ORBJECT': 'ORBJECT',
|
||||||
|
'Dark matter (Real)': 'Dark matter',
|
||||||
|
'Versus... MR. BEAST!!!': 'Vers', // ???
|
||||||
|
};
|
||||||
|
|
||||||
|
// horrid. thank you ids
|
||||||
|
const colors = {
|
||||||
|
'0,0,0': { tier: 'Fuck', broken: 'no' },
|
||||||
|
'74,134,232': { tier: 'Beginner', broken: 'no' },
|
||||||
|
'28,69,135': { tier: 'Beginner', broken: 'yes' },
|
||||||
|
'7,55,99': { tier: 'Beginner', broken: 'yes' }, // ????????
|
||||||
|
'0,255,255': { tier: 'Easy', broken: 'no' },
|
||||||
|
'0,171,171': { tier: 'Easy', broken: 'yes' },
|
||||||
|
'0,255,0': { tier: 'Medium', broken: 'no' },
|
||||||
|
'0,154,0': { tier: 'Medium', broken: 'yes' },
|
||||||
|
'255,255,0': { tier: 'Hard', broken: 'no' },
|
||||||
|
'191,144,0': { tier: 'Hard', broken: 'yes' },
|
||||||
|
// there's also a few 0,0,0s in very hard because i guess the IDS sheet editor like causing pain onto people like me
|
||||||
|
'255,153,0': { tier: 'Very Hard', broken: 'no' },
|
||||||
|
'180,95,6': { tier: 'Very Hard', broken: 'yes' },
|
||||||
|
'255,0,0': { tier: 'Insane', broken: 'no' },
|
||||||
|
'153,0,0': { tier: 'Insane', broken: 'yes' },
|
||||||
|
'255,0,255': { tier: 'Extreme', broken: 'no' },
|
||||||
|
'161,0,161': { tier: 'Extreme', broken: 'yes' },
|
||||||
|
'153,0,255': { tier: 'Extreme', broken: 'no' }, // ????
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchRegularCells(sheet) {
|
||||||
|
console.log('fetching cells');
|
||||||
|
// we need this for formatting :)
|
||||||
|
await sheet.loadCells('A:B');
|
||||||
|
|
||||||
|
let levels = [];
|
||||||
|
let tiers = [];
|
||||||
|
let broken = [];
|
||||||
|
|
||||||
|
let rowID = 1;
|
||||||
|
let previousTier = 'penis tier :33';
|
||||||
|
while (true) {
|
||||||
|
const cell = sheet.getCell(rowID, 0);
|
||||||
|
if (!cell || cell == null || cell.value == null) break;
|
||||||
|
const color = cell.backgroundColor || {};
|
||||||
|
const colorString = Math.round((color.red || 0) * 255) + ',' + Math.round((color.green || 0) * 255) + ',' + Math.round((color.blue || 0) * 255);
|
||||||
|
let tier = colors[colorString];
|
||||||
|
if (!tier) tier = { tier: previousTier };
|
||||||
|
if (rowID > 50 && tier.tier === 'Fuck') tier = { tier: previousTier, broken: 'no' };
|
||||||
|
levels.push(cell.value.toString())
|
||||||
|
tiers.push(tier.tier);
|
||||||
|
broken.push(tier.broken || null);
|
||||||
|
previousTier = tier.tier;
|
||||||
|
rowID++;
|
||||||
|
}
|
||||||
|
|
||||||
|
const creators = await sheet.getCellsInRange('C2:C');
|
||||||
|
const skillsets = await sheet.getCellsInRange('E2:E');
|
||||||
|
const descriptions = await sheet.getCellsInRange('F2:F');
|
||||||
|
|
||||||
|
return { levels, tiers, broken, creators, skillsets, descriptions };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLevels(sheet) {
|
||||||
|
const { levels, tiers, broken, creators, skillsets, descriptions } = await fetchRegularCells(sheet);
|
||||||
|
|
||||||
|
let levelObjs = [];
|
||||||
|
|
||||||
|
for (let i in levels) {
|
||||||
|
const level = levels[i], tier = tiers[i], broke = broken[i], creator = creators[i], skillset = skillsets[i], description = descriptions[i];
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
sheetIndex: parseInt(i) + 1,
|
||||||
|
tier: tier,
|
||||||
|
name: fruityLevels[level] || level || "",
|
||||||
|
creator: (creator && creator[0] || "").trim(),
|
||||||
|
skillset: (skillset && skillset[0] || "").trim(),
|
||||||
|
description: (description && description[0] || "").trim(),
|
||||||
|
broken: broke,
|
||||||
|
};
|
||||||
|
|
||||||
|
levelObjs.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return levelObjs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = new GoogleSpreadsheet(IDS_ID, { apiKey: process.env.API_KEY });
|
||||||
|
await doc.loadInfo(); // loads document properties and worksheets
|
||||||
|
console.log('loaded IDS spreadsheet');
|
||||||
|
|
||||||
|
export async function fetchAllLevels() {
|
||||||
|
console.log('fetching IDS levels');
|
||||||
|
return {
|
||||||
|
regular: await fetchLevels(doc.sheetsById[IDS_REGULAR_LEVELS_ID]),
|
||||||
|
};
|
||||||
|
}
|
126
index.js
126
index.js
|
@ -1,102 +1,26 @@
|
||||||
import { GoogleSpreadsheet } from 'google-spreadsheet';
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
|
import { fetchAllLevels as fetchNLWLevels } from './nlw.js';
|
||||||
|
import { fetchAllLevels as fetchIDSLevels } from './ids.js';
|
||||||
|
|
||||||
const doc = new GoogleSpreadsheet('1YxUE2kkvhT2E6AjnkvTf-o8iu_shSLbuFkEFcZOvieA', { apiKey: process.env.API_KEY });
|
let levels = {
|
||||||
|
nlw: {
|
||||||
const fruityLevels = {
|
regular: [],
|
||||||
'Zur🅱️': 'Zurb',
|
pending: [],
|
||||||
'Violently X𝕏': 'Violently X',
|
platformer: [],
|
||||||
'🎄 Shock Therapy 🎄': 'Shock Therapy',
|
},
|
||||||
'Collect All Pets 🦝': 'Collect All Pets',
|
ids: {
|
||||||
'Virtual Stigmata 🌚': 'Virtual Stigmata',
|
regular: [],
|
||||||
' Dimensional Breaking': 'Dimensional Breaking',
|
},
|
||||||
'Graphite Wordle': 'Graphite World',
|
|
||||||
'Matilda tha Machine': 'Matilda the Machine',
|
|
||||||
'Missing Benefi s': 'Missing Benefits',
|
|
||||||
'troll levle': 'troll level',
|
|
||||||
'gardening map': 'gardening map ',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
await doc.loadInfo(); // loads document properties and worksheets
|
async function fetchSheets() {
|
||||||
console.log(doc.title);
|
const nlw = await fetchNLWLevels();
|
||||||
|
const ids = await fetchIDSLevels();
|
||||||
async function fetchRegularCells(sheet) {
|
levels = { nlw, ids };
|
||||||
const levels = await sheet.getCellsInRange('A:A');
|
|
||||||
const creators = await sheet.getCellsInRange('B:B');
|
|
||||||
const skillsets = await sheet.getCellsInRange('D:D');
|
|
||||||
const enjoyments = await sheet.getCellsInRange('E:E');
|
|
||||||
const descriptions = await sheet.getCellsInRange('F:F');
|
|
||||||
return { levels, creators, skillsets, enjoyments, descriptions };
|
|
||||||
}
|
|
||||||
async function fetchPlatformerCells(sheet) {
|
|
||||||
const levels = await sheet.getCellsInRange('A:A');
|
|
||||||
const creators = await sheet.getCellsInRange('B:B');
|
|
||||||
const skillsets = await sheet.getCellsInRange('D:D');
|
|
||||||
const enjoyments = await sheet.getCellsInRange('E:E');
|
|
||||||
const descriptions = await sheet.getCellsInRange('F:F');
|
|
||||||
return { levels, creators, skillsets, enjoyments, descriptions };
|
|
||||||
}
|
|
||||||
async function fetchPendingCells(sheet) {
|
|
||||||
const levels = await sheet.getCellsInRange('B:B');
|
|
||||||
const creators = await sheet.getCellsInRange('C:C');
|
|
||||||
const skillsets = await sheet.getCellsInRange('E:E');
|
|
||||||
const enjoyments = [];
|
|
||||||
const descriptions = await sheet.getCellsInRange('F:F');
|
|
||||||
return { levels, creators, skillsets, enjoyments, descriptions };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchLevels(sheet, platformer, pending) {
|
await fetchSheets();
|
||||||
const { levels, creators, skillsets, enjoyments, descriptions } = await (
|
setInterval(fetchSheets, 1000 * 60 * 60);
|
||||||
pending ? fetchPendingCells(sheet) : (platformer ? fetchPlatformerCells(sheet) : fetchRegularCells(sheet))
|
|
||||||
);
|
|
||||||
|
|
||||||
let levelObjs = [];
|
|
||||||
|
|
||||||
let lastTier = null;
|
|
||||||
for (let i in levels) {
|
|
||||||
const level = levels[i], creator = creators[i], skillset = skillsets[i], enjoyment = enjoyments[i], description = descriptions[i];
|
|
||||||
|
|
||||||
if (level.length === 0 || level[0] === '') break;
|
|
||||||
|
|
||||||
if (level[0].startsWith('| ')) {
|
|
||||||
lastTier = level[0].slice(2).replace(' Tier', '').trim();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!lastTier) continue;
|
|
||||||
if (lastTier === 'Super Terrifying') break;
|
|
||||||
|
|
||||||
const enjoymentStr = enjoyment && enjoyment[0];
|
|
||||||
const enjoymentParse = parseFloat(enjoymentStr || "");
|
|
||||||
|
|
||||||
const obj = {
|
|
||||||
sheetIndex: parseInt(i),
|
|
||||||
tier: lastTier,
|
|
||||||
name: fruityLevels[level[0]] || level[0] || "",
|
|
||||||
creator: creator && creator[0] || "",
|
|
||||||
skillset: skillset && skillset[0] || "",
|
|
||||||
enjoyment: !isNaN(enjoymentParse) ? enjoymentParse : null,
|
|
||||||
description: description && description[0] || "",
|
|
||||||
};
|
|
||||||
|
|
||||||
levelObjs.push(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
return levelObjs;
|
|
||||||
}
|
|
||||||
|
|
||||||
let regularLevels;
|
|
||||||
let pendingLevels;
|
|
||||||
let platformerLevels;
|
|
||||||
|
|
||||||
async function fetchAllLevels() {
|
|
||||||
regularLevels = await fetchLevels(doc.sheetsById[0], false, false);
|
|
||||||
pendingLevels = await fetchLevels(doc.sheetsById[1134134033], false, true);
|
|
||||||
platformerLevels = await fetchLevels(doc.sheetsById[339121001], true, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
await fetchAllLevels();
|
|
||||||
setInterval(fetchAllLevels, 1000 * 60 * 60);
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
@ -109,16 +33,16 @@ app.get('/list', (req, res) => {
|
||||||
|
|
||||||
let list;
|
let list;
|
||||||
if (!type || type === '' || type === 'regular') {
|
if (!type || type === '' || type === 'regular') {
|
||||||
list = regularLevels;
|
list = levels.nlw.regular;
|
||||||
} else if (type === 'platformer') {
|
} else if (type === 'platformer') {
|
||||||
list = platformerLevels;
|
list = levels.nlw.platformer;
|
||||||
} else if (type === 'pending') {
|
} else if (type === 'pending') {
|
||||||
list = pendingLevels;
|
list = levels.nlw.pending;
|
||||||
} else if (type === 'all') {
|
} else if (type === 'all') {
|
||||||
return res.json([
|
return res.json([
|
||||||
...regularLevels.map(l => ({ type: 'regular', ...l })),
|
...levels.nlw.regular.map(l => ({ type: 'regular', ...l })),
|
||||||
...platformerLevels.map(l => ({ type: 'platformer', ...l })),
|
...levels.nlw.platformer.map(l => ({ type: 'platformer', ...l })),
|
||||||
...pendingLevels.map(l => ({ type: 'pending', ...l })),
|
...levels.nlw.pending.map(l => ({ type: 'pending', ...l })),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
return res.status(400);
|
return res.status(400);
|
||||||
|
@ -127,6 +51,10 @@ app.get('/list', (req, res) => {
|
||||||
res.json(list);
|
res.json(list);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/ids', (req, res) => {
|
||||||
|
res.json(levels.ids.regular);
|
||||||
|
});
|
||||||
|
|
||||||
const port = process.env.PORT || 8080
|
const port = process.env.PORT || 8080
|
||||||
app.listen(port);
|
app.listen(port);
|
||||||
console.log(`lisening on port ${port}`);
|
console.log(`lisening on port ${port}`);
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { GoogleSpreadsheet } from 'google-spreadsheet';
|
||||||
|
|
||||||
|
const NLW_ID = '1YxUE2kkvhT2E6AjnkvTf-o8iu_shSLbuFkEFcZOvieA';
|
||||||
|
const NLW_REGULAR_LEVELS_ID = 0;
|
||||||
|
const NLW_PENDING_LEVELS_ID = 1134134033;
|
||||||
|
const NLW_PLATFORMER_LEVELS_ID = 339121001;
|
||||||
|
|
||||||
|
const fruityLevels = {
|
||||||
|
'Zur🅱️': 'Zurb',
|
||||||
|
'Violently X𝕏': 'Violently X',
|
||||||
|
'🎄 Shock Therapy 🎄': 'Shock Therapy',
|
||||||
|
'Collect All Pets 🦝': 'Collect All Pets',
|
||||||
|
'Virtual Stigmata 🌚': 'Virtual Stigmata',
|
||||||
|
' Dimensional Breaking': 'Dimensional Breaking',
|
||||||
|
'Graphite Wordle': 'Graphite World',
|
||||||
|
'Matilda tha Machine': 'Matilda the Machine',
|
||||||
|
'Missing Benefi s': 'Missing Benefits',
|
||||||
|
'troll levle': 'troll level',
|
||||||
|
'gardening map': 'gardening map ',
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fetchRegularCells(sheet) {
|
||||||
|
console.log('fetching regular cells');
|
||||||
|
const levels = await sheet.getCellsInRange('A:A');
|
||||||
|
const creators = await sheet.getCellsInRange('B:B');
|
||||||
|
const skillsets = await sheet.getCellsInRange('D:D');
|
||||||
|
const enjoyments = await sheet.getCellsInRange('E:E');
|
||||||
|
const descriptions = await sheet.getCellsInRange('F:F');
|
||||||
|
return { levels, creators, skillsets, enjoyments, descriptions };
|
||||||
|
}
|
||||||
|
async function fetchPlatformerCells(sheet) {
|
||||||
|
console.log('fetching platformer cells');
|
||||||
|
const levels = await sheet.getCellsInRange('A:A');
|
||||||
|
const creators = await sheet.getCellsInRange('B:B');
|
||||||
|
const skillsets = await sheet.getCellsInRange('D:D');
|
||||||
|
const enjoyments = await sheet.getCellsInRange('E:E');
|
||||||
|
const descriptions = await sheet.getCellsInRange('F:F');
|
||||||
|
return { levels, creators, skillsets, enjoyments, descriptions };
|
||||||
|
}
|
||||||
|
async function fetchPendingCells(sheet) {
|
||||||
|
console.log('fetching pending cells');
|
||||||
|
const levels = await sheet.getCellsInRange('B:B');
|
||||||
|
const creators = await sheet.getCellsInRange('C:C');
|
||||||
|
const skillsets = await sheet.getCellsInRange('E:E');
|
||||||
|
const enjoyments = [];
|
||||||
|
const descriptions = await sheet.getCellsInRange('F:F');
|
||||||
|
return { levels, creators, skillsets, enjoyments, descriptions };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchLevels(sheet, platformer, pending) {
|
||||||
|
const { levels, creators, skillsets, enjoyments, descriptions } = await (
|
||||||
|
pending ? fetchPendingCells(sheet) : (platformer ? fetchPlatformerCells(sheet) : fetchRegularCells(sheet))
|
||||||
|
);
|
||||||
|
|
||||||
|
let levelObjs = [];
|
||||||
|
|
||||||
|
let lastTier = null;
|
||||||
|
for (let i in levels) {
|
||||||
|
const level = levels[i], creator = creators[i], skillset = skillsets[i], enjoyment = enjoyments[i], description = descriptions[i];
|
||||||
|
|
||||||
|
if (level.length === 0 || level[0] === '') break;
|
||||||
|
|
||||||
|
if (level[0].startsWith('| ')) {
|
||||||
|
lastTier = level[0].slice(2).replace(' Tier', '').trim();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lastTier) continue;
|
||||||
|
if (lastTier === 'Super Terrifying') break;
|
||||||
|
|
||||||
|
const enjoymentStr = enjoyment && enjoyment[0];
|
||||||
|
const enjoymentParse = parseFloat(enjoymentStr || "");
|
||||||
|
|
||||||
|
const obj = {
|
||||||
|
sheetIndex: parseInt(i),
|
||||||
|
tier: lastTier,
|
||||||
|
name: fruityLevels[level[0]] || level[0] || "",
|
||||||
|
creator: (creator && creator[0] || "").trim(),
|
||||||
|
skillset: (skillset && skillset[0] || "").trim(),
|
||||||
|
enjoyment: !isNaN(enjoymentParse) ? enjoymentParse : null,
|
||||||
|
description: (description && description[0] || "").trim(),
|
||||||
|
};
|
||||||
|
|
||||||
|
levelObjs.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return levelObjs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = new GoogleSpreadsheet(NLW_ID, { apiKey: process.env.API_KEY });
|
||||||
|
await doc.loadInfo(); // loads document properties and worksheets
|
||||||
|
console.log('loaded NLW spreadsheet');
|
||||||
|
|
||||||
|
export async function fetchAllLevels() {
|
||||||
|
console.log('fetching NLW levels');
|
||||||
|
return {
|
||||||
|
regular: await fetchLevels(doc.sheetsById[NLW_REGULAR_LEVELS_ID], false, false),
|
||||||
|
pending: await fetchLevels(doc.sheetsById[NLW_PENDING_LEVELS_ID], false, true),
|
||||||
|
platformer: await fetchLevels(doc.sheetsById[NLW_PLATFORMER_LEVELS_ID], true, false),
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue