mirror of
https://github.com/PR0M3TH3AN/bitvid-seeder.git
synced 2026-11-03 19:32:45 +00:00
update
This commit is contained in:
1 parent
55d6dad5fd
commit
2827d8182c
2 files changed
+65
-38
No files matched your search
+32
-15
@@ -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>
|
||||
@@ -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);
|
||||
Reference in new issue
Block a user