whisper.cat stuff

This commit is contained in:
[Harper Innes]
2023-10-05 23:28:32 +11:00
parent 2a6e28637c
commit f127b4fea8
5224 changed files with 919361 additions and 0 deletions

372
weboasis/torrent/app.js Normal file
View File

@ -0,0 +1,372 @@
const VERSION = '1.0'
const trackers = ['wss://tracker.sloppyta.co:443/announce', 'wss://tracker.openwebtorrent.com', 'wss://tracker.novage.com.ua:443/announce', 'wss://tracker.btorrent.xyz', 'wss://tracker.webtorrent.io']
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}`)
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
ngNotify.config({
duration: 5000,
html: true
})
if (!WebTorrent.WEBRTC_SUPPORT) {
$rootScope.disabled = true
ngNotify.set('Please use latest Chrome, Firefox or Opera', {
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(`<b>${file.name}</b> ready for download`, 'success')
}
})
})
torrent.on('done', function () {
if (!isSeed) {
dbg('Done', torrent)
}
ngNotify.set(`<b>${torrent.name}</b> 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)}%` } })

File diff suppressed because one or more lines are too long

849
weboasis/torrent/css/libraries.min.css vendored Normal file
View File

@ -0,0 +1,849 @@
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS and IE text size adjust after device orientation change,
* without disabling user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability of focused elements when they are also in an
* active/hover state.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
box-sizing: content-box; /* 2 */
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
/*
* Skeleton V2.0.4
* Copyright 2014, Dave Gamache
* www.getskeleton.com
* Free to use under the MIT license.
* http://www.opensource.org/licenses/mit-license.php
* 12/29/2014
*/
/* Table of contents
- Grid
- Base Styles
- Typography
- Links
- Buttons
- Forms
- Lists
- Code
- Tables
- Spacing
- Utilities
- Clearing
- Media Queries
*/
/* Grid
*/
.container {
position: relative;
width: 100%;
max-width: 960px;
margin: 0 auto;
padding: 0 20px;
box-sizing: border-box; }
.column,
.columns {
width: 100%;
float: left;
box-sizing: border-box; }
/* For devices larger than 400px */
@media (min-width: 400px) {
.container {
width: 85%;
padding: 0; }
}
/* For devices larger than 550px */
@media (min-width: 550px) {
.container {
width: 80%; }
.column,
.columns {
margin-left: 4%; }
.column:first-child,
.columns:first-child {
margin-left: 0; }
.one.column,
.one.columns { width: 4.66666666667%; }
.two.columns { width: 13.3333333333%; }
.three.columns { width: 22%; }
.four.columns { width: 30.6666666667%; }
.five.columns { width: 39.3333333333%; }
.six.columns { width: 48%; }
.seven.columns { width: 56.6666666667%; }
.eight.columns { width: 65.3333333333%; }
.nine.columns { width: 74.0%; }
.ten.columns { width: 82.6666666667%; }
.eleven.columns { width: 91.3333333333%; }
.twelve.columns { width: 100%; margin-left: 0; }
.one-third.column { width: 30.6666666667%; }
.two-thirds.column { width: 65.3333333333%; }
.one-half.column { width: 48%; }
/* Offsets */
.offset-by-one.column,
.offset-by-one.columns { margin-left: 8.66666666667%; }
.offset-by-two.column,
.offset-by-two.columns { margin-left: 17.3333333333%; }
.offset-by-three.column,
.offset-by-three.columns { margin-left: 26%; }
.offset-by-four.column,
.offset-by-four.columns { margin-left: 34.6666666667%; }
.offset-by-five.column,
.offset-by-five.columns { margin-left: 43.3333333333%; }
.offset-by-six.column,
.offset-by-six.columns { margin-left: 52%; }
.offset-by-seven.column,
.offset-by-seven.columns { margin-left: 60.6666666667%; }
.offset-by-eight.column,
.offset-by-eight.columns { margin-left: 69.3333333333%; }
.offset-by-nine.column,
.offset-by-nine.columns { margin-left: 78.0%; }
.offset-by-ten.column,
.offset-by-ten.columns { margin-left: 86.6666666667%; }
.offset-by-eleven.column,
.offset-by-eleven.columns { margin-left: 95.3333333333%; }
.offset-by-one-third.column,
.offset-by-one-third.columns { margin-left: 34.6666666667%; }
.offset-by-two-thirds.column,
.offset-by-two-thirds.columns { margin-left: 69.3333333333%; }
.offset-by-one-half.column,
.offset-by-one-half.columns { margin-left: 52%; }
}
/* Base Styles
*/
/* NOTE
html is set to 62.5% so that all the REM measurements throughout Skeleton
are based on 10px sizing. So basically 1.5rem = 15px :) */
html {
font-size: 62.5%; }
body {
font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */
line-height: 1.6;
font-weight: 400;
font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #222; }
/* Typography
*/
h1, h2, h3, h4, h5, h6 {
margin-top: 0;
margin-bottom: 2rem;
font-weight: 300; }
h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;}
h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; }
h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; }
h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; }
h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; }
h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; }
/* Larger than phablet */
@media (min-width: 550px) {
h1 { font-size: 5.0rem; }
h2 { font-size: 4.2rem; }
h3 { font-size: 3.6rem; }
h4 { font-size: 3.0rem; }
h5 { font-size: 2.4rem; }
h6 { font-size: 1.5rem; }
}
p {
margin-top: 0; }
/* Links
*/
a {
color: #1EAEDB; }
a:hover {
color: #0FA0CE; }
/* Buttons
*/
.button,
button,
input[type="submit"],
input[type="reset"],
input[type="button"] {
display: inline-block;
height: 38px;
padding: 0 30px;
color: #555;
text-align: center;
font-size: 11px;
font-weight: 600;
line-height: 38px;
letter-spacing: .1rem;
text-transform: uppercase;
text-decoration: none;
white-space: nowrap;
background-color: transparent;
border-radius: 4px;
border: 1px solid #bbb;
cursor: pointer;
box-sizing: border-box; }
.button:hover,
button:hover,
input[type="submit"]:hover,
input[type="reset"]:hover,
input[type="button"]:hover,
.button:focus,
button:focus,
input[type="submit"]:focus,
input[type="reset"]:focus,
input[type="button"]:focus {
color: #333;
border-color: #888;
outline: 0; }
.button.button-primary,
button.button-primary,
input[type="submit"].button-primary,
input[type="reset"].button-primary,
input[type="button"].button-primary {
color: #FFF;
background-color: #33C3F0;
border-color: #33C3F0; }
.button.button-primary:hover,
button.button-primary:hover,
input[type="submit"].button-primary:hover,
input[type="reset"].button-primary:hover,
input[type="button"].button-primary:hover,
.button.button-primary:focus,
button.button-primary:focus,
input[type="submit"].button-primary:focus,
input[type="reset"].button-primary:focus,
input[type="button"].button-primary:focus {
color: #FFF;
background-color: #1EAEDB;
border-color: #1EAEDB; }
/* Forms
*/
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea,
select {
height: 38px;
padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */
background-color: #fff;
border: 1px solid #D1D1D1;
border-radius: 4px;
box-shadow: none;
box-sizing: border-box; }
/* Removes awkward default styles on some inputs for iOS */
input[type="email"],
input[type="number"],
input[type="search"],
input[type="text"],
input[type="tel"],
input[type="url"],
input[type="password"],
textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none; }
textarea {
min-height: 65px;
padding-top: 6px;
padding-bottom: 6px; }
input[type="email"]:focus,
input[type="number"]:focus,
input[type="search"]:focus,
input[type="text"]:focus,
input[type="tel"]:focus,
input[type="url"]:focus,
input[type="password"]:focus,
textarea:focus,
select:focus {
border: 1px solid #33C3F0;
outline: 0; }
label,
legend {
display: block;
margin-bottom: .5rem;
font-weight: 600; }
fieldset {
padding: 0;
border-width: 0; }
input[type="checkbox"],
input[type="radio"] {
display: inline; }
label > .label-body {
display: inline-block;
margin-left: .5rem;
font-weight: normal; }
/* Lists
*/
ul {
list-style: circle inside; }
ol {
list-style: decimal inside; }
ol, ul {
padding-left: 0;
margin-top: 0; }
ul ul,
ul ol,
ol ol,
ol ul {
margin: 1.5rem 0 1.5rem 3rem;
font-size: 90%; }
li {
margin-bottom: 1rem; }
/* Code
*/
code {
padding: .2rem .5rem;
margin: 0 .2rem;
font-size: 90%;
white-space: nowrap;
background: #F1F1F1;
border: 1px solid #E1E1E1;
border-radius: 4px; }
pre > code {
display: block;
padding: 1rem 1.5rem;
white-space: pre; }
/* Tables
*/
th,
td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #E1E1E1; }
th:first-child,
td:first-child {
padding-left: 0; }
th:last-child,
td:last-child {
padding-right: 0; }
/* Spacing
*/
button,
.button {
margin-bottom: 1rem; }
input,
textarea,
select,
fieldset {
margin-bottom: 1.5rem; }
pre,
blockquote,
dl,
figure,
table,
p,
ul,
ol,
form {
margin-bottom: 2.5rem; }
/* Utilities
*/
.u-full-width {
width: 100%;
box-sizing: border-box; }
.u-max-full-width {
max-width: 100%;
box-sizing: border-box; }
.u-pull-right {
float: right; }
.u-pull-left {
float: left; }
/* Misc
*/
hr {
margin-top: 3rem;
margin-bottom: 3.5rem;
border-width: 0;
border-top: 1px solid #E1E1E1; }
/* Clearing
*/
/* Self Clearing Goodness */
.container:after,
.row:after,
.u-cf {
content: "";
display: table;
clear: both; }
/* Media Queries
*/
/*
Note: The best way to structure the use of media queries is to create the queries
near the relevant code. For example, if you wanted to change the styles for buttons
on small devices, paste the mobile query code up in the buttons section and style it
there.
*/
/* Larger than mobile */
@media (min-width: 400px) {}
/* Larger than phablet (also point when grid becomes active) */
@media (min-width: 550px) {}
/* Larger than tablet */
@media (min-width: 750px) {}
/* Larger than desktop */
@media (min-width: 1000px) {}
/* Larger than Desktop HD */
@media (min-width: 1200px) {}
.ngn{color:#FFF;cursor:default;display:none;font-size:1.3em;left:0;opacity:1;padding:25px 80px;position:fixed;right:0;text-align:center;user-select:none;z-index:9999;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none}.ngn-component{position:absolute}.ngn-top{top:0}.ngn-bottom{bottom:0}.ngn-dismiss{background-color:#333;border-radius:15px;box-shadow:inset 2px 2px 7px 2px #000;color:#DDD;cursor:pointer;display:none;font-size:1.25em;font-weight:700;height:30px;line-height:30px;opacity:.2;position:absolute;right:40px;text-shadow:1px 1px 5px #000;top:25px;width:30px}.ngn-sticky .ngn-dismiss{display:block}.ngn-dismiss:hover{background-color:#000}.ngn-dismiss:active{background-color:#666}@media only screen and (max-width:480px){.ngn{font-size:1em;padding:12px 25px}.ngn-dismiss{font-size:1em;height:20px;line-height:20px;right:5px;top:5px;width:20px}}.ngn-info{background-color:#0e90d2}.ngn-error{background-color:#dd514c}.ngn-success{background-color:#5eb95e}.ngn-warn{background-color:#f37b1d}.ngn-grimace{background-color:#8058a5}.ngn-prime.ngn-info{background-color:#03c}.ngn-prime.ngn-error{background-color:red}.ngn-prime.ngn-success{background-color:#0c0}.ngn-prime.ngn-warn{background-color:#f90}.ngn-prime.ngn-grimace{background-color:#609}.ngn-pastel.ngn-info{background-color:#7EA7D8}.ngn-pastel.ngn-error{background-color:#F6989D}.ngn-pastel.ngn-success{background-color:#82CA9D}.ngn-pastel.ngn-warn{background-color:#FDC68A}.ngn-pastel.ngn-grimace{background-color:#A187BE}.ngn-pitchy.ngn-info{background-color:#003471}.ngn-pitchy.ngn-error{background-color:#9E0B0F}.ngn-pitchy.ngn-success{background-color:#007236}.ngn-pitchy.ngn-warn{background-color:#A36209}.ngn-pitchy.ngn-grimace{background-color:#440E62}

1
weboasis/torrent/css/style.min.css vendored Normal file
View File

@ -0,0 +1 @@
body{height:100%;width:100%}.center,footer,header{text-align:center}td,th{padding:2px 15px;max-width:200px;overflow:auto;white-space:nowrap}h2,h3,h4,h5,h6,li,ul{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:.3em}.views{margin-top:-20px;margin-bottom:10px}.download-button{margin-left:10px}.no-margin{margin:0}.spinner{position:absolute;top:30%;left:30%;width:40%;height:40%;z-index:1000;background-color:gray;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=button].button-danger,input[type=reset].button-danger,input[type=submit].button-danger{color:#fff;background-color:#d9534f;border-color:#d9534f}.button.button-danger:focus,.button.button-danger:hover,button.button-danger:focus,button.button-danger:hover,input[type=button].button-danger:focus,input[type=button].button-danger:hover,input[type=reset].button-danger:focus,input[type=reset].button-danger:hover,input[type=submit].button-danger:focus,input[type=submit].button-danger:hover{color:#fff;background-color:#c43e3a;border-color:#c43e3a}a{text-decoration:none}

4
weboasis/torrent/css/ui-grid.min.css vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html ng-app="BTorrent" lang="en">
<head>
<base href="https://weboasis.whisper.cat/torrent/" />
<meta charset="UTF-8" />
<title>WebOasis - WebTorrent Client</title>
<meta name="description" content="Browser Torrent Client" />
<meta name="keywords" content="client, webtorrent, browser, torrent, stream, bittorrent, torrenting, sharing, filesharing, stream, streaming, stream torrents" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="libraries.min.js"></script>
<link rel="stylesheet" href="css/libraries.min.css" />
<link rel="stylesheet" href="css/font-awesome.min.css" />
<link rel="stylesheet" href="css/ui-grid.min.css" />
<link rel="stylesheet" href="css/style.min.css" />
</head>
<body ng-controller="BTorrentCtrl" ng-cloak="">
<header>
<h1>WebTorrent Client</h1>
<div class="views" ng-show="$root.client.torrents.length == 0">
<a ng-href="/torrent/#">Full</a> | <a ng-href="/torrent/download">Single Download</a> |
<a ng-href="/torrent/view">Stream / View</a>
</div>
<div class="aligncenter" style="width: 468px; display: inline-block;"></div>
</header>
<div id="viewer" ng-style="$root.viewerStyle"></div>
<div id="view" ng-view></div>
<footer>
<a class="button" href="https://weboasis.whisper.cat/torrent/make" target="_blank">Create A Torrent File</a><br />
<a class="button" href="https://weboasis.whisper.cat/torrent/t2m" target="_blank">Convert Torrent to Magnet</a><br />
<a class="button" href="https://yutzuko-torrent.appspot.com" target="_blank">Batch Torrent Editor</a><br />
<a class="button" href="https://webtor.io" target="_blank">WebTor.io</a><br />
<a class="button" href="https://weboasis.whisper.cat">Back to WebOasis.whisper.cat</a>
</footer>
<div class="spinner" ng-show="client.processing"><i class="fa fa-spinner fa-spin spinner-icon"></i></div>
<script src="app.js"></script>
</body>
</html>

405
weboasis/torrent/libraries.min.js vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1005 B

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 B

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="description" content="Convert torrent files to magnet URIs using Javascript" />
<meta name="keywords" content="torrent,magnet,javascript,convert,online,sha1,bencode,base32" />
<title>WebOas.is - Torrent to Magnet Converter</title>
<link rel="shortcut icon" href="favicon.png" />
<link rel="stylesheet" href="style.css" />
<script src="src/t2m.loader.js"></script>
</head>
<body>
<div class="header_bar">
<div class="main main_no_overflow header">
<table class="header_table"><tbody>
<tr>
<td class="header_table_cell header_table_cell_full">
<div class="header_table_description">
<div class="header_table_description_name"><h3>Torrent to Magnet</h3></div>
</div>
</td>
</tr>
</tbody></table>
</div>
</div>
<div class="main body">
<p><div class="script_disabled script_visible">Javascript is required to use the inline converter.</div>
<div class="script_enabled">
<div class="converter">
<div class="converter_container">
<div class="converter_table">
<div class="converter_cell converter_cell_left">
<div class="converter_svg_container">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1" class="converter_svg_graphic">
<polygon points="0.7,0 0.7,0.5 1,0.5 0.5,1 0,0.5 0.3,0.5 0.3,0" class="converter_svg_graphic_poly"></polygon>
</svg>
</div>
</div>
<div class="converter_cell converter_cell_right">
<div class="converter_info">
<div class="converter_info_line1">Drop <a class="link_external light_underline link_stop_propagation" href="https://en.wikipedia.org/wiki/Torrent_file" target="_blank"><span>.torrent</span></a> files here</div>
<div class="converter_info_line2">to convert them to <a class="link_external light_underline link_stop_propagation" href="https://en.wikipedia.org/wiki/Magnet_uri" target="_blank"><span>magnet URI</span></a>s</div>
<div class="converter_info_line3">Or click to open the file browser</div>
<input type="file" class="converter_files_input" multiple />
</div>
</div>
</div>
</div>
</div>
</div></p>
<div class="converted"></div>
</div>
</body>
</html>

View File

@ -0,0 +1,103 @@
// Module for encoding/decoding base32
var Base32 = (function () {
"use strict";
// Vars used
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
pad_lengths = [ 0, 1, 3, 4, 6 ],
pad_char = "=";
// Encode/decode functions
return {
/**
Encode a string into base32
@param str
The string to convert.
This string should be encoded in some way such that each character is in the range [0,255]
@return
A base32 encoded string
*/
encode: function (str) {
var len = str.length,
str_new = "",
i = 0,
c1, c2, c3, c4, c5;
// Null pad
while ((str.length % 5) !== 0) str += "\x00";
// String modify
while (i < len) {
c1 = str.charCodeAt(i++);
c2 = str.charCodeAt(i++);
c3 = str.charCodeAt(i++);
c4 = str.charCodeAt(i++);
c5 = str.charCodeAt(i++);
str_new += alphabet[(c1 >> 3)];
str_new += alphabet[((c1 & 0x07) << 2) | (c2 >> 6)];
str_new += alphabet[((c2 & 0x3F) >> 1)];
str_new += alphabet[((c2 & 0x01) << 4) | (c3 >> 4)];
str_new += alphabet[((c3 & 0x0F) << 1) | (c4 >> 7)];
str_new += alphabet[((c4 & 0x7F) >> 2)];
str_new += alphabet[((c4 & 0x03) << 3) | (c5 >> 5)];
str_new += alphabet[(c5 & 0x1F)];
}
// Padding
if (i > len) {
i = pad_lengths[i - len]; // (i - len) equals the number of times \x00 was padded
str_new = str_new.substr(0, str_new.length - i);
while ((str_new.length % 8) !== 0) str_new += pad_char;
}
// Done
return str_new;
},
/**
Decode a string from base32
@param str
A valid base32 string
@return
The original string
*/
decode: function (str) {
var len = str.length,
str_new = "",
bits = 0,
char_buffer = 0,
i;
// Cut off padding
while (len > 0 && str[len - 1] == "=") --len;
// Iterate
for (i = 0; i < len; ++i) {
// Update with the 32bit value
char_buffer = (char_buffer << 5) | alphabet.indexOf(str[i]);
// Update bitcount
bits += 5;
if (bits >= 8) {
// Update string
str_new += String.fromCharCode((char_buffer >> (bits - 8)) & 0xFF);
bits -= 8;
}
}
// Done
return str_new;
},
};
})();

View File

@ -0,0 +1,202 @@
// Module for encoding/decoding Bencoded data
var Bencode = (function () {
"use strict";
// Encoding functions
var encode = function (value) {
// Type
var t = typeof(value);
// Number
if (t == "number") return encode_int(Math.floor(value));
// String
if (t == "string") return encode_string(value);
// Array
if (Array.isArray(value)) return encode_list(value);
// Dict
return encode_dict(value);
};
var encode_int = function (value) {
return "i" + value + "e";
};
var encode_string = function (value) {
return "" + value.length + ":" + value;
};
var encode_list = function (value) {
var str = [ "l" ],
i;
// List values
for (i = 0; i < value.length; ++i) {
str.push(encode(value[i]));
}
// End
str.push("e");
return str.join("");
};
var encode_dict = function (value) {
var str = [ "d" ],
keys = [],
i;
// Get and sort keys
for (i in value) keys.push(i);
keys.sort();
// Push values
for (i = 0; i < keys.length; ++i) {
str.push(encode_string(keys[i]));
str.push(encode(value[keys[i]]));
}
// End
str.push("e");
return str.join("");
};
// Decoding class
var Decoder = function () {
this.pos = 0;
};
Decoder.prototype = {
constructor: Decoder,
decode: function (str) {
// Errors
var k = str[this.pos];
if (!(k in decode_generic)) throw "Invalid format";
// Call
return decode_generic[k].call(this, str);
},
decode_int: function (str) {
// Skip the "i" prefix
++this.pos;
var end = str.indexOf("e", this.pos),
value;
// No end
if (end < 0) throw "Invalid format";
// Assume proper number format
value = parseInt(str.substr(this.pos, end - this.pos), 10);
// Done
this.pos = end + 1;
return value;
},
decode_string: function (str) {
var delim = str.indexOf(":", this.pos),
length, value;
// No end
if (delim < 0) throw "Invalid format";
// Assume proper number format
length = parseInt(str.substr(this.pos, delim - this.pos), 10);
value = str.substr(delim + 1, length);
// Done
this.pos = delim + length + 1;
return value;
},
decode_list: function (str) {
// Skip the "l" prefix
++this.pos;
// Read list
var list = [],
value;
// Loop until end or exception
while (str[this.pos] != "e") {
value = this.decode(str); // this throws errors if str[this.pos] is out of bounds
list.push(value);
}
// Done; skip "e" suffix
++this.pos;
return list;
},
decode_dict: function (str) {
// Skip the "d" prefix
++this.pos;
// Read dict
var dict = {},
key, value;
// Loop until end or exception
while (str[this.pos] != "e") {
key = this.decode_string(str);
value = this.decode(str); // this throws errors if str[this.pos] is out of bounds
dict[key] = value;
}
// Done; skip "e" suffix
++this.pos;
return dict;
},
};
// Generic decode functions
var decode_generic = {
"l": Decoder.prototype.decode_list,
"d": Decoder.prototype.decode_dict,
"i": Decoder.prototype.decode_int,
},
i;
for (i = 0; i < 10; ++i) decode_generic[i.toString()] = Decoder.prototype.decode_string;
// Encode/decode functions
return {
/**
encode: function (obj)
Encodes an object into a Bencode'd string
@param obj
The object to encode
This should only be one of the following:
string
number (floats are floor'd to integers)
array (containing things only from this list)
object (containing things only from this list)
Strings should be encoded in some way such that each character is in the range [0,255]
@return
A string representing the object
*/
encode: encode,
/**
decode: function (str)
Decodes a Bencode'd string back into its original type
@param str
The string to decode
@return
The original object represented by str
@throws
Any one of the following self-explanatory strings
"Invalid format"
"Invalid string"
"Invalid int"
*/
decode: function (str) {
// Create a decoder and call
return (new Decoder()).decode(str);
},
};
})();

View File

@ -0,0 +1,235 @@
// Class for SHA1 computation
var SHA1 = (function () {
"use strict";
// Private variables
var hash_size = 20,
message_block_length = 64,
message_block_terminator = 0x80,
message_length_bytes = 8,
initial_intermediate_hash = new Uint32Array(5),
K_constants = new Uint32Array(4);
initial_intermediate_hash[0] = 0x67452301;
initial_intermediate_hash[1] = 0xEFCDAB89;
initial_intermediate_hash[2] = 0x98BADCFE;
initial_intermediate_hash[3] = 0x10325476;
initial_intermediate_hash[4] = 0xC3D2E1F0;
K_constants[0] = 0x5A827999;
K_constants[1] = 0x6ED9EBA1;
K_constants[2] = 0x8F1BBCDC;
K_constants[3] = 0xCA62C1D6;
/**
SHA1 instance constructor; creates an empty SHA1 object.
@return
A new SHA1 instance
*/
var SHA1 = function () {
this.length = 0;
this.message_block_index = 0;
this.message_block = new Uint8Array(message_block_length);
this.intermediate_hash = new Uint32Array(initial_intermediate_hash);
};
// Private methods
var pad = function () {
var maxlen = this.message_block.length - message_length_bytes,
high = Math.floor(this.length / 0x0FFFFFFFF) & 0xFFFFFFFF,
low = this.length & 0xFFFFFFFF,
message_block = this.message_block,
message_block_index = this.message_block_index,
input_intermediate_hash = this.intermediate_hash,
output_intermediate_hash = new Uint32Array(this.intermediate_hash.length);
// Termination byte
message_block[message_block_index] = message_block_terminator;
// Process another block if there's no space for the length
if (message_block_index >= maxlen) {
// 0-ify
while (++message_block_index < message_block.length) message_block[message_block_index] = 0;
// Process block
process.call(this, message_block, input_intermediate_hash, output_intermediate_hash);
// Create copies that don't interfere with "this"
message_block = new Uint8Array(message_block.length); // no 0-ifying needed
input_intermediate_hash = output_intermediate_hash;
}
else {
// 0-ify
while (++message_block_index < maxlen) message_block[message_block_index] = 0;
}
// Store length
message_block[maxlen] = (high >>> 24) & 0xFF;
message_block[++maxlen] = (high >>> 16) & 0xFF;
message_block[++maxlen] = (high >>> 8) & 0xFF;
message_block[++maxlen] = (high) & 0xFF;
message_block[++maxlen] = (low >>> 24) & 0xFF;
message_block[++maxlen] = (low >>> 16) & 0xFF;
message_block[++maxlen] = (low >>> 8) & 0xFF;
message_block[++maxlen] = (low) & 0xFF;
process.call(this, message_block, input_intermediate_hash, output_intermediate_hash);
// Return hash
return output_intermediate_hash;
};
var process = function (message_block, intermediate_hash_input, intermediate_hash_output) {
var W = new Uint32Array(80),
i, i4, temp, A, B, C, D, E;
// Init W
for (i = 0; i < 16; ++i) {
i4 = i * 4;
W[i] =
(message_block[i4] << 24) |
(message_block[i4 + 1] << 16) |
(message_block[i4 + 2] << 8) |
(message_block[i4 + 3]);
}
for (/*i = 16*/; i < 80; ++i) {
W[i] = circular_shift(1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]);
}
A = intermediate_hash_input[0];
B = intermediate_hash_input[1];
C = intermediate_hash_input[2];
D = intermediate_hash_input[3];
E = intermediate_hash_input[4];
for (i = 0; i < 20; ++i) {
temp = circular_shift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + K_constants[0];
E = D;
D = C;
C = circular_shift(30, B);
B = A;
A = temp & 0xFFFFFFFF;
}
for (/*i = 20*/; i < 40; ++i) {
temp = circular_shift(5, A) + (B ^ C ^ D) + E + W[i] + K_constants[1];
E = D;
D = C;
C = circular_shift(30, B);
B = A;
A = temp & 0xFFFFFFFF;
}
for (/*i = 40*/; i < 60; ++i) {
temp = circular_shift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + K_constants[2];
E = D;
D = C;
C = circular_shift(30, B);
B = A;
A = temp & 0xFFFFFFFF;
}
for (/*i = 60*/; i < 80; ++i) {
temp = circular_shift(5, A) + (B ^ C ^ D) + E + W[i] + K_constants[3];
E = D;
D = C;
C = circular_shift(30, B);
B = A;
A = temp & 0xFFFFFFFF;
}
intermediate_hash_output[0] = intermediate_hash_input[0] + A;
intermediate_hash_output[1] = intermediate_hash_input[1] + B;
intermediate_hash_output[2] = intermediate_hash_input[2] + C;
intermediate_hash_output[3] = intermediate_hash_input[3] + D;
intermediate_hash_output[4] = intermediate_hash_input[4] + E;
};
var circular_shift = function (bits, word) {
return (word << bits) | (word >>> (32 - bits));
};
// Public methods
SHA1.prototype = {
constructor: SHA1,
/**
Reset the state of the hashing object to its initial state.
*/
reset: function () {
// Reset everything
var i;
this.length = 0;
this.message_block_index = 0;
for (i = 0; i < this.intermediate_hash.length; ++i) {
this.intermediate_hash[i] = initial_intermediate_hash[i];
}
for (i = 0; i < this.message_block.length; ++i) {
this.message_block[i] = 0;
}
},
/**
Feed data into the instance to update the hash.
@param value_array
The values to update with. This can either be:
- A string (encoded so that every character is in the range [0,255])
- A (typed) array
*/
update: function (value_array) {
var is_string = (typeof(value_array) == "string"),
i;
for (i = 0; i < value_array.length; ++i) {
// Update block
this.message_block[this.message_block_index] = is_string ? value_array.charCodeAt(i) : value_array[i];
// Update length
this.length += 8;
// Process block
if (++this.message_block_index >= this.message_block.length) {
process.call(this, this.message_block, this.intermediate_hash, this.intermediate_hash);
this.message_block_index = 0;
}
}
},
/**
Computes the SHA1 digest of the data input so far.
@return
A Uint8Array of the computed digest
*/
digest: function () {
// Setup
var digest = new Uint8Array(hash_size),
intermediate_hash_temp = pad.call(this),
i;
// Hash
for (i = 0; i < digest.length; ++i) {
digest[i] = intermediate_hash_temp[i >> 2] >> (8 * (3 - (i & 0x03)));
}
// Done
return digest;
},
};
// Return the class object
return SHA1;
})();

View File

@ -0,0 +1,102 @@
// Functions for testing sha1.js
(function () {
"use strict";
// Tests
var tests = [
{
value: "abc",
repeat: 1,
correct: "A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D",
},
{
value: "abcdbcdecdefdefgefghfghighijhi" + "jkijkljklmklmnlmnomnopnopq",
repeat: 1,
correct: "84 98 3E 44 1C 3B D2 6E BA AE 4A A1 F9 51 29 E5 E5 46 70 F1",
},
{
value: "a",
repeat: 1000000,
correct: "34 AA 97 3C D4 C4 DA A4 F6 1E EB 2B DB AD 27 31 65 34 01 6F",
},
{
value: "01234567012345670123456701234567" + "01234567012345670123456701234567",
repeat: 10,
correct: "DE A3 56 A2 CD DD 90 C7 A7 EC ED C5 EB B5 63 93 4F 46 04 52",
},
{
value: "a",
repeat: 133047678,
correct: "94 A8 41 C9 02 1D 23 57 A8 0A 77 60 01 E2 7D 2F 27 93 91 05",
},
];
// Readable hex
var digest_to_spaced_hex = function (digest) {
// String build
var s = [],
c, i;
for (i = 0; i < digest.length; ++i) {
if (i > 0) s.push(" ");
c = digest[i].toString(16).toUpperCase();
if (c.length < 2) s.push("0");
s.push(c);
}
return s.join("");
};
// Test function
var execute_tests = function () {
// Vars for testing
var sha = new SHA1(),
failures = 0,
test, digest, i, j;
// Run tests
for (i = 0; i < tests.length; ++i) {
test = tests[i];
console.log("Test " + (i + 1) + ": " + JSON.stringify(test.value) + " repeated " + test.repeat + " time" + (test.repeat == 1 ? "" : "s"));
sha.reset();
for (j = 0; j < test.repeat; ++j) {
sha.update(test.value);
}
digest = digest_to_spaced_hex(sha.digest());
if (digest == test.correct) {
console.log("Digest matches: YES");
console.log(" " + digest);
}
else {
console.log("Digest matches: NO");
console.log(" " + digest + "(calculated)");
console.log(" " + test.correct + " (correct)");
++failures;
}
console.log("");
}
// Final status
if (failures === 0) {
console.log("All tests okay");
}
else {
console.log("" + failures + " test" + (failures == 1 ? "" : "s") + " failed");
}
};
execute_tests();
})();

View File

@ -0,0 +1,752 @@
var T2M = (function () {
"use strict";
// Module for encoding/decoding UTF8
var UTF8 = (function () {
return {
/**
Encode a string into UTF-8
@param str
The string to convert.
This string should be encoded in some way such that each character is in the range [0,255]
@return
A UTF-8 encoded string
*/
encode: function (str) {
return unescape(encodeURIComponent(str));
},
/**
Decode a string from UTF-8
@param str
A valid UTF-8 string
@return
The original string
*/
decode: function (str) {
return decodeURIComponent(escape(str));
},
};
})();
// Class for reading .torrent files
var Torrent = (function () {
var Torrent = function () {
this.events = {
"load": [],
"error": [],
"read_error": [],
"read_abort": [],
};
this.file_name = null;
this.data = null;
};
var on_reader_load = function (event) {
// Decode
var data_str = event.target.result;
if (data_str instanceof ArrayBuffer) {
// Convert to string
var data_str2 = "",
i;
data_str = new Uint8Array(data_str);
for (i = 0; i < data_str.length; ++i) {
data_str2 += String.fromCharCode(data_str[i]);
}
data_str = data_str2;
}
try {
this.data = Bencode.decode(data_str);
}
catch (e) {
// Throw an error
this.data = null;
this.file_name = null;
trigger.call(this, "error", {
type: "Bencode error",
exception: e,
});
return;
}
// Loaded
trigger.call(this, "load", {});
};
var on_reader_error = function () {
trigger.call(this, "read_error", {});
};
var on_reader_abort = function () {
trigger.call(this, "read_abort", {});
};
var trigger = function (event, data) {
// Trigger an event
var callbacks = this.events[event],
i;
for (i = 0; i < callbacks.length; ++i) {
callbacks[i].call(this, data, event);
}
};
var no_change = function (x) {
return x;
};
var magnet_component_order_default = [ "xt" , "xl" , "dn" , "tr" ];
var format_uri = function (array_values, encode_fcn) {
if (array_values.length <= 1) return encode_fcn(array_values[0]);
return array_values[0].replace(/\{([0-9]+)\}/, function (match) {
return encode_fcn(array_values[parseInt(match[1], 10) + 1] || "");
});
};
/**
Convert URI components object into a magnet URI.
This is used to format the same object multiple times without rehashing anything.
@param link_components
An object returned from convert_to_magnet with return_components=true
@param custom_name
Can take one of the following values:
null/undefined: name will remain the same as it originally was
string: the custom name to give the magnet URI
@param tracker_mode
Can take one of the following values:
null/undefined/false/number < 0: single tracker only (primary one)
true: multiple trackers (without numbered suffix)
number >= 0: multiple trackers (with numbered suffix starting at the specified number)
@param uri_encode
Can take one of the following values:
null/undefined/true: encode components using encodeURIComponent
false: no encoding; components are left as-is
function: custom encoding function
@param component_order
A list containing the order URI components should appear in.
Default is [ "xt" , "xl" , "dn" , "tr" ]
null/undefined will use the default
@return
A formatted URI
*/
Torrent.components_to_magnet = function (link_components, custom_name, tracker_mode, uri_encode, component_order) {
// Vars
var link, obj, list1, val, i, j;
uri_encode = (uri_encode === false) ? no_change : (typeof(uri_encode) == "function" ? uri_encode : encodeURIComponent);
component_order = (component_order === null) ? magnet_component_order_default : component_order;
// Setup
if (typeof(custom_name) == "string") {
link_components.dn.values = [ custom_name ];
}
link_components.tr.suffix = -1;
if (typeof(tracker_mode) == "number") {
tracker_mode = Math.floor(tracker_mode);
if (tracker_mode >= 0) link_components.tr.suffix = tracker_mode;
}
else if (tracker_mode === true) {
link_components.tr.suffix = -2;
}
// Form into a URL
link = "magnet:";
val = 0; // number of components added
for (i = 0; i < component_order.length; ++i) {
if (!(component_order[i] in link_components)) continue; // not valid
obj = link_components[component_order[i]];
list1 = obj.values;
for (j = 0; j < list1.length; ++j) {
// Separator
link += (val === 0 ? "?" : "&");
++val;
// Key
link += component_order[i];
// Number
if (obj.suffix >= 0 && list1.length > 1) {
link += ".";
link += obj.suffix;
++obj.suffix;
}
// Value
link += "=";
link += format_uri(list1[j], uri_encode);
// Done
if (obj.suffix == -1) break;
}
}
// Done
return link;
};
Torrent.prototype = {
constructor: Torrent,
read: function (file) {
this.data = null;
this.file_name = file.name;
var reader = new FileReader();
reader.addEventListener("load", on_reader_load.bind(this), false);
reader.addEventListener("error", on_reader_error.bind(this), false);
reader.addEventListener("abort", on_reader_abort.bind(this), false);
try {
reader.readAsBinaryString(file);
}
catch (e) {
reader.readAsArrayBuffer(file);
}
},
on: function (event, callback) {
if (event in this.events) {
this.events[event].push(callback);
return true;
}
return false;
},
off: function (event, callback) {
if (event in this.events) {
var callbacks = this.events[event],
i;
for (i = 0; i < callbacks.length; ++i) {
if (callbacks[i] == callback) {
callbacks.splice(i, 1);
return true;
}
}
}
return false;
},
/**
Convert the torrent data into a magnet link.
@param custom_name
Can take one of the following values:
null/undefined: no custom name will be generated, but if the name field is absent, it will be assumed from the original file's name
false: no custom name will be generated OR assumed from the original file name
string: the custom name to give the magnet URI
@param tracker_mode
Can take one of the following values:
null/undefined/false/number < 0: single tracker only (primary one)
true: multiple trackers (without numbered suffix)
number >= 0: multiple trackers (with numbered suffix starting at the specified number)
@param uri_encode
Can take one of the following values:
null/undefined/true: encode components using encodeURIComponent
false: no encoding; components are left as-is
function: custom encoding function
@param component_order
A list containing the order URI components should appear in.
Default is [ "xt" , "xl" , "dn" , "tr" ]
null/undefined will use the default
@param return_components
If true, this returns the link components which can then be used with components_to_magnet
@return
A formatted URI if return_components is falsy, else an object containing the parts of the link
Also can return null if insufficient data is found
*/
convert_to_magnet: function (custom_name, tracker_mode, uri_encode, component_order, return_components) {
// Insufficient data
if (this.data === null || !("info" in this.data)) return null;
// Bencode info
var info = this.data.info,
info_bencoded = Bencode.encode(info),
info_hasher = new SHA1(),
link_components = {},
info_hash, link, list1, list2, val, i, j;
// Hash
info_hasher.update(info_bencoded);
info_hash = info_hasher.digest();
info_hash = String.fromCharCode.apply(null, info_hash); // convert to binary string
info_hash = Base32.encode(info_hash); // convert to base32
// Setup link
for (i = 0; i < magnet_component_order_default.length; ++i) {
link_components[magnet_component_order_default[i]] = {
suffix: -1,
values: [],
};
}
// Create
link_components.xt.values.push([ "urn:btih:{0}", info_hash ]);
if ("length" in info) {
link_components.xl.values.push([ info.length ]);
}
if (typeof(custom_name) == "string") {
link_components.dn.values.push([ custom_name ]);
}
else if ("name" in info) {
link_components.dn.values.push([ UTF8.decode(info.name) ]);
}
else if (custom_name !== false && this.file_name) {
link_components.dn.values.push([ this.file_name ]);
}
list1 = link_components.tr.values;
if ("announce" in this.data) {
list1.push([ UTF8.decode(this.data.announce) ]);
}
if ("announce-list" in this.data && Array.isArray(list2 = this.data["announce-list"])) {
// Add more trackers
for (i = 0; i < list2.length; ++i) {
if (!Array.isArray(list2[i])) continue; // bad data
for (j = 0; j < list2[i].length; ++j) {
val = UTF8.decode(list2[i][j]);
if (list1.indexOf(val) < 0) list1.push([ val ]);
}
}
}
// Convert
if (return_components) return link_components;
link = Torrent.components_to_magnet(link_components, null, tracker_mode, uri_encode, component_order);
// Done
return link;
},
};
return Torrent;
})();
// Class for enumerating the results in the DOM
var Result = (function () {
var Result = function () {
this.torrent_magnet_components = null;
this.container = null;
this.magnet_link = null;
this.magnet_link_text = null;
this.magnet_textbox = null;
this.options_link = null;
this.options_container = null;
this.options = null;
};
var on_options_link_click = function () {
if (this.options_container.classList.contains("converted_item_options_container_visible")) {
this.options_container.classList.remove("converted_item_options_container_visible");
this.magnet_textbox.readOnly = true;
}
else {
this.options_container.classList.add("converted_item_options_container_visible");
this.magnet_textbox.readOnly = false;
}
};
var on_textbox_click = function () {
if (this.magnet_textbox.readOnly) {
this.magnet_textbox.select();
}
};
var on_textbox_keydown = function () {
if (this.magnet_textbox.readOnly) return;
setTimeout(on_textbox_update.bind(this), 10);
};
var on_textbox_change = function () {
on_textbox_update.call(this);
};
var on_textbox_update = function () {
// Get value
var uri = this.magnet_textbox.value,
protocol = "magnet:";
// Must have correct protocol
if (uri.substr(0, protocol.length).toLowerCase() != protocol) {
if (uri.length < protocol.length && uri.toLowerCase() == protocol.substr(0, uri.length)) {
// Almost correct
uri += protocol.substr(uri.length);
}
else {
// Wrong
uri = protocol + uri;
}
}
// Update
this.magnet_link.setAttribute("href", uri);
this.magnet_link_text.textContent = uri;
};
var on_option_change = function () {
update_links.call(this, true);
};
var update_links = function (update_displays) {
// Update magnet links
var magnet_uri = "magnet:asdf",
tracker_mode = false,
order = [ "xt" ];
if (this.options[0][0][1].checked) {
order.push("dn");
}
if (this.options[1][0][1].checked) {
order.push("xl");
}
if (this.options[2][0][1].checked) {
order.push("tr");
if (this.options[2][1][1].checked) {
tracker_mode = true;
if (this.options[2][2][1].checked) {
tracker_mode = 1;
}
}
}
magnet_uri = Torrent.components_to_magnet(this.torrent_magnet_components, null, tracker_mode, true, order);
// Update text/values
this.magnet_link.setAttribute("href", magnet_uri);
this.magnet_link_text.textContent = magnet_uri;
this.magnet_textbox.value = magnet_uri;
if (!update_displays) return;
// Update display
var i, j, opt_list;
for (i = 0; i < this.options.length; ++i) {
opt_list = this.options[i];
for (j = 0; j < opt_list.length; ++j) {
if (!opt_list[j][1].checked) {
// This is unchecked; modify boxes after
opt_list[j][0].classList.add("converted_item_option_part_visible");
// Hide
while (++j < opt_list.length) {
opt_list[j][0].classList.remove("converted_item_option_part_visible");
opt_list[j][1].checked = false;
}
break;
}
}
}
};
Result.prototype = {
constructor: Result,
generate: function (torrent_object, parent_node) {
var n1, n2, n3, n4, n5, i, j, ev_bind;
// Clear
this.options = [];
//{ Setup DOM nodes
this.container = document.createElement("div");
this.container.className = "converted_item";
// Title
n1 = document.createElement("div");
n1.className = "converted_item_title_container";
n2 = document.createElement("div");
n2.className = "converted_item_title";
n2.textContent = torrent_object.file_name || (torrent_object.data && torrent_object.data.info ? torrent_object.data.name : null) || ".torrent";
n1.appendChild(n2);
this.container.appendChild(n1);
// Contents
n1 = document.createElement("div");
n1.className = "converted_item_contents";
// Links
n2 = document.createElement("div");
n2.className = "converted_item_link_container";
this.magnet_link = document.createElement("a");
this.magnet_link.className = "converted_item_link";
this.magnet_link_text = document.createElement("span");
this.magnet_link.appendChild(this.magnet_link_text);
n2.appendChild(this.magnet_link);
this.magnet_textbox = document.createElement("input");
this.magnet_textbox.className = "converted_item_textbox";
this.magnet_textbox.setAttribute("type", "text");
this.magnet_textbox.readOnly = true;
n2.appendChild(this.magnet_textbox);
n1.appendChild(n2);
// Options container
this.options_container = document.createElement("div");
this.options_container.className = "converted_item_options_container";
// Header
n2 = document.createElement("div");
n2.className = "converted_item_header";
n3 = document.createElement("span");
n3.className = "converted_item_header_text";
n3.textContent = "Options:";
n2.appendChild(n3);
this.options_link = document.createElement("a");
this.options_link.className = "converted_item_options_toggle";
n3 = document.createElement("span");
this.options_link.appendChild(n3);
n2.appendChild(this.options_link);
this.options_container.appendChild(n2);
// Options
n2 = document.createElement("div");
n2.className = "converted_item_options";
// Name
this.options.push([]);
n3 = document.createElement("div");
n3.className = "converted_item_option";
n4 = document.createElement("label");
n4.className = "converted_item_option_part converted_item_option_part_visible";
n5 = document.createElement("input");
n5.className = "converted_item_option_checkbox checkbox";
n5.setAttribute("type", "checkbox");
n5.checked = true;
n4.appendChild(n5);
this.options[this.options.length - 1].push([ n4 , n5 ]);
n5 = document.createElement("span");
n5.className = "converted_item_option_text";
n5.textContent = "Include name";
n4.appendChild(n5);
n3.appendChild(n4);
n2.appendChild(n3);
// Data length
this.options.push([]);
n3 = document.createElement("div");
n3.className = "converted_item_option";
n4 = document.createElement("label");
n4.className = "converted_item_option_part converted_item_option_part_visible";
n5 = document.createElement("input");
n5.className = "converted_item_option_checkbox checkbox";
n5.setAttribute("type", "checkbox");
n5.checked = true;
n4.appendChild(n5);
this.options[this.options.length - 1].push([ n4 , n5 ]);
n5 = document.createElement("span");
n5.className = "converted_item_option_text";
n5.textContent = "Include data length";
n4.appendChild(n5);
n3.appendChild(n4);
n2.appendChild(n3);
// Tracker
this.options.push([]);
n3 = document.createElement("div");
n3.className = "converted_item_option";
n4 = document.createElement("label");
n4.className = "converted_item_option_part converted_item_option_part_visible";
n5 = document.createElement("input");
n5.className = "converted_item_option_checkbox checkbox";
n5.setAttribute("type", "checkbox");
n5.checked = true;
n4.appendChild(n5);
this.options[this.options.length - 1].push([ n4 , n5 ]);
n5 = document.createElement("span");
n5.className = "converted_item_option_text";
n5.textContent = "Include tracker";
n4.appendChild(n5);
n3.appendChild(n4);
n4 = document.createElement("label");
n4.className = "converted_item_option_part converted_item_option_part_visible";
n5 = document.createElement("input");
n5.className = "converted_item_option_checkbox checkbox";
n5.setAttribute("type", "checkbox");
n5.checked = false;
n4.appendChild(n5);
this.options[this.options.length - 1].push([ n4 , n5 ]);
n5 = document.createElement("span");
n5.className = "converted_item_option_text";
n5.textContent = "Allow multiple trackers";
n4.appendChild(n5);
n3.appendChild(n4);
n4 = document.createElement("label");
n4.className = "converted_item_option_part";
n5 = document.createElement("input");
n5.className = "converted_item_option_checkbox checkbox";
n5.setAttribute("type", "checkbox");
n5.checked = false;
n4.appendChild(n5);
this.options[this.options.length - 1].push([ n4 , n5 ]);
n5 = document.createElement("span");
n5.className = "converted_item_option_text";
n5.textContent = "Numbered keys";
n4.appendChild(n5);
n3.appendChild(n4);
n2.appendChild(n3);
// Add options
this.options_container.appendChild(n2);
n1.appendChild(this.options_container);
// Done
this.container.appendChild(n1);
//}
// Data
this.torrent_magnet_components = torrent_object.convert_to_magnet(null, false, true, null, true);
update_links.call(this, false);
// Events
this.options_link.addEventListener("click", on_options_link_click.bind(this), false);
this.magnet_textbox.addEventListener("click", on_textbox_click.bind(this), false);
this.magnet_textbox.addEventListener("keydown", on_textbox_keydown.bind(this), false);
this.magnet_textbox.addEventListener("change", on_textbox_change.bind(this), false);
ev_bind = on_option_change.bind(this);
for (i = 0; i < this.options.length; ++i) {
for (j = 0; j < this.options[i].length; ++j) {
this.options[i][j][1].addEventListener("change", ev_bind, false);
}
}
// Rice and add
if (rice_checkboxes) {
rice_checkboxes(this.container.querySelectorAll("input[type=checkbox].checkbox"));
}
if (parent_node) parent_node.appendChild(this.container);
},
update: function () {
},
};
return Result;
})();
// Other functions
var rice_checkboxes = null;
var on_torrent_load = function () {
var container = document.querySelector(".converted"),
result;
if (container === null) return;
container.classList.add("converted_visible");
result = new Result();
result.generate(this, container);
};
// Exposed functions
var functions = {
setup: function (rice_checkboxes_import) {
rice_checkboxes = rice_checkboxes_import;
},
queue_torrent_files: function (files) {
// Read files
var i, t;
for (i = 0; i < files.length; ++i) {
t = new Torrent();
t.on("load", on_torrent_load);
t.read(files[i]);
}
},
};
return functions;
})();

View File

@ -0,0 +1,418 @@
(function () {
"use strict";
// Function for performing actions as soon as possible
var on_ready = (function () {
// Vars
var callbacks = [],
check_interval = null,
check_interval_time = 250;
// Check if ready and run callbacks
var callback_check = function () {
if (
(document.readyState === "interactive" || document.readyState === "complete") &&
callbacks !== null
) {
// Run callbacks
var cbs = callbacks,
cb_count = cbs.length,
i;
// Clear
callbacks = null;
for (i = 0; i < cb_count; ++i) {
cbs[i].call(null);
}
// Clear events and checking interval
window.removeEventListener("load", callback_check, false);
window.removeEventListener("readystatechange", callback_check, false);
if (check_interval !== null) {
clearInterval(check_interval);
check_interval = null;
}
// Okay
return true;
}
// Not executed
return false;
};
// Listen
window.addEventListener("load", callback_check, false);
window.addEventListener("readystatechange", callback_check, false);
// Callback adding function
return function (cb) {
if (callbacks === null) {
// Ready to execute
cb.call(null);
}
else {
// Delay
callbacks.push(cb);
// Set a check interval
if (check_interval === null && callback_check() !== true) {
check_interval = setInterval(callback_check, check_interval_time);
}
}
};
})();
// Functions
var script_add = (function () {
var script_on_load = function (state) {
// Okay
script_remove_event_listeners.call(this, state, true);
};
var script_on_error = function (state) {
// Error
script_remove_event_listeners.call(this, state, false);
};
var script_on_readystatechange = function (state) {
if (this.readyState === "loaded" || this.readyState === "complete") {
// Okay
script_remove_event_listeners.call(this, state, true);
}
};
var script_remove_event_listeners = function (state, okay) {
// Remove event listeners
this.addEventListener("load", state.on_load, false);
this.addEventListener("error", state.on_error, false);
this.addEventListener("readystatechange", state.on_readystatechange, false);
state.on_load = null;
state.on_error = null;
state.on_readystatechange = null;
// Trigger
if (state.callback) state.callback.call(null, okay, this);
// Remove
var par = this.parentNode;
if (par) par.removeChild(this);
};
return function (url, callback) {
var head = document.head,
script, state;
if (!head) {
// Callback and done
callback.call(null, false, null);
return false;
}
// Load state
state = {
on_load: null,
on_error: null,
on_readystatechange: null,
callback: callback,
};
// New script tag
script = document.createElement("script");
script.async = true;
script.setAttribute("src", url);
// Events
script.addEventListener("load", (state.on_load = script_on_load.bind(script, state)), false);
script.addEventListener("error", (state.on_error = script_on_error.bind(script, state)), false);
script.addEventListener("readystatechange", (state.on_readystatechange = script_on_readystatechange.bind(script, state)), false);
// Add
head.appendChild(script);
// Done
return true;
};
})();
var on_generic_stop_propagation = function (event) {
event.stopPropagation();
};
var on_exclusive_mode_change = function (flag_node) {
exclusive_mode_update.call(this, flag_node, false);
};
var exclusive_mode_update = (function () {
var previous_fragment = "";
return function (flag_node, check_fragment) {
var hash_is_exclusive = (window.location.hash == "#converter.exclusive");
if (check_fragment) {
this.checked = hash_is_exclusive;
}
else {
if (this.checked ^ (!hash_is_exclusive)) {
previous_fragment = window.location.hash;
}
window.history.replaceState({}, "", window.location.pathname + (this.checked ? "#converter.exclusive" : previous_fragment));
}
if (this.checked) {
flag_node.classList.add("exclusive_enabled");
}
else {
flag_node.classList.remove("exclusive_enabled");
}
};
})();
var on_converter_click = function (converter_files_input, event) {
if (event.which != 2 && event.which != 3) {
converter_files_input.click();
}
};
var on_converter_files_change = function (converter) {
// Read
on_converter_test_files.call(converter, this.files);
// Nullify
this.value = null;
};
var on_file_dragover = function (converter, event) {
if (Array.prototype.indexOf.call(event.dataTransfer.types, "Files") < 0) return;
converter.classList.add("converter_files_active");
if (this === converter) converter.classList.add("converter_files_hover");
event.dataTransfer.dropEffect = "copy";
event.preventDefault();
event.stopPropagation();
return false;
};
var on_file_dragleave = function (converter, event) {
if (Array.prototype.indexOf.call(event.dataTransfer.types, "Files") < 0) return;
converter.classList.remove("converter_files_hover");
if (this !== converter) converter.classList.remove("converter_files_active");
event.preventDefault();
event.stopPropagation();
return false;
};
var on_file_drop = function (converter, event) {
// Reset style
converter.classList.remove("converter_files_active");
converter.classList.remove("converter_files_hover");
event.preventDefault();
event.stopPropagation();
// Not over the converter
if (this !== converter) return false;
// Read files
on_converter_test_files.call(converter, event.dataTransfer.files);
// Done
return false;
};
var on_converter_test_files = function (files) {
// Read
var re_ext = /(\.[^\.]*|)$/,
read_files = [],
ext, i;
for (i = 0; i < files.length; ++i) {
ext = re_ext.exec(files[i].name)[1].toLowerCase();
if (ext == ".torrent") {
read_files.push(files[i]);
}
}
// Nothing to do
if (read_files.length === 0) return;
// Load scripts if necessary
load_requirements(function (errors) {
if (errors === 0) {
// Load
var T2M_obj;
try {
T2M_obj = T2M;
}
catch(e) {
return; // not found
}
T2M_obj.queue_torrent_files(read_files);
}
});
};
var load_requirements = (function () {
// Script requirements
var requirements = [
"src/sha1.js",
"src/bencode.js",
"src/base32.js",
"src/t2m.js",
];
var on_all_scripts_loaded = function () {
var T2M_obj;
try {
T2M_obj = T2M;
}
catch(e) {
return; // not found
}
T2M_obj.setup(rice_checkboxes);
};
var on_script_load = function (state, callback, okay) {
if (okay) ++state.okay;
if (++state.count >= state.total) {
// All loaded/errored
if (state.total - state.okay === 0) on_all_scripts_loaded();
callback.call(null, state.total - state.okay);
}
};
// Return the loading function
return function (callback) {
// Already loaded?
if (requirements === null) {
// Yes
callback.call(null, 0);
return;
}
var head = document.head,
on_load, i;
if (!head) return false;
// Load
on_load = on_script_load.bind(null, { okay: 0, count: 0, total: requirements.length, }, callback);
for (i = 0; i < requirements.length; ++i) {
script_add(requirements[i], on_load);
}
// Done
requirements = null;
return true;
};
})();
var restyle_noscript = function () {
// Script
var nodes = document.querySelectorAll(".script_disabled"),
i;
for (i = 0; i < nodes.length; ++i) {
nodes[i].classList.remove("script_visible");
}
nodes = document.querySelectorAll(".script_enabled");
for (i = 0; i < nodes.length; ++i) {
nodes[i].classList.add("script_visible");
}
};
var rice_checkboxes = function (nodes) {
var svgns = "http://www.w3.org/2000/svg",
i, par, sib, node, n1, n2, n3;
nodes = nodes || document.querySelectorAll("input[type=checkbox].checkbox");
for (i = 0; i < nodes.length; ++i) {
node = nodes[i];
par = node.parentNode;
sib = node.nextSibling;
// Create new checkbox
n1 = document.createElement("label");
n1.className = node.className;
n2 = document.createElementNS(svgns, "svg");
n2.setAttribute("svgns", svgns);
n2.setAttribute("viewBox", "0 0 16 16");
n3 = document.createElementNS(svgns, "polygon");
n3.setAttribute("points", "13,0 16,2 8,16 5,16 0,11 2,8 6,11.5");
// Re-add
n2.appendChild(n3);
n1.appendChild(n2);
par.insertBefore(n1, node);
n1.insertBefore(node, n2);
}
};
// Execute
on_ready(function () {
// Noscript
var nodes, i;
// Rice
restyle_noscript();
rice_checkboxes();
// Stop propagation links
nodes = document.querySelectorAll(".link_stop_propagation");
for (i = 0; i < nodes.length; ++i) {
nodes[i].addEventListener("click", on_generic_stop_propagation, false);
}
// Setup converter
var converter = document.querySelector(".converter"),
converter_files = document.querySelector(".converter_files_input"),
exclusive_mode = document.querySelector("input.converter_exclusive_mode_check"),
non_exclusive_body = document.querySelector(".non_exclusive"),
body = document.body;
if (converter !== null) {
// File browser
if (converter_files !== null) {
converter.addEventListener("click", on_converter_click.bind(converter, converter_files), false);
converter_files.addEventListener("change", on_converter_files_change.bind(converter_files, converter), false);
}
// File drag/drop events
converter.addEventListener("dragover", on_file_dragover.bind(converter, converter), false);
converter.addEventListener("dragleave", on_file_dragleave.bind(converter, converter), false);
converter.addEventListener("drop", on_file_drop.bind(converter, converter), false);
body.addEventListener("dragover", on_file_dragover.bind(body, converter), false);
body.addEventListener("dragleave", on_file_dragleave.bind(body, converter), false);
body.addEventListener("drop", on_file_drop.bind(body, converter), false);
// Exclusive
if (exclusive_mode !== null) {
exclusive_mode_update.call(exclusive_mode, non_exclusive_body, true);
exclusive_mode.addEventListener("change", on_exclusive_mode_change.bind(exclusive_mode, non_exclusive_body), false);
}
}
});
})();

View File

@ -0,0 +1,617 @@
body {
padding: 0;
margin: 0;
border: none;
color: #111111;
background-color: #ffffff;
font-family: Arial;
font-size: 16px;
overflow-x: hidden;
overflow-y: scroll;
text-align: center;
}
table,tbody,tr,td {
margin: 0;
padding: 0;
border-spacing: 0;
}
p {
margin: 0;
padding: 0;
line-height: 1.5em;
}
ol,ul {
margin: 0 0 0 1em;
padding: 0;
}
ol>li,ul>li {
line-height: 1.5em;
}
ol>li+li,ul>li+li {
margin-top: 0.5em;
}
h1,h2,h3,h4,h5,h6 {
margin: 0;
padding: 0;
font-weight: bold;
line-height: 1.5em;
position: relative;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.8em;
}
h3 {
font-size: 1.6em;
}
h4 {
font-size: 1.4em;
}
h5 {
font-size: 1.2em;
}
h6 {
font-size: 1em;
}
input,
textarea {
font-size: inherit;
}
input:focus,
textarea:focus {
outline: none;
}
.hardlink_text {
vertical-align: middle;
}
.hardlink_text:not(:hover)>a.hardlink {
visibility: hidden;
opacity: 0;
-webkit-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
-moz-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
-o-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
}
a.hardlink {
display: block;
visibility: visible;
position: absolute;
right: 100%;
top: 0;
padding: 0 0.25em 0 0.5em;
color: #c8c8c8;
font-weight: bold;
-webkit-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
-moz-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
-o-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
}
a.hardlink:hover {
text-decoration: none;
color: #0280cf;
}
a.hardlink:after {
content: "#";
}
p+p,
h1+p,h2+p,h3+p,h4+p,h5+p,h6+p,
p+h1,p+h2,p+h3,p+h4,p+h5,p+h6,
h4+h6 {
margin-top: 1em;
}
strong {
font-size: 1em;
}
code {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
}
code.nowrap {
display: inline-block;
white-space: nowrap;
}
.codeblock {
font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
display: block;
background: #f3f3f3;
border: 1px solid #e5e5e5;
overflow-x: auto;
overflow-y: hidden;
padding: 0;
line-height: 1.5em;
}
.codeblock_inner {
display: inline-block;
padding: 0.5em;
}
.codeblock.codeblock_pre>.codeblock_inner {
white-space: pre;
}
*::selection {
color: #ffffff;
background: #0280cf;
text-shadow: none;
}
*::-moz-selection {
color: #ffffff ;
background: #0280cf;
text-shadow: none;
}
.section_id {
}
.script_disabled {
}
.script_disabled:not(.script_visible) {
display: none;
}
.script_enabled {
}
.script_enabled:not(.script_visible) {
display: none;
}
input[type=checkbox],
input[type=radio] {
padding: 0;
margin: 0;
vertical-align: middle;
}
label.checkbox {
display: inline-block;
width: 0.75em;
height: 0.75em;
vertical-align: middle;
border: 0.09375em solid #111111;
border-radius: 0.25em;
padding: 0.125em;
position: relative;
cursor: pointer;
}
label.checkbox:before {
z-index: -1;
position: absolute;
display: block;
left: 0;
right: 0;
bottom: 0;
top: 0;
content: "";
border: none;
border-radius: 0.125em;
background: #ffffff;
}
label.checkbox:hover:before,
label:not([for]):hover label.checkbox:before {
border: 0.125em solid #0280cf;
}
label.checkbox>input[type=checkbox] {
position: absolute;
visibility: hidden;
display: none;
}
label.checkbox>svg {
display: none;
width: 100%;
height: 100%;
position: relative;
}
label.checkbox>svg>polygon {
fill: #111111;
}
label.checkbox.delete_checkbox>svg>polygon:not(:first-child) {
visibility: hidden;
}
label.checkbox.delete_checkbox:hover>svg>polygon:first-child,
label:not([for]):hover label.checkbox.delete_checkbox>svg>polygon:first-child {
visibility: hidden;
}
label.checkbox.delete_checkbox:hover>svg>polygon:not(:first-child),
label:not([for]):hover label.checkbox.delete_checkbox>svg>polygon:not(:first-child) {
visibility: visible;
}
label.checkbox>input[type=checkbox]:checked+svg {
display: block;
}
a {
color: #0280cf;
cursor: pointer;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a.light_underline {
color: #c8c8c8;
}
a.light_underline>span {
color: #0280cf;
}
a.light_nohover_color_hover {
color: #c8c8c8;
}
a.light_nohover_color_hover:hover {
color: #0280cf;
text-decoration: none;
}
.main {
text-align: left;
display: inline-block;
width: 60em;
vertical-align: middle;
}
.main.main_no_overflow {
overflow: hidden;
}
.main.body {
margin: 0.5em 0 4em 0;
}
.header_bar {
position: relative;
background: #f3f3f3;
background: -webkit-linear-gradient(180deg, #f9f9f9, #f3f3f3);
background: -moz-linear-gradient(180deg, #f9f9f9, #f3f3f3);
background: -o-linear-gradient(180deg, #f9f9f9, #f3f3f3);
background: linear-gradient(180deg, #f9f9f9, #f3f3f3);
border-bottom: 1px solid #e5e5e5;
}
.header_table {
margin: 0.5em 0;
text-align: left;
}
.header_table td {
vertical-align: middle;
}
.header_table_cell {
width: 0;
}
.header_table_cell.header_table_cell_full {
width: 100%;
}
.header_table_name {
font-size: 2em;
line-height: 1.2em;
white-space: nowrap;
}
.header_table_name_user {
}
.header_table_name_separator {
display: inline-block;
margin: 0 0.125em;
color: #c8c8c8;
}
.header_table_name_title {
}
.header_table_name_title>span {
font-weight: bold;
}
.header_table_separator {
vertical-align: middle;
display: inline-block;
font-size: 2em;
height: 1.2em;
margin: 0 0.5em;
border-left: 1px solid #c8c8c8;
}
.header_table_description {
line-height: 1.1em;
}
.header_table_description_name {
font-weight: bold;
color: #404040;
}
.header_table_description_body {
color: #606060;
}
.header_table_view_on_github {
display: block;
white-space: nowrap;
margin-left: 1em;
text-align: right;
}
.header_table_view_on_github_line1 {
font-size: 0.8em;
line-height: 1em;
}
.header_table_view_on_github_line2 {
line-height: 1em;
}
.light {
color: #808080;
}
.italic {
font-style: italic;
}
.converter_exclusive_mode {
white-space: nowrap;
margin-left: 1em;
padding: 0 2em 0 1em;
display: inline-block;
font-size: 0.5em;
font-weight: normal;
line-height: 1em;
cursor: pointer;
}
.converter_exclusive_mode_text {
vertical-align: middle;
opacity: 0;
-webkit-transition: opacity 0.25s ease-in-out 0s;
-moz-transition: opacity 0.25s ease-in-out 0s;
-o-transition: opacity 0.25s ease-in-out 0s;
transition: opacity 0.25s ease-in-out 0s;
}
.converter_exclusive_mode_text:after {
content: "exclusive mode";
}
.converter_exclusive_mode:hover>.converter_exclusive_mode_text {
opacity: 1;
-webkit-transition: opacity 0.25s ease-in-out 0.5s;
-moz-transition: opacity 0.25s ease-in-out 0.5s;
-o-transition: opacity 0.25s ease-in-out 0.5s;
transition: opacity 0.25s ease-in-out 0.5s;
}
.converter_exclusive_mode_check {
margin-right: 0.25em;
opacity: 0;
-webkit-transition: opacity 0.25s ease-in-out 0s;
-moz-transition: opacity 0.25s ease-in-out 0s;
-o-transition: opacity 0.25s ease-in-out 0s;
transition: opacity 0.25s ease-in-out 0s;
}
.converter_exclusive_mode:hover>.converter_exclusive_mode_check {
opacity: 1;
-webkit-transition: opacity 0.25s ease-in-out 0.5s;
-moz-transition: opacity 0.25s ease-in-out 0.5s;
-o-transition: opacity 0.25s ease-in-out 0.5s;
transition: opacity 0.25s ease-in-out 0.5s;
}
.converter {
display: block;
padding: 2em;
border: 0.25em dashed #c8c8c8;
cursor: pointer;
text-align: center;
-webkit-transition: border-color 0.25s ease-in-out 0s;
-moz-transition: border-color 0.25s ease-in-out 0s;
-o-transition: border-color 0.25s ease-in-out 0s;
transition: border-color 0.25s ease-in-out 0s;
background: #f3f3f3;
}
.converter.converter_files_active {
border-color: #0280cf;
}
.converter_container {
display: inline-block;
padding-right: 3em;
}
.converter_table {
text-align: left;
display: table;
vertical-align: middle;
}
.converter_cell {
display: table-cell;
vertical-align: middle;
}
.converter_cell_left {
width: 0;
}
.converter_cell_right {
width: 100%;
}
.converter_svg_container {
width: 8.5em;
height: 10em;
}
.converter_svg_graphic {
width: 10em;
height: 10em;
-webkit-transition: transform 0.25s ease-in-out 0s;
-moz-transition: transform 0.25s ease-in-out 0s;
-o-transition: transform 0.25s ease-in-out 0s;
transition: transform 0.25s ease-in-out 0s;
}
.converter_svg_graphic_poly {
fill: #c8c8c8;
stroke: none;
-webkit-transition: fill 0.25s ease-in-out 0s;
-moz-transition: fill 0.25s ease-in-out 0s;
-o-transition: fill 0.25s ease-in-out 0s;
transition: fill 0.25s ease-in-out 0s;
}
.converter_info {
display: inline-block;
text-align: right;
}
.converter_info_line1 {
font-weight: bold;
font-size: 4em;
line-height: 1em;
}
.converter_info_line2 {
font-size: 2em;
line-height: 1em;
}
.converter_info_line3 {
margin-top: 2em;
line-height: 1em;
color: #a0a0a0;
}
.converter_files_input {
display: none;
}
.converter:hover .converter_svg_graphic,
.converter.converter_files_hover .converter_svg_graphic {
-webkit-transform-origin: 50% 50%;
-moz-transform-origin: 50% 50%;
-ms-transform-origin: 50% 50%;
-o-transform-origin: 50% 50%;
transform-origin: 50% 50%;
-webkit-transform: scale(1.25);
-moz-transform: scale(1.25);
-ms-transform: scale(1.25);
-o-transform: scale(1.25);
transform: scale(1.25);
}
.converter.converter_files_active .converter_svg_graphic_poly {
fill: #0280cf;
}
.converted {
margin-top: 2em;
}
.converted:not(.converted_visible) {
display: none;
}
.converted_item {
}
.converted_item+.converted_item {
margin-top: 1em;
}
.converted_item_title_container {
border-bottom: 0.25em solid #c8c8c8;
margin-bottom: 1em;
}
.converted_item_title {
color: #0280cf;
font-size: 2em;
font-weight: bold;
line-height: 1.2em;
max-height: 2.4em;
overflow: hidden;
}
.converted_item_contents {
margin-left: 2em;
}
.converted_item_link_container {
width: 100%;
overflow-x: hidden;
margin-bottom: 1em;
}
a.converted_item_link {
font-size: 1.25em;
color: #0280cf;
white-space: nowrap;
}
a.converted_item_link:hover {
text-decoration: underline;
}
a.converted_item_link>span {
color: #111111;
}
.converted_item_textbox {
display: block;
width: 100%;
margin: 0.5em 0 0 0;
padding: 0.5em;
border: 1px solid #c8c8c8;
box-sizing: border-box;
-moz-box-sizing: border-box;
line-height: 1.2em;
color: #111111;
}
.converted_item_textbox[readonly] {
color: #808080;
}
.converted_item_header {
margin: 0;
}
.converted_item_header_text {
display: inline-block;
font-weight: bold;
}
.converted_item_options_container:not(.converted_item_options_container_visible)>.converted_item_header>.converted_item_header_text {
display: none;
}
a.converted_item_options_toggle {
color: #0280cf;
display: inline-block;
}
.converted_item_options_container.converted_item_options_container_visible>.converted_item_header>a.converted_item_options_toggle {
margin-left: 1em;
}
a.converted_item_options_toggle>span {
color: #c8c8c8;
}
a.converted_item_options_toggle:hover>span {
color: #111111;
}
a.converted_item_options_toggle>span:after {
content: "Show options";
}
.converted_item_options_container.converted_item_options_container_visible>.converted_item_header>a.converted_item_options_toggle>span:after {
content: "Hide";
}
.converted_item_options_container {
}
.converted_item_options {
margin-top: 0.5em;
}
.converted_item_options_container:not(.converted_item_options_container_visible)>.converted_item_options {
display: none;
}
.converted_item_option {
}
.converted_item_option+.converted_item_option {
margin-top: 0.5em;
}
.converted_item_option_part {
cursor: pointer;
display: inline-block;
}
.converted_item_option_part+.converted_item_option_part {
margin-left: 1em;
}
.converted_item_option_part:not(.converted_item_option_part_visible) {
display: none;
}
.converted_item_option_checkbox {
vertical-align: middle;
margin-right: 0.25em;
}
.converted_item_option_text {
vertical-align: middle;
}
.non_exclusive {
margin-top: 2em;
}
.non_exclusive.exclusive_enabled {
display: none;
}

View File

@ -0,0 +1,83 @@
<div class="container">
<div ng-hide="$root.client.torrents.length != 0" style="vertical-align: middle; text-align: center">
<div class="row">
<form class="no-margin" ng-submit="addMagnet()">
<label>Enter Magnet/Hash & Press Enter</label>
<input type="text" placeholder="magnet, hash or http(s) .torrent" ng-model="torrentInput" ng-disabled="$root.disabled" style="width: 50%"/>
</form>
</div>
<div class="row">
<label>Otherwise Upload Torrent File Below</label>
<button type="file" ngf-select="$root.openTorrentFile($file)" ng-disabled="$root.disabled" ng-class="{'button-primary': !$root.disabled}"><i class="fa fa-folder-open"></i> Open torrent file</button>
</div>
</div>
<div class="div" ng-if="selectedTorrent" style="text-align: center">
<div class="four columns" style="overflow: auto">
<h4>Information</h4>
<table class="u-full-width">
<tbody>
<tr>
<td>Name</td>
<td>{{$root.selectedTorrent.name}}</td>
</tr>
<tr>
<td>Size</td>
<td>{{$root.selectedTorrent.length | pbytes}}</td>
</tr>
<tr>
<td>Completed</td>
<td>{{$root.selectedTorrent.progress | progress}} ({{$root.selectedTorrent.downloaded | pbytes}})</td>
</tr>
<tr>
<td>Peers</td>
<td>{{$root.selectedTorrent.numPeers}}</td>
</tr>
<tr>
<td>↓ Speed</td>
<td>{{$root.selectedTorrent.downloadSpeed | pbytes:1}}</td>
</tr>
<tr>
<td>ETA</td>
<td>{{$root.selectedTorrent.timeRemaining | humanTime}}</td>
</tr>
</tbody>
</table>
</div>
<div class="four columns">
<h4>Files</h4>
<table class="u-full-width" style="margin: auto">
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
<tr class="files" ng-repeat="file in $root.selectedTorrent.files">
<td ng-hide="file.done">{{file.name}}</td>
<td ng-show="file.done"><a ng-href="{{file.url}}" download="{{file.name}}" target="_self" ng-show="file.done">{{file.name}}</a></td>
<td>{{file.length | pbytes}}</td>
<td>
<select class="no-margin" name="{{file.name}}Priority" ng-model="file.priority" ng-init="file.priority = '0'" ng-change="$root.changePriority(file)">
<option value="1">High Priority</option>
<option value="0" selected="">Low Priority</option>
<option value="-1">Don't download</option>
</select>
</td>
</tr>
</tbody>
</table>
<h5>↑ Click a file to download it</h5>
</div>
<div class="four columns">
<h4>Share</h4>
<ul style="text-align: justify">
<li><a ng-href="#{{$root.selectedTorrent.infoHash}}" target="_blank">Torrent</a></li>
<li><a ng-href="{{$root.selectedTorrent.magnetURI}}" target="_blank">Magnet</a></li>
<li><a ng-href="{{$root.selectedTorrent.safeTorrentFileURL}}" target="_self" download="{{$root.selectedTorrent.fileName}}">.torrent</a></li>
<li><strong>Hash: </strong>{{$root.selectedTorrent.infoHash}}</li>
</ul>
</div>
</div>
</div>

View File

@ -0,0 +1,63 @@
<div class="container">
<div class="row">
<div class="four columns">
<input class="u-full-width" type="text" placeholder="magnet, hash or http(s) .torrent" ng-model="torrentInput" ng-disabled="$root.disabled"/>
</div>
<div class="two columns download-button">
<button ng-click="addMagnet()" ng-disabled="!torrentInput.length || $root.disabled" ng-class="{'button-primary': torrentInput.length}"><i class="fa fa-download"></i> Download</button>
</div>
<div class="three columns">
<button type="file" ngf-select="$root.openTorrentFile($file)" ng-disabled="$root.disabled" ng-class="{'button-primary': !$root.disabled}"><i class="fa fa-folder-open"></i> Open torrent file</button>
</div>
<div class="three columns u-pull-right">
<button class="u-pull-right" ngf-select="$root.seedFiles($files)" multiple="" ng-disabled="$root.disabled" ng-class="{'button-primary': !$root.disabled}"><i class="fa fa-upload"></i> Seed files</button>
</div>
</div>
<div class="row grid" ui-grid="gridOptions" ui-grid-resize-columns="ui-grid-resize-columns" ui-grid-selection="ui-grid-selection"></div>
<div class="row" ng-if="selectedTorrent">
<div class="six columns" style="overflow: auto">
<h5>{{$root.selectedTorrent.name}}
<button ng-if="!$root.selectedTorrent.paused" ng-click="$root.selectedTorrent.pause()"><i class="fa fa-pause"></i> Pause</button>
<button ng-if="$root.selectedTorrent.paused" ng-click="$root.selectedTorrent.resume()"><i class="fa fa-play"></i> Resume</button>
<button class="button-danger" ng-click="$root.selectedTorrent.destroy($root.destroyedTorrent)"><i class="fa fa-times"></i> Remove</button>
</h5>
<h6>Share</h6>
<ul>
<li><a ng-href="#{{$root.selectedTorrent.infoHash}}" target="_blank">Torrent</a></li>
<li><a ng-href="{{$root.selectedTorrent.magnetURI}}" target="_blank">Magnet</a></li>
<li><a ng-href="{{$root.selectedTorrent.safeTorrentFileURL}}" target="_self" download="{{$root.selectedTorrent.fileName}}">.torrent</a></li>
<li><strong>Hash: </strong>{{$root.selectedTorrent.infoHash}} </li>
</ul>
</div>
<div class="six columns">
<h5>Files</h5>
<table class="u-full-width">
<thead>
<tr>
<th>Name</th>
<th>Size</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
<tr class="files" ng-repeat="file in $root.selectedTorrent.files">
<td ng-hide="file.done">{{file.name}}</td>
<td ng-show="file.done"><a ng-href="{{file.url}}" download="{{file.name}}" target="_self" ng-show="file.done">{{file.name}}</a></td>
<td>{{file.length | pbytes}}</td>
<td>
<select class="no-margin" name="{{file.name}}Priority" ng-model="file.priority" ng-init="file.priority = '0'" ng-change="$root.changePriority(file)">
<option value="1">High Priority</option>
<option value="0" selected="">Low Priority</option>
<option value="-1">Don't download</option>
</select>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="center"><strong>Client Stats:
↓ {{$root.client.downloadSpeed | pbytes}}/s ·
↑ {{$root.client.uploadSpeed | pbytes}}/s ·
Ratio: {{$root.client.ratio | number:2}}</strong></div>
</div>

View File

@ -0,0 +1,11 @@
<div class="container">
<div ng-hide="$root.client.torrents.length != 0" style="vertical-align: middle; text-align: center">
<div class="row">
<form class="no-margin" ng-submit="addMagnet()">
<label>Stream/View: Torrent Must Contain Files That Can Play Within Browser Like MP3 or MP4<br />Example Hash: 08ada5a7a6183aae1e09d831df6748d566095a10</label>
<input type="text" placeholder="magnet, hash or http(s) .torrent" ng-model="torrentInput" ng-disabled="$root.disabled" style="width: 50%"/>
</form>
</div>
</div>
<div class="div" ng-if="selectedTorrent" style="text-align: center">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}}</div>
</div>