Compare commits
4 Commits
9b66df772a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e022b2745 | |||
| ef7c90840b | |||
| 5045f30a4e | |||
| 1f13ba2afb |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
node_modules/
|
||||
.env
|
||||
public/
|
||||
data/
|
||||
@ -9,6 +9,7 @@ async function search(query) {
|
||||
let data = sc.search(query);
|
||||
return data;
|
||||
}
|
||||
|
||||
// so um fuck lucida
|
||||
// based on https://github.com/imputnet/cobalt/blob/58ea4aed01383ead74d5e32e75335eddc2f015be/api/src/processing/services/soundcloud.js
|
||||
|
||||
@ -72,7 +73,7 @@ const findBestForPreset = (transcodings, preset) => {
|
||||
return inferior;
|
||||
}
|
||||
async function download(obj) {
|
||||
const clientId = await findClientID();
|
||||
const clientId = cachedID.id;
|
||||
if (!clientId) return { error: "fetch.fail" };
|
||||
|
||||
let link = obj;
|
||||
@ -150,4 +151,4 @@ async function download(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
export { search, download };
|
||||
export { search, download, findClientID };
|
||||
192
edu/index.html
Normal file
192
edu/index.html
Normal file
@ -0,0 +1,192 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="description" content="Open Learning Hub - Your gateway to free educational resources for coding, science, math, humanities, languages, and more.">
|
||||
<meta name="keywords" content="education, free learning, online courses, coding, science, math, humanities, languages, study tools">
|
||||
<meta name="author" content="Open Learning Hub">
|
||||
<meta property="og:title" content="Open Learning Hub">
|
||||
<meta property="og:description" content="Discover free educational resources and daily learning tips.">
|
||||
<meta property="og:type" content="website">
|
||||
<title>Open Learning Hub</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>Open Learning Hub</h1>
|
||||
<p>Your gateway to free educational resources across the web.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section id="tip-section">
|
||||
<h2>💡 Tip of the Day</h2>
|
||||
<p id="daily-tip">Loading tip...</p>
|
||||
<button id="new-tip-btn">Get Another Tip</button>
|
||||
</section>
|
||||
|
||||
<section id="resources">
|
||||
<h2>Educational Resources</h2>
|
||||
<div class="filter-controls">
|
||||
<button class="filter-btn active" data-filter="all">All</button>
|
||||
<button class="filter-btn" data-filter="coding">Coding</button>
|
||||
<button class="filter-btn" data-filter="science">Science</button>
|
||||
<button class="filter-btn" data-filter="math">Math</button>
|
||||
<button class="filter-btn" data-filter="humanities">Humanities</button>
|
||||
<button class="filter-btn" data-filter="languages">Languages</button>
|
||||
<button class="filter-btn" data-filter="courses">Online Courses</button>
|
||||
<button class="filter-btn" data-filter="tools">Study Tools</button>
|
||||
</div>
|
||||
|
||||
<div class="resource-grid">
|
||||
<!-- Coding -->
|
||||
<article class="card" data-category="coding">
|
||||
<h3>freeCodeCamp</h3>
|
||||
<p>Learn to code for free with interactive lessons and projects.</p>
|
||||
<a href="https://www.freecodecamp.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="coding">
|
||||
<h3>MDN Web Docs</h3>
|
||||
<p>Resources for developers, by developers. The bible of web dev.</p>
|
||||
<a href="https://developer.mozilla.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="coding">
|
||||
<h3>GitHub</h3>
|
||||
<p>The world's largest platform for software development and version control.</p>
|
||||
<a href="https://github.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="coding">
|
||||
<h3>The Odin Project</h3>
|
||||
<p>A full stack curriculum that is free and supported by a passionate open source community.</p>
|
||||
<a href="https://www.theodinproject.com/" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Science -->
|
||||
<article class="card" data-category="science">
|
||||
<h3>NASA</h3>
|
||||
<p>Explore the universe with images, videos, and news from NASA.</p>
|
||||
<a href="https://www.nasa.gov" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="science">
|
||||
<h3>National Geographic</h3>
|
||||
<p>World leader in geography, cartography and exploration.</p>
|
||||
<a href="https://www.nationalgeographic.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="science">
|
||||
<h3>Nature</h3>
|
||||
<p>Leading international weekly journal of science.</p>
|
||||
<a href="https://www.nature.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Math -->
|
||||
<article class="card" data-category="math">
|
||||
<h3>Khan Academy</h3>
|
||||
<p>Free, world-class education for anyone, anywhere.</p>
|
||||
<a href="https://www.khanacademy.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="math">
|
||||
<h3>Wolfram MathWorld</h3>
|
||||
<p>The web's most extensive mathematics resource.</p>
|
||||
<a href="https://mathworld.wolfram.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="math">
|
||||
<h3>Desmos</h3>
|
||||
<p>Beautiful, free online graphing calculator.</p>
|
||||
<a href="https://www.desmos.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Humanities -->
|
||||
<article class="card" data-category="humanities">
|
||||
<h3>Project Gutenberg</h3>
|
||||
<p>A library of over 60,000 free eBooks.</p>
|
||||
<a href="https://www.gutenberg.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="humanities">
|
||||
<h3>Smithsonian</h3>
|
||||
<p>Official website of the Smithsonian Institution.</p>
|
||||
<a href="https://www.si.edu" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="humanities">
|
||||
<h3>Internet Archive</h3>
|
||||
<p>Non-profit library of millions of free books, movies, software, and music.</p>
|
||||
<a href="https://archive.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Languages -->
|
||||
<article class="card" data-category="languages">
|
||||
<h3>Duolingo</h3>
|
||||
<p>The world's most popular way to learn a language. It's 100% free.</p>
|
||||
<a href="https://www.duolingo.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="languages">
|
||||
<h3>BBC Languages</h3>
|
||||
<p>Archived but excellent resources for learning 40 languages.</p>
|
||||
<a href="https://www.bbc.co.uk/languages/" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Online Courses -->
|
||||
<article class="card" data-category="courses">
|
||||
<h3>Coursera</h3>
|
||||
<p>Build skills with courses, certificates, and degrees online from world-class universities.</p>
|
||||
<a href="https://www.coursera.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="courses">
|
||||
<h3>edX</h3>
|
||||
<p>Access 2000+ free online courses from 140 leading institutions worldwide.</p>
|
||||
<a href="https://www.edx.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="courses">
|
||||
<h3>MIT OpenCourseWare</h3>
|
||||
<p>A web-based publication of virtually all MIT course content.</p>
|
||||
<a href="https://ocw.mit.edu" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Study Tools -->
|
||||
<article class="card" data-category="tools">
|
||||
<h3>Anki</h3>
|
||||
<p>Powerful, intelligent flashcards. Remembering things just became much easier.</p>
|
||||
<a href="https://apps.ankiweb.net/" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="tools">
|
||||
<h3>Notion</h3>
|
||||
<p>The all-in-one workspace for your notes, tasks, wikis, and databases.</p>
|
||||
<a href="https://www.notion.so/" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="tools">
|
||||
<h3>Pomofocus</h3>
|
||||
<p>A customizable Pomodoro timer that works on desktop & mobile browser.</p>
|
||||
<a href="https://pomofocus.io/" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="learning-paths">
|
||||
<h2>🚀 Learning Paths</h2>
|
||||
<div class="paths-container">
|
||||
<div class="path-card">
|
||||
<h3>For Beginners</h3>
|
||||
<p>Start with <strong>Khan Academy</strong> to build a strong foundation in Math and Science. Then, try <strong>Duolingo</strong> for a new language.</p>
|
||||
</div>
|
||||
<div class="path-card">
|
||||
<h3>For Career Switchers</h3>
|
||||
<p>Dive into <strong>freeCodeCamp</strong> or <strong>The Odin Project</strong> (add this!) to learn web development. Supplement with <strong>CS50</strong> on edX.</p>
|
||||
</div>
|
||||
<div class="path-card">
|
||||
<h3>For Lifelong Learners</h3>
|
||||
<p>Explore <strong>Project Gutenberg</strong> for classics, <strong>TED Talks</strong> for inspiration, and <strong>Coursera</strong> for specific interests.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2025 Open Learning Hub.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
65
edu/script.js
Normal file
65
edu/script.js
Normal file
@ -0,0 +1,65 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// --- Tip of the Day Logic ---
|
||||
const tips = [
|
||||
"Spaced repetition is a learning technique that incorporates increasing intervals of time between subsequent review of previously learned material.",
|
||||
"The Pomodoro Technique uses a timer to break down work into intervals, traditionally 25 minutes in length, separated by short breaks.",
|
||||
"Teaching someone else is one of the best ways to learn a new concept (The Feynman Technique).",
|
||||
"Sleep is crucial for memory consolidation. Pulling an all-nighter is often counterproductive.",
|
||||
"Active recall (testing yourself) is far more effective than passive re-reading.",
|
||||
"Interleaving practice (mixing different subjects or topics) improves long-term retention compared to blocked practice.",
|
||||
"Exercise increases blood flow to the brain and can improve cognitive performance.",
|
||||
"Setting specific, measurable goals (SMART goals) helps maintain motivation.",
|
||||
"Taking handwritten notes can improve conceptual understanding better than typing.",
|
||||
"Mnemonic devices like acronyms or rhymes can help encode information into long-term memory."
|
||||
];
|
||||
|
||||
const tipElement = document.getElementById('daily-tip');
|
||||
const newTipBtn = document.getElementById('new-tip-btn');
|
||||
|
||||
function showRandomTip() {
|
||||
const randomIndex = Math.floor(Math.random() * tips.length);
|
||||
tipElement.textContent = tips[randomIndex];
|
||||
|
||||
// Add a subtle animation effect
|
||||
tipElement.style.opacity = 0;
|
||||
setTimeout(() => {
|
||||
tipElement.style.opacity = 1;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (newTipBtn) {
|
||||
newTipBtn.addEventListener('click', showRandomTip);
|
||||
}
|
||||
|
||||
// Show initial tip
|
||||
if (tipElement) {
|
||||
showRandomTip();
|
||||
tipElement.style.transition = "opacity 0.5s ease-in-out";
|
||||
}
|
||||
|
||||
|
||||
// --- Filtering Logic ---
|
||||
const filterBtns = document.querySelectorAll('.filter-btn');
|
||||
const cards = document.querySelectorAll('.card');
|
||||
|
||||
filterBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
// Remove active class from all buttons
|
||||
filterBtns.forEach(b => b.classList.remove('active'));
|
||||
// Add active class to clicked button
|
||||
btn.classList.add('active');
|
||||
|
||||
const filterValue = btn.getAttribute('data-filter');
|
||||
|
||||
cards.forEach(card => {
|
||||
if (filterValue === 'all' || card.getAttribute('data-category') === filterValue) {
|
||||
card.style.display = 'flex';
|
||||
// Add animation for appearing elements
|
||||
card.style.animation = 'fadeIn 0.5s ease-in-out';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
185
edu/style.css
Normal file
185
edu/style.css
Normal file
@ -0,0 +1,185 @@
|
||||
:root {
|
||||
--primary-color: #2c3e50;
|
||||
--secondary-color: #3498db;
|
||||
--accent-color: #e74c3c;
|
||||
--bg-color: #f4f4f4;
|
||||
--card-bg: #ffffff;
|
||||
--text-color: #333;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--primary-color);
|
||||
border-bottom: 2px solid var(--secondary-color);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Tip Section */
|
||||
#tip-section {
|
||||
background-color: var(--card-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
border-left: 5px solid var(--accent-color);
|
||||
}
|
||||
|
||||
#new-tip-btn {
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-top: 1rem;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
#new-tip-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
/* Filter Controls */
|
||||
.filter-controls {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
background: transparent;
|
||||
border: 2px solid var(--secondary-color);
|
||||
color: var(--secondary-color);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-btn:hover, .filter-btn.active {
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Resource Grid */
|
||||
.resource-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.card p {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card a {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: var(--secondary-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
margin-top: 2rem;
|
||||
color: #777;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.resource-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Learning Paths */
|
||||
.paths-container {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.path-card {
|
||||
background-color: #e8f4f8;
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
border-left: 4px solid var(--secondary-color);
|
||||
}
|
||||
|
||||
.path-card h3 {
|
||||
color: var(--primary-color);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
@ -50,6 +50,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
function setRecat() {
|
||||
let domain = prompt("domain");
|
||||
if(domain) {
|
||||
fetch("/api/recat", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({domain: domain})
|
||||
})
|
||||
.then(data => data.text())
|
||||
.then(data => alert(data));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<alerts> </alerts>
|
||||
@ -57,6 +68,7 @@
|
||||
<h1 class="title">admin page</h1>
|
||||
<sections>
|
||||
<button onclick="sendAnnouncement()">send announcement</button>
|
||||
<button onclick="setRecat()">set a domain to recat</button>
|
||||
</sections>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
65
index.js
65
index.js
@ -10,18 +10,46 @@ import compression from "compression";
|
||||
// import { accs, infdb, polytrack } from "./database.js";
|
||||
import { accs } from "./database.js";
|
||||
import { } from "./accounts/friend.js";
|
||||
import { search, download } from "./accounts/music.js";
|
||||
import { findClientID, search, download } from "./accounts/music.js";
|
||||
import { banUser, removeAccount, verifyCookie, getUserFromCookie, createAccount, resetPassword, loginAccount, addBadge } from "./accounts/manage.js";
|
||||
import { } from "./accounts/misc.js";
|
||||
import { getRawData, generateAccountPage, editProfile, saveData, getUsers, isAdmin, retrieveData } from "./accounts/profile.js";
|
||||
import { callAI } from "./ai.js";
|
||||
import { Readable } from 'stream';
|
||||
import os from "node:os";
|
||||
import chokidar from 'chokidar';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
let recatters = JSON.parse(await fs.readFile("./data/recats.json"));
|
||||
const watcher = chokidar.watch('./data/recats.json', {
|
||||
persistent: true,
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 500,
|
||||
pollInterval: 100
|
||||
}
|
||||
});
|
||||
watcher.on('change', async (event, filePath) => {
|
||||
recatters = JSON.parse(await fs.readFile("./data/recats.json"));
|
||||
});
|
||||
|
||||
await findClientID();
|
||||
setInterval(async () => {
|
||||
await findClientID();
|
||||
}, 1000 * 60 * 20);
|
||||
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const app = express();
|
||||
|
||||
let recatFolder = express.static("./edu", { extensions: ["html"] });
|
||||
app.use((req, res, next) => {
|
||||
if(recatters.includes(req.hostname)) {
|
||||
return recatFolder(req, res, next);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// why the fuck does this have to exist?
|
||||
app.use("/resources/semag/hotline-miami/", (req,res,next) => {
|
||||
if(req.method == "HEAD") {
|
||||
@ -160,6 +188,41 @@ app.post("/api/ai/sendMessage", async (req, res) => {
|
||||
// stream back reply
|
||||
})
|
||||
|
||||
// music endpoints
|
||||
app.get("/api/music/search", async (req, res) => {
|
||||
res.status(200).send(await search(req.query.q))
|
||||
})
|
||||
|
||||
app.use("/api/music/download", async (req, res, next) => {
|
||||
// console.log()
|
||||
let song = req.query.url;
|
||||
song = song.replaceAll(" ", "");
|
||||
const processor = Bun.spawn([
|
||||
"yt-dlp",
|
||||
"bestaudio[abr<=160]",
|
||||
"-o", `-`,
|
||||
"https://soundcloud.com/" + req.query.url
|
||||
]);
|
||||
for await (const chunk of processor.stdout) {
|
||||
res.write(chunk);
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
app.post("/api/recat", async (req, res) => {
|
||||
if(isAdmin(req.cookies.token)) {
|
||||
let domain = JSON.parse(req.body)["domain"];
|
||||
let message;
|
||||
if(recatters.includes(domain)) {
|
||||
recatters.pop(domain);
|
||||
message = `Successfully set ${domain} to Selenite.`
|
||||
} else {
|
||||
recatters.push(domain);
|
||||
message = `Successfully set ${domain} as educational.`
|
||||
}
|
||||
fs.writeFile("./data/recats.json", JSON.stringify(recatters));
|
||||
res.send(message);
|
||||
};
|
||||
})
|
||||
|
||||
// friends endpoints
|
||||
app.get("/api/friends/list", async (req, res) => {
|
||||
|
||||
1356
package-lock.json
generated
1356
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@
|
||||
"axios": "^1.12.2",
|
||||
"body-parser": "^2.2.0",
|
||||
"chalk": "^5.6.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"compression": "^1.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
import { appendFile } from 'fs/promises';
|
||||
import WebSocket, { WebSocketServer } from "ws";
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
let dataFile = "data/online.txt";
|
||||
|
||||
|
||||
wss.on("connection", function connection(ws, req, res) {
|
||||
ws.send(`online=${wss.clients.size}`);
|
||||
@ -31,3 +34,6 @@ server.on("upgrade", (request, socket, head) => {
|
||||
wss.emit("connection", socket, request);
|
||||
});
|
||||
});
|
||||
setInterval(async () => {
|
||||
await appendFile(dataFile, String(wss.clients.size) + "\n")
|
||||
}, 10000)
|
||||
Reference in New Issue
Block a user