├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── README.md ├── client ├── index.js └── views │ └── index.js ├── config.js ├── package-lock.json ├── package.json ├── server ├── index.js └── views │ ├── index.pug │ └── layout.pug └── static ├── digital-ocean.jpg ├── favicon.ico ├── favicon.png └── main.css /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | ignore: 8 | - dependency-name: "*" 9 | update-types: ["version-update:semver-patch"] 10 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | on: [push,pull_request] 3 | jobs: 4 | test: 5 | name: Node ${{ matrix.node }} / ${{ matrix.os }} 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: 11 | - ubuntu-latest 12 | node: 13 | - '10' 14 | steps: 15 | - uses: actions/checkout@v2 16 | - uses: actions/setup-node@v2 17 | with: 18 | node-version: ${{ matrix.node }} 19 | - run: npm install 20 | - run: npm test 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Dependency directories 15 | node_modules/ 16 | jspm_packages/ 17 | 18 | # Typescript v1 declaration files 19 | typings/ 20 | 21 | # Optional npm cache directory 22 | .npm 23 | 24 | # IDE 25 | nbproject/ 26 | 27 | # Bundle 28 | static/bundle.js 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Alex 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Webtorrent Checker 2 | 3 | [![Build Status][webtorrent-checker-bi]][webtorrent-checker-bu] 4 | [![Standard - Javascript Style Guide][standard-image]][standard-url] 5 | 6 | Check the health of a torrent in Webtorrent and BitTorrent 7 | 8 | LIVE: [https://checker.openwebtorrent.com](https://checker.openwebtorrent.com) 9 | 10 | ## Build 11 | 12 | ```sh 13 | npm install 14 | npm build 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```sh 20 | npm start 21 | ``` 22 | 23 | ## License 24 | 25 | MIT. Copyright (c) [Alex](http://github.com/alxhotel) 26 | 27 | [webtorrent-checker-bi]: https://img.shields.io/github/actions/workflow/status/alxhotel/webtorrent-checker/ci.yml?branch=master 28 | [webtorrent-checker-bu]: https://github.com/alxhotel/webtorrent-checker/actions 29 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 30 | [standard-url]: https://standardjs.com 31 | -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | const pathname = window.location.pathname 2 | if (pathname === '/') require('./views/index')() 3 | -------------------------------------------------------------------------------- /client/views/index.js: -------------------------------------------------------------------------------- 1 | /* global XMLHttpRequest */ 2 | import parseTorrent from 'parse-torrent' 3 | 4 | module.exports = function () { 5 | // Initialize page 6 | init() 7 | 8 | function init () { 9 | // Initialize listeners 10 | const form = document.querySelector('#search form') 11 | if (form) { 12 | form.addEventListener('submit', function (e) { 13 | e.preventDefault() 14 | const magnetInput = document.querySelector('form #magnet-input') 15 | const magnetLink = magnetInput.value.trim() 16 | 17 | let parsedTorrent 18 | try { 19 | // Parse magnet link 20 | parsedTorrent = parseTorrent(magnetLink) 21 | } catch (err) { 22 | return showError('Invalid magnet link') 23 | } 24 | 25 | // Get health 26 | magnetInput.value = '' 27 | hideResponse() 28 | showLoading(parsedTorrent.infoHash) 29 | getHealth(magnetLink, function (response) { 30 | if (response.error) return showError(response.error.message) 31 | 32 | // Show response 33 | showResponse(response) 34 | }) 35 | }) 36 | } 37 | 38 | const fileInput = document.querySelector('#file-input') 39 | if (fileInput) { 40 | fileInput.addEventListener('change', function (e) { 41 | e.preventDefault() 42 | 43 | // Check amount 44 | if (fileInput.files.length === 0) return showError('No file selected') 45 | 46 | // Parse torrent 47 | parseTorrent.remote(fileInput.files[0], function (err, parsedTorrent) { 48 | if (err) return showError('Invalid torrent file') 49 | 50 | // Torrent to magnet link 51 | const magnetLink = parseTorrent.toMagnetURI(parsedTorrent) 52 | 53 | // Get health 54 | fileInput.value = fileInput.defaultValue 55 | hideResponse() 56 | showLoading(parsedTorrent.infoHash) 57 | getHealth(magnetLink, function (response) { 58 | if (response.error) return showError(response.error.message) 59 | 60 | // Show response 61 | showResponse(response) 62 | }) 63 | }) 64 | }) 65 | } 66 | } 67 | 68 | function getHealth (magnetLink, callback) { 69 | console.log('Getting health of: ' + magnetLink) 70 | 71 | const xmlhttp = new XMLHttpRequest() 72 | xmlhttp.open('GET', '/check?magnet=' + encodeURIComponent(magnetLink)) 73 | xmlhttp.send(null) 74 | xmlhttp.onreadystatechange = function () { 75 | if (this.readyState === 4 && this.status === 200) { 76 | const response = JSON.parse(this.responseText) 77 | if (callback) callback(response) 78 | } 79 | } 80 | } 81 | 82 | function showResponse (response) { 83 | hideLoading() 84 | 85 | // Log response 86 | console.log(response) 87 | 88 | const results = { 89 | webtorrent: { 90 | num_trackers: 0, 91 | seeders: 0, 92 | peers: 0 93 | }, 94 | bittorrent: { 95 | num_trackers: 0, 96 | seeders: 0, 97 | peers: 0 98 | } 99 | } 100 | 101 | for (let i = 0; i < response.extra.length; i++) { 102 | const info = response.extra[i] 103 | if (info.error) continue 104 | 105 | let torrent 106 | if (info.tracker.indexOf('wss') >= 0) { 107 | torrent = results.webtorrent 108 | } else { 109 | torrent = results.bittorrent 110 | } 111 | 112 | torrent.num_trackers++ 113 | torrent.seeders += info.seeds 114 | torrent.peers += info.peers 115 | } 116 | 117 | // Calculate average 118 | if (results.webtorrent.num_trackers === 0) results.webtorrent.num_trackers = 1 119 | if (results.bittorrent.num_trackers === 0) results.bittorrent.num_trackers = 1 120 | results.webtorrent.seeders = Math.round(results.webtorrent.seeders / results.webtorrent.num_trackers) 121 | results.webtorrent.peers = Math.round(results.webtorrent.peers / results.webtorrent.num_trackers) 122 | results.bittorrent.seeders = Math.round(results.bittorrent.seeders / results.bittorrent.num_trackers) 123 | results.bittorrent.peers = Math.round(results.bittorrent.peers / results.bittorrent.num_trackers) 124 | 125 | // Show numbers 126 | const webtorrentPeers = document.querySelector('#webtorrent-peers') 127 | const webtorrentSeeders = document.querySelector('#webtorrent-seeders') 128 | const bittorrentPeers = document.querySelector('#bittorrent-peers') 129 | const bittorrentSeeders = document.querySelector('#bittorrent-seeders') 130 | 131 | webtorrentPeers.textContent = results.webtorrent.peers 132 | webtorrentSeeders.textContent = results.webtorrent.seeders 133 | bittorrentPeers.textContent = results.bittorrent.peers 134 | bittorrentSeeders.textContent = results.bittorrent.seeders 135 | 136 | // Show results 137 | const resultsBox = document.querySelector('#results') 138 | resultsBox.style.display = 'block' 139 | } 140 | 141 | function showError (message) { 142 | hideLoading() 143 | const errorBox = document.querySelector('#error') 144 | errorBox.textContent = message 145 | errorBox.style.display = 'block' 146 | } 147 | 148 | function showLoading (infoHash) { 149 | const loadingBox = document.querySelector('#loading') 150 | loadingBox.style.display = 'block' 151 | document.querySelector('#loading .info-hash').textContent = infoHash 152 | } 153 | 154 | function hideLoading () { 155 | const loadingBox = document.querySelector('#loading') 156 | loadingBox.style.display = 'none' 157 | } 158 | 159 | function hideResponse () { 160 | // Hide response 161 | const resultsBox = document.querySelector('#results') 162 | resultsBox.style.display = 'none' 163 | const errorBox = document.querySelector('#error') 164 | errorBox.style.display = 'none' 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | const isProd = (process.env.NODE_ENV === 'production') 2 | 3 | /** 4 | * Is site running in production? 5 | **/ 6 | exports.isProd = isProd 7 | 8 | /** 9 | * Server listening port 10 | **/ 11 | exports.port = isProd 12 | ? 4000 13 | : 4000 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webtorrent-checker", 3 | "version": "1.0.0", 4 | "description": "Check the health of a magnet link or torrent file in Webtorrent or Bittorrent", 5 | "main": "index.js", 6 | "dependencies": { 7 | "bittorrent-tracker": "^10.0.0", 8 | "body-parser": "^1.20.0", 9 | "browserify": "^17.0.0", 10 | "express": "^4.21.0", 11 | "parse-torrent": "^11.0.1", 12 | "pug": "^3.0.3", 13 | "webtorrent-health": "^1.2.0" 14 | }, 15 | "devDependencies": { 16 | "esmify": "^2.1.1", 17 | "standard": "^17.1.0" 18 | }, 19 | "engine": { 20 | "node": ">=16.0.0" 21 | }, 22 | "scripts": { 23 | "test": "standard", 24 | "build": "npm run build-js", 25 | "build-js": "browserify client -p esmify > static/bundle.js", 26 | "start": "nodejs server/index.js" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/alxhotel/webtorrent-checker.git" 31 | }, 32 | "keywords": [ 33 | "webtorrent", 34 | "torrent", 35 | "status", 36 | "check", 37 | "checker", 38 | "webrtc", 39 | "tcp", 40 | "udp", 41 | "online", 42 | "web", 43 | "service", 44 | "seeds", 45 | "seeders", 46 | "leach", 47 | "leachers", 48 | "seed" 49 | ], 50 | "author": "Alex ", 51 | "license": "MIT", 52 | "bugs": { 53 | "url": "https://github.com/alxhotel/webtorrent-checker/issues" 54 | }, 55 | "homepage": "https://github.com/alxhotel/webtorrent-checker#readme" 56 | } 57 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const webtorrentHealth = require('webtorrent-health') 3 | const pug = require('pug') 4 | const express = require('express') 5 | const app = express() 6 | 7 | const config = require('../config') 8 | 9 | // Use pug for templates 10 | app.set('views', path.join(__dirname, 'views')) 11 | app.set('view engine', 'pug') 12 | app.engine('pug', pug.renderFile) 13 | 14 | // Serve static resources 15 | app.use(express.static(path.join(__dirname, '../static'))) 16 | 17 | // Serve all the pug pages 18 | app.get('/', function (req, res) { 19 | res.render('index', { rawTitle: 'WebTorrent Checker - Check any magnet link or torrent file' }) 20 | }) 21 | 22 | // GET /check 23 | app.get('/check', function (req, res) { 24 | console.log(req.query) 25 | 26 | if (!req.query.magnet) return res.send({ error: { code: 404, message: 'Missing magnet link' } }) 27 | 28 | webtorrentHealth(req.query.magnet, { 29 | timeout: 2000 30 | }, function (err, data) { 31 | if (err) return res.send({ error: { code: 500, message: err.message } }) 32 | 33 | // Send results 34 | res.send(data) 35 | }) 36 | }) 37 | 38 | app.listen(config.port, function () { 39 | console.log('Webtorrent-Checker app is listening on port ' + config.port + '!') 40 | }) 41 | -------------------------------------------------------------------------------- /server/views/index.pug: -------------------------------------------------------------------------------- 1 | extends layout.pug 2 | 3 | block body 4 | h1#logo Webtorrent Checker 5 | p.subtitle Check the health of a torrent in WebTorrent and BitTorrent 6 | 7 | #search 8 | form 9 | p 10 | input(type='text', id='magnet-input', name='magnet_link', placeholder='magnet:') 11 | input(type='submit', id='submit') 12 | div.separator or 13 | p 14 | input(type='file', id='file-input', name='torrent_file') 15 | 16 | #loading 17 | i(class='spinner') 18 | p Loading health info of  19 | span(class='info-hash') ... 20 | 21 | #error(style='display: none;') 22 | 23 | #results(style='display: none;') 24 | #bittorrent-results 25 | ul 26 | li BitTorrent 27 | li Peers: 28 | span(id='bittorrent-peers') 0 29 | li Seeders: 30 | span(id='bittorrent-seeders') 0 31 | #webtorrent-results 32 | ul 33 | li WebTorrent 34 | li Peers: 35 | span(id='webtorrent-peers') 0 36 | li Seeders: 37 | span(id='webtorrent-seeders') 0 38 | footer 39 | p 40 | small Powered by WebTorrent 41 | //-a(href='https://m.do.co/c/4ac6f03d9db2') 42 | //- img(src='/digital-ocean.jpg' width='320') 43 | 44 | -------------------------------------------------------------------------------- /server/views/layout.pug: -------------------------------------------------------------------------------- 1 | html(lang='en') 2 | head 3 | title= rawTitle || (title && (title + ' - WebTorrent Checker')) || 'WebTorrent Checker' 4 | meta(name='description', content='Check the health of any torrent in Webtorrent and BitTorrent. Find the amount of seeders and peers in seconds.') 5 | 6 | meta(charset='utf-8') 7 | meta(name='viewport', content='width=device-width, initial-scale=1') 8 | 9 | link(rel='stylesheet', href='/main.css', charset='utf-8') 10 | link(rel='icon', href='/favicon.ico', type='image/x-icon') 11 | 12 | block head 13 | body 14 | block body 15 | script(src='/bundle.js') 16 | 17 | script. 18 | window.dataLayer = window.dataLayer || []; 19 | function gtag(){dataLayer.push(arguments);} 20 | gtag('js', new Date()); 21 | gtag('config', 'G-S1RNG05QZN'); 22 | script(async, src='//www.googletagmanager.com/gtag/js?id=G-S1RNG05QZN') 23 | -------------------------------------------------------------------------------- /static/digital-ocean.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxhotel/webtorrent-checker/70743dad3fddbf278d39a0b3f34a3154790754ec/static/digital-ocean.jpg -------------------------------------------------------------------------------- /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxhotel/webtorrent-checker/70743dad3fddbf278d39a0b3f34a3154790754ec/static/favicon.ico -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alxhotel/webtorrent-checker/70743dad3fddbf278d39a0b3f34a3154790754ec/static/favicon.png -------------------------------------------------------------------------------- /static/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: center; 3 | font-family: sans-serif; 4 | } 5 | 6 | a { 7 | color: #2aab00; 8 | } 9 | 10 | #logo { 11 | margin: 20px 0px 10px 0px; 12 | } 13 | 14 | .subtitle { 15 | color: #7d868c; 16 | margin: 0px 0px 20px 0px; 17 | } 18 | 19 | #search { 20 | margin: 30px 0; 21 | } 22 | 23 | #search #magnet-input { 24 | width: 300px; 25 | font-size: 16px; 26 | } 27 | 28 | #search #file-input { 29 | width: 365px; 30 | font-size: 16px; 31 | } 32 | 33 | #search #submit { 34 | font-size: 16px; 35 | } 36 | 37 | #loading { 38 | display: none; 39 | } 40 | 41 | #error { 42 | color: red; 43 | } 44 | 45 | #bittorrent-results { 46 | display: inline-block; 47 | background: #f3901a; 48 | border-radius: 8px; 49 | color: #fff; 50 | text-shadow: 2px 2px rgba(0, 0, 0, 0.2); 51 | font-weight: bold; 52 | font-size: 16px; 53 | margin: 0px 15px; 54 | } 55 | 56 | #webtorrent-results { 57 | display: inline-block; 58 | background: #0465c0; 59 | border-radius: 8px; 60 | color: #fff; 61 | text-shadow: 2px 2px rgba(0, 0, 0, 0.2); 62 | font-weight: bold; 63 | font-size: 16px; 64 | margin: 0px 15px; 65 | } 66 | 67 | #results { 68 | margin: 30px 0px; 69 | } 70 | 71 | #results ul { 72 | padding: 10px 20px; 73 | margin: 0px; 74 | list-style: none; 75 | } 76 | 77 | #results ul li:first-child { 78 | text-decoration: underline; 79 | margin-bottom: 10px; 80 | } 81 | 82 | .translucent { 83 | opacity: 0.5; 84 | } 85 | 86 | .separator { 87 | position: relative; 88 | text-transform: uppercase; 89 | color: #7d868c; 90 | font-size: 13px; 91 | } 92 | 93 | .separator::before { 94 | width: 168px; 95 | height: 1px; 96 | background: #BDC2C4; 97 | content: " "; 98 | display: inline-block; 99 | text-align: right; 100 | position: absolute; 101 | top: 50%; 102 | margin-left: -170px; 103 | } 104 | 105 | .separator::after { 106 | width: 168px; 107 | height: 1px; 108 | background: #BDC2C4; 109 | content: " "; 110 | display: inline-block; 111 | text-align: right; 112 | position: absolute; 113 | top: 50%; 114 | margin-left: 2px; 115 | } 116 | --------------------------------------------------------------------------------