feat: Add support to specify year

+ Did some cleanup for the whole code

Signed-off-by: Avior <f.bouillon@aptatio.com>
This commit is contained in:
Florian Bouillon 2022-12-07 15:26:49 +01:00
parent abfd6d8445
commit 9a8cb6dff4
Signed by: Florian Bouillon
GPG Key ID: E05B3A94178D3A7C
9 changed files with 2834 additions and 2732 deletions

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

1
.gitignore vendored
View File

@ -1 +1,2 @@
node_modules node_modules
dist/

127
3d.scad
View File

@ -1,5 +1,6 @@
// -- TO CHANGE START -- // /*- TO CHANGE START */
// do not chang it yourself
// Number of XPs in a day for each months
jan = [6433, 12574, 12013, 14896, 6242, 12306, 11732, 22842, 25780, 7375, 1030, 2676, 8857, 7948, 11429, 21389, 13025, 5335, 8942, 4457, 14171, 9951, 10766, 7780, 29865, 24567, 7702, 24382, 17697, 12100, 16698]; jan = [6433, 12574, 12013, 14896, 6242, 12306, 11732, 22842, 25780, 7375, 1030, 2676, 8857, 7948, 11429, 21389, 13025, 5335, 8942, 4457, 14171, 9951, 10766, 7780, 29865, 24567, 7702, 24382, 17697, 12100, 16698];
feb = [21093, 2887, 10773, 10345, 5897, 6255, 20849, 17071, 17168, 8504, 27323, 33772, 16624, 16915, 14535, 10766, 24339, 14177, 24986, 13760, 47418, 59739, 19918, 12892, 20671, 13143, 9618, 9389, 0, 0]; feb = [21093, 2887, 10773, 10345, 5897, 6255, 20849, 17071, 17168, 8504, 27323, 33772, 16624, 16915, 14535, 10766, 24339, 14177, 24986, 13760, 47418, 59739, 19918, 12892, 20671, 13143, 9618, 9389, 0, 0];
mar = [23643, 29897, 23874, 26867, 20092, 8363, 6073, 10425, 26663, 111087, 30647, 19681, 19344, 15507, 17636, 12541, 7369, 17568, 17077, 15472, 9506, 6338, 8851, 21544, 24243, 7023, 5401, 7157, 20584, 23584, 16505]; mar = [23643, 29897, 23874, 26867, 20092, 8363, 6073, 10425, 26663, 111087, 30647, 19681, 19344, 15507, 17636, 12541, 7369, 17568, 17077, 15472, 9506, 6338, 8851, 21544, 24243, 7023, 5401, 7157, 20584, 23584, 16505];
@ -12,76 +13,108 @@ sep = [8156, 14355, 7410, 7958, 5638, 1974, 7015, 3924, 559, 8996, 19943, 28816,
oct = [7109, 11067, 13580, 24341, 10774, 11742, 19973, 23626, 30664, 9417, 16783, 9260, 14108, 9489, 4830, 28002, 21885, 16954, 15868, 16902, 19634, 22134, 24960, 11625, 10090, 13084, 20795, 8160, 11223, 5578, 1741]; oct = [7109, 11067, 13580, 24341, 10774, 11742, 19973, 23626, 30664, 9417, 16783, 9260, 14108, 9489, 4830, 28002, 21885, 16954, 15868, 16902, 19634, 22134, 24960, 11625, 10090, 13084, 20795, 8160, 11223, 5578, 1741];
nov = [0, 13537, 30169, 11276, 6862, 13438, 10021, 14033, 11938, 10941, 3634, 23317, 33661, 22821, 30040, 13944, 13980, 10466, 8872, 9020, 10166, 37281, 31896, 15089, 15184, 14277, 8070, 13321, 11396, 9223]; nov = [0, 13537, 30169, 11276, 6862, 13438, 10021, 14033, 11938, 10941, 3634, 23317, 33661, 22821, 30040, 13944, 13980, 10466, 8872, 9020, 10166, 37281, 31896, 15089, 15184, 14277, 8070, 13321, 11396, 9223];
dec = [8826, 11600, 5676, 8647, 8214, 26350, 12609, 9070, 21900, 17233, 11803, 17161, 12652, 2534, 5066, 26487, 27623, 21798, 17840, 11337, 3662, 9396, 16589, 242, 0, 0, 2674, 1892, 98, 10846, 9384]; dec = [8826, 11600, 5676, 8647, 8214, 26350, 12609, 9070, 21900, 17233, 11803, 17161, 12652, 2534, 5066, 26487, 27623, 21798, 17840, 11337, 3662, 9396, 16589, 242, 0, 0, 2674, 1892, 98, 10846, 9384];
// the maximum number of XPs in a day
max = 111087; max = 111087;
// the minimum number of XPs in a day
min = 0; min = 0;
// Left aligned text for the username
text = "Aviortheking"; text = "Aviortheking";
// -- TO CHANGE END -- //
// Right aligned text for the year
year = "2021";
/* TO CHANGE END -*/
// Define the maximum height
maxHeight = 100; maxHeight = 100;
// Define the spacing between values
spacing = 0; spacing = 0;
socleHeight = 20; // Define the base height
textHeight = 10; baseHeight = 20;
// Define the text height
textHeight = baseHeight / 2;
// define the minimum value to be before displaying
minValue = 0; minValue = 0;
barSize = 10;
// 33 = 31 days + 2 border)
baseWidth = barSize * 33;
// 14 = 12 months + 2 border
baseLength = barSize * 14;
// Function that generate a whole month
module generateMonth(month, offset = 0) { module generateMonth(month, offset = 0) {
// loop through each days for the month
for (index = [0 : len(month) -1 ]) { for (index = [0 : len(month) -1 ]) {
// get the XP
it = month[index]; it = month[index];
translate([10 + (10 + spacing) * index,10 + (10 + spacing) * offset, socleHeight - 1]) {
cube([10,10,it < minValue ? 0 : it * maxHeight / max + 1]); // Render the bar
translate([
barSize + (barSize + spacing) * index,
barSize + (barSize + spacing) * offset,
baseHeight - 1 // put it in the bottom part to make sure they are one
]) {
cube([
barSize,
barSize,
it < minValue ? 0 : it * maxHeight / max + 1 // make it higher for the reason of the comment above
]);
} }
} }
} }
// merge everyting
union() { union() {
// remove part of bottom with text
difference() { difference() {
cube([330, 140, socleHeight]);
translate([5,1.9,(socleHeight - textHeight) / 2]) { cube([baseWidth, baseLength, baseHeight]);
rotate([90,0,0]) {
// if year is specified
if (len(year) > 0) {
// move the year to the right
translate([baseWidth - 5, 1.9, (baseHeight - textHeight) / 2]) {
// rotate it
rotate([90, 0, 0]) {
// extrude the base
linear_extrude(2)
text(year, size=textHeight, halign="right");
}
}
}
// move the username to the left
translate([5, 1.9, (baseHeight - textHeight) / 2]) {
// rotate it
rotate([90, 0, 0]) {
// extrude the base
linear_extrude(2) linear_extrude(2)
text(text, size=textHeight); text(text, size=textHeight);
} }
} }
} }
for (monthIndex = [ 0 : 11 ]) { // Generate each months :D
if (monthIndex == 0) { months = [jan, feb, mar, apr, may, jun, jul, aug, sep, oct, nov, dec];
generateMonth(jan, monthIndex);
}
if (monthIndex == 1) {
generateMonth(feb, monthIndex);
}
if (monthIndex == 2) {
generateMonth(mar, monthIndex);
}
if (monthIndex == 3) {
generateMonth(apr, monthIndex);
}
if (monthIndex == 4) {
generateMonth(may, monthIndex);
}
if (monthIndex == 5) {
generateMonth(jun, monthIndex);
}
if (monthIndex == 6) {
generateMonth(jul, monthIndex);
}
if (monthIndex == 7) {
generateMonth(aug, monthIndex);
}
if (monthIndex == 8) {
generateMonth(sep, monthIndex);
}
if (monthIndex == 9) {
generateMonth(oct, monthIndex);
}
if (monthIndex == 10) { for (monthIndex = [ 0 : 11 ]) {
generateMonth(nov, monthIndex); generateMonth(months[monthIndex], monthIndex);
}
if (monthIndex == 11) {
generateMonth(dec, monthIndex);
}
} }
} }

View File

@ -3,12 +3,12 @@
######### #########
# openscad doesn't seems to work on Alpine Linux :( # openscad doesn't seems to work on Alpine Linux :(
# FROM docker.io/node:alpine as IMAGE # FROM docker.io/node:alpine as IMAGE
FROM docker.io/node:latest as IMAGE FROM docker.io/node:slim as IMAGE
# External deps (for node-gyp add: "python3 make g++") # External deps (for node-gyp add: "python3 make g++")
# git is used to install the npm packages with git deps # git is used to install the npm packages with git deps
RUN apt-get update &&\ RUN apt-get update &&\
apt-get install git openscad -y apt-get install openscad -y
# run as non root user # run as non root user
USER node USER node

View File

@ -20,6 +20,18 @@ https://codestats-skyline.avior.me/?username=Aviortheking
It will generate and allows you to download the generated STL! It will generate and allows you to download the generated STL!
### render a specific year
You can also render a specific year!
simply add the year to the URL
```
https://codestats-skyline.avior.me/?username=Aviortheking&year=2021
```
and voilà!
## Deploy the tool yourself! ## Deploy the tool yourself!
You can use the Dockerfile to deploy the tool yourself! You can use the Dockerfile to deploy the tool yourself!

View File

@ -5,22 +5,35 @@ import { generateThingy } from './main'
const app = express() const app = express()
app.get('/', async (req, res) => { app.get('/', async (req, res) => {
const { username } = req.query
// Fetch the query
const { username, year } = req.query as Record<string, string>
// if no username is sent in the request return this simple HTML lul
if (!username) { if (!username) {
res.status(400).send('Error: missing username parameter in query') res.status(400).send('Error: missing username parameter in query')
return return
} }
const bfr = await generateThingy(username as string) // transform the year intoo a number
const yearN = year ? isNaN(parseInt(year)) ? undefined : parseInt(year) : undefined
res.setHeader('Content-Disposition', `attachment; filename=${username}.stl`) // Generate the STL
res.status(200).send(bfr) const bfr = await generateThingy(username, yearN)
// force download the STL with a good name
res
.setHeader('Content-Disposition', `attachment; filename=${username}-${year ? year : 'full'}.stl`)
.status(200)
.send(bfr)
}) })
// Create the server
const server = http const server = http
.createServer(app) .createServer(app)
// start the server on the specified port or the 3000
const port = process.env.PORT ? parseInt(process.env.PORT) : 3000 const port = process.env.PORT ? parseInt(process.env.PORT) : 3000
server.listen(port, '0.0.0.0') server.listen(port, '0.0.0.0')
console.log(`Server started on port ${port}!`) console.log(`Server started on port ${port}!`)

69
main.ts
View File

@ -2,7 +2,7 @@ import { objectLoop, objectValues } from '@dzeio/object-util'
import fetch from 'node-fetch' import fetch from 'node-fetch'
import { promises as fs } from 'fs' import { promises as fs } from 'fs'
import { CodeStatsJSON } from './interfaces' import { CodeStatsJSON } from './interfaces'
// @ts-expect-error no typing for this lib // @ts-expect-error no typing available for this lib
import nodescad from 'nodescad' import nodescad from 'nodescad'
const months = [ const months = [
@ -30,14 +30,32 @@ async function exists(path: string) {
} }
} }
export const generateThingy = async (username: string) => { export const generateThingy = async (username: string, filterYear?: number): Promise<Buffer | null> => {
const res = await fetch(`https://codestats.net/api/users/${username}`) // Start generation
const now = new Date()
console.log(`Generating for ${username} (year = ${filterYear})`)
// Fetch from code::stats the informations (currently no cache is used, but is too much requests are sent one will be put)
const res = await fetch(`https://codestats.net/api/users/${username}`, {
headers: {
// Identify the software
'User-Agent': '@aviortheking/codestats-skyline'
}
})
if (res.status !== 200) {
console.error(`Could not generate STL, Code::Stats is being mean to me (statusCode: ${res.status})`)
return null
}
// Response from Code::Stats
const data = await res.json() as CodeStatsJSON const data = await res.json() as CodeStatsJSON
// object that will contain aggregated values
const correctedData: Record<string, Array<number>> = {} const correctedData: Record<string, Array<number>> = {}
// Fill the arrays
for (const month of months) { for (const month of months) {
if (month === '') { if (month === '') {
continue continue
@ -45,50 +63,58 @@ export const generateThingy = async (username: string) => {
correctedData[month] = Array(31).fill(0) correctedData[month] = Array(31).fill(0)
} }
// loop through each dates
objectLoop(data.dates, (value, key) => { objectLoop(data.dates, (value, key) => {
// get the year-month-day from the key
const [yearTxt, monthTxt, dayTxt] = key.split('-') const [yearTxt, monthTxt, dayTxt] = key.split('-')
// if we want to add a way to filter by year we can by using the `_` var const [year, month, day] = [parseInt(yearTxt), parseInt(monthTxt), parseInt(dayTxt)]
const [_, month, day] = [parseInt(yearTxt), parseInt(monthTxt), parseInt(dayTxt)]
if (!(months[month] in correctedData)) { // if there is year filtering and it is not the correct year, filter out
correctedData[months[month]] = [] if (filterYear && filterYear !== year) {
} return
if (!(day in correctedData[months[month]])) {
correctedData[months[month]][day] = 0
} }
// add XP points to the new object
correctedData[months[month]][day] += value correctedData[months[month]][day] += value
}) })
// get the maximum value from the list
const max = objectValues(correctedData).reduce((p, c) => { const max = objectValues(correctedData).reduce((p, c) => {
const monthMax = Math.max(...c) const monthMax = Math.max(...c.slice(1))
return monthMax > p ? monthMax : p return monthMax > p ? monthMax : p
}, 0) }, 0)
// get the minimum value from the list
const min = objectValues(correctedData).reduce((p, c) => { const min = objectValues(correctedData).reduce((p, c) => {
const monthMax = Math.min(...c) const monthMax = Math.min(...c.slice(1))
return monthMax < p ? monthMax : p return monthMax < p ? monthMax : p
}, 0) }, 0)
// prepare the file replacement
let final = '' let final = ''
// Add each months
objectLoop(correctedData, (value, key) => { objectLoop(correctedData, (value, key) => {
final += `${key} = [${value.slice(1).join(', ')}];\n` final += `${key} = [${value.slice(1).join(', ')}];\n`
}) })
// Add additionnal informations for generation
final += `max = ${max};\n` final += `max = ${max};\n`
final += `min = ${min};\n` final += `min = ${min};\n`
final += `text = "${username}";` final += `text = "${username}";\n`
final += `year = ${filterYear};\n`
console.log('Output:') // Fetch the CAD file
console.log(final)
// handle prod file
let cadPath = './3d.scad' let cadPath = './3d.scad'
if (!await exists(cadPath)) { if (!await exists(cadPath)) {
cadPath = '../3d.scad' cadPath = '../3d.scad'
} }
const file = await fs.readFile(cadPath, 'utf-8') const file = await fs.readFile(cadPath, 'utf-8')
const modified = file.replace(/\/\/.+\/\//gs, final)
// await fs.writeFile('output.scad', file.replace(/\/\/.+\/\//gs, final))
// Modify the file content with our previous values
const modified = file.replace(/\/\*-.+-\*\//gs, final)
// Render the file to an STL with OpenSCAD
const stl = await new Promise<any>((res, rej) => { const stl = await new Promise<any>((res, rej) => {
nodescad.render({ nodescad.render({
input: modified input: modified
@ -102,5 +128,8 @@ export const generateThingy = async (username: string) => {
}) })
}) })
console.log(`Finished generating for ${username} (year = ${filterYear}, timeToGenerate = ${new Date().getTime() - now.getTime()}ms)`)
// return the buffer
return stl.buffer as Buffer return stl.buffer as Buffer
} }

5302
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,17 +7,17 @@
"start": "node dist/index.js" "start": "node dist/index.js"
}, },
"dependencies": { "dependencies": {
"@dzeio/config": "^1.1.8",
"@dzeio/object-util": "^1.4.2", "@dzeio/object-util": "^1.4.2",
"express": "^4.18.2", "express": "^4.18.2",
"node-fetch": "^2", "node-fetch": "^2",
"nodescad": "^1.1.0", "nodescad": "^1.1.0"
"typescript": "^4.9.3"
}, },
"devDependencies": { "devDependencies": {
"@dzeio/config": "^1.1.8",
"@types/express": "^4.17.14", "@types/express": "^4.17.14",
"@types/node-fetch": "^2", "@types/node-fetch": "^2",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"ts-node-dev": "^2.0.0" "ts-node-dev": "^2.0.0",
"typescript": "^4.9.3"
} }
} }