diff --git a/index.html b/index.html
index 0909cf0..ad03067 100644
--- a/index.html
+++ b/index.html
@@ -15,5 +15,5 @@
File upload/host
Pastebin
Donate
-Calculator
-Games
\ No newline at end of file
+Games
+Tools
\ No newline at end of file
diff --git a/tools/Btorrent/CNAME b/tools/Btorrent/CNAME
new file mode 100644
index 0000000..e246f7d
--- /dev/null
+++ b/tools/Btorrent/CNAME
@@ -0,0 +1 @@
+btorrent.xyz
\ No newline at end of file
diff --git a/tools/Btorrent/LICENSE b/tools/Btorrent/LICENSE
new file mode 100644
index 0000000..e1c1ba3
--- /dev/null
+++ b/tools/Btorrent/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Diego Rodríguez Baquero
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/tools/Btorrent/README.md b/tools/Btorrent/README.md
new file mode 100644
index 0000000..5be5e46
--- /dev/null
+++ b/tools/Btorrent/README.md
@@ -0,0 +1,79 @@
+βTorrent
+========
+**[βTorrent]** is a fully-featured **[WebTorrent]** browser client written in HTML, JS and CSS
+
+### Features
+- [x] Informative GUI with easy sharing options
+- [x] Downloading from an info hash or magnet URI
+- [x] Downloading from a .torrent file (Coming Soon)
+- [x] Seeding files (Single/multiple files)
+- [ ] Seeding CORS-enabled remote files (Coming Soon)
+- [x] Download/Upload speed per torrent
+- [x] Download/Upload speed of client (All torrents)
+- [x] Removing torrents from the client
+- [x] Pause/Resume torrent
+- [x] Selecting/Deselecting files (Coming Soon)
+- [x] Client Debugging
+- [ ] Use custom trackers/rtcConfig
+
+### Built with
+- [WebTorrent]
+- [AngularJS]
+- [Skeleton]
+- [Normalize.css]
+- [Moment.js]
+- [ui-grid]
+- [pretty-bytes]
+- [ng-file-upload]
+- [ng-notify]
+
+Website powered by [jsDelivr] and [CloudFlare]. I use [nginx] in my server.
+
+### HTML5 serving
+**You must serve index.html as the default**
+
+For nginx, use this conf:
+```
+ location / {
+ try_files $uri$args $uri$args/ /index.html;
+ }
+```
+
+### Enable Debugging
+Enable βTorrent (Debug logging) and WebTorrent (Logs logging) debug logs by running this in the developer console:
+```js
+localStorage.debug = '*'
+```
+Disable by running this:
+```js
+localStorage.removeItem('debug')
+```
+
+### Help βTorrent
+- **[Create a new issue](https://github.com/DiegoRBaquero/bTorrent/issues/new)** to report bugs or suggest new features
+- **[Send a PR](https://github.com/DiegoRBaquero/BTorrent/pull/new/master)** with your changes
+
+### Thanks
+- [jasalo](https://github.com/jasalo) For the logo and favicon
+- [whitef0x0](https://github.com/whitef0x0) For cleanup and ng-file-upload and other ideas
+
+### License
+MIT. Copyright (c) [Diego Rodríguez Baquero](https://diegorbaquero.com)
+
+[βTorrent]: https://btorrent.xyz
+[WebTorrent]: https://webtorrent.io
+[AngularJS]: https://angularjs.org/
+[Skeleton]: http://getskeleton.com/
+[Normalize.css]: https://necolas.github.io/normalize.css/
+[Moment.js]: http://momentjs.com/
+[ui-grid]: http://ui-grid.info/
+[pretty-bytes]: https://github.com/sindresorhus/pretty-bytes
+[ng-file-upload]: https://github.com/danialfarid/ng-file-upload
+[ng-notify]: https://github.com/matowens/ng-notify
+[Jade]: http://jade-lang.com/
+[CoffeeScript]: http://coffeescript.org/
+[Sass]: http://sass-lang.com/
+[Harp]: http://harpjs.com/
+[jsDelivr]: https://www.jsdelivr.com/
+[CloudFlare]: https://www.cloudflare.com/
+[nginx]: http://nginx.org/
diff --git a/tools/Btorrent/app.js b/tools/Btorrent/app.js
new file mode 100644
index 0000000..c3b648a
--- /dev/null
+++ b/tools/Btorrent/app.js
@@ -0,0 +1,373 @@
+/* global WebTorrent, angular, moment, prompt */
+
+const VERSION = '1.1'
+const trackers = ['wss://tracker.btorrent.xyz', 'wss://tracker.openwebtorrent.com']
+const rtcConfig = {
+ 'iceServers': [
+ {
+ 'urls': ['stun:stun.l.google.com:19305', 'stun:stun1.l.google.com:19305']
+ }
+ ]
+}
+
+const torrentOpts = {
+ announce: trackers
+}
+
+const trackerOpts = {
+ announce: trackers,
+ rtcConfig: rtcConfig
+}
+
+const debug = window.localStorage.getItem('debug') !== null
+
+const dbg = function (string, item, color) {
+ color = color !== null ? color : '#333333'
+ if (debug) {
+ if (item && item.name) {
+ 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}`)
+ } else {
+ return console.debug(`%cβTorrent:client %c${string}`, 'color: #33C3F0', `color: ${color}`)
+ }
+ }
+}
+
+const er = function (err, item) { dbg(err, item, '#FF0000') }
+
+dbg(`Starting v${VERSION}. WebTorrent ${WebTorrent.VERSION}`)
+
+const client = new WebTorrent({
+ tracker: trackerOpts
+})
+
+const app = angular.module('BTorrent',
+ ['ngRoute', 'ui.grid', 'ui.grid.resizeColumns', 'ui.grid.selection', 'ngFileUpload', 'ngNotify'],
+ ['$compileProvider', '$locationProvider', '$routeProvider', function ($compileProvider, $locationProvider, $routeProvider) {
+ $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|magnet|blob|javascript):/)
+ $locationProvider.html5Mode({
+ enabled: true,
+ requireBase: false
+ }).hashPrefix('#')
+ $routeProvider.when('/view', {
+ templateUrl: 'views/view.html',
+ controller: 'ViewCtrl'
+ }).when('/download', {
+ templateUrl: 'views/download.html',
+ controller: 'DownloadCtrl'
+ }).otherwise({
+ templateUrl: 'views/full.html',
+ controller: 'FullCtrl'
+ })
+ }]
+)
+
+app.controller('BTorrentCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) {
+ let updateAll
+ $rootScope.version = VERSION
+ $rootScope.webtorrentVersion = WebTorrent.VERSION
+ ngNotify.config({
+ duration: 5000,
+ html: true
+ })
+ if (!WebTorrent.WEBRTC_SUPPORT) {
+ $rootScope.disabled = true
+ ngNotify.set('Please use a WebRTC compatible browser', {
+ type: 'error',
+ sticky: true,
+ button: false
+ })
+ }
+ $rootScope.client = client
+ updateAll = function () {
+ if ($rootScope.client.processing) {
+ return
+ }
+ $rootScope.$apply()
+ }
+ setInterval(updateAll, 500)
+ $rootScope.seedFiles = function (files) {
+ let name
+ if ((files != null) && files.length > 0) {
+ if (files.length === 1) {
+ dbg(`Seeding file ${files[0].name}`)
+ } else {
+ dbg(`Seeding ${files.length} files`)
+ name = prompt('Please name your torrent', 'My Awesome Torrent') || 'My Awesome Torrent'
+ torrentOpts.name = name
+ }
+ $rootScope.client.processing = true
+ $rootScope.client.seed(files, torrentOpts, $rootScope.onSeed)
+ delete torrentOpts.name
+ }
+ }
+ $rootScope.openTorrentFile = function (file) {
+ if (file != null) {
+ dbg(`Adding torrent file ${file.name}`)
+ $rootScope.client.processing = true
+ $rootScope.client.add(file, torrentOpts, $rootScope.onTorrent)
+ }
+ }
+ $rootScope.client.on('error', function (err, torrent) {
+ $rootScope.client.processing = false
+ ngNotify.set(err, 'error')
+ er(err, torrent)
+ })
+ $rootScope.addMagnet = function (magnet, onTorrent) {
+ if ((magnet != null) && magnet.length > 0) {
+ dbg(`Adding magnet/hash ${magnet}`)
+ $rootScope.client.processing = true
+ $rootScope.client.add(magnet, torrentOpts, onTorrent || $rootScope.onTorrent)
+ }
+ }
+ $rootScope.destroyedTorrent = function (err) {
+ if (err) {
+ throw err
+ }
+ dbg('Destroyed torrent', $rootScope.selectedTorrent)
+ $rootScope.selectedTorrent = null
+ $rootScope.client.processing = false
+ }
+ $rootScope.changePriority = function (file) {
+ if (file.priority === '-1') {
+ dbg('Deselected', file)
+ file.deselect()
+ } else {
+ dbg(`Selected with priority ${file.priority}`, file)
+ file.select(file.priority)
+ }
+ }
+ $rootScope.onTorrent = function (torrent, isSeed) {
+ dbg(torrent.magnetURI)
+ torrent.safeTorrentFileURL = torrent.torrentFileBlobURL
+ torrent.fileName = `${torrent.name}.torrent`
+ if (!isSeed) {
+ dbg('Received metadata', torrent)
+ ngNotify.set(`Received ${torrent.name} metadata`)
+ if (!($rootScope.selectedTorrent != null)) {
+ $rootScope.selectedTorrent = torrent
+ }
+ $rootScope.client.processing = false
+ }
+ torrent.files.forEach(function (file) {
+ file.getBlobURL(function (err, url) {
+ if (err) {
+ throw err
+ }
+ if (isSeed) {
+ dbg('Started seeding', torrent)
+ if (!($rootScope.selectedTorrent != null)) {
+ $rootScope.selectedTorrent = torrent
+ }
+ $rootScope.client.processing = false
+ }
+ file.url = url
+ if (!isSeed) {
+ dbg('Done ', file)
+ ngNotify.set(`${file.name} ready for download`, 'success')
+ }
+ })
+ })
+ torrent.on('done', function () {
+ if (!isSeed) {
+ dbg('Done', torrent)
+ }
+ ngNotify.set(`${torrent.name} has finished downloading`, 'success')
+ })
+ torrent.on('wire', function (wire, addr) { dbg(`Wire ${addr}`, torrent) })
+ torrent.on('error', er)
+ }
+ $rootScope.onSeed = function (torrent) { $rootScope.onTorrent(torrent, true) }
+ dbg('Ready')
+}
+])
+
+app.controller('FullCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) {
+ ngNotify.config({
+ duration: 5000,
+ html: true
+ })
+ $scope.addMagnet = function () {
+ $rootScope.addMagnet($scope.torrentInput)
+ $scope.torrentInput = ''
+ }
+ $scope.columns = [
+ {
+ field: 'name',
+ cellTooltip: true,
+ minWidth: '200'
+ }, {
+ field: 'length',
+ name: 'Size',
+ cellFilter: 'pbytes',
+ width: '80'
+ }, {
+ field: 'received',
+ displayName: 'Downloaded',
+ cellFilter: 'pbytes',
+ width: '135'
+ }, {
+ field: 'downloadSpeed',
+ displayName: '↓ Speed',
+ cellFilter: 'pbytes:1',
+ width: '100'
+ }, {
+ field: 'progress',
+ displayName: 'Progress',
+ cellFilter: 'progress',
+ width: '100'
+ }, {
+ field: 'timeRemaining',
+ displayName: 'ETA',
+ cellFilter: 'humanTime',
+ width: '140'
+ }, {
+ field: 'uploaded',
+ displayName: 'Uploaded',
+ cellFilter: 'pbytes',
+ width: '125'
+ }, {
+ field: 'uploadSpeed',
+ displayName: '↑ Speed',
+ cellFilter: 'pbytes:1',
+ width: '100'
+ }, {
+ field: 'numPeers',
+ displayName: 'Peers',
+ width: '80'
+ }, {
+ field: 'ratio',
+ cellFilter: 'number:2',
+ width: '80'
+ }
+ ]
+ $scope.gridOptions = {
+ columnDefs: $scope.columns,
+ data: $rootScope.client.torrents,
+ enableColumnResizing: true,
+ enableColumnMenus: false,
+ enableRowSelection: true,
+ enableRowHeaderSelection: false,
+ multiSelect: false
+ }
+ $scope.gridOptions.onRegisterApi = function (gridApi) {
+ $scope.gridApi = gridApi
+ gridApi.selection.on.rowSelectionChanged($scope, function (row) {
+ if (!row.isSelected && ($rootScope.selectedTorrent != null) && ($rootScope.selectedTorrent.infoHash = row.entity.infoHash)) {
+ $rootScope.selectedTorrent = null
+ } else {
+ $rootScope.selectedTorrent = row.entity
+ }
+ })
+ }
+ if ($location.hash() !== '') {
+ $rootScope.client.processing = true
+ setTimeout(function () {
+ dbg(`Adding ${$location.hash()}`)
+ $rootScope.addMagnet($location.hash())
+ }, 0)
+ }
+}
+])
+
+app.controller('DownloadCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) {
+ ngNotify.config({
+ duration: 5000,
+ html: true
+ })
+ $scope.addMagnet = function () {
+ $rootScope.addMagnet($scope.torrentInput)
+ $scope.torrentInput = ''
+ }
+ if ($location.hash() !== '') {
+ $rootScope.client.processing = true
+ setTimeout(function () {
+ dbg(`Adding ${$location.hash()}`)
+ $rootScope.addMagnet($location.hash())
+ }, 0)
+ }
+}
+])
+
+app.controller('ViewCtrl', ['$scope', '$rootScope', '$http', '$log', '$location', 'ngNotify', function ($scope, $rootScope, $http, $log, $location, ngNotify) {
+ let onTorrent
+ ngNotify.config({
+ duration: 2000,
+ html: true
+ })
+ onTorrent = function (torrent) {
+ $rootScope.viewerStyle = {
+ 'margin-top': '-20px',
+ 'text-align': 'center'
+ }
+ dbg(torrent.magnetURI)
+ torrent.safeTorrentFileURL = torrent.torrentFileBlobURL
+ torrent.fileName = `${torrent.name}.torrent`
+ $rootScope.selectedTorrent = torrent
+ $rootScope.client.processing = false
+ dbg('Received metadata', torrent)
+ ngNotify.set(`Received ${torrent.name} metadata`)
+ torrent.files.forEach(function (file) {
+ file.appendTo('#viewer')
+ file.getBlobURL(function (err, url) {
+ if (err) {
+ throw err
+ }
+ file.url = url
+ dbg('Done ', file)
+ })
+ })
+ torrent.on('done', function () { dbg('Done', torrent) })
+ torrent.on('wire', function (wire, addr) { dbg(`Wire ${addr}`, torrent) })
+ torrent.on('error', er)
+ }
+ $scope.addMagnet = function () {
+ $rootScope.addMagnet($scope.torrentInput, onTorrent)
+ $scope.torrentInput = ''
+ }
+ if ($location.hash() !== '') {
+ $rootScope.client.processing = true
+ setTimeout(function () {
+ dbg(`Adding ${$location.hash()}`)
+ $rootScope.addMagnet($location.hash(), onTorrent)
+ }, 0)
+ }
+}
+])
+
+app.filter('html', [
+ '$sce', function ($sce) {
+ return function (input) {
+ $sce.trustAsHtml(input)
+ }
+ }
+])
+
+app.filter('pbytes', function () {
+ return function (num, speed) {
+ let exponent, unit, units
+ if (isNaN(num)) {
+ return ''
+ }
+ units = ['B', 'kB', 'MB', 'GB', 'TB']
+ if (num < 1) {
+ return (speed ? '' : '0 B')
+ }
+ exponent = Math.min(Math.floor(Math.log(num) / 6.907755278982137), 8)
+ num = (num / Math.pow(1000, exponent)).toFixed(1) * 1
+ unit = units[exponent]
+ return `${num} ${unit}${speed ? '/s' : ''}`
+ }
+})
+
+app.filter('humanTime', function () {
+ return function (millis) {
+ let remaining
+ if (millis < 1000) {
+ return ''
+ }
+ remaining = moment.duration(millis).humanize()
+ return remaining[0].toUpperCase() + remaining.substr(1)
+ }
+})
+
+app.filter('progress', function () { return function (num) { return `${(100 * num).toFixed(1)}%` } })
diff --git a/tools/Btorrent/favicon.ico b/tools/Btorrent/favicon.ico
new file mode 100644
index 0000000..2e58017
Binary files /dev/null and b/tools/Btorrent/favicon.ico differ
diff --git a/tools/Btorrent/index.html b/tools/Btorrent/index.html
new file mode 100644
index 0000000..4700e08
--- /dev/null
+++ b/tools/Btorrent/index.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+ βTorrent: Browser WebTorrent Client
+
+
+
+
+
+
+
+
+
+
+
+
+ βTorrent v{{$root.version}}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tools/Btorrent/style.css b/tools/Btorrent/style.css
new file mode 100644
index 0000000..caec711
--- /dev/null
+++ b/tools/Btorrent/style.css
@@ -0,0 +1,87 @@
+body {
+ height: 100%;
+ width: 100%; }
+
+header, footer, .center {
+ text-align: center; }
+
+th, td {
+ padding: 2px 15px;
+ max-width: 200px;
+ overflow: auto;
+ white-space: nowrap; }
+
+h2, h3, h4, h5, h6, ul, li {
+ margin-bottom: 0; }
+
+footer {
+ margin-top: 10px; }
+
+.container {
+ width: 95%;
+ max-width: 95%; }
+
+.grid {
+ margin-bottom: 20px;
+ width: 100%;
+ height: 200px; }
+
+.version {
+ color: #ccc;
+ font-size: 0.3em; }
+
+.views {
+ margin-top: -20px;
+ margin-bottom: 10px; }
+
+.download-button {
+ margin-left: 10px; }
+
+.no-margin {
+ margin: 0px; }
+
+.spinner {
+ position: absolute;
+ top: 30%;
+ left: 30%;
+ width: 40%;
+ height: 40%;
+ z-index: 1000;
+ background-color: grey;
+ border-radius: 25px;
+ opacity: .8;
+ text-align: center; }
+
+.spinner-icon {
+ position: relative;
+ top: 50%;
+ margin-top: -100px;
+ font-size: 200px;
+ transform: translateY(-50%); }
+
+.button.button-danger,
+button.button-danger,
+input[type="submit"].button-danger,
+input[type="reset"].button-danger,
+input[type="button"].button-danger {
+ color: #FFF;
+ background-color: #D9534F;
+ border-color: #D9534F; }
+
+.button.button-danger:hover,
+button.button-danger:hover,
+input[type="submit"].button-danger:hover,
+input[type="reset"].button-danger:hover,
+input[type="button"].button-danger:hover,
+.button.button-danger:focus,
+button.button-danger:focus,
+input[type="submit"].button-danger:focus,
+input[type="reset"].button-danger:focus,
+input[type="button"].button-danger:focus {
+ color: #FFF;
+ background-color: #C43E3A;
+ border-color: #C43E3A; }
+
+a {
+ text-decoration: none
+}
diff --git a/tools/Btorrent/views/download.html b/tools/Btorrent/views/download.html
new file mode 100644
index 0000000..fde09c1
--- /dev/null
+++ b/tools/Btorrent/views/download.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+ or...
+ Open torrent file
+
+
+
+
+
Information
+
+
+
+ Name
+ {{$root.selectedTorrent.name}}
+
+
+ Size
+ {{$root.selectedTorrent.length | pbytes}}
+
+
+ Completed
+ {{$root.selectedTorrent.progress | progress}} ({{$root.selectedTorrent.downloaded | pbytes}})
+
+
+ Peers
+ {{$root.selectedTorrent.numPeers}}
+
+
+ ↓ Speed
+ {{$root.selectedTorrent.downloadSpeed | pbytes:1}}
+
+
+ ETA
+ {{$root.selectedTorrent.timeRemaining | humanTime}}
+
+
+
+
+
+
Files
+
+
+
+ Name
+ Size
+ Priority
+
+
+
+
+ {{file.name}}
+ {{file.name}}
+ {{file.length | pbytes}}
+
+
+ High Priority
+ Low Priority
+ Don't download
+
+
+
+
+
+
↑ Click a file to download it
+
+
+
+
\ No newline at end of file
diff --git a/tools/Btorrent/views/full.html b/tools/Btorrent/views/full.html
new file mode 100644
index 0000000..2d094ec
--- /dev/null
+++ b/tools/Btorrent/views/full.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+ Download
+
+
+ Open torrent file
+
+
+ Seed files
+
+
+
+
+
+
{{$root.selectedTorrent.name}}
+ Pause
+ Resume
+ Remove
+
+
Share
+
+
+
+
Files
+
+
+
+ Name
+ Size
+ Priority
+
+
+
+
+ {{file.name}}
+ {{file.name}}
+ {{file.length | pbytes}}
+
+
+ High Priority
+ Low Priority
+ Don't download
+
+
+
+
+
+
+
+
Client Stats:
+ ↓ {{$root.client.downloadSpeed | pbytes}}/s ·
+ ↑ {{$root.client.uploadSpeed | pbytes}}/s ·
+ Ratio: {{$root.client.ratio | number:2}}
+
\ No newline at end of file
diff --git a/tools/Btorrent/views/view.html b/tools/Btorrent/views/view.html
new file mode 100644
index 0000000..040a902
--- /dev/null
+++ b/tools/Btorrent/views/view.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+ or...
+ Open torrent file
+
+
+
Downloaded {{$root.selectedTorrent.downloaded | pbytes}}/{{$root.selectedTorrent.length | pbytes}} ({{$root.selectedTorrent.progress | progress}}) at {{$root.selectedTorrent.downloadSpeed | pbytes:1}} from {{$root.selectedTorrent.numPeers}} peers. ETA: {{$root.selectedTorrent.timeRemaining | humanTime}}
+
\ No newline at end of file
diff --git a/tools/index.html b/tools/index.html
new file mode 100644
index 0000000..86c61db
--- /dev/null
+++ b/tools/index.html
@@ -0,0 +1,3 @@
+Calculator
+Online torrent downloader/seeder
+Chat app made with scratch's cloud variable system
\ No newline at end of file