This commit is contained in:
Guillaume Dorce 2024-10-10 23:02:50 +02:00
parent 05324771cf
commit b8cc774517
9 changed files with 1134 additions and 0 deletions

116
index.js Normal file
View File

@ -0,0 +1,116 @@
import express from "express";
import dotenv from "dotenv";
import path from "path";
const usernames = [
{name: "rubylium__", status: "off", id: ""},
{name: "lukkstv", status: "off", id: ""},
{name: "havvenfr", status: "off", id: ""},
{name: "olmanntv", status: "off", id: ""},
{name: "nina_asmr", status: "off", id: ""},
{name: "krw_aka", status: "off", id: ""},
{name: "leetgamer54", status: "off", id: ""},
{name: "coshiho", status: "off", id: ""},
{name: "soramicia", status: "off", id: ""},
{name: "entocraft", status: "off", id: ""},
{name: "steakfritees_", status: "off", id: ""},
{name: "dancyy_", status: "off", id: ""},
{name: "tchoupyy_", status: "off", id: ""},
{name: "thesollann", status: "off", id: ""},
];
dotenv.config();
const app = express();
const port = process.env.PORT || 3000;
let token = "";
let config = {};
const checkEnvVars = () => {
const requiredEnvVars = ["CLIENT_ID", "CLIENT_SECRET"];
let error = false;
requiredEnvVars.forEach((envVar) => {
if (process.env[envVar] === undefined) {
error = true;
console.log(`${envVar} is undefined`);
} else {
config[envVar] = process.env[envVar];
}
});
if (error) {
process.exit(1);
}
};
checkEnvVars();
function init() {
const url = `https://id.twitch.tv/oauth2/token?client_id=${config.CLIENT_ID}&client_secret=${config.CLIENT_SECRET}&grant_type=client_credentials`;
fetch(url, { method: "POST" })
.then((response) => response.json())
.then((data) => {
token = data.access_token;
getAllUsersStatus();
setInterval(getAllUsersStatus, 60000);
});
}
init();
async function getAllUsersStatus() {
for (let i = 0; i < usernames.length; i++) {
const userData = await getUserStatus(usernames[i].name, usernames[i].id);
usernames[i] = { ...usernames[i], ...userData };
if (i === usernames.length - 1) {
console.log("Successfully updated all users");
}
}
}
app.use("/api/status", api);
function api(req, res, next) {
res.json(usernames);
}
async function getUserStatus(username, id) {
const userData = {};
if (id === "") {
const userUrl = `https://api.twitch.tv/helix/users?login=${username}`;
const response = await fetch(userUrl, {
headers: {
Authorization: `Bearer ${token}`,
"Client-ID": config.CLIENT_ID,
},
});
const data = await response.json();
userData.id = data.data[0].id;
userData.profilePicture = data.data[0].profile_image_url;
id = data.data[0].id;
}
const streamUrl = `https://api.twitch.tv/helix/streams?user_id=${id}`;
const streamResponse = await fetch(streamUrl, {
headers: {
Authorization: `Bearer ${token}`,
"Client-ID": config.CLIENT_ID,
},
});
const streamData = await streamResponse.json();
if (streamData.data.length > 0) {
userData.status = "live";
} else {
userData.status = "offline";
}
return userData;
}
const staticDist = express.static(path.join(path.resolve(), "./public"));
app.use(staticDist);
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});

18
package.json Normal file
View File

@ -0,0 +1,18 @@
{
"name": "donatien",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"start": "node index.js"
},
"entry": "index.js",
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"dotenv": "^16.4.5",
"express": "^4.21.1"
}
}

485
pnpm-lock.yaml Normal file
View File

@ -0,0 +1,485 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
dotenv:
specifier: ^16.4.5
version: 16.4.5
express:
specifier: ^4.21.1
version: 4.21.1
packages:
/accepts@1.3.8:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'}
dependencies:
mime-types: 2.1.35
negotiator: 0.6.3
dev: false
/array-flatten@1.1.1:
resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==}
dev: false
/body-parser@1.20.3:
resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dependencies:
bytes: 3.1.2
content-type: 1.0.5
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
http-errors: 2.0.0
iconv-lite: 0.4.24
on-finished: 2.4.1
qs: 6.13.0
raw-body: 2.5.2
type-is: 1.6.18
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
dev: false
/call-bind@1.0.7:
resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==}
engines: {node: '>= 0.4'}
dependencies:
es-define-property: 1.0.0
es-errors: 1.3.0
function-bind: 1.1.2
get-intrinsic: 1.2.4
set-function-length: 1.2.2
dev: false
/content-disposition@0.5.4:
resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
engines: {node: '>= 0.6'}
dependencies:
safe-buffer: 5.2.1
dev: false
/content-type@1.0.5:
resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
engines: {node: '>= 0.6'}
dev: false
/cookie-signature@1.0.6:
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
dev: false
/cookie@0.7.1:
resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==}
engines: {node: '>= 0.6'}
dev: false
/debug@2.6.9:
resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.0.0
dev: false
/define-data-property@1.1.4:
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
engines: {node: '>= 0.4'}
dependencies:
es-define-property: 1.0.0
es-errors: 1.3.0
gopd: 1.0.1
dev: false
/depd@2.0.0:
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
engines: {node: '>= 0.8'}
dev: false
/destroy@1.2.0:
resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==}
engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16}
dev: false
/dotenv@16.4.5:
resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==}
engines: {node: '>=12'}
dev: false
/ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
dev: false
/encodeurl@1.0.2:
resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==}
engines: {node: '>= 0.8'}
dev: false
/encodeurl@2.0.0:
resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
engines: {node: '>= 0.8'}
dev: false
/es-define-property@1.0.0:
resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==}
engines: {node: '>= 0.4'}
dependencies:
get-intrinsic: 1.2.4
dev: false
/es-errors@1.3.0:
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
engines: {node: '>= 0.4'}
dev: false
/escape-html@1.0.3:
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
dev: false
/etag@1.8.1:
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
engines: {node: '>= 0.6'}
dev: false
/express@4.21.1:
resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==}
engines: {node: '>= 0.10.0'}
dependencies:
accepts: 1.3.8
array-flatten: 1.1.1
body-parser: 1.20.3
content-disposition: 0.5.4
content-type: 1.0.5
cookie: 0.7.1
cookie-signature: 1.0.6
debug: 2.6.9
depd: 2.0.0
encodeurl: 2.0.0
escape-html: 1.0.3
etag: 1.8.1
finalhandler: 1.3.1
fresh: 0.5.2
http-errors: 2.0.0
merge-descriptors: 1.0.3
methods: 1.1.2
on-finished: 2.4.1
parseurl: 1.3.3
path-to-regexp: 0.1.10
proxy-addr: 2.0.7
qs: 6.13.0
range-parser: 1.2.1
safe-buffer: 5.2.1
send: 0.19.0
serve-static: 1.16.2
setprototypeof: 1.2.0
statuses: 2.0.1
type-is: 1.6.18
utils-merge: 1.0.1
vary: 1.1.2
transitivePeerDependencies:
- supports-color
dev: false
/finalhandler@1.3.1:
resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==}
engines: {node: '>= 0.8'}
dependencies:
debug: 2.6.9
encodeurl: 2.0.0
escape-html: 1.0.3
on-finished: 2.4.1
parseurl: 1.3.3
statuses: 2.0.1
unpipe: 1.0.0
transitivePeerDependencies:
- supports-color
dev: false
/forwarded@0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'}
dev: false
/fresh@0.5.2:
resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==}
engines: {node: '>= 0.6'}
dev: false
/function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
dev: false
/get-intrinsic@1.2.4:
resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==}
engines: {node: '>= 0.4'}
dependencies:
es-errors: 1.3.0
function-bind: 1.1.2
has-proto: 1.0.3
has-symbols: 1.0.3
hasown: 2.0.2
dev: false
/gopd@1.0.1:
resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
dependencies:
get-intrinsic: 1.2.4
dev: false
/has-property-descriptors@1.0.2:
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
dependencies:
es-define-property: 1.0.0
dev: false
/has-proto@1.0.3:
resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==}
engines: {node: '>= 0.4'}
dev: false
/has-symbols@1.0.3:
resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==}
engines: {node: '>= 0.4'}
dev: false
/hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
dependencies:
function-bind: 1.1.2
dev: false
/http-errors@2.0.0:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
dependencies:
depd: 2.0.0
inherits: 2.0.4
setprototypeof: 1.2.0
statuses: 2.0.1
toidentifier: 1.0.1
dev: false
/iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
dependencies:
safer-buffer: 2.1.2
dev: false
/inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: false
/ipaddr.js@1.9.1:
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
engines: {node: '>= 0.10'}
dev: false
/media-typer@0.3.0:
resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
engines: {node: '>= 0.6'}
dev: false
/merge-descriptors@1.0.3:
resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==}
dev: false
/methods@1.1.2:
resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
engines: {node: '>= 0.6'}
dev: false
/mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
dev: false
/mime-types@2.1.35:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
dependencies:
mime-db: 1.52.0
dev: false
/mime@1.6.0:
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
engines: {node: '>=4'}
hasBin: true
dev: false
/ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: false
/ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: false
/negotiator@0.6.3:
resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
engines: {node: '>= 0.6'}
dev: false
/object-inspect@1.13.2:
resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==}
engines: {node: '>= 0.4'}
dev: false
/on-finished@2.4.1:
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
engines: {node: '>= 0.8'}
dependencies:
ee-first: 1.1.1
dev: false
/parseurl@1.3.3:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
dev: false
/path-to-regexp@0.1.10:
resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==}
dev: false
/proxy-addr@2.0.7:
resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
engines: {node: '>= 0.10'}
dependencies:
forwarded: 0.2.0
ipaddr.js: 1.9.1
dev: false
/qs@6.13.0:
resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==}
engines: {node: '>=0.6'}
dependencies:
side-channel: 1.0.6
dev: false
/range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}
dev: false
/raw-body@2.5.2:
resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==}
engines: {node: '>= 0.8'}
dependencies:
bytes: 3.1.2
http-errors: 2.0.0
iconv-lite: 0.4.24
unpipe: 1.0.0
dev: false
/safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
dev: false
/safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: false
/send@0.19.0:
resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==}
engines: {node: '>= 0.8.0'}
dependencies:
debug: 2.6.9
depd: 2.0.0
destroy: 1.2.0
encodeurl: 1.0.2
escape-html: 1.0.3
etag: 1.8.1
fresh: 0.5.2
http-errors: 2.0.0
mime: 1.6.0
ms: 2.1.3
on-finished: 2.4.1
range-parser: 1.2.1
statuses: 2.0.1
transitivePeerDependencies:
- supports-color
dev: false
/serve-static@1.16.2:
resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==}
engines: {node: '>= 0.8.0'}
dependencies:
encodeurl: 2.0.0
escape-html: 1.0.3
parseurl: 1.3.3
send: 0.19.0
transitivePeerDependencies:
- supports-color
dev: false
/set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
dependencies:
define-data-property: 1.1.4
es-errors: 1.3.0
function-bind: 1.1.2
get-intrinsic: 1.2.4
gopd: 1.0.1
has-property-descriptors: 1.0.2
dev: false
/setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
dev: false
/side-channel@1.0.6:
resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==}
engines: {node: '>= 0.4'}
dependencies:
call-bind: 1.0.7
es-errors: 1.3.0
get-intrinsic: 1.2.4
object-inspect: 1.13.2
dev: false
/statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
dev: false
/toidentifier@1.0.1:
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
engines: {node: '>=0.6'}
dev: false
/type-is@1.6.18:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
dependencies:
media-typer: 0.3.0
mime-types: 2.1.35
dev: false
/unpipe@1.0.0:
resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
engines: {node: '>= 0.8'}
dev: false
/utils-merge@1.0.1:
resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
engines: {node: '>= 0.4.0'}
dev: false
/vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: false

BIN
public/images/.DS_Store vendored Normal file

Binary file not shown.

BIN
public/images/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

52
public/index.html Normal file
View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta content="donathon interface" property="og:title" />
<meta content="Interface pour le planning du Donathon" property="og:description" />
<meta content="https://able.moe/" property="og:url" />
<meta content="https://able.moe/images/literallyme.png" property="og:image" />
<meta content="#e858de" data-react-helmet="true" name="theme-color" />
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/jpg" href="images/favicon.png">
<link rel="stylesheet" href="styles.css">
<title>interface dnth</title>
</head>
<body>
<header>
</header>
<main>
<style>
.live-indicator { display: none; }
</style>
<div></div>
<div id="streamersContainer">
<h2>En ligne</h2>
<span class="small" id="onlineCount"></span>
<div id="onlineStreamers" class="streamers-category">
<span class="loader" id="loader1"></span>
<span class="loadertext" id="onlineLoaderText">Le premier chargement peut être long...</span>
</div>
<br>
<h2>Hors ligne</h2> <span class="small" id="offlineCount"></span>
<div id="offlineStreamers" class="streamers-category">
<span class="loader" id="loader2"></span>
</div>
</div>
<div></div>
</main>
<script src="scripts/infos.js"></script>
<script src="scripts/twitch.js"></script>
</body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</html>

245
public/scripts/infos.js Normal file
View File

@ -0,0 +1,245 @@
// Fonction pour obtenir l'access token Twitch
async function getAccessToken() {
const clientId = 'z2tcrq9oeynzf4ijgzhv6br5feoprr';
const clientSecret = 'ybm8e512lrsylqqkl78ow08nv3jgx9';
const url = `https://id.twitch.tv/oauth2/token?client_id=${clientId}&client_secret=${clientSecret}&grant_type=client_credentials`;
const response = await fetch(url, { method: 'POST' });
const data = await response.json();
return data.access_token;
}
// Fonction pour obtenir les informations utilisateur et le statut du stream
async function getUserInfoAndStreamStatus(username, accessToken, clientId) {
const userUrl = `https://api.twitch.tv/helix/users?login=${username}`;
const userResponse = await fetch(userUrl, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Client-ID': clientId,
},
});
if (userResponse.status !== 200) {
console.error('Erreur lors de la récupération des informations utilisateur.');
return null;
}
const userData = await userResponse.json();
if (userData.data.length === 0) {
console.error('Utilisateur non trouvé.');
return null;
}
const userId = userData.data[0].id;
const profileImageUrl = userData.data[0].profile_image_url;
const displayName = userData.data[0].display_name;
const streamUrl = `https://api.twitch.tv/helix/streams?user_id=${userId}`;
const streamResponse = await fetch(streamUrl, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'Client-ID': clientId,
},
});
const streamData = await streamResponse.json();
let streamType = 'offline';
if (streamData.data.length > 0) {
streamType = streamData.data[0].type;
}
return {
profileImageUrl,
displayName,
streamType,
};
}
// Lire le fichier streamers.json depuis le localStorage
function readStreamersFile() {
const fileContent = localStorage.getItem('streamers');
return fileContent ? JSON.parse(fileContent) : { streamers: [] };
}
// Sauvegarder dans streamers.json dans le localStorage
function saveStreamersFile(streamersData) {
localStorage.setItem('streamers', JSON.stringify(streamersData, null, 2));
}
// Vérifier et ajouter les streamers manquants
async function updateMissingStreamers(usernames) {
let streamersData = readStreamersFile();
const currentStreamers = streamersData.streamers.map(streamer => streamer.username);
const missingUsernames = usernames.filter(username => !currentStreamers.includes(username));
if (missingUsernames.length === 0) {
console.log("Tous les streamers sont déjà présents.");
return;
}
console.log(`Les streamers suivants sont manquants: ${missingUsernames.join(', ')}`);
const accessToken = await getAccessToken();
const clientId = 'z2tcrq9oeynzf4ijgzhv6br5feoprr';
for (const username of missingUsernames) {
const data = await getUserInfoAndStreamStatus(username, accessToken, clientId);
if (data) {
streamersData.streamers.push({
username: username,
displayName: data.displayName,
profileImageUrl: data.profileImageUrl,
streamType: data.streamType
});
}
}
saveStreamersFile(streamersData);
console.log("Les streamers manquants ont été ajoutés.");
}
// Fonction pour afficher les streamers
async function displayStreamers(usernames) {
const accessToken = await getAccessToken();
const clientId = 'z2tcrq9oeynzf4ijgzhv6br5feoprr';
const streamersData = readStreamersFile();
const onlineStreamers = [];
const offlineStreamers = [];
for (const username of usernames) {
const streamerInfo = streamersData.streamers.find(streamer => streamer.username === username);
if (streamerInfo && streamerInfo.streamType === 'live') {
onlineStreamers.push(streamerInfo);
} else {
offlineStreamers.push(streamerInfo);
}
}
// Trier les streamers par nom
onlineStreamers.sort((a, b) => a.displayName.localeCompare(b.displayName));
offlineStreamers.sort((a, b) => a.displayName.localeCompare(b.displayName));
// Mettre à jour le nombre de streamers en ligne et hors ligne
document.getElementById('onlineCount').textContent = onlineStreamers.length;
document.getElementById('offlineCount').textContent = offlineStreamers.length;
const onlineContainer = document.getElementById('onlineStreamers');
const offlineContainer = document.getElementById('offlineStreamers');
// Réinitialiser les conteneurs
onlineContainer.innerHTML = '';
offlineContainer.innerHTML = '';
if (onlineStreamers.length > 0) {
// Supprimer le texte de chargement
const loaderText = document.getElementById('loadertext');
if (loaderText) {
loaderText.style.display = 'none';
}
} else {
// Ajouter le texte de chargement si aucun streamer en ligne
const loaderText = document.getElementById('loadertext');
if (loaderText) {
loaderText.textContent = "Il n'y a aucun streamer en ligne actuellement";
loaderText.style.display = 'block';
}
}
onlineStreamers.forEach(data => {
const profileLink = `https://twitch.tv/${data.displayName}`;
const image = document.createElement('img');
image.src = data.profileImageUrl;
image.alt = data.displayName;
image.className = 'streamer-image';
const liveIndicator = document.createElement('div');
liveIndicator.className = 'live-indicator';
liveIndicator.style.display = 'block';
const name = document.createElement('div');
name.className = 'streamer-name';
name.textContent = data.displayName;
const link = document.createElement('a');
link.href = profileLink;
link.target = '_blank';
link.appendChild(image);
link.appendChild(liveIndicator);
const infoDiv = document.createElement('div');
infoDiv.className = 'streamer-info';
infoDiv.appendChild(link);
infoDiv.appendChild(name);
onlineContainer.appendChild(infoDiv);
});
// Réinitialiser et remplir le conteneur des streamers hors ligne
if (offlineStreamers.length === 0) {
// Ajouter le texte de chargement si aucun streamer hors ligne
const loaderText = document.getElementById('loadertext');
if (loaderText) {
loaderText.textContent = "Il n'y a aucun streamer hors ligne actuellement";
loaderText.style.display = 'block';
}
} else {
// Supprimer le texte de chargement
const loaderText = document.getElementById('loadertext');
if (loaderText) {
loaderText.style.display = 'none';
}
}
offlineStreamers.forEach(data => {
const profileLink = `https://twitch.tv/${data.displayName}`;
const image = document.createElement('img');
image.src = data.profileImageUrl;
image.alt = data.displayName;
image.className = 'streamer-image';
image.classList.toggle('offline', data.streamType !== 'live');
const liveIndicator = document.createElement('div');
liveIndicator.className = 'live-indicator';
liveIndicator.style.display = 'none';
const name = document.createElement('div');
name.className = 'streamer-name';
name.textContent = data.displayName;
const link = document.createElement('a');
link.href = profileLink;
link.target = '_blank';
link.appendChild(image);
link.appendChild(liveIndicator);
const infoDiv = document.createElement('div');
infoDiv.className = 'streamer-info';
infoDiv.appendChild(link);
infoDiv.appendChild(name);
offlineContainer.appendChild(infoDiv);
});
if (onlineStreamers.length > 0) {
// Supprimer le texte "aucun streamer en ligne" et le texte de chargement
const loaderText = document.getElementById('onlineLoaderText');
if (loaderText) {
loaderText.style.display = 'none';
}
} else {
// Ajouter le texte "aucun streamer en ligne"
const loaderText = document.getElementById('onlineLoaderText');
if (loaderText) {
loaderText.textContent = "Il n'y a aucun streamer en ligne actuellement";
loaderText.style.display = 'block'; // Assurez-vous que le texte est visible
}
}
}

11
public/scripts/twitch.js Normal file
View File

@ -0,0 +1,11 @@
console.log("twitch.js chargé");
const usernames = ['rubylium__', 'lukkstv', 'havvenfr', 'olmanntv', 'nina_asmr', 'krw_aka', 'leetgamer54', 'coshiho', 'soramicia', 'entocraft', 'steakfritees_', 'dancyy_', 'tchoupyy_', 'thesollann'];
(async function() {
// Vérifier et ajouter les streamers manquants dans streamers.json
await updateMissingStreamers(usernames);
// Une fois que tout est à jour, afficher les streamers
displayStreamers(usernames);
})();

207
public/styles.css Normal file
View File

@ -0,0 +1,207 @@
/* work-sans-regular - latin */
@import url('https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible:ital,wght@0,400;0,700;1,400;1,700&display=swap');
.atkinson-hyperlegible-regular {
font-family: "Atkinson Hyperlegible", sans-serif;
font-weight: 400;
font-style: normal;
}
.atkinson-hyperlegible-bold {
font-family: "Atkinson Hyperlegible", sans-serif;
font-weight: 700;
font-style: normal;
}
.atkinson-hyperlegible-regular-italic {
font-family: "Atkinson Hyperlegible", sans-serif;
font-weight: 400;
font-style: italic;
}
.atkinson-hyperlegible-bold-italic {
font-family: "Atkinson Hyperlegible", sans-serif;
font-weight: 700;
font-style: italic;
}
/* Contenu principal */
/* Contenu principal */
/* Contenu principal */
body {
background: rgb(29, 25, 27);
color: rgba(161, 208, 252);
font-family: "Atkinson Hyperlegible";
width: 100%;
margin: 0;
}
html {
width: 100%;
justify-content: center;
}
/* Catégories */
/* Catégories */
/* Catégories */
h1 {
margin-top: 0;
margin-bottom: 1rem;
font-weight: bold;
font-style: italic;
font-size: 3rem;
}
h2 {
margin-bottom: 0.5rem;
}
h2, .small {
display: inline;
margin: 0; /* Supprime les marges par défaut */
}
.small {
margin-bottom: 0.5rem;
font-style: italic;
color: rgba(161, 208, 252, 0.49);
}
strong {
font-weight: bold;
}
/* Throbber */
/* HTML: <div class="loader"></div> */
/* HTML: <div class="loader"></div> */
.loadertext {
width: 140px;
font-style: italic;
color: rgba(161, 208, 252, 0.49);
}
#loadertext {
margin-left: 20px;
width: 140px;
font-style: italic;
color: rgba(161, 208, 252, 0.49);
}
.loader {
width: 60px;
display: flex;
align-items: flex-start;
aspect-ratio: 1;
}
.loader:before,
.loader:after {
content: "";
flex: 1;
aspect-ratio: 1;
--g: conic-gradient(from -90deg at 10px 10px, #a1d0fc 90deg,#0000 0);
background: var(--g), var(--g), var(--g);
filter: drop-shadow(30px 30px 0 #A1D0FC);
animation: l20 1s infinite;
}
.loader:after {
transform: scaleX(-1);
}
@keyframes l20 {
0% {background-position:0 0, 10px 10px, 20px 20px}
33% {background-position:10px 10px}
66% {background-position:0 20px,10px 10px,20px 0 }
100% {background-position:0 0, 10px 10px, 20px 20px}
}
/* Contenu */
/* Contenu */
/* Contenu */
main {
display: flex;
justify-content: center;
}
#streamersContainer {
margin: 4rem;
width: 50%;
padding: 1rem;
border: 1px solid #A1D0FC;
border-radius: 0.5rem;
box-sizing: border-box;
}
.streamers-container {
margin-right: 1rem;
display: flex;
flex-direction: column;
gap: 20px;
}
.streamers-category {
display: flex;
flex-wrap: wrap;
gap: 10px; /* Espace entre les images */
}
.streamer-info {
position: relative;
text-align: center;
width: 60px; /* Assurez-vous que la largeur est fixe */
margin: 0; /* Supprime les marges éventuelles */
}
.streamer-image {
width: 60px;
height: 60px;
border-radius: 50%; /* Pour une image ronde */
transition: filter 0.3s ease, transform 0.3s ease;
justify-content: center; /* Centre les images horizontalement */
transform: scale(1); /* Échelle normale par défaut */
z-index: 1; /* Assure que l'image est au-dessus des autres éléments */
}
.streamer-info:hover .streamer-image {
filter: grayscale(0%); /* Réduit le niveaux de gris à 0% lors du survol */
transform: scale(1.2); /* Zoom de 10% sur l'image lors du survol */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); /* Optionnel : ajout d'une ombre portée pour plus d'effet */
}
.offline:hover {
filter: grayscale(0%); /* Réduit le niveaux de gris à 0% lors du survol */
}
.offline {
filter: grayscale(100%);
}
.live-indicator {
position: absolute;
top: 0;
right: 0;
width: 7px;
height: 7px;
background-color: red;
border-radius: 50%;
border: 0.25rem solid rgb(29, 25, 27);
}
.streamer-name {
z-index: 999;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 5px;
border-radius: 99999px;
opacity: 0;
transition: opacity 0.3s ease;
white-space: nowrap;
pointer-events: none;
}
.streamer-info:hover .streamer-name {
opacity: 1;
}