├── .gitattributes
├── .gitignore
├── .nojekyll
├── LICENSE.md
├── README.md
├── favicon.png
├── index.html
├── src
├── base32.js
├── bencode.js
├── sha1.js
├── sha1.test.js
├── t2m.js
└── t2m.loader.js
└── style.css
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Windows image file caches
2 | Thumbs.db
3 | ehthumbs.db
4 |
5 | # Folder config file
6 | Desktop.ini
7 |
8 | # Recycle Bin used on file shares
9 | $RECYCLE.BIN/
10 |
11 | # Windows Installer files
12 | *.cab
13 | *.msi
14 | *.msm
15 | *.msp
16 |
17 | # =========================
18 | # Operating System Files
19 | # =========================
20 |
21 | # OSX
22 | # =========================
23 |
24 | .DS_Store
25 | .AppleDouble
26 | .LSOverride
27 |
28 | # Icon must ends with two \r.
29 | Icon
30 |
31 | # Thumbnails
32 | ._*
33 |
34 | # Files that might appear on external disk
35 | .Spotlight-V100
36 | .Trashes
37 |
--------------------------------------------------------------------------------
/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nutbread/t2m/6a9d26d5c2cf040a6c6ac5c658b4b2e1d8841545/.nojekyll
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Use and/or steal to your heart's desire
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [https://nutbread.github.io/t2m/](https://nutbread.github.io/t2m/)
--------------------------------------------------------------------------------
/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nutbread/t2m/6a9d26d5c2cf040a6c6ac5c658b4b2e1d8841545/favicon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
47 |
48 |
Converter
49 |
50 |
51 | Javascript is required to use the inline converter.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
65 |
66 |
67 |
68 |
69 |
Or click to open the file browser
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
Libraries
85 |
86 | The following Javascript libraries are used on this page:
87 |
88 |
89 |
100 |
101 |
102 | They are primarly designed for web usage, but they can be trivially modified to be used with something such as node.js .
103 |
104 |
105 |
sha1.js
106 |
107 | Computes the SHA-1 hash of the specified data using the basic algorithm described in RFC 3174 , with a paradigm similar to python's hashlib . Data can be input in chunks, and the digest can be calculated at any point without modifying the internal state.
108 |
109 |
110 |
Public interface
111 |
112 | The following are public methods on the SHA1
class:
113 |
114 |
115 | constructor ()
116 | Creates a new SHA1
hashing instance.
117 |
118 |
119 | reset ()
120 | Resets the internal state of the instance.
121 |
122 |
123 | update (value_array)
124 | Update the internal state of the hashing object with the values contained in value_array
.
125 | value_array
can be a string, with each character in the range [0,255]
; or a (typed ) array, with each value in the range [0,255]
.
126 |
127 |
128 | digest () : Uint8Array
129 | Computes and returns the SHA1 digest of all the data input so far. The return value is a Uint8Array
of length 20
.
130 |
131 |
132 |
133 |
134 |
bencode.js
135 |
136 | Encode/decode an object or string using the Bencode format.
137 |
138 |
139 |
Public interface
140 |
141 | The following are public methods on the Bencode
module:
142 |
143 |
144 | encode (obj) : string
145 | Encode obj
into a Bencoded string.
146 | obj
can only be a string, number, array, or object. Arrays and objects can also only contain the aforementioned types.
147 |
148 |
149 | decode (str) : [object | array | string | number]
150 | throws "Invalid format"
151 | Converts Bencoded str
back into its original type. If data is malformed, an exception is thrown.
152 |
153 |
154 |
155 |
156 |
base32.js
157 |
158 | Encode/decode a string using the RFC 4648 Base32 format.
159 |
160 |
161 |
Public interface
162 |
163 | The following are public methods on the Base32
module:
164 |
165 |
166 | encode (str) : string
167 | Encode str
into Base32.
168 |
169 |
170 | decode (str) : string
171 | Decode str
from Base32 into its original string.
172 |
173 |
174 |
175 |
176 |
Other Javascript files
177 |
178 | These are the other Javascript files included in this repository:
179 |
180 |
181 |
182 |
183 | sha1.test.js - Test case file similar to the one here
184 |
185 |
186 | t2m.loader.js - The loader file used to set up this page
187 |
188 |
189 | t2m.js - The main file used to process .torrent files on this page
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
--------------------------------------------------------------------------------
/src/base32.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | // Module for encoding/decoding base32
5 | var Base32 = (function () {
6 | "use strict";
7 |
8 | // Vars used
9 | var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",
10 | pad_lengths = [ 0, 1, 3, 4, 6 ],
11 | pad_char = "=";
12 |
13 |
14 |
15 | // Encode/decode functions
16 | return {
17 | /**
18 | Encode a string into base32
19 |
20 | @param str
21 | The string to convert.
22 | This string should be encoded in some way such that each character is in the range [0,255]
23 | @return
24 | A base32 encoded string
25 | */
26 | encode: function (str) {
27 | var len = str.length,
28 | str_new = "",
29 | i = 0,
30 | c1, c2, c3, c4, c5;
31 |
32 | // Null pad
33 | while ((str.length % 5) !== 0) str += "\x00";
34 |
35 | // String modify
36 | while (i < len) {
37 | c1 = str.charCodeAt(i++);
38 | c2 = str.charCodeAt(i++);
39 | c3 = str.charCodeAt(i++);
40 | c4 = str.charCodeAt(i++);
41 | c5 = str.charCodeAt(i++);
42 |
43 | str_new += alphabet[(c1 >> 3)];
44 | str_new += alphabet[((c1 & 0x07) << 2) | (c2 >> 6)];
45 | str_new += alphabet[((c2 & 0x3F) >> 1)];
46 | str_new += alphabet[((c2 & 0x01) << 4) | (c3 >> 4)];
47 | str_new += alphabet[((c3 & 0x0F) << 1) | (c4 >> 7)];
48 | str_new += alphabet[((c4 & 0x7F) >> 2)];
49 | str_new += alphabet[((c4 & 0x03) << 3) | (c5 >> 5)];
50 | str_new += alphabet[(c5 & 0x1F)];
51 | }
52 |
53 | // Padding
54 | if (i > len) {
55 | i = pad_lengths[i - len]; // (i - len) equals the number of times \x00 was padded
56 | str_new = str_new.substr(0, str_new.length - i);
57 | while ((str_new.length % 8) !== 0) str_new += pad_char;
58 | }
59 |
60 | // Done
61 | return str_new;
62 | },
63 | /**
64 | Decode a string from base32
65 |
66 | @param str
67 | A valid base32 string
68 | @return
69 | The original string
70 | */
71 | decode: function (str) {
72 | var len = str.length,
73 | str_new = "",
74 | bits = 0,
75 | char_buffer = 0,
76 | i;
77 |
78 | // Cut off padding
79 | while (len > 0 && str[len - 1] == "=") --len;
80 |
81 | // Iterate
82 | for (i = 0; i < len; ++i) {
83 | // Update with the 32bit value
84 | char_buffer = (char_buffer << 5) | alphabet.indexOf(str[i]);
85 |
86 | // Update bitcount
87 | bits += 5;
88 | if (bits >= 8) {
89 | // Update string
90 | str_new += String.fromCharCode((char_buffer >> (bits - 8)) & 0xFF);
91 | bits -= 8;
92 | }
93 | }
94 |
95 | // Done
96 | return str_new;
97 | },
98 | };
99 |
100 | })();
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/src/bencode.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | // Module for encoding/decoding Bencoded data
5 | var Bencode = (function () {
6 | "use strict";
7 |
8 | // Encoding functions
9 | var encode = function (value) {
10 | // Type
11 | var t = typeof(value);
12 |
13 | // Number
14 | if (t == "number") return encode_int(Math.floor(value));
15 | // String
16 | if (t == "string") return encode_string(value);
17 | // Array
18 | if (Array.isArray(value)) return encode_list(value);
19 | // Dict
20 | return encode_dict(value);
21 | };
22 |
23 | var encode_int = function (value) {
24 | return "i" + value + "e";
25 | };
26 | var encode_string = function (value) {
27 | return "" + value.length + ":" + value;
28 | };
29 | var encode_list = function (value) {
30 | var str = [ "l" ],
31 | i;
32 |
33 | // List values
34 | for (i = 0; i < value.length; ++i) {
35 | str.push(encode(value[i]));
36 | }
37 |
38 | // End
39 | str.push("e");
40 | return str.join("");
41 | };
42 | var encode_dict = function (value) {
43 | var str = [ "d" ],
44 | keys = [],
45 | i;
46 |
47 | // Get and sort keys
48 | for (i in value) keys.push(i);
49 | keys.sort();
50 |
51 | // Push values
52 | for (i = 0; i < keys.length; ++i) {
53 | str.push(encode_string(keys[i]));
54 | str.push(encode(value[keys[i]]));
55 | }
56 |
57 | // End
58 | str.push("e");
59 | return str.join("");
60 | };
61 |
62 |
63 |
64 | // Decoding class
65 | var Decoder = function () {
66 | this.pos = 0;
67 | };
68 |
69 | Decoder.prototype = {
70 | constructor: Decoder,
71 |
72 | decode: function (str) {
73 | // Errors
74 | var k = str[this.pos];
75 | if (!(k in decode_generic)) throw "Invalid format";
76 |
77 | // Call
78 | return decode_generic[k].call(this, str);
79 | },
80 | decode_int: function (str) {
81 | // Skip the "i" prefix
82 | ++this.pos;
83 |
84 | var end = str.indexOf("e", this.pos),
85 | value;
86 |
87 | // No end
88 | if (end < 0) throw "Invalid format";
89 |
90 | // Assume proper number format
91 | value = parseInt(str.substr(this.pos, end - this.pos), 10);
92 |
93 | // Done
94 | this.pos = end + 1;
95 | return value;
96 | },
97 | decode_string: function (str) {
98 | var delim = str.indexOf(":", this.pos),
99 | length, value;
100 |
101 | // No end
102 | if (delim < 0) throw "Invalid format";
103 |
104 | // Assume proper number format
105 | length = parseInt(str.substr(this.pos, delim - this.pos), 10);
106 | value = str.substr(delim + 1, length);
107 |
108 | // Done
109 | this.pos = delim + length + 1;
110 | return value;
111 | },
112 | decode_list: function (str) {
113 | // Skip the "l" prefix
114 | ++this.pos;
115 |
116 | // Read list
117 | var list = [],
118 | value;
119 |
120 | // Loop until end or exception
121 | while (str[this.pos] != "e") {
122 | value = this.decode(str); // this throws errors if str[this.pos] is out of bounds
123 | list.push(value);
124 | }
125 |
126 | // Done; skip "e" suffix
127 | ++this.pos;
128 | return list;
129 | },
130 | decode_dict: function (str) {
131 | // Skip the "d" prefix
132 | ++this.pos;
133 |
134 | // Read dict
135 | var dict = {},
136 | key, value;
137 |
138 | // Loop until end or exception
139 | while (str[this.pos] != "e") {
140 | key = this.decode_string(str);
141 | value = this.decode(str); // this throws errors if str[this.pos] is out of bounds
142 | dict[key] = value;
143 | }
144 |
145 | // Done; skip "e" suffix
146 | ++this.pos;
147 | return dict;
148 | },
149 | };
150 |
151 | // Generic decode functions
152 | var decode_generic = {
153 | "l": Decoder.prototype.decode_list,
154 | "d": Decoder.prototype.decode_dict,
155 | "i": Decoder.prototype.decode_int,
156 | },
157 | i;
158 | for (i = 0; i < 10; ++i) decode_generic[i.toString()] = Decoder.prototype.decode_string;
159 |
160 |
161 |
162 | // Encode/decode functions
163 | return {
164 | /**
165 | encode: function (obj)
166 | Encodes an object into a Bencode'd string
167 |
168 | @param obj
169 | The object to encode
170 | This should only be one of the following:
171 | string
172 | number (floats are floor'd to integers)
173 | array (containing things only from this list)
174 | object (containing things only from this list)
175 | Strings should be encoded in some way such that each character is in the range [0,255]
176 | @return
177 | A string representing the object
178 | */
179 | encode: encode,
180 | /**
181 | decode: function (str)
182 | Decodes a Bencode'd string back into its original type
183 |
184 | @param str
185 | The string to decode
186 | @return
187 | The original object represented by str
188 | @throws
189 | Any one of the following self-explanatory strings
190 | "Invalid format"
191 | "Invalid string"
192 | "Invalid int"
193 | */
194 | decode: function (str) {
195 | // Create a decoder and call
196 | return (new Decoder()).decode(str);
197 | },
198 | };
199 |
200 | })();
201 |
202 |
203 |
--------------------------------------------------------------------------------
/src/sha1.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | // Class for SHA1 computation
5 | var SHA1 = (function () {
6 | "use strict";
7 |
8 | // Private variables
9 | var hash_size = 20,
10 | message_block_length = 64,
11 | message_block_terminator = 0x80,
12 | message_length_bytes = 8,
13 | initial_intermediate_hash = new Uint32Array(5),
14 | K_constants = new Uint32Array(4);
15 |
16 | initial_intermediate_hash[0] = 0x67452301;
17 | initial_intermediate_hash[1] = 0xEFCDAB89;
18 | initial_intermediate_hash[2] = 0x98BADCFE;
19 | initial_intermediate_hash[3] = 0x10325476;
20 | initial_intermediate_hash[4] = 0xC3D2E1F0;
21 |
22 | K_constants[0] = 0x5A827999;
23 | K_constants[1] = 0x6ED9EBA1;
24 | K_constants[2] = 0x8F1BBCDC;
25 | K_constants[3] = 0xCA62C1D6;
26 |
27 |
28 |
29 | /**
30 | SHA1 instance constructor; creates an empty SHA1 object.
31 |
32 | @return
33 | A new SHA1 instance
34 | */
35 | var SHA1 = function () {
36 | this.length = 0;
37 | this.message_block_index = 0;
38 | this.message_block = new Uint8Array(message_block_length);
39 | this.intermediate_hash = new Uint32Array(initial_intermediate_hash);
40 | };
41 |
42 |
43 |
44 | // Private methods
45 | var pad = function () {
46 | var maxlen = this.message_block.length - message_length_bytes,
47 | high = Math.floor(this.length / 0x0FFFFFFFF) & 0xFFFFFFFF,
48 | low = this.length & 0xFFFFFFFF,
49 | message_block = this.message_block,
50 | message_block_index = this.message_block_index,
51 | input_intermediate_hash = this.intermediate_hash,
52 | output_intermediate_hash = new Uint32Array(this.intermediate_hash.length);
53 |
54 | // Termination byte
55 | message_block[message_block_index] = message_block_terminator;
56 |
57 | // Process another block if there's no space for the length
58 | if (message_block_index >= maxlen) {
59 | // 0-ify
60 | while (++message_block_index < message_block.length) message_block[message_block_index] = 0;
61 |
62 | // Process block
63 | process.call(this, message_block, input_intermediate_hash, output_intermediate_hash);
64 |
65 | // Create copies that don't interfere with "this"
66 | message_block = new Uint8Array(message_block.length); // no 0-ifying needed
67 | input_intermediate_hash = output_intermediate_hash;
68 | }
69 | else {
70 | // 0-ify
71 | while (++message_block_index < maxlen) message_block[message_block_index] = 0;
72 | }
73 |
74 | // Store length
75 | message_block[maxlen] = (high >>> 24) & 0xFF;
76 | message_block[++maxlen] = (high >>> 16) & 0xFF;
77 | message_block[++maxlen] = (high >>> 8) & 0xFF;
78 | message_block[++maxlen] = (high) & 0xFF;
79 | message_block[++maxlen] = (low >>> 24) & 0xFF;
80 | message_block[++maxlen] = (low >>> 16) & 0xFF;
81 | message_block[++maxlen] = (low >>> 8) & 0xFF;
82 | message_block[++maxlen] = (low) & 0xFF;
83 |
84 | process.call(this, message_block, input_intermediate_hash, output_intermediate_hash);
85 |
86 | // Return hash
87 | return output_intermediate_hash;
88 | };
89 | var process = function (message_block, intermediate_hash_input, intermediate_hash_output) {
90 | var W = new Uint32Array(80),
91 | i, i4, temp, A, B, C, D, E;
92 |
93 | // Init W
94 | for (i = 0; i < 16; ++i) {
95 | i4 = i * 4;
96 | W[i] =
97 | (message_block[i4] << 24) |
98 | (message_block[i4 + 1] << 16) |
99 | (message_block[i4 + 2] << 8) |
100 | (message_block[i4 + 3]);
101 | }
102 | for (/*i = 16*/; i < 80; ++i) {
103 | W[i] = circular_shift(1, W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]);
104 | }
105 |
106 | A = intermediate_hash_input[0];
107 | B = intermediate_hash_input[1];
108 | C = intermediate_hash_input[2];
109 | D = intermediate_hash_input[3];
110 | E = intermediate_hash_input[4];
111 |
112 | for (i = 0; i < 20; ++i) {
113 | temp = circular_shift(5, A) + ((B & C) | ((~B) & D)) + E + W[i] + K_constants[0];
114 | E = D;
115 | D = C;
116 | C = circular_shift(30, B);
117 | B = A;
118 | A = temp & 0xFFFFFFFF;
119 | }
120 | for (/*i = 20*/; i < 40; ++i) {
121 | temp = circular_shift(5, A) + (B ^ C ^ D) + E + W[i] + K_constants[1];
122 | E = D;
123 | D = C;
124 | C = circular_shift(30, B);
125 | B = A;
126 | A = temp & 0xFFFFFFFF;
127 | }
128 | for (/*i = 40*/; i < 60; ++i) {
129 | temp = circular_shift(5, A) + ((B & C) | (B & D) | (C & D)) + E + W[i] + K_constants[2];
130 | E = D;
131 | D = C;
132 | C = circular_shift(30, B);
133 | B = A;
134 | A = temp & 0xFFFFFFFF;
135 | }
136 | for (/*i = 60*/; i < 80; ++i) {
137 | temp = circular_shift(5, A) + (B ^ C ^ D) + E + W[i] + K_constants[3];
138 | E = D;
139 | D = C;
140 | C = circular_shift(30, B);
141 | B = A;
142 | A = temp & 0xFFFFFFFF;
143 | }
144 |
145 | intermediate_hash_output[0] = intermediate_hash_input[0] + A;
146 | intermediate_hash_output[1] = intermediate_hash_input[1] + B;
147 | intermediate_hash_output[2] = intermediate_hash_input[2] + C;
148 | intermediate_hash_output[3] = intermediate_hash_input[3] + D;
149 | intermediate_hash_output[4] = intermediate_hash_input[4] + E;
150 | };
151 | var circular_shift = function (bits, word) {
152 | return (word << bits) | (word >>> (32 - bits));
153 | };
154 |
155 |
156 |
157 | // Public methods
158 | SHA1.prototype = {
159 | constructor: SHA1,
160 |
161 | /**
162 | Reset the state of the hashing object to its initial state.
163 | */
164 | reset: function () {
165 | // Reset everything
166 | var i;
167 |
168 | this.length = 0;
169 | this.message_block_index = 0;
170 |
171 | for (i = 0; i < this.intermediate_hash.length; ++i) {
172 | this.intermediate_hash[i] = initial_intermediate_hash[i];
173 | }
174 | for (i = 0; i < this.message_block.length; ++i) {
175 | this.message_block[i] = 0;
176 | }
177 | },
178 |
179 | /**
180 | Feed data into the instance to update the hash.
181 |
182 | @param value_array
183 | The values to update with. This can either be:
184 | - A string (encoded so that every character is in the range [0,255])
185 | - A (typed) array
186 | */
187 | update: function (value_array) {
188 | var is_string = (typeof(value_array) == "string"),
189 | i;
190 |
191 | for (i = 0; i < value_array.length; ++i) {
192 | // Update block
193 | this.message_block[this.message_block_index] = is_string ? value_array.charCodeAt(i) : value_array[i];
194 |
195 | // Update length
196 | this.length += 8;
197 |
198 | // Process block
199 | if (++this.message_block_index >= this.message_block.length) {
200 | process.call(this, this.message_block, this.intermediate_hash, this.intermediate_hash);
201 | this.message_block_index = 0;
202 | }
203 | }
204 | },
205 |
206 | /**
207 | Computes the SHA1 digest of the data input so far.
208 |
209 | @return
210 | A Uint8Array of the computed digest
211 | */
212 | digest: function () {
213 | // Setup
214 | var digest = new Uint8Array(hash_size),
215 | intermediate_hash_temp = pad.call(this),
216 | i;
217 |
218 | // Hash
219 | for (i = 0; i < digest.length; ++i) {
220 | digest[i] = intermediate_hash_temp[i >> 2] >> (8 * (3 - (i & 0x03)));
221 | }
222 |
223 | // Done
224 | return digest;
225 | },
226 | };
227 |
228 |
229 |
230 | // Return the class object
231 | return SHA1;
232 |
233 | })();
234 |
235 |
236 |
--------------------------------------------------------------------------------
/src/sha1.test.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | // Functions for testing sha1.js
5 | (function () {
6 | "use strict";
7 |
8 | // Tests
9 | var tests = [
10 | {
11 | value: "abc",
12 | repeat: 1,
13 | correct: "A9 99 3E 36 47 06 81 6A BA 3E 25 71 78 50 C2 6C 9C D0 D8 9D",
14 | },
15 | {
16 | value: "abcdbcdecdefdefgefghfghighijhi" + "jkijkljklmklmnlmnomnopnopq",
17 | repeat: 1,
18 | correct: "84 98 3E 44 1C 3B D2 6E BA AE 4A A1 F9 51 29 E5 E5 46 70 F1",
19 | },
20 | {
21 | value: "a",
22 | repeat: 1000000,
23 | correct: "34 AA 97 3C D4 C4 DA A4 F6 1E EB 2B DB AD 27 31 65 34 01 6F",
24 | },
25 | {
26 | value: "01234567012345670123456701234567" + "01234567012345670123456701234567",
27 | repeat: 10,
28 | correct: "DE A3 56 A2 CD DD 90 C7 A7 EC ED C5 EB B5 63 93 4F 46 04 52",
29 | },
30 | {
31 | value: "a",
32 | repeat: 133047678,
33 | correct: "94 A8 41 C9 02 1D 23 57 A8 0A 77 60 01 E2 7D 2F 27 93 91 05",
34 | },
35 | ];
36 |
37 |
38 | // Readable hex
39 | var digest_to_spaced_hex = function (digest) {
40 | // String build
41 | var s = [],
42 | c, i;
43 |
44 | for (i = 0; i < digest.length; ++i) {
45 | if (i > 0) s.push(" ");
46 | c = digest[i].toString(16).toUpperCase();
47 | if (c.length < 2) s.push("0");
48 | s.push(c);
49 | }
50 |
51 | return s.join("");
52 | };
53 |
54 |
55 | // Test function
56 | var execute_tests = function () {
57 | // Vars for testing
58 | var sha = new SHA1(),
59 | failures = 0,
60 | test, digest, i, j;
61 |
62 | // Run tests
63 | for (i = 0; i < tests.length; ++i) {
64 | test = tests[i];
65 | console.log("Test " + (i + 1) + ": " + JSON.stringify(test.value) + " repeated " + test.repeat + " time" + (test.repeat == 1 ? "" : "s"));
66 |
67 | sha.reset();
68 |
69 | for (j = 0; j < test.repeat; ++j) {
70 | sha.update(test.value);
71 | }
72 |
73 | digest = digest_to_spaced_hex(sha.digest());
74 |
75 | if (digest == test.correct) {
76 | console.log("Digest matches: YES");
77 | console.log(" " + digest);
78 | }
79 | else {
80 | console.log("Digest matches: NO");
81 | console.log(" " + digest + "(calculated)");
82 | console.log(" " + test.correct + " (correct)");
83 | ++failures;
84 | }
85 |
86 | console.log("");
87 | }
88 |
89 | // Final status
90 | if (failures === 0) {
91 | console.log("All tests okay");
92 | }
93 | else {
94 | console.log("" + failures + " test" + (failures == 1 ? "" : "s") + " failed");
95 | }
96 | };
97 |
98 | execute_tests();
99 |
100 | })();
101 |
102 |
103 |
--------------------------------------------------------------------------------
/src/t2m.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | var T2M = (function () {
5 | "use strict";
6 |
7 | // Module for encoding/decoding UTF8
8 | var UTF8 = (function () {
9 |
10 | return {
11 | /**
12 | Encode a string into UTF-8
13 |
14 | @param str
15 | The string to convert.
16 | This string should be encoded in some way such that each character is in the range [0,255]
17 | @return
18 | A UTF-8 encoded string
19 | */
20 | encode: function (str) {
21 | return unescape(encodeURIComponent(str));
22 | },
23 | /**
24 | Decode a string from UTF-8
25 |
26 | @param str
27 | A valid UTF-8 string
28 | @return
29 | The original string
30 | */
31 | decode: function (str) {
32 | return decodeURIComponent(escape(str));
33 | },
34 | };
35 |
36 | })();
37 |
38 |
39 |
40 | // Class for reading .torrent files
41 | var Torrent = (function () {
42 |
43 | var Torrent = function () {
44 | this.events = {
45 | "load": [],
46 | "error": [],
47 | "read_error": [],
48 | "read_abort": [],
49 | };
50 | this.file_name = null;
51 | this.data = null;
52 | };
53 |
54 |
55 |
56 | var on_reader_load = function (event) {
57 | // Decode
58 | var data_str = event.target.result;
59 |
60 | if (data_str instanceof ArrayBuffer) {
61 | // Convert to string
62 | var data_str2 = "",
63 | i;
64 |
65 | data_str = new Uint8Array(data_str);
66 | for (i = 0; i < data_str.length; ++i) {
67 | data_str2 += String.fromCharCode(data_str[i]);
68 | }
69 |
70 | data_str = data_str2;
71 | }
72 |
73 | try {
74 | this.data = Bencode.decode(data_str);
75 | }
76 | catch (e) {
77 | // Throw an error
78 | this.data = null;
79 | this.file_name = null;
80 | trigger.call(this, "error", {
81 | type: "Bencode error",
82 | exception: e,
83 | });
84 | return;
85 | }
86 |
87 | // Loaded
88 | trigger.call(this, "load", {});
89 | };
90 | var on_reader_error = function () {
91 | trigger.call(this, "read_error", {});
92 | };
93 | var on_reader_abort = function () {
94 | trigger.call(this, "read_abort", {});
95 | };
96 | var trigger = function (event, data) {
97 | // Trigger an event
98 | var callbacks = this.events[event],
99 | i;
100 |
101 | for (i = 0; i < callbacks.length; ++i) {
102 | callbacks[i].call(this, data, event);
103 | }
104 | };
105 | var no_change = function (x) {
106 | return x;
107 | };
108 |
109 | var magnet_component_order_default = [ "xt" , "xl" , "dn" , "tr" ];
110 |
111 | var format_uri = function (array_values, encode_fcn) {
112 | if (array_values.length <= 1) return encode_fcn(array_values[0]);
113 |
114 | return array_values[0].replace(/\{([0-9]+)\}/, function (match) {
115 | return encode_fcn(array_values[parseInt(match[1], 10) + 1] || "");
116 | });
117 | };
118 |
119 |
120 |
121 | /**
122 | Convert URI components object into a magnet URI.
123 | This is used to format the same object multiple times without rehashing anything.
124 |
125 | @param link_components
126 | An object returned from convert_to_magnet with return_components=true
127 | @param custom_name
128 | Can take one of the following values:
129 | null/undefined: name will remain the same as it originally was
130 | string: the custom name to give the magnet URI
131 | @param tracker_mode
132 | Can take one of the following values:
133 | null/undefined/false/number < 0: single tracker only (primary one)
134 | true: multiple trackers (without numbered suffix)
135 | number >= 0: multiple trackers (with numbered suffix starting at the specified number)
136 | @param uri_encode
137 | Can take one of the following values:
138 | null/undefined/true: encode components using encodeURIComponent
139 | false: no encoding; components are left as-is
140 | function: custom encoding function
141 | @param component_order
142 | A list containing the order URI components should appear in.
143 | Default is [ "xt" , "xl" , "dn" , "tr" ]
144 | null/undefined will use the default
145 | @return
146 | A formatted URI
147 | */
148 | Torrent.components_to_magnet = function (link_components, custom_name, tracker_mode, uri_encode, component_order) {
149 | // Vars
150 | var link, obj, list1, val, i, j;
151 |
152 | uri_encode = (uri_encode === false) ? no_change : (typeof(uri_encode) == "function" ? uri_encode : encodeURIComponent);
153 | component_order = (component_order === null) ? magnet_component_order_default : component_order;
154 |
155 | // Setup
156 | if (typeof(custom_name) == "string") {
157 | link_components.dn.values = [ custom_name ];
158 | }
159 |
160 | link_components.tr.suffix = -1;
161 | if (typeof(tracker_mode) == "number") {
162 | tracker_mode = Math.floor(tracker_mode);
163 | if (tracker_mode >= 0) link_components.tr.suffix = tracker_mode;
164 | }
165 | else if (tracker_mode === true) {
166 | link_components.tr.suffix = -2;
167 | }
168 |
169 | // Form into a URL
170 | link = "magnet:";
171 | val = 0; // number of components added
172 | for (i = 0; i < component_order.length; ++i) {
173 | if (!(component_order[i] in link_components)) continue; // not valid
174 | obj = link_components[component_order[i]];
175 | list1 = obj.values;
176 | for (j = 0; j < list1.length; ++j) {
177 | // Separator
178 | link += (val === 0 ? "?" : "&");
179 | ++val;
180 |
181 | // Key
182 | link += component_order[i];
183 |
184 | // Number
185 | if (obj.suffix >= 0 && list1.length > 1) {
186 | link += ".";
187 | link += obj.suffix;
188 | ++obj.suffix;
189 | }
190 |
191 | // Value
192 | link += "=";
193 | link += format_uri(list1[j], uri_encode);
194 |
195 | // Done
196 | if (obj.suffix == -1) break;
197 | }
198 | }
199 |
200 | // Done
201 | return link;
202 | };
203 |
204 |
205 |
206 | Torrent.prototype = {
207 | constructor: Torrent,
208 |
209 | read: function (file) {
210 | this.data = null;
211 | this.file_name = file.name;
212 |
213 | var reader = new FileReader();
214 |
215 | reader.addEventListener("load", on_reader_load.bind(this), false);
216 | reader.addEventListener("error", on_reader_error.bind(this), false);
217 | reader.addEventListener("abort", on_reader_abort.bind(this), false);
218 |
219 | try {
220 | reader.readAsBinaryString(file);
221 | }
222 | catch (e) {
223 | reader.readAsArrayBuffer(file);
224 | }
225 | },
226 |
227 | on: function (event, callback) {
228 | if (event in this.events) {
229 | this.events[event].push(callback);
230 | return true;
231 | }
232 | return false;
233 | },
234 | off: function (event, callback) {
235 | if (event in this.events) {
236 | var callbacks = this.events[event],
237 | i;
238 |
239 | for (i = 0; i < callbacks.length; ++i) {
240 | if (callbacks[i] == callback) {
241 | callbacks.splice(i, 1);
242 | return true;
243 | }
244 | }
245 | }
246 | return false;
247 | },
248 |
249 | /**
250 | Convert the torrent data into a magnet link.
251 |
252 | @param custom_name
253 | Can take one of the following values:
254 | 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
255 | false: no custom name will be generated OR assumed from the original file name
256 | string: the custom name to give the magnet URI
257 | @param tracker_mode
258 | Can take one of the following values:
259 | null/undefined/false/number < 0: single tracker only (primary one)
260 | true: multiple trackers (without numbered suffix)
261 | number >= 0: multiple trackers (with numbered suffix starting at the specified number)
262 | @param uri_encode
263 | Can take one of the following values:
264 | null/undefined/true: encode components using encodeURIComponent
265 | false: no encoding; components are left as-is
266 | function: custom encoding function
267 | @param component_order
268 | A list containing the order URI components should appear in.
269 | Default is [ "xt" , "xl" , "dn" , "tr" ]
270 | null/undefined will use the default
271 | @param return_components
272 | If true, this returns the link components which can then be used with components_to_magnet
273 | @return
274 | A formatted URI if return_components is falsy, else an object containing the parts of the link
275 | Also can return null if insufficient data is found
276 | */
277 | convert_to_magnet: function (custom_name, tracker_mode, uri_encode, component_order, return_components) {
278 | // Insufficient data
279 | if (this.data === null || !("info" in this.data)) return null;
280 |
281 | // Bencode info
282 | var info = this.data.info,
283 | info_bencoded = Bencode.encode(info),
284 | info_hasher = new SHA1(),
285 | link_components = {},
286 | info_hash, link, list1, list2, val, i, j;
287 |
288 | // Hash
289 | info_hasher.update(info_bencoded);
290 | info_hash = info_hasher.digest();
291 | info_hash = String.fromCharCode.apply(null, info_hash); // convert to binary string
292 | info_hash = Base32.encode(info_hash); // convert to base32
293 |
294 | // Setup link
295 | for (i = 0; i < magnet_component_order_default.length; ++i) {
296 | link_components[magnet_component_order_default[i]] = {
297 | suffix: -1,
298 | values: [],
299 | };
300 | }
301 |
302 | // Create
303 | link_components.xt.values.push([ "urn:btih:{0}", info_hash ]);
304 |
305 | if ("length" in info) {
306 | link_components.xl.values.push([ info.length ]);
307 | }
308 |
309 | if (typeof(custom_name) == "string") {
310 | link_components.dn.values.push([ custom_name ]);
311 | }
312 | else if ("name" in info) {
313 | link_components.dn.values.push([ UTF8.decode(info.name) ]);
314 | }
315 | else if (custom_name !== false && this.file_name) {
316 | link_components.dn.values.push([ this.file_name ]);
317 | }
318 |
319 | list1 = link_components.tr.values;
320 | if ("announce" in this.data) {
321 | list1.push([ UTF8.decode(this.data.announce) ]);
322 | }
323 | if ("announce-list" in this.data && Array.isArray(list2 = this.data["announce-list"])) {
324 | // Add more trackers
325 | for (i = 0; i < list2.length; ++i) {
326 | if (!Array.isArray(list2[i])) continue; // bad data
327 | for (j = 0; j < list2[i].length; ++j) {
328 | val = UTF8.decode(list2[i][j]);
329 | if (list1.indexOf(val) < 0) list1.push([ val ]);
330 | }
331 | }
332 | }
333 |
334 | // Convert
335 | if (return_components) return link_components;
336 | link = Torrent.components_to_magnet(link_components, null, tracker_mode, uri_encode, component_order);
337 |
338 | // Done
339 | return link;
340 | },
341 | };
342 |
343 |
344 |
345 | return Torrent;
346 |
347 | })();
348 |
349 |
350 |
351 | // Class for enumerating the results in the DOM
352 | var Result = (function () {
353 |
354 | var Result = function () {
355 | this.torrent_magnet_components = null;
356 |
357 | this.container = null;
358 | this.magnet_link = null;
359 | this.magnet_link_text = null;
360 | this.magnet_textbox = null;
361 | this.options_link = null;
362 | this.options_container = null;
363 | this.options = null;
364 | };
365 |
366 |
367 |
368 | var on_options_link_click = function () {
369 | if (this.options_container.classList.contains("converted_item_options_container_visible")) {
370 | this.options_container.classList.remove("converted_item_options_container_visible");
371 | this.magnet_textbox.readOnly = true;
372 | }
373 | else {
374 | this.options_container.classList.add("converted_item_options_container_visible");
375 | this.magnet_textbox.readOnly = false;
376 | }
377 | };
378 |
379 | var on_textbox_click = function () {
380 | if (this.magnet_textbox.readOnly) {
381 | this.magnet_textbox.select();
382 | }
383 | };
384 | var on_textbox_keydown = function () {
385 | if (this.magnet_textbox.readOnly) return;
386 |
387 | setTimeout(on_textbox_update.bind(this), 10);
388 | };
389 | var on_textbox_change = function () {
390 | on_textbox_update.call(this);
391 | };
392 | var on_textbox_update = function () {
393 | // Get value
394 | var uri = this.magnet_textbox.value,
395 | protocol = "magnet:";
396 |
397 | // Must have correct protocol
398 | if (uri.substr(0, protocol.length).toLowerCase() != protocol) {
399 | if (uri.length < protocol.length && uri.toLowerCase() == protocol.substr(0, uri.length)) {
400 | // Almost correct
401 | uri += protocol.substr(uri.length);
402 | }
403 | else {
404 | // Wrong
405 | uri = protocol + uri;
406 | }
407 | }
408 |
409 | // Update
410 | this.magnet_link.setAttribute("href", uri);
411 | this.magnet_link_text.textContent = uri;
412 | };
413 |
414 | var on_option_change = function () {
415 | update_links.call(this, true);
416 | };
417 |
418 | var update_links = function (update_displays) {
419 | // Update magnet links
420 | var magnet_uri = "magnet:asdf",
421 | tracker_mode = false,
422 | order = [ "xt" ];
423 |
424 | if (this.options[0][0][1].checked) {
425 | order.push("dn");
426 | }
427 | if (this.options[1][0][1].checked) {
428 | order.push("xl");
429 | }
430 | if (this.options[2][0][1].checked) {
431 | order.push("tr");
432 | if (this.options[2][1][1].checked) {
433 | tracker_mode = true;
434 | if (this.options[2][2][1].checked) {
435 | tracker_mode = 1;
436 | }
437 | }
438 | }
439 |
440 | magnet_uri = Torrent.components_to_magnet(this.torrent_magnet_components, null, tracker_mode, true, order);
441 |
442 | // Update text/values
443 | this.magnet_link.setAttribute("href", magnet_uri);
444 | this.magnet_link_text.textContent = magnet_uri;
445 | this.magnet_textbox.value = magnet_uri;
446 |
447 | if (!update_displays) return;
448 |
449 | // Update display
450 | var i, j, opt_list;
451 | for (i = 0; i < this.options.length; ++i) {
452 | opt_list = this.options[i];
453 |
454 | for (j = 0; j < opt_list.length; ++j) {
455 | if (!opt_list[j][1].checked) {
456 | // This is unchecked; modify boxes after
457 | opt_list[j][0].classList.add("converted_item_option_part_visible");
458 | // Hide
459 | while (++j < opt_list.length) {
460 | opt_list[j][0].classList.remove("converted_item_option_part_visible");
461 | opt_list[j][1].checked = false;
462 | }
463 | break;
464 | }
465 | }
466 | }
467 | };
468 |
469 |
470 |
471 | Result.prototype = {
472 | constructor: Result,
473 |
474 | generate: function (torrent_object, parent_node) {
475 | var n1, n2, n3, n4, n5, i, j, ev_bind;
476 |
477 | // Clear
478 | this.options = [];
479 |
480 |
481 | //{ Setup DOM nodes
482 | this.container = document.createElement("div");
483 | this.container.className = "converted_item";
484 |
485 |
486 | // Title
487 | n1 = document.createElement("div");
488 | n1.className = "converted_item_title_container";
489 |
490 | n2 = document.createElement("div");
491 | n2.className = "converted_item_title";
492 | n2.textContent = torrent_object.file_name || (torrent_object.data && torrent_object.data.info ? torrent_object.data.name : null) || ".torrent";
493 |
494 | n1.appendChild(n2);
495 | this.container.appendChild(n1);
496 |
497 | // Contents
498 | n1 = document.createElement("div");
499 | n1.className = "converted_item_contents";
500 |
501 | // Links
502 | n2 = document.createElement("div");
503 | n2.className = "converted_item_link_container";
504 |
505 | this.magnet_link = document.createElement("a");
506 | this.magnet_link.className = "converted_item_link";
507 |
508 | this.magnet_link_text = document.createElement("span");
509 |
510 | this.magnet_link.appendChild(this.magnet_link_text);
511 | n2.appendChild(this.magnet_link);
512 |
513 | this.magnet_textbox = document.createElement("input");
514 | this.magnet_textbox.className = "converted_item_textbox";
515 | this.magnet_textbox.setAttribute("type", "text");
516 | this.magnet_textbox.readOnly = true;
517 |
518 | n2.appendChild(this.magnet_textbox);
519 | n1.appendChild(n2);
520 |
521 |
522 | // Options container
523 | this.options_container = document.createElement("div");
524 | this.options_container.className = "converted_item_options_container";
525 |
526 | // Header
527 | n2 = document.createElement("div");
528 | n2.className = "converted_item_header";
529 |
530 | n3 = document.createElement("span");
531 | n3.className = "converted_item_header_text";
532 | n3.textContent = "Options:";
533 |
534 | n2.appendChild(n3);
535 |
536 | this.options_link = document.createElement("a");
537 | this.options_link.className = "converted_item_options_toggle";
538 |
539 | n3 = document.createElement("span");
540 | this.options_link.appendChild(n3);
541 |
542 | n2.appendChild(this.options_link);
543 | this.options_container.appendChild(n2);
544 |
545 |
546 | // Options
547 | n2 = document.createElement("div");
548 | n2.className = "converted_item_options";
549 |
550 | // Name
551 | this.options.push([]);
552 |
553 | n3 = document.createElement("div");
554 | n3.className = "converted_item_option";
555 |
556 | n4 = document.createElement("label");
557 | n4.className = "converted_item_option_part converted_item_option_part_visible";
558 |
559 | n5 = document.createElement("input");
560 | n5.className = "converted_item_option_checkbox checkbox";
561 | n5.setAttribute("type", "checkbox");
562 | n5.checked = true;
563 | n4.appendChild(n5);
564 |
565 | this.options[this.options.length - 1].push([ n4 , n5 ]);
566 |
567 | n5 = document.createElement("span");
568 | n5.className = "converted_item_option_text";
569 | n5.textContent = "Include name";
570 | n4.appendChild(n5);
571 |
572 | n3.appendChild(n4);
573 | n2.appendChild(n3);
574 |
575 |
576 | // Data length
577 | this.options.push([]);
578 |
579 | n3 = document.createElement("div");
580 | n3.className = "converted_item_option";
581 |
582 | n4 = document.createElement("label");
583 | n4.className = "converted_item_option_part converted_item_option_part_visible";
584 |
585 | n5 = document.createElement("input");
586 | n5.className = "converted_item_option_checkbox checkbox";
587 | n5.setAttribute("type", "checkbox");
588 | n5.checked = true;
589 | n4.appendChild(n5);
590 |
591 | this.options[this.options.length - 1].push([ n4 , n5 ]);
592 |
593 | n5 = document.createElement("span");
594 | n5.className = "converted_item_option_text";
595 | n5.textContent = "Include data length";
596 | n4.appendChild(n5);
597 |
598 | n3.appendChild(n4);
599 | n2.appendChild(n3);
600 |
601 |
602 | // Tracker
603 | this.options.push([]);
604 |
605 | n3 = document.createElement("div");
606 | n3.className = "converted_item_option";
607 |
608 | n4 = document.createElement("label");
609 | n4.className = "converted_item_option_part converted_item_option_part_visible";
610 |
611 | n5 = document.createElement("input");
612 | n5.className = "converted_item_option_checkbox checkbox";
613 | n5.setAttribute("type", "checkbox");
614 | n5.checked = true;
615 | n4.appendChild(n5);
616 |
617 | this.options[this.options.length - 1].push([ n4 , n5 ]);
618 |
619 | n5 = document.createElement("span");
620 | n5.className = "converted_item_option_text";
621 | n5.textContent = "Include tracker";
622 | n4.appendChild(n5);
623 |
624 | n3.appendChild(n4);
625 |
626 |
627 | n4 = document.createElement("label");
628 | n4.className = "converted_item_option_part converted_item_option_part_visible";
629 |
630 | n5 = document.createElement("input");
631 | n5.className = "converted_item_option_checkbox checkbox";
632 | n5.setAttribute("type", "checkbox");
633 | n5.checked = false;
634 | n4.appendChild(n5);
635 |
636 | this.options[this.options.length - 1].push([ n4 , n5 ]);
637 |
638 | n5 = document.createElement("span");
639 | n5.className = "converted_item_option_text";
640 | n5.textContent = "Allow multiple trackers";
641 | n4.appendChild(n5);
642 |
643 | n3.appendChild(n4);
644 |
645 |
646 | n4 = document.createElement("label");
647 | n4.className = "converted_item_option_part";
648 |
649 | n5 = document.createElement("input");
650 | n5.className = "converted_item_option_checkbox checkbox";
651 | n5.setAttribute("type", "checkbox");
652 | n5.checked = false;
653 | n4.appendChild(n5);
654 |
655 | this.options[this.options.length - 1].push([ n4 , n5 ]);
656 |
657 | n5 = document.createElement("span");
658 | n5.className = "converted_item_option_text";
659 | n5.textContent = "Numbered keys";
660 | n4.appendChild(n5);
661 |
662 | n3.appendChild(n4);
663 | n2.appendChild(n3);
664 |
665 |
666 | // Add options
667 | this.options_container.appendChild(n2);
668 | n1.appendChild(this.options_container);
669 |
670 |
671 | // Done
672 | this.container.appendChild(n1);
673 | //}
674 |
675 |
676 | // Data
677 | this.torrent_magnet_components = torrent_object.convert_to_magnet(null, false, true, null, true);
678 | update_links.call(this, false);
679 |
680 |
681 | // Events
682 | this.options_link.addEventListener("click", on_options_link_click.bind(this), false);
683 | this.magnet_textbox.addEventListener("click", on_textbox_click.bind(this), false);
684 | this.magnet_textbox.addEventListener("keydown", on_textbox_keydown.bind(this), false);
685 | this.magnet_textbox.addEventListener("change", on_textbox_change.bind(this), false);
686 |
687 | ev_bind = on_option_change.bind(this);
688 | for (i = 0; i < this.options.length; ++i) {
689 | for (j = 0; j < this.options[i].length; ++j) {
690 | this.options[i][j][1].addEventListener("change", ev_bind, false);
691 | }
692 | }
693 |
694 |
695 | // Rice and add
696 | if (rice_checkboxes) {
697 | rice_checkboxes(this.container.querySelectorAll("input[type=checkbox].checkbox"));
698 | }
699 | if (parent_node) parent_node.appendChild(this.container);
700 |
701 | },
702 | update: function () {
703 |
704 | },
705 | };
706 |
707 |
708 |
709 | return Result;
710 |
711 | })();
712 |
713 |
714 |
715 | // Other functions
716 | var rice_checkboxes = null;
717 | var on_torrent_load = function () {
718 | var container = document.querySelector(".converted"),
719 | result;
720 |
721 | if (container === null) return;
722 |
723 | container.classList.add("converted_visible");
724 |
725 | result = new Result();
726 | result.generate(this, container);
727 | };
728 |
729 |
730 |
731 | // Exposed functions
732 | var functions = {
733 | setup: function (rice_checkboxes_import) {
734 | rice_checkboxes = rice_checkboxes_import;
735 | },
736 | queue_torrent_files: function (files) {
737 | // Read files
738 | var i, t;
739 |
740 | for (i = 0; i < files.length; ++i) {
741 | t = new Torrent();
742 | t.on("load", on_torrent_load);
743 | t.read(files[i]);
744 | }
745 | },
746 | };
747 |
748 | return functions;
749 |
750 | })();
751 |
752 |
753 |
--------------------------------------------------------------------------------
/src/t2m.loader.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | (function () {
5 | "use strict";
6 |
7 | // Function for performing actions as soon as possible
8 | var on_ready = (function () {
9 |
10 | // Vars
11 | var callbacks = [],
12 | check_interval = null,
13 | check_interval_time = 250;
14 |
15 | // Check if ready and run callbacks
16 | var callback_check = function () {
17 | if (
18 | (document.readyState === "interactive" || document.readyState === "complete") &&
19 | callbacks !== null
20 | ) {
21 | // Run callbacks
22 | var cbs = callbacks,
23 | cb_count = cbs.length,
24 | i;
25 |
26 | // Clear
27 | callbacks = null;
28 |
29 | for (i = 0; i < cb_count; ++i) {
30 | cbs[i].call(null);
31 | }
32 |
33 | // Clear events and checking interval
34 | window.removeEventListener("load", callback_check, false);
35 | window.removeEventListener("readystatechange", callback_check, false);
36 |
37 | if (check_interval !== null) {
38 | clearInterval(check_interval);
39 | check_interval = null;
40 | }
41 |
42 | // Okay
43 | return true;
44 | }
45 |
46 | // Not executed
47 | return false;
48 | };
49 |
50 | // Listen
51 | window.addEventListener("load", callback_check, false);
52 | window.addEventListener("readystatechange", callback_check, false);
53 |
54 | // Callback adding function
55 | return function (cb) {
56 | if (callbacks === null) {
57 | // Ready to execute
58 | cb.call(null);
59 | }
60 | else {
61 | // Delay
62 | callbacks.push(cb);
63 |
64 | // Set a check interval
65 | if (check_interval === null && callback_check() !== true) {
66 | check_interval = setInterval(callback_check, check_interval_time);
67 | }
68 | }
69 | };
70 |
71 | })();
72 |
73 |
74 |
75 | // Functions
76 | var script_add = (function () {
77 |
78 | var script_on_load = function (state) {
79 | // Okay
80 | script_remove_event_listeners.call(this, state, true);
81 | };
82 | var script_on_error = function (state) {
83 | // Error
84 | script_remove_event_listeners.call(this, state, false);
85 | };
86 | var script_on_readystatechange = function (state) {
87 | if (this.readyState === "loaded" || this.readyState === "complete") {
88 | // Okay
89 | script_remove_event_listeners.call(this, state, true);
90 | }
91 | };
92 | var script_remove_event_listeners = function (state, okay) {
93 | // Remove event listeners
94 | this.addEventListener("load", state.on_load, false);
95 | this.addEventListener("error", state.on_error, false);
96 | this.addEventListener("readystatechange", state.on_readystatechange, false);
97 |
98 | state.on_load = null;
99 | state.on_error = null;
100 | state.on_readystatechange = null;
101 |
102 | // Trigger
103 | if (state.callback) state.callback.call(null, okay, this);
104 |
105 | // Remove
106 | var par = this.parentNode;
107 | if (par) par.removeChild(this);
108 | };
109 |
110 |
111 |
112 | return function (url, callback) {
113 | var head = document.head,
114 | script, state;
115 |
116 | if (!head) {
117 | // Callback and done
118 | callback.call(null, false, null);
119 | return false;
120 | }
121 |
122 | // Load state
123 | state = {
124 | on_load: null,
125 | on_error: null,
126 | on_readystatechange: null,
127 | callback: callback,
128 | };
129 |
130 | // New script tag
131 | script = document.createElement("script");
132 | script.async = true;
133 | script.setAttribute("src", url);
134 |
135 | // Events
136 | script.addEventListener("load", (state.on_load = script_on_load.bind(script, state)), false);
137 | script.addEventListener("error", (state.on_error = script_on_error.bind(script, state)), false);
138 | script.addEventListener("readystatechange", (state.on_readystatechange = script_on_readystatechange.bind(script, state)), false);
139 |
140 | // Add
141 | head.appendChild(script);
142 |
143 | // Done
144 | return true;
145 | };
146 |
147 | })();
148 |
149 | var on_generic_stop_propagation = function (event) {
150 | event.stopPropagation();
151 | };
152 |
153 | var on_exclusive_mode_change = function (flag_node) {
154 | exclusive_mode_update.call(this, flag_node, false);
155 | };
156 | var exclusive_mode_update = (function () {
157 | var previous_fragment = "";
158 |
159 | return function (flag_node, check_fragment) {
160 | var hash_is_exclusive = (window.location.hash == "#converter.exclusive");
161 |
162 | if (check_fragment) {
163 | this.checked = hash_is_exclusive;
164 | }
165 | else {
166 | if (this.checked ^ (!hash_is_exclusive)) {
167 | previous_fragment = window.location.hash;
168 | }
169 | window.history.replaceState({}, "", window.location.pathname + (this.checked ? "#converter.exclusive" : previous_fragment));
170 | }
171 |
172 | if (this.checked) {
173 | flag_node.classList.add("exclusive_enabled");
174 | }
175 | else {
176 | flag_node.classList.remove("exclusive_enabled");
177 | }
178 | };
179 | })();
180 |
181 | var on_converter_click = function (converter_files_input, event) {
182 | if (event.which != 2 && event.which != 3) {
183 | converter_files_input.click();
184 | }
185 | };
186 | var on_converter_files_change = function (converter) {
187 | // Read
188 | on_converter_test_files.call(converter, this.files);
189 |
190 | // Nullify
191 | this.value = null;
192 | };
193 |
194 | var on_file_dragover = function (converter, event) {
195 | if (Array.prototype.indexOf.call(event.dataTransfer.types, "Files") < 0) return;
196 |
197 | converter.classList.add("converter_files_active");
198 | if (this === converter) converter.classList.add("converter_files_hover");
199 |
200 | event.dataTransfer.dropEffect = "copy";
201 | event.preventDefault();
202 | event.stopPropagation();
203 | return false;
204 | };
205 | var on_file_dragleave = function (converter, event) {
206 | if (Array.prototype.indexOf.call(event.dataTransfer.types, "Files") < 0) return;
207 |
208 | converter.classList.remove("converter_files_hover");
209 | if (this !== converter) converter.classList.remove("converter_files_active");
210 |
211 | event.preventDefault();
212 | event.stopPropagation();
213 | return false;
214 | };
215 | var on_file_drop = function (converter, event) {
216 | // Reset style
217 | converter.classList.remove("converter_files_active");
218 | converter.classList.remove("converter_files_hover");
219 | event.preventDefault();
220 | event.stopPropagation();
221 |
222 | // Not over the converter
223 | if (this !== converter) return false;
224 |
225 | // Read files
226 | on_converter_test_files.call(converter, event.dataTransfer.files);
227 |
228 | // Done
229 | return false;
230 | };
231 |
232 | var on_converter_test_files = function (files) {
233 | // Read
234 | var re_ext = /(\.[^\.]*|)$/,
235 | read_files = [],
236 | ext, i;
237 |
238 | for (i = 0; i < files.length; ++i) {
239 | ext = re_ext.exec(files[i].name)[1].toLowerCase();
240 | if (ext == ".torrent") {
241 | read_files.push(files[i]);
242 | }
243 | }
244 |
245 | // Nothing to do
246 | if (read_files.length === 0) return;
247 |
248 | // Load scripts if necessary
249 | load_requirements(function (errors) {
250 | if (errors === 0) {
251 | // Load
252 | var T2M_obj;
253 | try {
254 | T2M_obj = T2M;
255 | }
256 | catch(e) {
257 | return; // not found
258 | }
259 | T2M_obj.queue_torrent_files(read_files);
260 | }
261 | });
262 | };
263 |
264 | var load_requirements = (function () {
265 |
266 | // Script requirements
267 | var requirements = [
268 | "src/sha1.js",
269 | "src/bencode.js",
270 | "src/base32.js",
271 | "src/t2m.js",
272 | ];
273 |
274 |
275 | var on_all_scripts_loaded = function () {
276 | var T2M_obj;
277 | try {
278 | T2M_obj = T2M;
279 | }
280 | catch(e) {
281 | return; // not found
282 | }
283 |
284 | T2M_obj.setup(rice_checkboxes);
285 | };
286 | var on_script_load = function (state, callback, okay) {
287 | if (okay) ++state.okay;
288 |
289 | if (++state.count >= state.total) {
290 | // All loaded/errored
291 | if (state.total - state.okay === 0) on_all_scripts_loaded();
292 | callback.call(null, state.total - state.okay);
293 | }
294 | };
295 |
296 | // Return the loading function
297 | return function (callback) {
298 | // Already loaded?
299 | if (requirements === null) {
300 | // Yes
301 | callback.call(null, 0);
302 | return;
303 | }
304 |
305 | var head = document.head,
306 | on_load, i;
307 |
308 | if (!head) return false;
309 |
310 | // Load
311 | on_load = on_script_load.bind(null, { okay: 0, count: 0, total: requirements.length, }, callback);
312 | for (i = 0; i < requirements.length; ++i) {
313 | script_add(requirements[i], on_load);
314 | }
315 |
316 | // Done
317 | requirements = null;
318 | return true;
319 | };
320 |
321 | })();
322 |
323 | var restyle_noscript = function () {
324 | // Script
325 | var nodes = document.querySelectorAll(".script_disabled"),
326 | i;
327 |
328 | for (i = 0; i < nodes.length; ++i) {
329 | nodes[i].classList.remove("script_visible");
330 | }
331 |
332 | nodes = document.querySelectorAll(".script_enabled");
333 | for (i = 0; i < nodes.length; ++i) {
334 | nodes[i].classList.add("script_visible");
335 | }
336 | };
337 |
338 | var rice_checkboxes = function (nodes) {
339 | var svgns = "http://www.w3.org/2000/svg",
340 | i, par, sib, node, n1, n2, n3;
341 |
342 | nodes = nodes || document.querySelectorAll("input[type=checkbox].checkbox");
343 |
344 | for (i = 0; i < nodes.length; ++i) {
345 | node = nodes[i];
346 | par = node.parentNode;
347 | sib = node.nextSibling;
348 |
349 | // Create new checkbox
350 | n1 = document.createElement("label");
351 | n1.className = node.className;
352 |
353 | n2 = document.createElementNS(svgns, "svg");
354 | n2.setAttribute("svgns", svgns);
355 | n2.setAttribute("viewBox", "0 0 16 16");
356 |
357 | n3 = document.createElementNS(svgns, "polygon");
358 | n3.setAttribute("points", "13,0 16,2 8,16 5,16 0,11 2,8 6,11.5");
359 |
360 | // Re-add
361 | n2.appendChild(n3);
362 | n1.appendChild(n2);
363 | par.insertBefore(n1, node);
364 | n1.insertBefore(node, n2);
365 | }
366 | };
367 |
368 |
369 |
370 | // Execute
371 | on_ready(function () {
372 | // Noscript
373 | var nodes, i;
374 |
375 | // Rice
376 | restyle_noscript();
377 | rice_checkboxes();
378 |
379 | // Stop propagation links
380 | nodes = document.querySelectorAll(".link_stop_propagation");
381 | for (i = 0; i < nodes.length; ++i) {
382 | nodes[i].addEventListener("click", on_generic_stop_propagation, false);
383 | }
384 |
385 | // Setup converter
386 | var converter = document.querySelector(".converter"),
387 | converter_files = document.querySelector(".converter_files_input"),
388 | exclusive_mode = document.querySelector("input.converter_exclusive_mode_check"),
389 | non_exclusive_body = document.querySelector(".non_exclusive"),
390 | body = document.body;
391 |
392 | if (converter !== null) {
393 | // File browser
394 | if (converter_files !== null) {
395 | converter.addEventListener("click", on_converter_click.bind(converter, converter_files), false);
396 | converter_files.addEventListener("change", on_converter_files_change.bind(converter_files, converter), false);
397 | }
398 |
399 | // File drag/drop events
400 | converter.addEventListener("dragover", on_file_dragover.bind(converter, converter), false);
401 | converter.addEventListener("dragleave", on_file_dragleave.bind(converter, converter), false);
402 | converter.addEventListener("drop", on_file_drop.bind(converter, converter), false);
403 |
404 | body.addEventListener("dragover", on_file_dragover.bind(body, converter), false);
405 | body.addEventListener("dragleave", on_file_dragleave.bind(body, converter), false);
406 | body.addEventListener("drop", on_file_drop.bind(body, converter), false);
407 |
408 | // Exclusive
409 | if (exclusive_mode !== null) {
410 | exclusive_mode_update.call(exclusive_mode, non_exclusive_body, true);
411 | exclusive_mode.addEventListener("change", on_exclusive_mode_change.bind(exclusive_mode, non_exclusive_body), false);
412 | }
413 | }
414 | });
415 |
416 | })();
417 |
418 |
419 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 0;
3 | margin: 0;
4 | border: none;
5 | color: #111111;
6 | background-color: #ffffff;
7 | font-family: Arial;
8 | font-size: 16px;
9 | overflow-x: hidden;
10 | overflow-y: scroll;
11 | text-align: center;
12 | }
13 |
14 | table,tbody,tr,td {
15 | margin: 0;
16 | padding: 0;
17 | border-spacing: 0;
18 | }
19 |
20 | p {
21 | margin: 0;
22 | padding: 0;
23 | line-height: 1.5em;
24 | }
25 | ol,ul {
26 | margin: 0 0 0 1em;
27 | padding: 0;
28 | }
29 | ol>li,ul>li {
30 | line-height: 1.5em;
31 | }
32 | ol>li+li,ul>li+li {
33 | margin-top: 0.5em;
34 | }
35 |
36 | h1,h2,h3,h4,h5,h6 {
37 | margin: 0;
38 | padding: 0;
39 | font-weight: bold;
40 | line-height: 1.5em;
41 | position: relative;
42 | }
43 | h1 {
44 | font-size: 2em;
45 | }
46 | h2 {
47 | font-size: 1.8em;
48 | }
49 | h3 {
50 | font-size: 1.6em;
51 | }
52 | h4 {
53 | font-size: 1.4em;
54 | }
55 | h5 {
56 | font-size: 1.2em;
57 | }
58 | h6 {
59 | font-size: 1em;
60 | }
61 |
62 | input,
63 | textarea {
64 | font-size: inherit;
65 | }
66 | input:focus,
67 | textarea:focus {
68 | outline: none;
69 | }
70 |
71 | .hardlink_text {
72 | vertical-align: middle;
73 | }
74 | .hardlink_text:not(:hover)>a.hardlink {
75 | visibility: hidden;
76 | opacity: 0;
77 |
78 | -webkit-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
79 | -moz-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
80 | -o-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
81 | transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0s, visibility 0.25s linear 0s;
82 | }
83 | a.hardlink {
84 | display: block;
85 | visibility: visible;
86 | position: absolute;
87 | right: 100%;
88 | top: 0;
89 | padding: 0 0.25em 0 0.5em;
90 | color: #c8c8c8;
91 | font-weight: bold;
92 |
93 | -webkit-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
94 | -moz-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
95 | -o-transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
96 | transition: color 0.25s ease-in-out 0s, opacity 0.25s ease-in-out 0.5s, visibility 0s linear 0.5s;
97 | }
98 | a.hardlink:hover {
99 | text-decoration: none;
100 | color: #30b194;
101 | }
102 | a.hardlink:after {
103 | content: "#";
104 | }
105 |
106 | p+p,
107 | h1+p,h2+p,h3+p,h4+p,h5+p,h6+p,
108 | p+h1,p+h2,p+h3,p+h4,p+h5,p+h6,
109 | h4+h6 {
110 | margin-top: 1em;
111 | }
112 |
113 | strong {
114 | font-size: 1em;
115 | }
116 |
117 | code {
118 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
119 | }
120 | code.nowrap {
121 | display: inline-block;
122 | white-space: nowrap;
123 | }
124 |
125 | .codeblock {
126 | font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
127 | display: block;
128 | background: #f3f3f3;
129 | border: 1px solid #e5e5e5;
130 | overflow-x: auto;
131 | overflow-y: hidden;
132 | padding: 0;
133 | line-height: 1.5em;
134 | }
135 | .codeblock_inner {
136 | display: inline-block;
137 | padding: 0.5em;
138 | }
139 | .codeblock.codeblock_pre>.codeblock_inner {
140 | white-space: pre;
141 | }
142 |
143 | *::selection {
144 | color: #ffffff;
145 | background: #30b194;
146 | text-shadow: none;
147 | }
148 | *::-moz-selection {
149 | color: #ffffff ;
150 | background: #30b194;
151 | text-shadow: none;
152 | }
153 |
154 | .section_id {
155 | }
156 |
157 | .script_disabled {
158 | }
159 | .script_disabled:not(.script_visible) {
160 | display: none;
161 | }
162 | .script_enabled {
163 | }
164 | .script_enabled:not(.script_visible) {
165 | display: none;
166 | }
167 |
168 | input[type=checkbox],
169 | input[type=radio] {
170 | padding: 0;
171 | margin: 0;
172 | vertical-align: middle;
173 | }
174 |
175 | label.checkbox {
176 | display: inline-block;
177 | width: 0.75em;
178 | height: 0.75em;
179 | vertical-align: middle;
180 | border: 0.09375em solid #111111;
181 | border-radius: 0.25em;
182 | padding: 0.125em;
183 | position: relative;
184 | cursor: pointer;
185 | }
186 | label.checkbox:before {
187 | z-index: -1;
188 | position: absolute;
189 | display: block;
190 | left: 0;
191 | right: 0;
192 | bottom: 0;
193 | top: 0;
194 | content: "";
195 | border: none;
196 | border-radius: 0.125em;
197 | background: #ffffff;
198 | }
199 | label.checkbox:hover:before,
200 | label:not([for]):hover label.checkbox:before {
201 | border: 0.125em solid #30b194;
202 | }
203 | label.checkbox>input[type=checkbox] {
204 | position: absolute;
205 | visibility: hidden;
206 | display: none;
207 | }
208 | label.checkbox>svg {
209 | display: none;
210 | width: 100%;
211 | height: 100%;
212 | position: relative;
213 | }
214 | label.checkbox>svg>polygon {
215 | fill: #111111;
216 | }
217 | label.checkbox.delete_checkbox>svg>polygon:not(:first-child) {
218 | visibility: hidden;
219 | }
220 | label.checkbox.delete_checkbox:hover>svg>polygon:first-child,
221 | label:not([for]):hover label.checkbox.delete_checkbox>svg>polygon:first-child {
222 | visibility: hidden;
223 | }
224 | label.checkbox.delete_checkbox:hover>svg>polygon:not(:first-child),
225 | label:not([for]):hover label.checkbox.delete_checkbox>svg>polygon:not(:first-child) {
226 | visibility: visible;
227 | }
228 | label.checkbox>input[type=checkbox]:checked+svg {
229 | display: block;
230 | }
231 |
232 |
233 | a {
234 | color: #30b194;
235 | cursor: pointer;
236 | text-decoration: none;
237 | }
238 | a:hover {
239 | text-decoration: underline;
240 | }
241 | a.light_underline {
242 | color: #c8c8c8;
243 | }
244 | a.light_underline>span {
245 | color: #30b194;
246 | }
247 | a.light_nohover_color_hover {
248 | color: #c8c8c8;
249 | }
250 | a.light_nohover_color_hover:hover {
251 | color: #30b194;
252 | text-decoration: none;
253 | }
254 |
255 | .main {
256 | text-align: left;
257 | display: inline-block;
258 | width: 60em;
259 | vertical-align: middle;
260 | }
261 | .main.main_no_overflow {
262 | overflow: hidden;
263 | }
264 | .main.body {
265 | margin: 0.5em 0 4em 0;
266 | }
267 |
268 | .header_bar {
269 | position: relative;
270 |
271 | background: #f3f3f3;
272 | background: -webkit-linear-gradient(180deg, #f9f9f9, #f3f3f3);
273 | background: -moz-linear-gradient(180deg, #f9f9f9, #f3f3f3);
274 | background: -o-linear-gradient(180deg, #f9f9f9, #f3f3f3);
275 | background: linear-gradient(180deg, #f9f9f9, #f3f3f3);
276 |
277 | border-bottom: 1px solid #e5e5e5;
278 | }
279 | .header_table {
280 | margin: 0.5em 0;
281 | text-align: left;
282 | }
283 | .header_table td {
284 | vertical-align: middle;
285 | }
286 | .header_table_cell {
287 | width: 0;
288 | }
289 | .header_table_cell.header_table_cell_full {
290 | width: 100%;
291 | }
292 |
293 | .header_table_name {
294 | font-size: 2em;
295 | line-height: 1.2em;
296 | white-space: nowrap;
297 | }
298 | .header_table_name_user {
299 | }
300 | .header_table_name_separator {
301 | display: inline-block;
302 | margin: 0 0.125em;
303 | color: #c8c8c8;
304 | }
305 | .header_table_name_title {
306 | }
307 | .header_table_name_title>span {
308 | font-weight: bold;
309 | }
310 |
311 | .header_table_separator {
312 | vertical-align: middle;
313 | display: inline-block;
314 | font-size: 2em;
315 | height: 1.2em;
316 | margin: 0 0.5em;
317 | border-left: 1px solid #c8c8c8;
318 | }
319 |
320 | .header_table_description {
321 | line-height: 1.1em;
322 | }
323 | .header_table_description_name {
324 | text-transform: lowercase;
325 | font-weight: bold;
326 | color: #404040;
327 | }
328 | .header_table_description_body {
329 | text-transform: lowercase;
330 | color: #606060;
331 | }
332 |
333 | .header_table_view_on_github {
334 | display: block;
335 | white-space: nowrap;
336 | margin-left: 1em;
337 | text-align: right;
338 | }
339 | .header_table_view_on_github_line1 {
340 | font-size: 0.8em;
341 | line-height: 1em;
342 | }
343 | .header_table_view_on_github_line2 {
344 | line-height: 1em;
345 | }
346 |
347 | .light {
348 | color: #808080;
349 | }
350 | .italic {
351 | font-style: italic;
352 | }
353 |
354 |
355 | .converter_exclusive_mode {
356 | white-space: nowrap;
357 | margin-left: 1em;
358 | padding: 0 2em 0 1em;
359 | display: inline-block;
360 | font-size: 0.5em;
361 | font-weight: normal;
362 | line-height: 1em;
363 | cursor: pointer;
364 | }
365 | .converter_exclusive_mode_text {
366 | vertical-align: middle;
367 | opacity: 0;
368 | -webkit-transition: opacity 0.25s ease-in-out 0s;
369 | -moz-transition: opacity 0.25s ease-in-out 0s;
370 | -o-transition: opacity 0.25s ease-in-out 0s;
371 | transition: opacity 0.25s ease-in-out 0s;
372 | }
373 | .converter_exclusive_mode_text:after {
374 | content: "exclusive mode";
375 | }
376 | .converter_exclusive_mode:hover>.converter_exclusive_mode_text {
377 | opacity: 1;
378 | -webkit-transition: opacity 0.25s ease-in-out 0.5s;
379 | -moz-transition: opacity 0.25s ease-in-out 0.5s;
380 | -o-transition: opacity 0.25s ease-in-out 0.5s;
381 | transition: opacity 0.25s ease-in-out 0.5s;
382 | }
383 | .converter_exclusive_mode_check {
384 | margin-right: 0.25em;
385 | opacity: 0;
386 | -webkit-transition: opacity 0.25s ease-in-out 0s;
387 | -moz-transition: opacity 0.25s ease-in-out 0s;
388 | -o-transition: opacity 0.25s ease-in-out 0s;
389 | transition: opacity 0.25s ease-in-out 0s;
390 | }
391 | .converter_exclusive_mode:hover>.converter_exclusive_mode_check {
392 | opacity: 1;
393 | -webkit-transition: opacity 0.25s ease-in-out 0.5s;
394 | -moz-transition: opacity 0.25s ease-in-out 0.5s;
395 | -o-transition: opacity 0.25s ease-in-out 0.5s;
396 | transition: opacity 0.25s ease-in-out 0.5s;
397 | }
398 |
399 | .converter {
400 | display: block;
401 | padding: 2em;
402 | border: 0.25em dashed #c8c8c8;
403 | cursor: pointer;
404 | text-align: center;
405 | -webkit-transition: border-color 0.25s ease-in-out 0s;
406 | -moz-transition: border-color 0.25s ease-in-out 0s;
407 | -o-transition: border-color 0.25s ease-in-out 0s;
408 | transition: border-color 0.25s ease-in-out 0s;
409 | background: #f3f3f3;
410 |
411 | }
412 | .converter.converter_files_active {
413 | border-color: #30b194;
414 | }
415 | .converter_container {
416 | display: inline-block;
417 | padding-right: 3em;
418 | }
419 | .converter_table {
420 | text-align: left;
421 | display: table;
422 | vertical-align: middle;
423 | }
424 | .converter_cell {
425 | display: table-cell;
426 | vertical-align: middle;
427 | }
428 | .converter_cell_left {
429 | width: 0;
430 | }
431 | .converter_cell_right {
432 | width: 100%;
433 | }
434 | .converter_svg_container {
435 | width: 8.5em;
436 | height: 10em;
437 | }
438 | .converter_svg_graphic {
439 | width: 10em;
440 | height: 10em;
441 | -webkit-transition: transform 0.25s ease-in-out 0s;
442 | -moz-transition: transform 0.25s ease-in-out 0s;
443 | -o-transition: transform 0.25s ease-in-out 0s;
444 | transition: transform 0.25s ease-in-out 0s;
445 | }
446 | .converter_svg_graphic_poly {
447 | fill: #c8c8c8;
448 | stroke: none;
449 | -webkit-transition: fill 0.25s ease-in-out 0s;
450 | -moz-transition: fill 0.25s ease-in-out 0s;
451 | -o-transition: fill 0.25s ease-in-out 0s;
452 | transition: fill 0.25s ease-in-out 0s;
453 | }
454 | .converter_info {
455 | display: inline-block;
456 | text-align: right;
457 | }
458 | .converter_info_line1 {
459 | font-weight: bold;
460 | font-size: 4em;
461 | line-height: 1em;
462 | }
463 | .converter_info_line2 {
464 | font-size: 2em;
465 | line-height: 1em;
466 | }
467 | .converter_info_line3 {
468 | margin-top: 2em;
469 | line-height: 1em;
470 | color: #a0a0a0;
471 | }
472 | .converter_files_input {
473 | display: none;
474 | }
475 |
476 | .converter:hover .converter_svg_graphic,
477 | .converter.converter_files_hover .converter_svg_graphic {
478 | -webkit-transform-origin: 50% 50%;
479 | -moz-transform-origin: 50% 50%;
480 | -ms-transform-origin: 50% 50%;
481 | -o-transform-origin: 50% 50%;
482 | transform-origin: 50% 50%;
483 | -webkit-transform: scale(1.25);
484 | -moz-transform: scale(1.25);
485 | -ms-transform: scale(1.25);
486 | -o-transform: scale(1.25);
487 | transform: scale(1.25);
488 | }
489 | .converter.converter_files_active .converter_svg_graphic_poly {
490 | fill: #30b194;
491 | }
492 |
493 | .converted {
494 | margin-top: 2em;
495 | }
496 | .converted:not(.converted_visible) {
497 | display: none;
498 | }
499 | .converted_item {
500 | }
501 | .converted_item+.converted_item {
502 | margin-top: 1em;
503 | }
504 | .converted_item_title_container {
505 | border-bottom: 0.25em solid #c8c8c8;
506 | margin-bottom: 1em;
507 | }
508 | .converted_item_title {
509 | color: #30b194;
510 | font-size: 2em;
511 | font-weight: bold;
512 | line-height: 1.2em;
513 | max-height: 2.4em;
514 | overflow: hidden;
515 | }
516 | .converted_item_contents {
517 | margin-left: 2em;
518 | }
519 |
520 |
521 | .converted_item_link_container {
522 | width: 100%;
523 | overflow-x: hidden;
524 | margin-bottom: 1em;
525 | }
526 | a.converted_item_link {
527 | font-size: 1.25em;
528 | color: #30b194;
529 | white-space: nowrap;
530 | }
531 | a.converted_item_link:hover {
532 | text-decoration: underline;
533 | }
534 | a.converted_item_link>span {
535 | color: #111111;
536 | }
537 | .converted_item_textbox {
538 | display: block;
539 | width: 100%;
540 | margin: 0.5em 0 0 0;
541 | padding: 0.5em;
542 | border: 1px solid #c8c8c8;
543 | box-sizing: border-box;
544 | -moz-box-sizing: border-box;
545 | line-height: 1.2em;
546 | color: #111111;
547 | }
548 | .converted_item_textbox[readonly] {
549 | color: #808080;
550 | }
551 |
552 | .converted_item_header {
553 | margin: 0;
554 | }
555 | .converted_item_header_text {
556 | display: inline-block;
557 | font-weight: bold;
558 | }
559 | .converted_item_options_container:not(.converted_item_options_container_visible)>.converted_item_header>.converted_item_header_text {
560 | display: none;
561 | }
562 |
563 | a.converted_item_options_toggle {
564 | color: #30b194;
565 | display: inline-block;
566 | }
567 | .converted_item_options_container.converted_item_options_container_visible>.converted_item_header>a.converted_item_options_toggle {
568 | margin-left: 1em;
569 | }
570 | a.converted_item_options_toggle>span {
571 | color: #c8c8c8;
572 | }
573 | a.converted_item_options_toggle:hover>span {
574 | color: #111111;
575 | }
576 | a.converted_item_options_toggle>span:after {
577 | content: "Show options";
578 | }
579 | .converted_item_options_container.converted_item_options_container_visible>.converted_item_header>a.converted_item_options_toggle>span:after {
580 | content: "Hide";
581 | }
582 |
583 | .converted_item_options_container {
584 | }
585 | .converted_item_options {
586 | margin-top: 0.5em;
587 | }
588 | .converted_item_options_container:not(.converted_item_options_container_visible)>.converted_item_options {
589 | display: none;
590 | }
591 | .converted_item_option {
592 | }
593 | .converted_item_option+.converted_item_option {
594 | margin-top: 0.5em;
595 | }
596 | .converted_item_option_part {
597 | cursor: pointer;
598 | display: inline-block;
599 | }
600 | .converted_item_option_part+.converted_item_option_part {
601 | margin-left: 1em;
602 | }
603 | .converted_item_option_part:not(.converted_item_option_part_visible) {
604 | display: none;
605 | }
606 | .converted_item_option_checkbox {
607 | vertical-align: middle;
608 | margin-right: 0.25em;
609 | }
610 | .converted_item_option_text {
611 | vertical-align: middle;
612 | }
613 |
614 | .non_exclusive {
615 | margin-top: 2em;
616 | }
617 | .non_exclusive.exclusive_enabled {
618 | display: none;
619 | }
620 |
--------------------------------------------------------------------------------