├── .gitignore
├── CHANGELOG.md
├── Chrome
├── chrome
│ ├── icon128.png
│ ├── icon48.png
│ ├── requests.js
│ └── script.js
├── manifest.json
└── template__
│ └── manifest.json
├── Firefox
├── bootstrap.js
├── defaults
│ └── preferences
│ │ └── prefs.js
├── harness-options.json
├── icon.png
├── install.rdf
├── locales.json
├── resources
│ └── bttv4ffz
│ │ └── lib
│ │ └── main.js
└── template__
│ ├── harness-options.json
│ └── install.rdf
├── README.md
├── Safari
└── BTTV4FFZ.safariextension
│ ├── Icon.png
│ ├── Info.plist
│ ├── Settings.plist
│ └── script.js
├── Userscript
└── bttv4ffz_injector.user.js
├── inject.js
└── pack.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | Archives
2 | Firefox
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.0.7.3:
2 | - Bring back an actual Firefox Plugin
3 | - Ditching the GreaseMonkey plugin - no longer needed
4 | - Support for Pale Moon added
5 |
6 | ## 1.0.7.2:
7 | - Ditching Firefox, since they don't want to allow me the remote-script-loading
8 | - Adding GreaseMonkey support
9 | The file can be installed from [here](https://cdn.lordmau5.com/bttv4ffz.user.js).
10 |
11 | ## 1.0.7.1:
12 | - Initial release of remote-script-loading from my CDN :)
13 |
--------------------------------------------------------------------------------
/Chrome/chrome/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordmau5/BTTV4FFZ/3e877de4734524861c44cae7905fe768751d344c/Chrome/chrome/icon128.png
--------------------------------------------------------------------------------
/Chrome/chrome/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordmau5/BTTV4FFZ/3e877de4734524861c44cae7905fe768751d344c/Chrome/chrome/icon48.png
--------------------------------------------------------------------------------
/Chrome/chrome/requests.js:
--------------------------------------------------------------------------------
1 | chrome.webRequest.onHeadersReceived.addListener(function(info) {
2 | return {
3 | responseHeaders: info.responseHeaders.concat([{
4 | name: 'access-control-allow-origin',
5 | value: '*'
6 | }])
7 | };
8 | }, {
9 | urls: [
10 | "*://cdn.betterttv.net/*"
11 | ]
12 | }, ["blocking", "responseHeaders"]);
13 |
--------------------------------------------------------------------------------
/Chrome/chrome/script.js:
--------------------------------------------------------------------------------
1 | function inject() {
2 | var s = document.createElement('script');
3 | s.src = "https://cdn.lordmau5.com/inject.js";
4 | s.onload = function() {
5 | this.parentNode.removeChild(this);
6 | };
7 | (document.head || document.documentElement).appendChild(s);
8 | }
9 |
10 | inject();
11 |
--------------------------------------------------------------------------------
/Chrome/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "BetterTTV Emotes for FrankerFaceZ",
4 | "short_name": "BTTV4FFZ",
5 | "version": "1.1.2",
6 | "description": "Add the BTTV emotes on-top of FFZ!",
7 |
8 | "icons": {
9 | "48": "chrome/icon48.png",
10 | "128": "chrome/icon128.png"
11 | },
12 |
13 | "background": {
14 | "scripts": ["chrome/requests.js"]
15 | },
16 |
17 | "content_scripts": [ {
18 | "all_frames": true,
19 | "js": [ "chrome/script.js" ],
20 | "matches": [ "*://*.twitch.tv/*" ],
21 | "exclude_globs": [ "*://api.twitch.tv/*" ]
22 | } ],
23 |
24 | "permissions": [
25 | "*://*.twitch.tv/*",
26 | "*://*.frankerfacez.com/*",
27 | "*://*.betterttv.net/*",
28 | "webRequest",
29 | "webRequestBlocking"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/Chrome/template__/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 2,
3 | "name": "BetterTTV Emotes for FrankerFaceZ",
4 | "short_name": "BTTV4FFZ",
5 | "version": "%%VERSION%%",
6 | "description": "Add the BTTV emotes on-top of FFZ!",
7 |
8 | "icons": {
9 | "48": "chrome/icon48.png",
10 | "128": "chrome/icon128.png"
11 | },
12 |
13 | "background": {
14 | "scripts": ["chrome/requests.js"]
15 | },
16 |
17 | "content_scripts": [ {
18 | "all_frames": true,
19 | "js": [ "chrome/script.js" ],
20 | "matches": [ "*://*.twitch.tv/*" ],
21 | "exclude_globs": [ "*://api.twitch.tv/*" ]
22 | } ],
23 |
24 | "permissions": [
25 | "*://*.twitch.tv/*",
26 | "*://*.frankerfacez.com/*",
27 | "*://*.betterttv.net/*",
28 | "webRequest",
29 | "webRequestBlocking"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/Firefox/bootstrap.js:
--------------------------------------------------------------------------------
1 | /* This Source Code Form is subject to the terms of the Mozilla Public
2 | * License, v. 2.0. If a copy of the MPL was not distributed with this
3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 |
5 | // @see http://mxr.mozilla.org/mozilla-central/source/js/src/xpconnect/loader/mozJSComponentLoader.cpp
6 |
7 | 'use strict';
8 |
9 | // IMPORTANT: Avoid adding any initialization tasks here, if you need to do
10 | // something before add-on is loaded consider addon/runner module instead!
11 |
12 | const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu,
13 | results: Cr, manager: Cm } = Components;
14 | const ioService = Cc['@mozilla.org/network/io-service;1'].
15 | getService(Ci.nsIIOService);
16 | const resourceHandler = ioService.getProtocolHandler('resource').
17 | QueryInterface(Ci.nsIResProtocolHandler);
18 | const systemPrincipal = CC('@mozilla.org/systemprincipal;1', 'nsIPrincipal')();
19 | const scriptLoader = Cc['@mozilla.org/moz/jssubscript-loader;1'].
20 | getService(Ci.mozIJSSubScriptLoader);
21 | const prefService = Cc['@mozilla.org/preferences-service;1'].
22 | getService(Ci.nsIPrefService).
23 | QueryInterface(Ci.nsIPrefBranch);
24 | const appInfo = Cc["@mozilla.org/xre/app-info;1"].
25 | getService(Ci.nsIXULAppInfo);
26 | const vc = Cc["@mozilla.org/xpcom/version-comparator;1"].
27 | getService(Ci.nsIVersionComparator);
28 |
29 |
30 | const REASON = [ 'unknown', 'startup', 'shutdown', 'enable', 'disable',
31 | 'install', 'uninstall', 'upgrade', 'downgrade' ];
32 |
33 | const bind = Function.call.bind(Function.bind);
34 |
35 | let loader = null;
36 | let unload = null;
37 | let cuddlefishSandbox = null;
38 | let nukeTimer = null;
39 |
40 | let resourceDomains = [];
41 | function setResourceSubstitution(domain, uri) {
42 | resourceDomains.push(domain);
43 | resourceHandler.setSubstitution(domain, uri);
44 | }
45 |
46 | // Utility function that synchronously reads local resource from the given
47 | // `uri` and returns content string.
48 | function readURI(uri) {
49 | let ioservice = Cc['@mozilla.org/network/io-service;1'].
50 | getService(Ci.nsIIOService);
51 | let channel = ioservice.newChannel(uri, 'UTF-8', null);
52 | let stream = channel.open();
53 |
54 | let cstream = Cc['@mozilla.org/intl/converter-input-stream;1'].
55 | createInstance(Ci.nsIConverterInputStream);
56 | cstream.init(stream, 'UTF-8', 0, 0);
57 |
58 | let str = {};
59 | let data = '';
60 | let read = 0;
61 | do {
62 | read = cstream.readString(0xffffffff, str);
63 | data += str.value;
64 | } while (read != 0);
65 |
66 | cstream.close();
67 |
68 | return data;
69 | }
70 |
71 | // We don't do anything on install & uninstall yet, but in a future
72 | // we should allow add-ons to cleanup after uninstall.
73 | function install(data, reason) {}
74 | function uninstall(data, reason) {}
75 |
76 | function startup(data, reasonCode) {
77 | try {
78 | let reason = REASON[reasonCode];
79 | // URI for the root of the XPI file.
80 | // 'jar:' URI if the addon is packed, 'file:' URI otherwise.
81 | // (Used by l10n module in order to fetch `locale` folder)
82 | let rootURI = data.resourceURI.spec;
83 |
84 | // TODO: Maybe we should perform read harness-options.json asynchronously,
85 | // since we can't do anything until 'sessionstore-windows-restored' anyway.
86 | let options = JSON.parse(readURI(rootURI + './harness-options.json'));
87 |
88 | let id = options.jetpackID;
89 | let name = options.name;
90 |
91 | // Clean the metadata
92 | options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {};
93 |
94 | // freeze the permissionss
95 | Object.freeze(options.metadata[name]['permissions']);
96 | // freeze the metadata
97 | Object.freeze(options.metadata[name]);
98 |
99 | // Register a new resource 'domain' for this addon which is mapping to
100 | // XPI's `resources` folder.
101 | // Generate the domain name by using jetpack ID, which is the extension ID
102 | // by stripping common characters that doesn't work as a domain name:
103 | let uuidRe =
104 | /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
105 |
106 | let domain = id.
107 | toLowerCase().
108 | replace(/@/g, '-at-').
109 | replace(/\./g, '-dot-').
110 | replace(uuidRe, '$1');
111 |
112 | let prefixURI = 'resource://' + domain + '/';
113 | let resourcesURI = ioService.newURI(rootURI + '/resources/', null, null);
114 | setResourceSubstitution(domain, resourcesURI);
115 |
116 | // Create path to URLs mapping supported by loader.
117 | let paths = {
118 | // Relative modules resolve to add-on package lib
119 | './': prefixURI + name + '/lib/',
120 | './tests/': prefixURI + name + '/tests/',
121 | '': 'resource://gre/modules/commonjs/'
122 | };
123 |
124 | // Maps addon lib and tests ressource folders for each package
125 | paths = Object.keys(options.metadata).reduce(function(result, name) {
126 | result[name + '/'] = prefixURI + name + '/lib/'
127 | result[name + '/tests/'] = prefixURI + name + '/tests/'
128 | return result;
129 | }, paths);
130 |
131 | // We need to map tests folder when we run sdk tests whose package name
132 | // is stripped
133 | if (name == 'addon-sdk')
134 | paths['tests/'] = prefixURI + name + '/tests/';
135 |
136 | let useBundledSDK = options['force-use-bundled-sdk'];
137 | if (!useBundledSDK) {
138 | try {
139 | useBundledSDK = prefService.getBoolPref("extensions.addon-sdk.useBundledSDK");
140 | }
141 | catch (e) {
142 | // Pref doesn't exist, allow using Firefox shipped SDK
143 | }
144 | }
145 |
146 | // Starting with Firefox 21.0a1, we start using modules shipped into firefox
147 | // Still allow using modules from the xpi if the manifest tell us to do so.
148 | // And only try to look for sdk modules in xpi if the xpi actually ship them
149 | if (options['is-sdk-bundled'] &&
150 | (vc.compare(appInfo.version, '21.0a1') < 0 || useBundledSDK)) {
151 | // Maps sdk module folders to their resource folder
152 | paths[''] = prefixURI + 'addon-sdk/lib/';
153 | // test.js is usually found in root commonjs or SDK_ROOT/lib/ folder,
154 | // so that it isn't shipped in the xpi. Keep a copy of it in sdk/ folder
155 | // until we no longer support SDK modules in XPI:
156 | paths['test'] = prefixURI + 'addon-sdk/lib/sdk/test.js';
157 | }
158 |
159 | // Retrieve list of module folder overloads based on preferences in order to
160 | // eventually used a local modules instead of files shipped into Firefox.
161 | let branch = prefService.getBranch('extensions.modules.' + id + '.path');
162 | paths = branch.getChildList('', {}).reduce(function (result, name) {
163 | // Allows overloading of any sub folder by replacing . by / in pref name
164 | let path = name.substr(1).split('.').join('/');
165 | // Only accept overloading folder by ensuring always ending with `/`
166 | if (path) path += '/';
167 | let fileURI = branch.getCharPref(name);
168 |
169 | // On mobile, file URI has to end with a `/` otherwise, setSubstitution
170 | // takes the parent folder instead.
171 | if (fileURI[fileURI.length-1] !== '/')
172 | fileURI += '/';
173 |
174 | // Maps the given file:// URI to a resource:// in order to avoid various
175 | // failure that happens with file:// URI and be close to production env
176 | let resourcesURI = ioService.newURI(fileURI, null, null);
177 | let resName = 'extensions.modules.' + domain + '.commonjs.path' + name;
178 | setResourceSubstitution(resName, resourcesURI);
179 |
180 | result[path] = 'resource://' + resName + '/';
181 | return result;
182 | }, paths);
183 |
184 | // Make version 2 of the manifest
185 | let manifest = options.manifest;
186 |
187 | // Import `cuddlefish.js` module using a Sandbox and bootstrap loader.
188 | let cuddlefishPath = 'loader/cuddlefish.js';
189 | let cuddlefishURI = 'resource://gre/modules/commonjs/sdk/' + cuddlefishPath;
190 | if (paths['sdk/']) { // sdk folder has been overloaded
191 | // (from pref, or cuddlefish is still in the xpi)
192 | cuddlefishURI = paths['sdk/'] + cuddlefishPath;
193 | }
194 | else if (paths['']) { // root modules folder has been overloaded
195 | cuddlefishURI = paths[''] + 'sdk/' + cuddlefishPath;
196 | }
197 |
198 | cuddlefishSandbox = loadSandbox(cuddlefishURI);
199 | let cuddlefish = cuddlefishSandbox.exports;
200 |
201 | // Normalize `options.mainPath` so that it looks like one that will come
202 | // in a new version of linker.
203 | let main = options.mainPath;
204 |
205 | unload = cuddlefish.unload;
206 | loader = cuddlefish.Loader({
207 | paths: paths,
208 | // modules manifest.
209 | manifest: manifest,
210 |
211 | // Add-on ID used by different APIs as a unique identifier.
212 | id: id,
213 | // Add-on name.
214 | name: name,
215 | // Add-on version.
216 | version: options.metadata[name].version,
217 | // Add-on package descriptor.
218 | metadata: options.metadata[name],
219 | // Add-on load reason.
220 | loadReason: reason,
221 |
222 | prefixURI: prefixURI,
223 | // Add-on URI.
224 | rootURI: rootURI,
225 | // options used by system module.
226 | // File to write 'OK' or 'FAIL' (exit code emulation).
227 | resultFile: options.resultFile,
228 | // Arguments passed as --static-args
229 | staticArgs: options.staticArgs,
230 | // Add-on preferences branch name
231 | preferencesBranch: options.preferencesBranch,
232 |
233 | // Arguments related to test runner.
234 | modules: {
235 | '@test/options': {
236 | allTestModules: options.allTestModules,
237 | iterations: options.iterations,
238 | filter: options.filter,
239 | profileMemory: options.profileMemory,
240 | stopOnError: options.stopOnError,
241 | verbose: options.verbose,
242 | parseable: options.parseable,
243 | checkMemory: options.check_memory,
244 | }
245 | }
246 | });
247 |
248 | let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
249 | let require = cuddlefish.Require(loader, module);
250 |
251 | require('sdk/addon/runner').startup(reason, {
252 | loader: loader,
253 | main: main,
254 | prefsURI: rootURI + 'defaults/preferences/prefs.js'
255 | });
256 | } catch (error) {
257 | dump('Bootstrap error: ' +
258 | (error.message ? error.message : String(error)) + '\n' +
259 | (error.stack || error.fileName + ': ' + error.lineNumber) + '\n');
260 | throw error;
261 | }
262 | };
263 |
264 | function loadSandbox(uri) {
265 | let proto = {
266 | sandboxPrototype: {
267 | loadSandbox: loadSandbox,
268 | ChromeWorker: ChromeWorker
269 | }
270 | };
271 | let sandbox = Cu.Sandbox(systemPrincipal, proto);
272 | // Create a fake commonjs environnement just to enable loading loader.js
273 | // correctly
274 | sandbox.exports = {};
275 | sandbox.module = { uri: uri, exports: sandbox.exports };
276 | sandbox.require = function (id) {
277 | if (id !== "chrome")
278 | throw new Error("Bootstrap sandbox `require` method isn't implemented.");
279 |
280 | return Object.freeze({ Cc: Cc, Ci: Ci, Cu: Cu, Cr: Cr, Cm: Cm,
281 | CC: bind(CC, Components), components: Components,
282 | ChromeWorker: ChromeWorker });
283 | };
284 | scriptLoader.loadSubScript(uri, sandbox, 'UTF-8');
285 | return sandbox;
286 | }
287 |
288 | function unloadSandbox(sandbox) {
289 | if ("nukeSandbox" in Cu)
290 | Cu.nukeSandbox(sandbox);
291 | }
292 |
293 | function setTimeout(callback, delay) {
294 | let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
295 | timer.initWithCallback({ notify: callback }, delay,
296 | Ci.nsITimer.TYPE_ONE_SHOT);
297 | return timer;
298 | }
299 |
300 | function shutdown(data, reasonCode) {
301 | let reason = REASON[reasonCode];
302 | if (loader) {
303 | unload(loader, reason);
304 | unload = null;
305 |
306 | // Don't waste time cleaning up if the application is shutting down
307 | if (reason != "shutdown") {
308 | // Avoid leaking all modules when something goes wrong with one particular
309 | // module. Do not clean it up immediatly in order to allow executing some
310 | // actions on addon disabling.
311 | // We need to keep a reference to the timer, otherwise it is collected
312 | // and won't ever fire.
313 | nukeTimer = setTimeout(nukeModules, 1000);
314 |
315 | // Bug 944951 - bootstrap.js must remove the added resource: URIs on unload
316 | resourceDomains.forEach(domain => {
317 | resourceHandler.setSubstitution(domain, null);
318 | })
319 | }
320 | }
321 | };
322 |
323 | function nukeModules() {
324 | nukeTimer = null;
325 | // module objects store `exports` which comes from sandboxes
326 | // We should avoid keeping link to these object to avoid leaking sandboxes
327 | for (let key in loader.modules) {
328 | delete loader.modules[key];
329 | }
330 | // Direct links to sandboxes should be removed too
331 | for (let key in loader.sandboxes) {
332 | let sandbox = loader.sandboxes[key];
333 | delete loader.sandboxes[key];
334 | // Bug 775067: From FF17 we can kill all CCW from a given sandbox
335 | unloadSandbox(sandbox);
336 | }
337 | loader = null;
338 |
339 | // both `toolkit/loader` and `system/xul-app` are loaded as JSM's via
340 | // `cuddlefish.js`, and needs to be unloaded to avoid memory leaks, when
341 | // the addon is unload.
342 |
343 | unloadSandbox(cuddlefishSandbox.loaderSandbox);
344 | unloadSandbox(cuddlefishSandbox.xulappSandbox);
345 |
346 | // Bug 764840: We need to unload cuddlefish otherwise it will stay alive
347 | // and keep a reference to this compartment.
348 | unloadSandbox(cuddlefishSandbox);
349 | cuddlefishSandbox = null;
350 | }
351 |
--------------------------------------------------------------------------------
/Firefox/defaults/preferences/prefs.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordmau5/BTTV4FFZ/3e877de4734524861c44cae7905fe768751d344c/Firefox/defaults/preferences/prefs.js
--------------------------------------------------------------------------------
/Firefox/harness-options.json:
--------------------------------------------------------------------------------
1 | {
2 | "abort_on_missing": false,
3 | "check_memory": false,
4 | "enable_e10s": false,
5 | "is-sdk-bundled": false,
6 | "jetpackID": "BTTV4FFZ@jetpack",
7 | "loader": "addon-sdk/lib/sdk/loader/cuddlefish.js",
8 | "main": "main",
9 | "mainPath": "bttv4ffz/main",
10 | "manifest": {
11 | "bttv4ffz/main": {
12 | "docsSHA256": null,
13 | "jsSHA256": "58e3acca682b421ca901e5896eeaf7a7d2ec82104faf908afc5a6ead34d34bcb",
14 | "moduleName": "main",
15 | "packageName": "bttv4ffz",
16 | "requirements": {
17 | "sdk/page-mod": "sdk/page-mod"
18 | },
19 | "sectionName": "lib"
20 | }
21 | },
22 | "metadata": {
23 | "addon-sdk": {
24 | "description": "Add-on development made easy.",
25 | "keywords": [
26 | "javascript",
27 | "engine",
28 | "addon",
29 | "extension",
30 | "xulrunner",
31 | "firefox",
32 | "browser"
33 | ],
34 | "license": "MPL 2.0",
35 | "name": "addon-sdk"
36 | },
37 | "bttv4ffz": {
38 | "author": "Lordmau5",
39 | "description": "Get those BetterTTV emotes in FFZ!",
40 | "main": "main",
41 | "name": "bttv4ffz",
42 | "permissions": {
43 | "private-browsing": true,
44 | "multiprocess": true
45 | },
46 | "version": "1.1.2"
47 | }
48 | },
49 | "name": "bttv4ffz",
50 | "parseable": false,
51 | "preferencesBranch": "BTTV4FFZ@jetpack",
52 | "sdkVersion": "1.17",
53 | "staticArgs": {},
54 | "verbose": false
55 | }
56 |
--------------------------------------------------------------------------------
/Firefox/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordmau5/BTTV4FFZ/3e877de4734524861c44cae7905fe768751d344c/Firefox/icon.png
--------------------------------------------------------------------------------
/Firefox/install.rdf:
--------------------------------------------------------------------------------
1 |
4 |
5 | @BTTV4FFZ
6 | 1.1.2
7 | 2
8 | true
9 | false
10 | https://cdn.lordmau5.com/update.rdf
11 |
12 |
13 |
14 |
15 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
16 | 26.0
17 | 39.0
18 |
19 |
20 |
21 |
22 |
23 |
24 | {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
25 | 25.0
26 | 39.0
27 |
28 |
29 |
30 |
31 | BetterTTV for FrankerFaceZ
32 | Get those BetterTTV emotes in FFZ!
33 | Lordmau5
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Firefox/locales.json:
--------------------------------------------------------------------------------
1 | {"locales": []}
2 |
--------------------------------------------------------------------------------
/Firefox/resources/bttv4ffz/lib/main.js:
--------------------------------------------------------------------------------
1 | var pageMod = require("sdk/page-mod");
2 |
3 | pageMod.PageMod({
4 | include: "*.twitch.tv",
5 | contentScript: 'var ce = document["createElement"].bind(document);' +
6 | 'var s = ce("script");' +
7 | //'s.src = "//cdn.lordmau5.com/inject.js";' +
8 | 's.src = "//lordmau5.com/nocache/inject.js";' +
9 | 's.onload = function() {' +
10 | 'this.parentNode.removeChild(this);' +
11 | '};' +
12 | '(document.head || document.documentElement).appendChild(s);'
13 | });
14 |
15 | browser.webRequest.onHeadersReceived.addListener(function(info) {
16 | return {
17 | responseHeaders: info.responseHeaders.concat([{
18 | name: 'access-control-allow-origin',
19 | value: '*'
20 | }])
21 | };
22 | }, {
23 | urls: [
24 | "*://cdn.betterttv.net/*"
25 | ]
26 | }, ["blocking", "responseHeaders"]);
27 |
--------------------------------------------------------------------------------
/Firefox/template__/harness-options.json:
--------------------------------------------------------------------------------
1 | {
2 | "abort_on_missing": false,
3 | "check_memory": false,
4 | "enable_e10s": false,
5 | "is-sdk-bundled": false,
6 | "jetpackID": "BTTV4FFZ@jetpack",
7 | "loader": "addon-sdk/lib/sdk/loader/cuddlefish.js",
8 | "main": "main",
9 | "mainPath": "bttv4ffz/main",
10 | "manifest": {
11 | "bttv4ffz/main": {
12 | "docsSHA256": null,
13 | "jsSHA256": "58e3acca682b421ca901e5896eeaf7a7d2ec82104faf908afc5a6ead34d34bcb",
14 | "moduleName": "main",
15 | "packageName": "bttv4ffz",
16 | "requirements": {
17 | "sdk/page-mod": "sdk/page-mod"
18 | },
19 | "sectionName": "lib"
20 | }
21 | },
22 | "metadata": {
23 | "addon-sdk": {
24 | "description": "Add-on development made easy.",
25 | "keywords": [
26 | "javascript",
27 | "engine",
28 | "addon",
29 | "extension",
30 | "xulrunner",
31 | "firefox",
32 | "browser"
33 | ],
34 | "license": "MPL 2.0",
35 | "name": "addon-sdk"
36 | },
37 | "bttv4ffz": {
38 | "author": "Lordmau5",
39 | "description": "Get those BetterTTV emotes in FFZ!",
40 | "main": "main",
41 | "name": "bttv4ffz",
42 | "permissions": {
43 | "private-browsing": true,
44 | "multiprocess": true
45 | },
46 | "version": "%%VERSION%%"
47 | }
48 | },
49 | "name": "bttv4ffz",
50 | "parseable": false,
51 | "preferencesBranch": "BTTV4FFZ@jetpack",
52 | "sdkVersion": "1.17",
53 | "staticArgs": {},
54 | "verbose": false
55 | }
56 |
--------------------------------------------------------------------------------
/Firefox/template__/install.rdf:
--------------------------------------------------------------------------------
1 |
4 |
5 | @BTTV4FFZ
6 | %%VERSION%%
7 | 2
8 | true
9 | false
10 | https://cdn.lordmau5.com/update.rdf
11 |
12 |
13 |
14 |
15 | {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
16 | 26.0
17 | 39.0
18 |
19 |
20 |
21 |
22 |
23 |
24 | {8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}
25 | 25.0
26 | 39.0
27 |
28 |
29 |
30 |
31 | BetterTTV for FrankerFaceZ
32 | Get those BetterTTV emotes in FFZ!
33 | Lordmau5
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Important information!
2 | **BTTV4FFZ is deprecated.**
3 |
4 | **Please move over to the new repository located at:**
5 |
6 | **https://github.com/Lordmau5/The-FFZ-Addon-Pack**
7 |
8 |
9 | ___
10 |
11 | # BTTV4FFZ
12 | Better Twitch TV emotes for FrankerFaceZ
13 | ___
14 |
15 | _Ever wanted to have these slick Better Twitch TV emotes but you already have FrankerFaceZ installed?_
16 |
17 | _Don't want to install Better Twitch TV because you prefer the design of FrankerFaceZ?_
18 |
19 | _Can't get your favorite streamer to add per-channel-emotes via. FrankerFaceZ, because they prefer Better Twitch TV?_
20 |
21 |
22 | **Well, LOOK NO FURTHER!**
23 | This is a extension for FrankerFaceZ that allows **EXACTLY THAT**!
24 |
25 | With this, you have access to the **Better Twitch TV's** arsenal of **global emotes**, as well as their **per-channel-emotes**.
26 |
27 | Never miss out on that RareParrot again 
28 |
29 | # _**FAQ:**_
30 | **"GIF emotes aren't working! Are they broken?"**
31 | _**No**, they aren't. They are disabled by default._
32 | You have to get into the **FrankerFaceZ advanced settings** tab and go to _**"BTTV4FFZ"**_, then enable them.
33 |
34 | **"I just installed this for the Better Twitch TV per-channel-emotes. Can I disable the global ones?"**
35 | _**Yes.**_
36 | Check the question above on how to do that. There is another toggle-bar to disable them :
37 |
38 | ## Links
39 | Download: https://lordmau5.com/bttv4ffz/
40 |
41 | ## IMPORTANT:
42 | FrankerFaceZ is **required** for this extension to work!
43 | If FrankerFaceZ is not found, the emotes will **not be available!**
44 | Also, don't use this together with BetterTTV.
45 |
--------------------------------------------------------------------------------
/Safari/BTTV4FFZ.safariextension/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Lordmau5/BTTV4FFZ/3e877de4734524861c44cae7905fe768751d344c/Safari/BTTV4FFZ.safariextension/Icon.png
--------------------------------------------------------------------------------
/Safari/BTTV4FFZ.safariextension/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Author
6 | lordmau5.com/bttv4ffz/
7 | Builder Version
8 | 11601.6.17
9 | CFBundleDisplayName
10 | BTTV4FFZ
11 | CFBundleIdentifier
12 | com.rennsport.bttv4ffz
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleShortVersionString
16 | 1.0
17 | CFBundleVersion
18 | 1
19 | Chrome
20 |
21 | Database Quota
22 | 0
23 |
24 | Companion Bundle Identifier
25 |
26 | Content
27 |
28 | Blacklist
29 |
30 | https://api.twitch.tv/*
31 | http://api.twitch.tv/*
32 |
33 | Scripts
34 |
35 | End
36 |
37 | script.js
38 |
39 |
40 | Whitelist
41 |
42 | https://www.twitch.tv/*
43 | http://www.twitch.tv/*
44 |
45 |
46 | Description
47 | Add the BTTV emotes on-top of FFZ!
48 | DeveloperIdentifier
49 | LBHS782YH3
50 | ExtensionInfoDictionaryVersion
51 | 1.0
52 | Permissions
53 |
54 | Website Access
55 |
56 | Include Secure Pages
57 |
58 | Level
59 | All
60 |
61 |
62 | Website
63 | https://lordmau5.com/bttv4ffz/
64 |
65 |
66 |
--------------------------------------------------------------------------------
/Safari/BTTV4FFZ.safariextension/Settings.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Safari/BTTV4FFZ.safariextension/script.js:
--------------------------------------------------------------------------------
1 | function inject() {
2 | var s = document.createElement('script');
3 | s.src = "//cdn.lordmau5.com/inject.js";
4 | s.onload = function() {
5 | this.parentNode.removeChild(this);
6 | };
7 | (document.head || document.documentElement).appendChild(s);
8 | }
9 |
10 | inject();
11 |
--------------------------------------------------------------------------------
/Userscript/bttv4ffz_injector.user.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @id bttv4ffz
3 | // @name BetterTTV Emotes for FrankerFaceZ
4 | // @namespace BTTV4FFZ
5 | //
6 | // @version 1.1.2
7 | // @updateURL https://cdn.lordmau5.com/script/bttv4ffz_injector.user.js
8 | //
9 | // @description Ever wanted to have these slick Better Twitch TV emotes but you already have FrankerFaceZ installed? BetterTwitchTV for FrankerFaceZ takes care of that problem.
10 | // @author Lordmau5
11 | // @homepage https://lordmau5.com/bttv4ffz/
12 | // @icon https://cdn.lordmau5.com/script/icon32.png
13 | // @icon64 https://cdn.lordmau5.com/script/icon64.png
14 | // @icon128 https://cdn.lordmau5.com/script/icon128.png
15 | //
16 | // @include http://twitch.tv/*
17 | // @include https://twitch.tv/*
18 | // @include http://*.twitch.tv/*
19 | // @include https://*.twitch.tv/*
20 | //
21 | // @exclude http://api.twitch.tv/*
22 | // @exclude https://api.twitch.tv/*
23 | //
24 | // @grant none
25 | // @run-at document-end
26 | // ==/UserScript==
27 |
28 | function bttv4ffz_init() {
29 | var script = document.createElement('script');
30 |
31 | script.id = 'bttv4ffz_script';
32 | script.type = 'text/javascript';
33 | script.src = 'https://cdn.lordmau5.com/inject.js';
34 |
35 | document.head.appendChild(s);
36 | }
37 |
38 | bttv4ffz_script();
39 |
--------------------------------------------------------------------------------
/inject.js:
--------------------------------------------------------------------------------
1 | /*
2 | This file is being updated on my server (cdn.lordmau5.com) first before changes to the GitHub repo happen.
3 | It will not be embedded in the addon anymore.
4 | This makes it easier for me to do updates when necessary (sudden FFZ-API changes, for example)
5 | */
6 |
7 | (function(){
8 |
9 | // Global Storage / Settings
10 |
11 | var version = "1.3.10";
12 |
13 | var _initialized,
14 |
15 | api,
16 | ffz,
17 | last_emote_set_id = 9000,
18 | channels = {},
19 | enable_global_emotes,
20 | enable_gif_emotes,
21 | enable_override_emotes,
22 | enable_pro_emotes,
23 |
24 | socketClient;
25 |
26 | var global_emotes_loaded = false,
27 | gif_emotes_loaded = false;
28 |
29 | var override_emotes = [ ":'(", "D:" ];
30 | var isOverrideEmote = function(emote_regex) {
31 | for(var i = 0; i < override_emotes.length; i++) {
32 | if(emote_regex === override_emotes[i])
33 | return true;
34 | }
35 | return false;
36 | };
37 |
38 | // Initialization
39 |
40 | var check_existance = function(attempts) {
41 | if (window.FrankerFaceZ !== undefined && window.jQuery !== undefined && window.App !== undefined) {
42 | // Register with FFZ.
43 | ffz = FrankerFaceZ.get();
44 |
45 | // Check for BTTV
46 | if (ffz.has_bttv) {
47 | console.log("BTTV was found. BTTV4FFZ will not work in combination with it.");
48 | return;
49 | }
50 | api = ffz.api("BetterTTV", "https://cdn.betterttv.net/tags/developer.png", version);
51 | socketClient = new SocketClient();
52 |
53 | api.log("Injected successfully.");
54 |
55 | // Create emote URLs.
56 | api.emote_url_generator = function(set_id, emote_id) {
57 | return "https://manage.betterttv.net/emotes/" + emote_id;
58 | };
59 |
60 | doSettings();
61 | setupAPIHooks();
62 |
63 | implementBTTVBadges();
64 | if (enable_global_emotes)
65 | implementBTTVGlobals();
66 |
67 | if(enable_pro_emotes) {
68 | socketClient.connect();
69 | }
70 | }
71 | else {
72 | attempts = (attempts || 0) + 1;
73 | if (attempts < 60)
74 | return setTimeout(check_existance.bind(this, attempts), 1000);
75 |
76 | console.log("Could not find FFZ. Injection unsuccessful.");
77 | }
78 | };
79 |
80 |
81 | var doSettings = function() {
82 | FrankerFaceZ.settings_info.bttv_global_emotes = {
83 | type: "boolean",
84 | value: true,
85 | category: "BTTV4FFZ",
86 | name: "Global Emoticons",
87 | help: "Enable this to make the BTTV global emotes available.",
88 | on_update: function(enabled) {
89 | if (!global_emotes_loaded) {
90 | if (enabled)
91 | implementBTTVGlobals();
92 | return;
93 | }
94 |
95 | if (enabled) {
96 | api.register_global_set(1);
97 |
98 | if(enable_gif_emotes)
99 | api.register_global_set(2);
100 |
101 | if(enable_override_emotes)
102 | api.register_global_set(3);
103 | }
104 | else {
105 | api.unregister_global_set(1);
106 | api.unregister_global_set(2);
107 | api.unregister_global_set(3);
108 | }
109 |
110 | enable_global_emotes = enabled;
111 | }
112 | };
113 |
114 | enable_global_emotes = ffz.settings.get("bttv_global_emotes");
115 |
116 | FrankerFaceZ.settings_info.bttv_allow_gif_emotes = {
117 | type: "boolean",
118 | value: false,
119 | category: "BTTV4FFZ",
120 | name: "GIF Emoticons",
121 | help: "Enable this to show GIF emotes.",
122 | on_update: function(enabled) {
123 | if (enabled) {
124 | if(enable_global_emotes)
125 | api.register_global_set(2);
126 |
127 | var i = channels.length;
128 | while(i--) {
129 | var name = channels[i];
130 | api.register_room_set(name, channels[name]["gifemotes_setid"], channels[name]["gifemotes"]);
131 | }
132 | }
133 | else {
134 | api.unregister_global_set(2);
135 |
136 | var i = channels.length;
137 | while(i--) {
138 | var name = channels[i];
139 | if(channels[name]["gifemotes_still"])
140 | api.register_room_set(name, channels[name]["gifemotes_setid"], channels[name]["gifemotes_still"]);
141 | else
142 | api.unload_set(channels[name]["gifemotes_setid"]);
143 | }
144 | }
145 |
146 | enable_gif_emotes = enabled;
147 | }
148 | };
149 |
150 | enable_gif_emotes = ffz.settings.get("bttv_allow_gif_emotes");
151 |
152 | FrankerFaceZ.settings_info.bttv_enable_override_emotes = {
153 | type: "boolean",
154 | value: false,
155 | category: "BTTV4FFZ",
156 | name: "Enable Override Emotes",
157 | help: "Enable this to show override emotes (like D:).",
158 | on_update: function(enabled) {
159 | if (enabled) {
160 | if(enable_global_emotes)
161 | api.register_global_set(3);
162 | }
163 | else {
164 | api.unregister_global_set(3);
165 | }
166 |
167 | enable_override_emotes = enabled;
168 | }
169 | };
170 |
171 | enable_override_emotes = ffz.settings.get("bttv_enable_override_emotes");
172 |
173 | FrankerFaceZ.settings_info.bttv_enable_pro_emotes = {
174 | type: "boolean",
175 | value: true,
176 | category: "BTTV4FFZ",
177 | name: "Enable BTTV Pro Emotes",
178 | help: "Enable this to show Pro emotes from other users. (Requires refresh!)",
179 | on_update: function(enabled) {
180 | if(!enabled) {
181 | var i = bttv_pro_users.length;
182 | while(i--) {
183 | var user = bttv_pro_users[i];
184 | api.unload_set(user._id_emotes);
185 | }
186 | }
187 |
188 | enable_pro_emotes = enabled;
189 | }
190 | };
191 |
192 | enable_pro_emotes = ffz.settings.get("bttv_enable_pro_emotes");
193 |
194 | FrankerFaceZ.settings_info.bttv_show_emotes_in_menu = {
195 | type: "boolean",
196 | value: true,
197 | category: "BTTV4FFZ",
198 | name: "Show emotes in Emoticon Menu",
199 | help: "Enable this to show the emotes in the Emoticon Menu (you can still enter the emotes manually when this is disabled)",
200 | on_update: function(enabled) {
201 | api.emote_sets[1].hidden = !enabled;
202 | api.emote_sets[2].hidden = !enabled;
203 | api.emote_sets[3].hidden = !enabled;
204 |
205 | for(var name in channels) {
206 | api.log(name);
207 | api.emote_sets[channels[name]["emotes"]].hidden = !enabled;
208 | api.emote_sets[channels[name]["gifemotes_setid"]].hidden = !enabled;
209 | }
210 | }
211 | };
212 |
213 | show_emotes_in_menu = ffz.settings.get("bttv_show_emotes_in_menu");
214 | };
215 |
216 |
217 | var setupAPIHooks = function() {
218 | api.on('room-add', channelCallback);
219 | api.on('room-message', chatFilter);
220 |
221 | api.iterate_rooms();
222 | };
223 |
224 | var chatFilter = function(msg) {
225 | if(enable_pro_emotes && ffz.get_user() && msg.from === ffz.get_user().login) {
226 | socketClient.broadcastMe(msg.room);
227 | }
228 | };
229 |
230 | var hatEmotes = [];
231 |
232 | var channelCallback = function(room_id, reg_function, attempts) {
233 | if(enable_pro_emotes) {
234 | socketClient.joinChannel(room_id);
235 | }
236 |
237 | $.getJSON("https://api.betterttv.net/2/channels/" + room_id)
238 | .done(function(data) {
239 | var channelBTTV = new Array(),
240 | channelBTTV_GIF = new Array(),
241 | emotes = data["emotes"];
242 |
243 | var i = emotes.length;
244 | while(i--) {
245 | var req_spaces = /[^A-Za-z0-9]/.test(emotes[i]["code"]);
246 |
247 | var emote = emotes[i],
248 | id = emote["id"],
249 |
250 | xMote = {
251 | urls: {
252 | 1: "https://cdn.betterttv.net/emote/" + id + "/1x",
253 | 2: "https://cdn.betterttv.net/emote/" + id + "/2x",
254 | 4: "https://cdn.betterttv.net/emote/" + id + "/3x"
255 | },
256 | id: id,
257 | name: emote["code"],
258 | width: 28,
259 | height: 28,
260 | owner: {
261 | display_name: emote["channel"] || room_id,
262 | name: emote["channel"]
263 | },
264 | require_spaces: req_spaces
265 | };
266 |
267 | if (emote["imageType"] === "png")
268 | channelBTTV.push(xMote);
269 |
270 | if (emote["imageType"] === "gif")
271 | channelBTTV_GIF.push(xMote);
272 | }
273 |
274 | if (!channelBTTV.length && !channelBTTV_GIF.length)
275 | return;
276 |
277 | channels[room_id] = {
278 | emotes: last_emote_set_id,
279 | gifemotes_setid: last_emote_set_id + 1
280 | };
281 | last_emote_set_id += 2;
282 |
283 | var set = {
284 | emoticons: channelBTTV,
285 | title: "Emoticons"
286 | };
287 |
288 | if(channelBTTV.length) {
289 | api.register_room_set(room_id, channels[room_id]["emotes"], set); // Load normal emotes
290 | api.emote_sets[channels[room_id]["emotes"]].hidden = !show_emotes_in_menu;
291 | }
292 |
293 | set = {
294 | emoticons: channelBTTV_GIF,
295 | title: "Emoticons (GIF)"
296 | };
297 |
298 | channels[room_id]["gifemotes"] = jQuery.extend(true, {}, set);
299 | var tempStillEmotes = jQuery.extend(true, {}, set);
300 |
301 | var stillEmotes = tempStillEmotes["emoticons"];
302 |
303 | i = stillEmotes.length;
304 | while(i--) {
305 | var element = stillEmotes[i];
306 | var j = element["urls"].length;
307 | while(j--) {
308 | var key = element["urls"][i];
309 | var img = new Image();
310 |
311 | img.onload = (function(array_index, size) {
312 | var canvas = document.createElement("canvas");
313 | var ctx = canvas.getContext("2d");
314 |
315 | canvas.width = this.width;
316 | canvas.height = this.height;
317 |
318 | ctx.drawImage(this, 0, 0);
319 |
320 | if(!channels[room_id]["gifemotes_still"]) {
321 | channels[room_id]["gifemotes_still"] = tempStillEmotes;
322 | }
323 |
324 | stillEmotes[array_index]["urls"][size] = canvas.toDataURL();
325 |
326 | api.register_room_set(room_id, channels[room_id]["gifemotes_setid"], channels[room_id]["gifemotes_still"]); // Load static GIF emotes
327 | api.emote_sets[channels[room_id]["gifemotes_setid"]].hidden = !show_emotes_in_menu;
328 | }).bind(img, i, key);
329 | img.onerror = function(errorMsg, url, lineNumber, column, errorObj) {
330 | console.log("Couldn't load.");
331 | };
332 | img.crossOrigin = "anonymous";
333 | img.src = element["urls"][key] + ".png";
334 | }
335 | }
336 |
337 | api.register_room_set(room_id, channels[room_id]["gifemotes_setid"], set); // Load GIF emotes
338 | api.emote_sets[channels[room_id]["gifemotes_setid"]].hidden = !show_emotes_in_menu;
339 |
340 | if(!enable_gif_emotes)
341 | api.unload_set(channels[room_id]["gifemotes_setid"]);
342 | }).fail(function(data) {
343 | if (data["status"] === 404) {
344 | return;
345 | }
346 |
347 | attempts = (attempts || 0) + 1;
348 | if (attempts < 12) {
349 | api.log("Failed to fetch BTTV channel emotes. Trying again in 5 seconds.");
350 | return setTimeout(channelCallback.bind(this, room_id, reg_function, attempts), 5000);
351 | }
352 | });
353 | };
354 |
355 | var implementBTTVGlobals = function(attempts) {
356 | $.getJSON("https://api.betterttv.net/emotes")
357 | .done(function(data) {
358 | var globalBTTV = new Array(),
359 | globalBTTV_GIF = new Array(),
360 | overrideEmotes = new Array(),
361 |
362 | emotes = data["emotes"];
363 |
364 | var i = emotes.length;
365 | while(i--) {
366 | var req_spaces = /[^A-Za-z0-9]/.test(emotes[i]["regex"]);
367 |
368 | var emote = emotes[i],
369 | match = /cdn.betterttv.net\/emote\/(\w+)/.exec(emote["url"]),
370 | id = match && match[1];
371 |
372 | if (emote["channel"])
373 | continue;
374 |
375 | var xMote = {
376 | urls: { 1: emote["url"] },
377 | name: emote["regex"],
378 | width: emote["width"],
379 | height: emote["height"],
380 | require_spaces: req_spaces
381 | };
382 |
383 | if (id) {
384 | xMote["id"] = id;
385 | xMote["urls"] = {
386 | 1: "https://cdn.betterttv.net/emote/" + id + "/1x",
387 | 2: "https://cdn.betterttv.net/emote/" + id + "/2x",
388 | 4: "https://cdn.betterttv.net/emote/" + id + "/3x"
389 | };
390 | }
391 |
392 | // Hat emote check
393 | if (hatEmotes.indexOf(emote["regex"]) != -1) {
394 | xMote["margins"] = "0px 0px 8px 0px";
395 | xMote["modifier"] = true;
396 | }
397 |
398 | if(isOverrideEmote(emote["regex"]))
399 | overrideEmotes.push(xMote);
400 | else {
401 | emote["imageType"] === "gif" ? globalBTTV_GIF.push(xMote) : globalBTTV.push(xMote);
402 | }
403 | }
404 |
405 | var set = {
406 | emoticons: globalBTTV
407 | };
408 | api.register_global_set(1, set);
409 | if(!enable_global_emotes)
410 | api.unregister_global_set(1);
411 | api.emote_sets[1].hidden = !show_emotes_in_menu;
412 |
413 | set = {
414 | emoticons: globalBTTV_GIF,
415 | title: "Global Emoticons (GIF)"
416 | };
417 | api.register_global_set(2, set);
418 | if(!enable_global_emotes || !enable_gif_emotes)
419 | api.unregister_global_set(2);
420 | api.emote_sets[2].hidden = !show_emotes_in_menu;
421 |
422 | set = {
423 | emoticons: overrideEmotes,
424 | title: "Global Emoticons (Override)"
425 | };
426 | api.register_global_set(3, set);
427 | if(!enable_global_emotes || !enable_override_emotes)
428 | api.unregister_global_set(3);
429 | api.emote_sets[3].hidden = !show_emotes_in_menu;
430 |
431 | global_emotes_loaded = true;
432 |
433 | }).fail(function(data) {
434 | if (data["status"] === 404)
435 | return;
436 |
437 | attempts = (attempts || 0) + 1;
438 | if (attempts < 12) {
439 | api.log("Failed to fetch BTTV global emotes. Trying again in 5 seconds.");
440 | return setTimeout(implementBTTVGlobals.bind(this, attempts), 5000);
441 | }
442 | });
443 | };
444 |
445 | var implementBTTVBadges = function(attempts) {
446 | api.add_badge("bttv4ffz", {
447 | name: "bttv4ffz",
448 | title: "BTTV4FFZ Developer",
449 | image: "https://cdn.lordmau5.com/Mau5Badge.png",
450 | alpha_image: "https://cdn.lordmau5.com/Mau5Badge_Alpha.png",
451 | color: "#49acff"
452 | });
453 | api.user_add_badge("lordmau5", 20, "bttv4ffz");
454 |
455 | $.getJSON("https://api.betterttv.net/2/badges")
456 | .done(function(data) {
457 | var types = new Array(),
458 | badges = new Array(),
459 |
460 | _types = data["types"],
461 | _users = data["badges"];
462 |
463 | var i = _types.length;
464 | while(i--) {
465 | var _type = _types[i];
466 |
467 | var type = {
468 | name: _type.name,
469 | title: _type.description,
470 | image: _type.svg,
471 | no_invert: true
472 | };
473 |
474 | types[type.name] = type;
475 | api.add_badge(type.name, type);
476 | }
477 |
478 | i = _users.length;
479 | while(i--) {
480 | var _user = _users[i];
481 |
482 | if(types[_user.type] !== undefined) {
483 | api.log("Adding badge '" + _user.type + "' for user '" + _user.name + "'.");
484 | api.user_add_badge(_user.name, 21, _user.type);
485 | }
486 | }
487 | }).fail(function(data) {
488 | if (data["status"] === 404)
489 | return;
490 |
491 | attempts = (attempts || 0) + 1;
492 | if (attempts < 12) {
493 | api.log("Failed to fetch BTTV badges emotes. Trying again in 5 seconds.");
494 | return setTimeout(implementBTTVBadges.bind(this, attempts), 5000);
495 | }
496 | });
497 | };
498 |
499 | /* Attempt on hooking into the BTTV WebSocket servers for BTTV-Pro emotes */
500 | var bttv_pro_users = {};
501 |
502 | BTTVProUser = function(username, emotes_array) {
503 | this.username = username;
504 | this.emotes_array = emotes_array;
505 |
506 | this.initialize();
507 |
508 | bttv_pro_users[this.username] = this;
509 | };
510 |
511 | BTTVProUser.prototype.loadEmotes = function() {
512 | this.emotes_array.forEach(function(emote, index, array) {
513 | var xMote = {
514 | urls: {
515 | 1: "https://cdn.betterttv.net/emote/" + emote["id"] + "/1x",
516 | 2: "https://cdn.betterttv.net/emote/" + emote["id"] + "/2x",
517 | 4: "https://cdn.betterttv.net/emote/" + emote["id"] + "/3x"
518 | },
519 | id: emote["id"],
520 | name: emote["code"],
521 | width: 28,
522 | height: 28,
523 | owner: {
524 | display_name: emote["channel"] || "",
525 | name: emote["channel"]
526 | },
527 | require_spaces: true
528 | };
529 |
530 | if(emote["imageType"] === "png")
531 | this.emotes.push(xMote);
532 |
533 | if(emote["imageType"] === "gif")
534 | this.gif_emotes.push(xMote);
535 | }, this);
536 |
537 | var set = {
538 | emoticons: this.emotes,
539 | title: "Personal Emotes"
540 | };
541 |
542 | if(this.emotes.length) {
543 | api.load_set(this._id_emotes, set);
544 | api.user_add_set(this.username, this._id_emotes);
545 | }
546 | };
547 |
548 | BTTVProUser.prototype.initialize = function() {
549 | this._id_emotes = this.username + "_images";
550 | this.emotes = new Array();
551 |
552 | this.loadEmotes();
553 | };
554 |
555 | var bttv_pro_events = {};
556 |
557 | // BetterTTV Pro
558 | bttv_pro_events.lookup_user = function(subscription) {
559 | if (!subscription.pro || !enable_pro_emotes) return;
560 |
561 | if (subscription.pro && subscription.emotes) {
562 | if(subscription.name in bttv_pro_users) {
563 | bttv_pro_users[subscription.name].emotes_array = subscription.emotes;
564 | bttv_pro_users[subscription.name].loadEmotes();
565 | }
566 | else {
567 | new BTTVProUser(subscription.name, subscription.emotes);
568 | }
569 | }
570 | };
571 |
572 | SocketClient = function() {
573 | this.socket = false;
574 | this._lookedUpUsers = [];
575 | this._connected = false;
576 | this._connecting = false;
577 | this._connectAttempts = 1;
578 | this._joinedChannels = [];
579 | this._connectionBuffer = [];
580 | this._events = bttv_pro_events;
581 | }
582 |
583 | SocketClient.prototype.connect = function() {
584 | if (ffz.get_user() === undefined) return;
585 | if (this._connected || this._connecting) return;
586 | this._connecting = true;
587 |
588 | api.log('SocketClient: Connecting to Beta BetterTTV Socket Server');
589 |
590 | var _self = this;
591 | this.socket = new WebSocket('wss://sockets.betterttv.net/ws');
592 |
593 | this.socket.onopen = function() {
594 | api.log('SocketClient: Connected to Beta BetterTTV Socket Server');
595 |
596 | _self._connected = true;
597 | _self._connectAttempts = 1;
598 |
599 | if(_self._connectionBuffer.length > 0) {
600 | var i = _self._connectionBuffer.length;
601 | while(i--) {
602 | var channel = _self._connectionBuffer[i];
603 | _self.joinChannel(channel);
604 | _self.broadcastMe(channel);
605 | }
606 | _self._connectionBuffer = [];
607 | }
608 | };
609 |
610 | this.socket.onerror = function() {
611 | api.log('SocketClient: Error from Beta BetterTTV Socket Server');
612 |
613 | _self._connectAttempts++;
614 | _self.reconnect();
615 | };
616 |
617 | this.socket.onclose = function() {
618 | if (!_self._connected || !_self.socket) return;
619 |
620 | api.log('SocketClient: Disconnected from Beta BetterTTV Socket Server');
621 |
622 | _self._connectAttempts++;
623 | _self.reconnect();
624 | };
625 |
626 | this.socket.onmessage = function(message) {
627 | var evt;
628 |
629 | try {
630 | evt = JSON.parse(message.data);
631 | }
632 | catch (e) {
633 | debug.log('SocketClient: Error Parsing Message', e);
634 | }
635 |
636 | if (!evt || !(evt.name in _self._events)) return;
637 |
638 | api.log('SocketClient: Received Event');
639 | api.log(evt);
640 |
641 | _self._events[evt.name](evt.data);
642 | };
643 | };
644 |
645 | SocketClient.prototype.reconnect = function() {
646 | var _self = this;
647 |
648 | this.disconnect();
649 |
650 | if (this._connecting === false) return;
651 | this._connecting = false;
652 |
653 | setTimeout(function() {
654 | _self.connect();
655 | }, Math.random() * (Math.pow(2, this._connectAttempts) - 1) * 30000);
656 | };
657 |
658 | SocketClient.prototype.disconnect = function() {
659 | var _self = this;
660 |
661 | if (this.socket) {
662 | try {
663 | this.socket.close();
664 | }
665 | catch (e) {}
666 | }
667 |
668 | delete this.socket;
669 |
670 | this._connected = false;
671 | };
672 |
673 | SocketClient.prototype.emit = function(evt, data) {
674 | if (!this._connected || !this.socket) return;
675 |
676 | this.socket.send(JSON.stringify({
677 | name: evt,
678 | data: data
679 | }));
680 | };
681 |
682 | // Introduce myself
683 | SocketClient.prototype.broadcastMe = function(channel) {
684 | if (!this._connected) return;
685 | if (!ffz.get_user()) return;
686 |
687 | this.emit('broadcast_me', { name: ffz.get_user().login, channel: channel });
688 | };
689 |
690 | SocketClient.prototype.joinChannel = function(channel) {
691 | if (!this._connected) {
692 | if (!this._connectionBuffer.includes(channel))
693 | this._connectionBuffer.push(channel);
694 | return;
695 | }
696 |
697 | if (!channel.length) return;
698 |
699 | if (this._joinedChannels[channel]) {
700 | this.emit('part_channel', { name: channel });
701 | }
702 |
703 | this.emit('join_channel', { name: channel });
704 | this._joinedChannels[channel] = true;
705 | };
706 |
707 | // Finally, load.
708 | check_existance();
709 | })();
710 |
--------------------------------------------------------------------------------
/pack.rb:
--------------------------------------------------------------------------------
1 | require "fileutils"
2 |
3 | puts "Please enter the version (Pattern: 1.0.4, 2.0.5, 3.3.3): "
4 | $version = gets.strip
5 |
6 | ##########################################################
7 | ## Firefox ##
8 | ##########################################################
9 |
10 | def ff_copy_template()
11 | FileUtils.cp("Firefox/template__/install.rdf", "Firefox/install.rdf")
12 | FileUtils.cp("Firefox/template__/harness-options.json", "Firefox/harness-options.json")
13 | end
14 | ff_copy_template
15 |
16 | def ff_replace_version()
17 | text = File.read("Firefox/install.rdf")
18 | new_content = text.gsub(/%%VERSION%%/, $version)
19 |
20 | File.open("Firefox/install.rdf", "w") { |file| file.puts new_content }
21 |
22 | ######################################################################
23 |
24 | text = File.read("Firefox/harness-options.json")
25 | new_content = text.gsub(/%%VERSION%%/, $version)
26 |
27 | File.open("Firefox/harness-options.json", "w") { |file| file.puts new_content }
28 | end
29 | ff_replace_version
30 |
31 | def ff_archive_xpi()
32 | %x(files/7za.exe a archives/BTTV4FFZ-#{$version}.xpi .\\Firefox\\*)
33 | %x(files/7za.exe d archives/BTTV4FFZ-#{$version}.xpi template__)
34 | end
35 | ff_archive_xpi
36 |
37 | ##########################################################
38 | ## Chrome ##
39 | ##########################################################
40 |
41 | def ch_copy_template()
42 | FileUtils.cp("Chrome/template__/manifest.json", "Chrome/manifest.json")
43 | end
44 | ch_copy_template
45 |
46 | def ch_replace_version()
47 | text = File.read("Chrome/manifest.json")
48 | new_content = text.gsub(/%%VERSION%%/, $version)
49 |
50 | File.open("Chrome/manifest.json", "w") { |file| file.puts new_content }
51 | end
52 | ch_replace_version
53 |
54 | def ch_archive_zip()
55 | %x(files/7za.exe a archives/BTTV4FFZ-#{$version}.zip .\\Chrome\\*)
56 | %x(files/7za.exe d archives/BTTV4FFZ-#{$version}.zip template__)
57 | end
58 | ch_archive_zip
59 |
--------------------------------------------------------------------------------