This commit is contained in:
thePR0M3TH3AN committed 2025-02-17 18:46:38 -05:00
1 parent 55d6dad5fd
commit 2827d8182c
2 files changed
+65 -38

No files matched your search

+32 -15
View File
@@ -6,6 +6,8 @@
<title>bitvid Torrent Seeder</title>
<!-- Bootstrap CSS -->
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" />
<!-- Font Awesome for the gear icon -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css" rel="stylesheet" />
</head>
<body>
<div class="container my-4">
@@ -28,7 +30,13 @@
<!-- Active Torrents Table -->
<div class="card mb-4">
<div class="card-header">Active Torrents</div>
<div class="card-header d-flex justify-content-between align-items-center">
Active Torrents
<!-- Settings Gear Icon -->
<button class="btn btn-link" data-bs-toggle="modal" data-bs-target="#settingsModal">
<i class="fas fa-cog"></i>
</button>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="torrentTable" class="table table-striped">
@@ -50,20 +58,27 @@
</div>
</div>
</div>
</div>
<!-- Storage Settings -->
<div class="card mb-4">
<div class="card-header">Storage Settings</div>
<div class="card-body">
<form id="configForm" class="row g-3">
<div class="col-md-9">
<input type="text" id="storageDir" class="form-control" placeholder="Storage directory" required>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-secondary w-100">Update Storage Directory</button>
</div>
</form>
<p id="currentStorage" class="mt-3"></p>
<!-- Settings Modal -->
<div class="modal fade" id="settingsModal" tabindex="-1" aria-labelledby="settingsModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="settingsModalLabel">Settings</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<!-- Storage Settings Form -->
<form id="configForm">
<div class="mb-3">
<label for="storageDir" class="form-label">Storage Directory</label>
<input type="text" id="storageDir" class="form-control" placeholder="Storage directory" required>
</div>
<button type="submit" class="btn btn-primary">Update Storage Directory</button>
</form>
<p id="currentStorage" class="mt-3"></p>
</div>
</div>
</div>
</div>
@@ -192,6 +207,8 @@
});
const result = await res.json();
document.getElementById('currentStorage').textContent = 'Current storage directory: ' + result.storageDir;
// Close the modal after updating
bootstrap.Modal.getInstance(document.getElementById('settingsModal')).hide();
});
// Refresh the list and config every 5 seconds
@@ -206,4 +223,4 @@
<!-- Bootstrap Bundle JS -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
</html>
+33 -23
View File
@@ -19,7 +19,7 @@ const logger = winston.createLogger({
({ timestamp, level, message }) => `${timestamp} ${level}: ${message}`
)
),
transports: [new winston.transports.Console()]
transports: [new winston.transports.Console()],
});
const app = express();
@@ -36,7 +36,7 @@ const __dirname = path.dirname(__filename);
// Rate limiting middleware (100 requests per minute per IP)
const limiter = rateLimit({
windowMs: 60 * 1000,
max: 100
max: 100,
});
app.use(limiter);
@@ -103,26 +103,26 @@ async function pathExists(p) {
app.get('/torrents', (req, res) => {
try {
// Active torrents: those in the client
const active = client.torrents.map(t => ({
const active = client.torrents.map((t) => ({
infoHash: t.infoHash,
name: t.name,
progress: t.progress,
numPeers: t.numPeers,
downloaded: t.downloaded,
uploaded: t.uploaded,
paused: false
paused: false,
}));
// Paused torrents: those saved with paused: true
const paused = savedTorrents
.filter(s => s.paused)
.map(s => ({
.filter((s) => s.paused)
.map((s) => ({
infoHash: s.infoHash,
name: s.name,
progress: 1,
numPeers: 0,
downloaded: 0,
uploaded: 0,
paused: true
paused: true,
}));
const torrents = [...active, ...paused];
logger.info('GET /torrents returning: ' + JSON.stringify(torrents));
@@ -140,13 +140,13 @@ app.post('/pause/:infoHash', (req, res) => {
if (!torrent) {
return res.status(400).json({ error: 'Torrent not active or already paused' });
}
client.remove(infoHash, { destroyStore: false }, err => {
client.remove(infoHash, { destroyStore: false }, (err) => {
if (err) {
logger.error('Error pausing torrent: ' + err);
return res.status(500).json({ error: 'Error pausing torrent' });
}
// Update savedTorrents to mark this torrent as paused.
const entry = savedTorrents.find(s => s.infoHash === infoHash);
const entry = savedTorrents.find((s) => s.infoHash === infoHash);
if (entry) entry.paused = true;
saveTorrents();
logger.info(`Torrent paused: ${infoHash}`);
@@ -157,7 +157,7 @@ app.post('/pause/:infoHash', (req, res) => {
// POST /resume/:infoHash - Resume a paused torrent (re-add it to the client)
app.post('/resume/:infoHash', async (req, res) => {
const infoHash = req.params.infoHash;
const entry = savedTorrents.find(s => s.infoHash === infoHash);
const entry = savedTorrents.find((s) => s.infoHash === infoHash);
if (!entry) {
return res.status(400).json({ error: 'Torrent not found in saved data' });
}
@@ -167,12 +167,12 @@ app.post('/resume/:infoHash', async (req, res) => {
const exists = await pathExists(torrentPath);
try {
if (exists) {
client.seed(torrentPath, { name: entry.name, keepSeeding: true }, torrent => {
client.seed(torrentPath, { name: entry.name, keepSeeding: true }, (torrent) => {
logger.info(`Torrent resumed (seeding from file): ${torrent.infoHash}`);
res.json({ message: 'Torrent resumed' });
});
} else {
client.add(entry.magnet, { path: STORAGE_DIR, keepSeeding: true }, torrent => {
client.add(entry.magnet, { path: STORAGE_DIR, keepSeeding: true }, (torrent) => {
logger.info(`Torrent resumed (downloading): ${torrent.infoHash}`);
res.json({ message: 'Torrent resumed' });
});
@@ -189,18 +189,18 @@ app.post('/resume/:infoHash', async (req, res) => {
// (Paused torrents will have "paused": true in savedTorrents and should not be added automatically.)
async function loadSavedTorrents() {
await Promise.all(
savedTorrents.map(async torrentData => {
savedTorrents.map(async (torrentData) => {
// Only auto-load if not marked paused.
if (torrentData.paused) return;
const torrentPath = path.join(STORAGE_DIR, torrentData.name);
const exists = await pathExists(torrentPath);
try {
if (exists) {
client.seed(torrentPath, { name: torrentData.name, keepSeeding: true }, torrent => {
client.seed(torrentPath, { name: torrentData.name, keepSeeding: true }, (torrent) => {
logger.info(`Seeding torrent from existing files: ${torrent.infoHash}`);
});
} else {
client.add(torrentData.magnet, { path: STORAGE_DIR, keepSeeding: true }, torrent => {
client.add(torrentData.magnet, { path: STORAGE_DIR, keepSeeding: true }, (torrent) => {
logger.info(`Downloading torrent: ${torrent.infoHash}`);
});
}
@@ -212,6 +212,16 @@ async function loadSavedTorrents() {
}
loadSavedTorrents();
// GET /config - Return the current configuration
app.get('/config', (req, res) => {
try {
res.json({ storageDir: STORAGE_DIR });
} catch (err) {
logger.error('Error fetching config: ' + err);
res.status(500).json({ error: 'Error fetching config' });
}
});
// POST /config - Update storage directory (with input validation)
app.post('/config', (req, res) => {
const { storageDir } = req.body;
@@ -241,18 +251,18 @@ app.post('/add', (req, res) => {
if (!magnet.startsWith('magnet:?')) {
return res.status(400).json({ error: 'Invalid magnet link format' });
}
if (savedTorrents.some(t => t.magnet === magnet)) {
if (savedTorrents.some((t) => t.magnet === magnet)) {
logger.info('Attempted to add duplicate torrent.');
return res.status(409).json({ error: 'Torrent already added.' });
}
try {
client.add(magnet, { path: STORAGE_DIR, keepSeeding: true }, torrent => {
client.add(magnet, { path: STORAGE_DIR, keepSeeding: true }, (torrent) => {
logger.info(`Torrent added: ${torrent.infoHash}`);
const torrentData = {
magnet,
infoHash: torrent.infoHash,
name: torrent.name,
paused: false
paused: false,
};
savedTorrents.push(torrentData);
saveTorrents();
@@ -271,18 +281,18 @@ app.delete('/remove/:infoHash', (req, res) => {
return res.status(400).json({ error: 'Invalid infoHash' });
}
const torrent = client.get(infoHash);
const savedData = savedTorrents.find(t => t.infoHash === infoHash);
const savedData = savedTorrents.find((t) => t.infoHash === infoHash);
const torrentName = (torrent && torrent.name) || (savedData && savedData.name);
if (!torrentName) {
return res.status(400).json({ error: 'Torrent name not found' });
}
try {
client.remove(infoHash, { destroyStore: true }, async err => {
client.remove(infoHash, { destroyStore: true }, async (err) => {
if (err) {
logger.error('Error removing torrent: ' + err);
return res.status(500).json({ error: 'Error removing torrent' });
}
savedTorrents = savedTorrents.filter(t => t.infoHash !== infoHash);
savedTorrents = savedTorrents.filter((t) => t.infoHash !== infoHash);
saveTorrents();
const torrentPath = path.join(STORAGE_DIR, torrentName);
if (await pathExists(torrentPath)) {
@@ -311,7 +321,7 @@ function shutdown() {
logger.info('Shutting down server...');
server.close(() => {
logger.info('HTTP server closed.');
client.destroy(err => {
client.destroy((err) => {
if (err) {
logger.error('Error destroying WebTorrent client: ' + err);
} else {
@@ -323,4 +333,4 @@ function shutdown() {
}
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
process.on('SIGTERM', shutdown);