1/* global WebTorrent, angular, moment, prompt */ 2import WebTorrent from 'https://cdn.jsdelivr.net/npm/webtorrent@3/dist/webtorrent.min.js' 3 4const VERSION = '2' 5const trackers = ['wss://tracker.btorrent.xyz', 'wss://tracker.openwebtorrent.com', 'wss://open.ftorrent.com'] 6const rtcConfig = { 7 'iceServers': [ 8 { 9 'urls': ['stun:stun.l.google.com:19305', 'stun:stun1.l.google.com:19305'] 10 } 11 ] 12} 13 14const torrentOpts = { 15 announce: trackers 16} 17 18const trackerOpts = { 19 announce: trackers, 20 rtcConfig: rtcConfig 21} 22 23const debug = window.localStorage.getItem('debug') !== null 24 25const dbg = function (string, item, color) { 26 color = color !== null ? color : '#333333' 27 if (debug) { 28 if (item && item.name) { 29 return console.debug(`%cβTorrent:${item.infoHash !== null ? 'torrent ' : 'torrent ' + item._torrent.name + ':file '}${item.name}${item.infoHash !== null ? ' (' + item.infoHash + ')' : ''} %c${string}`, 'color: #33C3F0', `color: ${color}`) 30 } else { 31 return console.debug(`%cβTorrent:client %c${string}`, 'color: #33C3F0', `color: ${color}`) 32 } 33 } 34} 35 36const er = function (err, item) { dbg(err, item, '#FF0000') } 37 38dbg(`Starting v${VERSION}. WebTorrent ${WebTorrent.VERSION}`) 39 40const client = new WebTorrent({ 41 tracker: trackerOpts 42}) 43 44const app = angular.module('BTorrent', 45 ['ngRoute', 'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.selection', 'ngFileUpload', 'ngNotify'], 46 ['$compileProvider', '$locationProvider', '$routeProvider', function ($compileProvider, $locationProvider, $routeProvider) { 47 $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|magnet|blob|javascript):/) 48 $locationProvider.html5Mode({ 49 enabled: true, 50 requireBase: false 51 }).hashPrefix('#') 52 $routeProvider.when('/view', { 53 templateUrl: 'views/view.html', 54 controller: 'ViewCtrl' 55 }).when('/download', { 56 templateUrl: 'views/download.html', 57 controller: 'DownloadCtrl' 58 }).otherwise({ 59 templateUrl: 'views/full.html', 60 controller: 'FullCtrl' 61 }) 62 }] 63) 64 65app.controller('BTorrentCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) { 66 let updateAll 67 $rootScope.version = VERSION 68 $rootScope.webtorrentVersion = WebTorrent.VERSION 69 ngNotify.config({ 70 duration: 5000, 71 html: true 72 }) 73 if (!WebTorrent.WEBRTC_SUPPORT) { 74 $rootScope.disabled = true 75 ngNotify.set('Please use a WebRTC compatible browser', { 76 type: 'error', 77 sticky: true, 78 button: false 79 }) 80 } 81 $rootScope.client = client 82 updateAll = function () { 83 if ($rootScope.client.processing) { 84 return 85 } 86 $rootScope.$apply() 87 } 88 setInterval(updateAll, 500) 89 $rootScope.seedFiles = function (files) { 90 let name 91 if ((files != null) && files.length > 0) { 92 if (files.length === 1) { 93 dbg(`Seeding file ${files[0].name}`) 94 } else { 95 dbg(`Seeding ${files.length} files`) 96 name = prompt('Please name your torrent', 'My Awesome Torrent') || 'My Awesome Torrent' 97 torrentOpts.name = name 98 } 99 $rootScope.client.processing = true 100 $rootScope.client.seed(files, torrentOpts, $rootScope.onSeed) 101 delete torrentOpts.name 102 } 103 } 104 $rootScope.openTorrentFile = function (file) { 105 if (file != null) { 106 dbg(`Adding torrent file ${file.name}`) 107 $rootScope.client.processing = true 108 $rootScope.client.add(file, torrentOpts, $rootScope.onTorrent) 109 } 110 } 111 $rootScope.client.on('error', function (err, torrent) { 112 $rootScope.client.processing = false 113 ngNotify.set(err, 'error') 114 er(err, torrent) 115 }) 116 $rootScope.addMagnet = function (magnet, onTorrent) { 117 if ((magnet != null) && magnet.length > 0) { 118 dbg(`Adding magnet/hash ${magnet}`) 119 $rootScope.client.processing = true 120 $rootScope.client.add(magnet, torrentOpts, onTorrent || $rootScope.onTorrent) 121 } 122 } 123 $rootScope.destroyedTorrent = function (err) { 124 if (err) { 125 throw err 126 } 127 dbg('Destroyed torrent', $rootScope.selectedTorrent) 128 $rootScope.selectedTorrent = null 129 $rootScope.client.processing = false 130 } 131 $rootScope.changePriority = function (file) { 132 if (file.priority === '-1') { 133 dbg('Deselected', file) 134 file.deselect() 135 } else { 136 dbg(`Selected with priority ${file.priority}`, file) 137 file.select(file.priority) 138 } 139 } 140 $rootScope.onTorrent = function (torrent, isSeed) { 141 dbg(torrent.magnetURI) 142 torrent.safeTorrentFileURL = URL.createObjectURL(torrent.torrentFileBlob) 143 torrent.fileName = `${torrent.name}.torrent` 144 if (!isSeed) { 145 dbg('Received metadata', torrent) 146 ngNotify.set(`Received ${torrent.name} metadata`) 147 if (!($rootScope.selectedTorrent != null)) { 148 $rootScope.selectedTorrent = torrent 149 } 150 $rootScope.client.processing = false 151 }
152 torrent.files.forEach(async function (file) { 153 if (isSeed) { 154 dbg('Started seeding', torrent) 155 if (!($rootScope.selectedTorrent != null)) { 156 $rootScope.selectedTorrent = torrent 157 } 158 $rootScope.client.processing = false 159 } 160 const blob = await file.blob() 161 file.url = URL.createObjectURL(blob) 162 }) 163 torrent.on('done', function () { 164 if (!isSeed) { 165 dbg('Done', torrent) 166 } 167 ngNotify.set(`<b>${torrent.name}</b> has finished downloading`, 'success') 168 }) 169 torrent.on('wire', function (wire, addr) { dbg(`Wire ${addr}`, torrent) }) 170 torrent.on('error', er) 171 } 172 $rootScope.onSeed = function (torrent) { $rootScope.onTorrent(torrent, true) } 173 dbg('Ready') 174} 175]) 176 177app.controller('FullCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) { 178 ngNotify.config({ 179 duration: 5000, 180 html: true 181 }) 182 $scope.addMagnet = function () { 183 $rootScope.addMagnet($scope.torrentInput) 184 $scope.torrentInput = '' 185 } 186 $scope.columns = [ 187 { 188 field: 'name', 189 cellTooltip: true, 190 minWidth: '200' 191 }, { 192 field: 'length', 193 name: 'Size', 194 cellFilter: 'pbytes', 195 width: '80' 196 }, { 197 field: 'received', 198 displayName: 'Downloaded', 199 cellFilter: 'pbytes', 200 width: '135' 201 }, { 202 field: 'downloadSpeed', 203 displayName: 'â Speed', 204 cellFilter: 'pbytes:1', 205 width: '100' 206 }, { 207 field: 'progress', 208 displayName: 'Progress', 209 cellFilter: 'progress', 210 width: '100' 211 }, { 212 field: 'timeRemaining', 213 displayName: 'ETA', 214 cellFilter: 'humanTime', 215 width: '140' 216 }, { 217 field: 'uploaded', 218 displayName: 'Uploaded', 219 cellFilter: 'pbytes', 220 width: '125' 221 }, { 222 field: 'uploadSpeed', 223 displayName: 'â Speed', 224 cellFilter: 'pbytes:1', 225 width: '100' 226 }, { 227 field: 'numPeers', 228 displayName: 'Peers', 229 width: '80' 230 }, { 231 field: 'ratio', 232 cellFilter: 'number:2', 233 width: '80' 234 } 235 ] 236 $scope.gridOptions = { 237 columnDefs: $scope.columns, 238 data: $rootScope.client.torrents, 239 enableColumnResizing: true, 240 enableColumnMenus: false, 241 enableRowSelection: true, 242 enableRowHeaderSelection: false, 243 multiSelect: false 244 } 245 $scope.gridOptions.onRegisterApi = function (gridApi) { 246 $scope.gridApi = gridApi 247 gridApi.selection.on.rowSelectionChanged($scope, function (row) { 248 if (!row.isSelected && ($rootScope.selectedTorrent != null) && ($rootScope.selectedTorrent.infoHash === row.entity.infoHash)) { 249 $rootScope.selectedTorrent = null 250 } else { 251 $rootScope.selectedTorrent = row.entity 252 } 253 }) 254 } 255 if ($location.hash() !== '') { 256 $rootScope.client.processing = true 257 setTimeout(function () { 258 dbg(`Adding ${$location.hash()}`) 259 $rootScope.addMagnet($location.hash()) 260 }, 0) 261 } 262} 263]) 264 265app.controller('DownloadCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) { 266 ngNotify.config({ 267 duration: 5000, 268 html: true 269 }) 270 $scope.addMagnet = function () { 271 $rootScope.addMagnet($scope.torrentInput) 272 $scope.torrentInput = '' 273 } 274 if ($location.hash() !== '') { 275 $rootScope.client.processing = true 276 setTimeout(function () { 277 dbg(`Adding ${$location.hash()}`) 278 $rootScope.addMagnet($location.hash()) 279 }, 0) 280 } 281} 282]) 283 284app.controller('ViewCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) { 285 let onTorrent 286 ngNotify.config({ 287 duration: 2000, 288 html: true 289 }) 290 onTorrent = function (torrent) { 291 $rootScope.viewerStyle = { 292 'margin-top': '-20px', 293 'text-align': 'center' 294 } 295 dbg(torrent.magnetURI) 296 torrent.safeTorrentFileURL = URL.createObjectURL(torrent.torrentFileBlob) 297 torrent.fileName = `${torrent.name}.torrent` 298 $rootScope.selectedTorrent = torrent 299 $rootScope.client.processing = false 300 dbg('Received metadata', torrent) 301 ngNotify.set(`Received ${torrent.name} metadata`)
302 torrent.files.forEach(function (file) { 303 file.streamTo(document.querySelector('#viewer')) 304 file.on('done', async function () { 305 dbg('Done ', file) 306 const blob = await file.blob() 307 file.url = URL.createObjectURL(blob) 308 }) 309 }) 310 torrent.on('done', function () { dbg('Done', torrent) }) 311 torrent.on('wire', function (wire, addr) { dbg(`Wire ${addr}`, torrent) }) 312 torrent.on('error', er) 313 } 314 $scope.addMagnet = function () { 315 $rootScope.addMagnet($scope.torrentInput, onTorrent) 316 $scope.torrentInput = '' 317 } 318 if ($location.hash() !== '') { 319 $rootScope.client.processing = true 320 setTimeout(function () { 321 dbg(`Adding ${$location.hash()}`) 322 $rootScope.addMagnet($location.hash(), onTorrent) 323 }, 0) 324 } 325} 326]) 327 328app.filter('html', [ 329 '$sce', function ($sce) { 330 return function (input) { 331 $sce.trustAsHtml(input) 332 } 333 } 334]) 335 336app.filter('pbytes', function () { 337 return function (num, speed) { 338 let exponent, unit, units 339 if (isNaN(num)) { 340 return '' 341 } 342 units = ['B', 'kB', 'MB', 'GB', 'TB'] 343 if (num < 1) { 344 return (speed ? '' : '0 B') 345 } 346 exponent = Math.min(Math.floor(Math.log(num) / 6.907755278982137), 8) 347 num = (num / Math.pow(1000, exponent)).toFixed(1) * 1 348 unit = units[exponent] 349 return `${num} ${unit}${speed ? '/s' : ''}` 350 } 351}) 352 353app.filter('humanTime', function () { 354 return function (millis) { 355 let remaining 356 if (millis < 1000) { 357 return '' 358 } 359 remaining = moment.duration(millis).humanize() 360 return remaining[0].toUpperCase() + remaining.substr(1) 361 } 362}) 363 364app.filter('progress', function () { return function (num) { return `${(100 * num).toFixed(1)}%` } })
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.