├── scripts
├── pages
│ ├── localization.js
│ ├── context_menu.js
│ ├── newznab-check.js
│ ├── common.js
│ ├── newznab-autoadd.js
│ ├── manifest.js
│ └── background.js
├── utility.js
├── convert.js
├── content
│ ├── bintube.js
│ ├── animenzb.js
│ ├── nzbclub.js
│ ├── nzbrss.js
│ ├── fanzub.js
│ ├── animezb.js
│ ├── binsearch.js
│ ├── dognzb.js
│ ├── usenet4ever.js
│ ├── yubse.js
│ ├── omgwtfnzbs.js
│ ├── common.js
│ ├── nzbindex.js
│ └── newznab.js
├── profile.js
└── compatibility
│ └── globals.js
├── third_party
├── fancy-settings-mod
│ ├── .gitignore
│ ├── icon.png
│ ├── css
│ │ ├── select.png
│ │ ├── main.css
│ │ ├── button.css
│ │ ├── setting.css
│ │ ├── select.css
│ │ └── chrome_shared.css
│ ├── js
│ │ ├── i18n.js
│ │ └── classes
│ │ │ ├── tab.js
│ │ │ ├── search.js
│ │ │ └── setting.js
│ ├── custom.css
│ ├── resources
│ │ └── icon.drawit
│ │ │ ├── Data
│ │ │ ├── QuickLook
│ │ │ ├── Preview.jpg
│ │ │ └── Thumbnail.jpg
│ │ │ └── Info.plist
│ ├── lib
│ │ ├── default.css
│ │ ├── mootools-core.js
│ │ └── store.js
│ ├── CHANGELOG.md
│ ├── default.settings.js
│ ├── index.html
│ ├── i18n.js
│ ├── README.md
│ └── default.manifest.js
├── jquery
│ ├── jquery.notify.js
│ └── jquery.urldecoder.min.js
├── webtoolkit
│ └── webtoolkit.base64.js
└── jqplot
│ ├── jquery.jqplot.css
│ └── jqplot.pointLabels.js
├── images
├── sab_48.png
├── sab_128.png
├── content_icon.png
├── control_pause.png
├── control_play.png
├── control_cancel.png
├── content_icon_error.png
├── content_icon_fetching.png
├── content_icon_success.png
├── play.svg
├── chevron-up.svg
├── chevron-down.svg
├── pause.svg
├── power-off.svg
├── pause-item.svg
├── refresh.svg
├── external-link.svg
├── delete-item.svg
├── restart.svg
├── settings.svg
└── addon_icon.svg
├── .gitmodules
├── css
├── newznab.css
├── common.css
├── nzbmatrix.css
└── nabnotify.css
├── project
└── sabconnect++.pnproj
├── CONTRIBUTING.md
├── README.md
├── settings.html
├── manifest.json
└── popup.html
/scripts/pages/localization.js:
--------------------------------------------------------------------------------
1 | this.il8n = {
2 | };
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/.gitignore:
--------------------------------------------------------------------------------
1 | manifest.js
2 | settings.js
--------------------------------------------------------------------------------
/images/sab_48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/sab_48.png
--------------------------------------------------------------------------------
/images/sab_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/sab_128.png
--------------------------------------------------------------------------------
/images/content_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/content_icon.png
--------------------------------------------------------------------------------
/images/control_pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/control_pause.png
--------------------------------------------------------------------------------
/images/control_play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/control_play.png
--------------------------------------------------------------------------------
/images/control_cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/control_cancel.png
--------------------------------------------------------------------------------
/images/content_icon_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/content_icon_error.png
--------------------------------------------------------------------------------
/images/content_icon_fetching.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/content_icon_fetching.png
--------------------------------------------------------------------------------
/images/content_icon_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/images/content_icon_success.png
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/third_party/fancy-settings-mod/icon.png
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "third_party/fancy-settings"]
2 | path = third_party/fancy-settings
3 | url = git://github.com/jsterken/fancy-settings.git
4 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/css/select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/third_party/fancy-settings-mod/css/select.png
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/js/i18n.js:
--------------------------------------------------------------------------------
1 | // Minimal i18n implementation for fancy-settings compatibility
2 | // This is a stub implementation for compatibility
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/js/classes/tab.js:
--------------------------------------------------------------------------------
1 | // Minimal Tab implementation for fancy-settings compatibility
2 | // This is a stub implementation for compatibility
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/custom.css:
--------------------------------------------------------------------------------
1 | /*
2 | // Add your own style rules here, not in css/main.css
3 | // or css/setting.css for easy updating reasons.
4 | */
5 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/js/classes/search.js:
--------------------------------------------------------------------------------
1 | // Minimal Search implementation for fancy-settings compatibility
2 | // This is a stub implementation for compatibility
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/js/classes/setting.js:
--------------------------------------------------------------------------------
1 | // Minimal Setting implementation for fancy-settings compatibility
2 | // This is a stub implementation for compatibility
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/resources/icon.drawit/Data:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/third_party/fancy-settings-mod/resources/icon.drawit/Data
--------------------------------------------------------------------------------
/css/newznab.css:
--------------------------------------------------------------------------------
1 |
2 | #browsetable a.addSABnzbd {
3 | margin-right: 3px; position: relative; top: 3px;
4 | }
5 |
6 | #infohead div.icon {
7 | float: none;
8 | display: inline;
9 | top: 4px;
10 | }
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/resources/icon.drawit/QuickLook/Preview.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/third_party/fancy-settings-mod/resources/icon.drawit/QuickLook/Preview.jpg
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/resources/icon.drawit/QuickLook/Thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gboudreau/sabconnectplusplus/HEAD/third_party/fancy-settings-mod/resources/icon.drawit/QuickLook/Thumbnail.jpg
--------------------------------------------------------------------------------
/images/play.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/chevron-up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/chevron-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/project/sabconnect++.pnproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/css/common.css:
--------------------------------------------------------------------------------
1 | .serversPriorities ul,
2 | .serversPriorities ul li
3 | {
4 | margin: 0;
5 | padding: 0;
6 | }
7 |
8 | .serversPriorities ul {
9 | padding: 0 5px;
10 | }
11 |
12 | .serversPriorities ul li {
13 | list-style-type: none;
14 | }
--------------------------------------------------------------------------------
/images/pause.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/power-off.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/pause-item.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/resources/icon.drawit/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | fileVersion
6 | 2
7 |
8 |
9 |
--------------------------------------------------------------------------------
/scripts/utility.js:
--------------------------------------------------------------------------------
1 | /// Gets the first item in the object. This is
2 | /// particularly useful for associative arrays.
3 | /// @return Return value will be 'undefined' if
4 | /// the object is empty.
5 | function first( object )
6 | {
7 | for( var obj in object ) {
8 | return obj;
9 | }
10 |
11 | return undefined;
12 | }
--------------------------------------------------------------------------------
/images/refresh.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/external-link.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/delete-item.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Sadly, there is no more 'main developer' working on this Chrome extension.
2 | Past developers who worked on most of the code have now stopped working on it.
3 |
4 | If you are interested in taking charge of this extension, let us know by opening a new issue.
5 |
6 | Otherwise, keep the Pull requests coming; I (Guillaume Boudreau) will try to merge them and release new versions that include the changes on the Chrome Web Store ASAP.
7 |
8 | Cheers.
9 |
--------------------------------------------------------------------------------
/scripts/convert.js:
--------------------------------------------------------------------------------
1 | // 'value' must be a string.
2 | function toBoolean( value )
3 | {
4 | if( typeof value != 'string' ) {
5 | throw 'Parameter "value" must be of type string';
6 | }
7 |
8 | value = value.toLowerCase();
9 |
10 | if( value == 'yes' || value == 'true' || value == '1' ) {
11 | return true;
12 | }
13 | else if( value == 'no' || value == 'false' || value == '0' ) {
14 | return false;
15 | }
16 |
17 | return null;
18 | }
--------------------------------------------------------------------------------
/css/nzbmatrix.css:
--------------------------------------------------------------------------------
1 | span.downloadButton {
2 | display: inline-block;
3 | width: 171px;
4 | height: 26px;
5 | text-decoration: none;
6 | color: black;
7 | position: relative;
8 | top: -15px;
9 | }
10 |
11 | span.downloadButton .text {
12 | position: relative;
13 | top: 5px;
14 | }
15 |
16 | span.downloadButton img {
17 | position: relative;
18 | top: 5px;
19 | }
20 |
21 | a:.downloadButton:hover {
22 | text-decoration: none;
23 | }
24 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/lib/default.css:
--------------------------------------------------------------------------------
1 | /* Basic CSS reset and defaults for fancy-settings */
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10 | background-color: #f5f5f5;
11 | }
12 |
13 | .no-select {
14 | -webkit-user-select: none;
15 | -moz-user-select: none;
16 | -ms-user-select: none;
17 | user-select: none;
18 | }
--------------------------------------------------------------------------------
/images/restart.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/css/nabnotify.css:
--------------------------------------------------------------------------------
1 | @charset "utf-8";
2 |
3 | .autonabSticky p { float:left; padding:0px; margin:0px; margin-left:10px; line-height:45px; color:#fff; font-size:12px;}
4 | .autonabSticky p a { color:#fbffa2; }
5 | .autonabSticky a.close { float:right; margin:13px 10px 0px 0px; color:#fff; font-size: 14px;}
6 | .autonabSticky {
7 | position: fixed;
8 | top: 0;
9 | left: 0;
10 | z-index: 999999999;
11 | width: 100%;
12 | border-bottom: 3px solid #fff !important;
13 | height: 45px;
14 | background-color: #91BD09;
15 | box-shadow: 1px 1px 7px #676767;
16 | }
17 |
--------------------------------------------------------------------------------
/images/settings.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/css/main.css:
--------------------------------------------------------------------------------
1 | /* Main layout styles for fancy-settings - Overridden by SABconnect++ modern design */
2 |
3 | /* These styles are overridden by css/settings.css for consistent modern design */
4 | /* Keeping minimal fallback styles for compatibility */
5 |
6 | #sidebar {
7 | position: fixed;
8 | left: 0;
9 | top: 0;
10 | width: 280px;
11 | height: 100vh;
12 | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13 | color: white;
14 | overflow-y: auto;
15 | z-index: 1000;
16 | }
17 |
18 | #content {
19 | margin-left: 280px;
20 | padding: 40px;
21 | min-height: 100vh;
22 | background: #f8f9fa;
23 | }
24 |
25 | .tab-content {
26 | display: none;
27 | }
28 |
29 | .tab-content.active {
30 | display: block;
31 | }
32 |
33 | .settings-group {
34 | background: white;
35 | margin-bottom: 30px;
36 | border-radius: 12px;
37 | box-shadow: 0 4px 20px rgba(0,0,0,0.1);
38 | overflow: hidden;
39 | }
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v2.0
4 | Feature: chrome.storage.sync support
5 | Feature: queue to reduce storage calls
6 | Feature: syncronous access to chrome.storage after initialization
7 | Change: moved everything up one directory level
8 |
9 | ## v1.2 [ API CHANGE! ]
10 | * **Feature**: Added version number to README file
11 | * **Feature**: Added CHANGELOG
12 | * **Feature**: Added alignment support
13 | * **Feature**: Added support for linking to specific tabs
14 | * **Change**: Removed the possibility to use objects to define options for elements other than popupButton
15 | * **Change**: Added support for option groups in popupButtons, changing the format when defining options as objects
16 | * **Change**: Changed the default value of "display" in the slider params from true to false
17 | * **Change**: Search doesn't change the DOM structure of settings any more
18 | * **Change**: i18n now returns the string you entered instead of undefined if no translation can be found
19 | * **Bug**: Fixed an issue were the search field had the wrong placeholder when "search" had no translation
20 |
--------------------------------------------------------------------------------
/scripts/content/bintube.js:
--------------------------------------------------------------------------------
1 | function addToSABnzbdFromBintube() {
2 | // Set the image to an in-progress image
3 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
4 | $(this).find('img').attr("src", img);
5 |
6 | var nzburl = $(this).attr('href');
7 | var addLink = this;
8 |
9 | addToSABnzbd(addLink, nzburl, "addurl");
10 |
11 | return false;
12 | }
13 |
14 | function handleAllDownloadLinks() {
15 | $('a.dlbtn').each(function() {
16 | var href = $(this).attr('href');
17 | var img = chrome.runtime.getURL('/images/content_icon.png');
18 | var link = '
';
19 | $(this).before(link);
20 | $(this).remove();
21 | });
22 |
23 | // Change the on click handler to send to sabnzbd
24 | // moved because the way it was the click was firing multiple times
25 | $('.addSABnzbd').each(function() {
26 | $(this).click(addToSABnzbdFromBintube);
27 | });
28 |
29 | return;
30 | }
31 |
32 | Initialize( 'bintube', null, function() {
33 | handleAllDownloadLinks();
34 | });
--------------------------------------------------------------------------------
/scripts/content/animenzb.js:
--------------------------------------------------------------------------------
1 | function addToSABnzbdFromAnimenzb() {
2 | var addLink = this;
3 |
4 | // Set the image to an in-progress image
5 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
6 | var nzburl = this.href;
7 |
8 | var category = null;
9 | try {
10 | category = this.parent().parent().find('td > a > img').first().attr('alt').toLowerCase();
11 | } catch (ex) { }
12 | $(this).find('img').first().attr('src', img);
13 | addToSABnzbd(addLink, nzburl, "addurl", null, category);
14 |
15 | return false;
16 | }
17 |
18 |
19 | function handleAllDownloadLinks() {
20 | // Find all Table Rows - $('td.file > a[type="application/x-nzb"]').parent().parent()
21 | // Find all Download links - $('td.file > a[type="application/x-nzb"]')
22 | $('td.file > a[type="application/x-nzb"]').each(function() {
23 | var img = chrome.runtime.getURL('/images/content_icon.png');
24 | var href = $(this).attr('href');
25 | var link = '
';
26 | $(this).before(link);
27 | $(this).parent().find('a[class="addSABnzbd"]').first().click(addToSABnzbdFromAnimenzb);
28 | });
29 | }
30 |
31 | Initialize( 'animenzb', null, function() {
32 | handleAllDownloadLinks();
33 | });
34 |
--------------------------------------------------------------------------------
/images/addon_icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/scripts/pages/context_menu.js:
--------------------------------------------------------------------------------
1 | function AddLinkResult( result, data )
2 | {
3 | switch( result.ret )
4 | {
5 | case 'success':
6 | break;
7 | case 'error':
8 | alert( 'Attempt to add a URL to SabNZBd via context menu failed' );
9 | break;
10 | }
11 | }
12 |
13 | function ContextClicked( info, tab )
14 | {
15 | var request = {
16 | mode: 'addurl',
17 | nzburl: info.linkUrl
18 | };
19 |
20 | addToSABnzbd( request, AddLinkResult );
21 | }
22 |
23 | function CreateContextMenuResult()
24 | {
25 | var error = chrome.runtime.lastError;
26 | if( error )
27 | {
28 | alert(
29 | 'The context menu for SabConnect++ failed to load.\n\nDetails:\t' + error
30 | );
31 | }
32 | }
33 |
34 | function CreateContextMenu()
35 | {
36 | var urlPatterns = [
37 | 'http://*/*',
38 | 'https://*/*'
39 | ]
40 |
41 | var properties = {
42 | type: 'normal',
43 | title: 'Send link to SABnzbd',
44 | contexts: ['link'],
45 | onclick: ContextClicked,
46 | targetUrlPatterns: urlPatterns
47 | }
48 |
49 | chrome.contextMenus.create( properties, CreateContextMenuResult );
50 | }
51 |
52 | function DestroyContextMenu()
53 | {
54 | chrome.contextMenus.removeAll();
55 | }
56 |
57 | function SetupContextMenu()
58 | {
59 | if( store.get( 'config_enable_context_menu' ) )
60 | {
61 | CreateContextMenu();
62 | }
63 | else
64 | {
65 | DestroyContextMenu();
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | **Important note:**
3 | Nobody is maintaining this codebase right now. Anyone who needs a fix will need to submit a Pull Request with the required changes.
4 |
5 | ---
6 |
7 | SABconnect++ adds one-click 'Send to SABnzbd' buttons to many popular NZB index sites.
8 |
9 | You also get a taskbar button that allows you to keep an eye on your SABnzbd: current downloads, pause (individual downloads, or pause all), or remove individual queued downloads.
10 |
11 | Install SABconnect++ at our [Chrome Web Store page](https://chrome.google.com/webstore/detail/okphadhbbjadcifjplhifajfacbkkbod).
12 |
13 | Features:
14 |
15 | * One-click NZB downloads for the following sites:
16 | * binsearch.info (binsearch.net)
17 | * bintube.com
18 | * dognzb.com
19 | * fanzub.com
20 | * nzbclub.com
21 | * nzbindex.com (nzbindex.nl)
22 | * omgwtfnzbs.me
23 | * yubse.com
24 | * animezb.com
25 | * animenzb.com
26 | * Any Newznab-based indexer
27 | * Context menu option for sending links to SABnzbd
28 | * Options page that looks consistent with Chrome's own options layout
29 | * Download speed graph
30 | * Pause individual downloads
31 | * Pause all downloads
32 | * Remove individual downloads
33 | * Desktop notifications (Download Complete/Failed)
34 | * Storage sync for settings
35 |
36 | SABconnect++ is a fork of the now unmaintained Chrome extension [SABconnect](http://code.google.com/p/sabconnect/).
37 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/css/button.css:
--------------------------------------------------------------------------------
1 | /* Chrome button.css */
2 |
3 | button,
4 | input[type='button'],
5 | input[type='submit'] {
6 | -webkit-border-radius: 2px;
7 | -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
8 | -webkit-user-select: none;
9 | background: -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
10 | border: 1px solid #aaa;
11 | color: #444;
12 | font-size: inherit;
13 | margin-bottom: 0px;
14 | min-width: 4em;
15 | padding: 3px 12px 3px 12px;
16 | }
17 |
18 | button:hover,
19 | input[type='button']:hover,
20 | input[type='submit']:hover {
21 | -webkit-box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.2);
22 | background: #ebebeb -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);
23 | border-color: #999;
24 | color: #222;
25 | }
26 |
27 | button:active,
28 | input[type='button']:active,
29 | input[type='submit']:active {
30 | -webkit-box-shadow: inset 0px 1px 3px rgba(0, 0, 0, 0.2);
31 | background: #ebebeb -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);
32 | color: #333;
33 | }
34 |
35 | button[disabled],
36 | input[type='button'][disabled],
37 | input[type='submit'][disabled],
38 | button[disabled]:hover,
39 | input[type='button'][disabled]:hover,
40 | input[type='submit'][disabled]:hover {
41 | -webkit-box-shadow: none;
42 | background: -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
43 | border-color: #aaa;
44 | color: #888;
45 | }
46 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/css/setting.css:
--------------------------------------------------------------------------------
1 | /* Setting styles for fancy-settings - Overridden by SABconnect++ modern design */
2 |
3 | /* These styles are overridden by css/settings.css for consistent modern design */
4 | /* Keeping minimal fallback styles for compatibility */
5 |
6 | .setting {
7 | margin: 0;
8 | padding: 25px 30px;
9 | border: none;
10 | border-bottom: 1px solid #e1e8ed;
11 | background: white;
12 | transition: all 0.2s ease;
13 | }
14 |
15 | .setting label {
16 | display: block;
17 | font-weight: 600;
18 | margin-bottom: 12px;
19 | color: #2c3e50;
20 | font-size: 15px;
21 | }
22 |
23 | .setting input,
24 | .setting select,
25 | .setting textarea {
26 | width: 100%;
27 | padding: 12px 16px;
28 | border: 1px solid #e1e8ed;
29 | border-radius: 8px;
30 | font-size: 14px;
31 | background: white;
32 | color: #2c3e50;
33 | transition: all 0.2s ease;
34 | box-sizing: border-box;
35 | font-family: inherit;
36 | }
37 |
38 | .setting input[type="checkbox"] {
39 | width: auto;
40 | margin-right: 12px;
41 | transform: scale(1.2);
42 | }
43 |
44 | .setting button {
45 | background: linear-gradient(135deg, #3498db, #0984e3);
46 | color: white;
47 | border: none;
48 | padding: 12px 24px;
49 | border-radius: 8px;
50 | cursor: pointer;
51 | font-size: 14px;
52 | font-weight: 500;
53 | transition: all 0.2s ease;
54 | width: auto;
55 | box-shadow: 0 2px 8px rgba(52, 152, 219, 0.2);
56 | }
--------------------------------------------------------------------------------
/scripts/content/nzbclub.js:
--------------------------------------------------------------------------------
1 | function addToSABnzbdFromNZBCLUB() {
2 | // Set the image to an in-progress image
3 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
4 | $(this).find('img').first().attr("src", img);
5 |
6 | var nzburl = $(this).attr('href');
7 | var addLink = $(this).parent();
8 |
9 | addToSABnzbd(addLink, nzburl, "addurl");
10 |
11 | return false;
12 | }
13 |
14 | function handleAllDownloadLinks() {
15 | $('div.project-action > div > button[id="get"]').each(function() {
16 | var img = chrome.runtime.getURL('/images/content_icon.png');
17 | var href = "/nzb_get/" + $(this).parent().parent().attr('collectionid');
18 | var link = '
';
19 | $(this).after(link);
20 | $(this).parent().find('a[class="addSABnzbd"]').first().click(addToSABnzbdFromNZBCLUB);
21 | });
22 | return;
23 | }
24 |
25 | Initialize( 'nzbclub', null, function() {
26 | console.log('SABconnect++ NZBClub: Script initialized');
27 | handleAllDownloadLinks();
28 |
29 | // Fallback if no icons were added
30 | if ($('a.addSABnzbd').length === 0) {
31 | console.log('SABconnect++ NZBClub: No icons added, trying fallback...');
32 | addIconsWithFallback({
33 | linkSelector: 'a[href*="/nzb_get.aspx"], a[href*="/nzb/"]',
34 | iconClass: 'addSABnzbd',
35 | clickHandler: addToSABnzbdFromNZBCLUB
36 | });
37 | }
38 | });
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/css/select.css:
--------------------------------------------------------------------------------
1 | /* Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 | * Use of this source code is governed by a BSD-style license that can be
3 | * found in the LICENSE file.
4 | *
5 | * This is the generic select css used on various WebUI implementations.
6 | */
7 |
8 | select {
9 | -webkit-appearance: button;
10 | -webkit-border-radius: 2px;
11 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
12 | -webkit-padding-end: 20px;
13 | -webkit-padding-start: 2px;
14 | -webkit-user-select: none;
15 | background-image: url("select.png"),
16 | -webkit-linear-gradient(#fafafa, #f4f4f4 40%, #e5e5e5);
17 | background-position: center right;
18 | background-repeat: no-repeat;
19 | border: 1px solid #aaa;
20 | color: #555;
21 | font-size: inherit;
22 | margin: 0;
23 | overflow: hidden;
24 | padding-top: 2px;
25 | padding-bottom: 2px;
26 | text-overflow: ellipsis;
27 | white-space: nowrap;
28 | }
29 |
30 | html[dir='rtl'] select {
31 | background-position: center left;
32 | }
33 |
34 |
35 | select:disabled {
36 | color: graytext;
37 | }
38 |
39 | select:enabled:hover {
40 | -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
41 | background-image: url("select.png"),
42 | -webkit-linear-gradient(#fefefe, #f8f8f8 40%, #e9e9e9);
43 | color: #333;
44 | }
45 |
46 | select:enabled:active {
47 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.2);
48 | background-image: url("select.png"),
49 | -webkit-linear-gradient(#f4f4f4, #efefef 40%, #dcdcdc);
50 | color: #444;
51 | }
--------------------------------------------------------------------------------
/settings.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
31 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/default.settings.js:
--------------------------------------------------------------------------------
1 | window.addEvent("domready", function () {
2 | // Option 1: Use the manifest:
3 | new FancySettings.initWithManifest(function (settings) {
4 | settings.manifest.myButton.addEvent("action", function () {
5 | alert("You clicked me!");
6 | });
7 | });
8 |
9 | // Option 2: Do everything manually:
10 | /*
11 | var settings = new FancySettings("My Extension", "icon.png");
12 |
13 | var username = settings.create({
14 | "tab": i18n.get("information"),
15 | "group": i18n.get("login"),
16 | "name": "username",
17 | "type": "text",
18 | "label": i18n.get("username"),
19 | "text": i18n.get("x-characters")
20 | });
21 |
22 | var password = settings.create({
23 | "tab": i18n.get("information"),
24 | "group": i18n.get("login"),
25 | "name": "password",
26 | "type": "text",
27 | "label": i18n.get("password"),
28 | "text": i18n.get("x-characters-pw"),
29 | "masked": true
30 | });
31 |
32 | var myDescription = settings.create({
33 | "tab": i18n.get("information"),
34 | "group": i18n.get("login"),
35 | "name": "myDescription",
36 | "type": "description",
37 | "text": i18n.get("description")
38 | });
39 |
40 | var myButton = settings.create({
41 | "tab": "Information",
42 | "group": "Logout",
43 | "name": "myButton",
44 | "type": "button",
45 | "label": "Disconnect:",
46 | "text": "Logout"
47 | });
48 |
49 | // ...
50 |
51 | myButton.addEvent("action", function () {
52 | alert("You clicked me!");
53 | });
54 |
55 | settings.align([
56 | username,
57 | password
58 | ]);
59 | */
60 | });
61 |
--------------------------------------------------------------------------------
/scripts/content/nzbrss.js:
--------------------------------------------------------------------------------
1 | function addToSABnzbdFromNzbrss() {
2 | // Set the image to an in-progress image
3 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
4 |
5 | // Only replace the image if it isn't a Bootstrap button style
6 | if ($(this).hasClass('btn') == false) {
7 | $(this).find('img').attr("src", img);
8 | }
9 |
10 | var nzburl = $(this).attr('href');
11 | var addLink = this;
12 |
13 | // Send the NZB to SABnzbd
14 | addToSABnzbd(
15 | addLink,
16 | nzburl,
17 | "addurl",
18 | null,
19 | null);
20 |
21 | return false;
22 | }
23 |
24 | function handleAllDownloadLinks() {
25 | // Process each NZB link on the front page and show page
26 | $.each($('a[href^="/nzb/"]'), function(index, val) {
27 | // Prevent it from detecting the NZB link in the Breadcrumbs on the NZB page
28 | if ($(this).parents('ul').hasClass('breadcrumb') == false) {
29 | var href = $(this).attr('href').replace("/nzb/", "/get/");
30 | var img = chrome.runtime.getURL('/images/content_icon.png');
31 | var link = '
';
32 | $(this).before(link);
33 | }
34 | });
35 |
36 | // Process the "Download Button" on the NZB page
37 | var nzburl = $(".downloadBtn").children("a:first").attr("href");
38 | var link = 'Send to SABnzbd';
39 | $(".downloadBtn").append(link);
40 |
41 | // Change the on click handler to send to sabnzbd
42 | // moved because the way it was the click was firing multiple times
43 | $('.addSABnzbd').each(function() {
44 | $(this).click(addToSABnzbdFromNzbrss);
45 | });
46 |
47 | return;
48 | }
49 |
50 | Initialize( 'nzbrss', null, function() {
51 | handleAllDownloadLinks();
52 | });
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/lib/mootools-core.js:
--------------------------------------------------------------------------------
1 | // Minimal MooTools compatibility layer
2 | var Class = function(properties) {
3 | var klass = function(){
4 | return this.initialize ? this.initialize.apply(this, arguments) : this;
5 | };
6 |
7 | for (var key in properties) {
8 | klass.prototype[key] = properties[key];
9 | }
10 |
11 | return klass;
12 | };
13 |
14 | var Element = function(tag, properties) {
15 | var element = document.createElement(tag);
16 | if (properties) {
17 | for (var key in properties) {
18 | if (key === 'text') {
19 | element.textContent = properties[key];
20 | } else {
21 | element.setAttribute(key, properties[key]);
22 | }
23 | }
24 | }
25 |
26 | element.inject = function(parent) {
27 | parent.appendChild(this);
28 | return this;
29 | };
30 |
31 | element.set = function(property, value) {
32 | if (property === 'text') {
33 | this.textContent = value;
34 | } else if (property === 'html') {
35 | this.innerHTML = value;
36 | } else if (property === 'class') {
37 | this.className = value;
38 | } else {
39 | this.setAttribute(property, value);
40 | }
41 | return this;
42 | };
43 |
44 | element.dispose = function() {
45 | if (this.parentNode) {
46 | this.parentNode.removeChild(this);
47 | }
48 | return this;
49 | };
50 |
51 | return element;
52 | };
53 |
54 | // Extend Element to get by ID
55 | var $ = function(id) {
56 | return document.getElementById(id);
57 | };
58 |
59 | // Add typeOf function
60 | var typeOf = function(obj) {
61 | if (obj === null) return 'null';
62 | if (obj === undefined) return 'undefined';
63 | return typeof obj;
64 | };
--------------------------------------------------------------------------------
/scripts/content/fanzub.js:
--------------------------------------------------------------------------------
1 | function addToSABnzbdFromFanzub() {
2 | var addLink = this;
3 |
4 | // Set the image to an in-progress image
5 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
6 | var nzburl = this.href;
7 |
8 | var category = null;
9 | try {
10 | category = $(this).parent().parent().find('img[src*="/images/cat/"]')[0].alt.toLowerCase();
11 | } catch (ex) { }
12 | addToSABnzbd(addLink, nzburl, "addurl", null, category);
13 |
14 | return false;
15 | }
16 |
17 | function addMultiToSABnzbdFromFanzub() {
18 | if ($('table.fanzub input[type=checkbox][checked]').size() == 0)
19 | {
20 | alert('Please select at least one file');
21 | return false;
22 | } else {
23 | $('table.fanzub input[type=checkbox][checked]').each(function() {
24 | $(this).parent().parent().find('a[class="addSABnzbd"]').first().click();
25 | $(this).parent().parent().click();
26 | });
27 | }
28 | }
29 |
30 | function handleAllDownloadLinks() {
31 | $('table[class="fanzub"] td[class="file"] a').each(function() {
32 | var img = chrome.runtime.getURL('/images/content_icon.png');
33 | var href = $(this).attr('href');
34 | var link = '
';
35 | $(this).before(link);
36 | $(this).parent().find('a[class="addSABnzbd"]').first().click(addToSABnzbdFromFanzub);
37 | });
38 | }
39 |
40 | function addDownloadAllButton() {
41 | $('table[class="nav"] td button').each(function() {
42 | var img = chrome.runtime.getURL('/images/content_icon.png');
43 | var href = $(this).attr('href');
44 | var link = '';
45 | $(this).after(link);
46 | $(this).parent().find('input[id="addMultiSABnzbd"]').first().click(addMultiToSABnzbdFromFanzub);
47 | });
48 | }
49 |
50 | Initialize( 'fanzub', null, function() {
51 | handleAllDownloadLinks();
52 | addDownloadAllButton();
53 | });
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/index.html:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
43 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/scripts/content/animezb.js:
--------------------------------------------------------------------------------
1 | function addToSABnzbdFromAnimezb() {
2 | var addLink = this;
3 |
4 | // Set the image to an in-progress image
5 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
6 | var nzburl = this.href;
7 |
8 | var category = null;
9 | try {
10 | category = $(this).parent().parent().find('span[class*="label"]')[0].innerHTML.toLowerCase();
11 | } catch (ex) { }
12 | addToSABnzbd(addLink, nzburl, "addurl", null, category);
13 |
14 | return false;
15 | }
16 |
17 | function addMultiToSABnzbdFromAnimezb() {
18 | //if ($('form input[type=checkbox][checked]').size() == 0)
19 | if ($('table[id="search-results"] tr[class*="highlighted"][class*="results-top-tr"]').size() == 0)
20 | {
21 | alert('Please select at least one file');
22 | return false;
23 | } else {
24 | $('table[id="search-results"] tr[class*="highlighted"][class*="results-top-tr"]').each(function() {
25 | $(this).find('a[class="addSABnzbd"]').first().click();
26 | $(this).click();
27 | });
28 | }
29 | }
30 |
31 | function handleAllDownloadLinks() {
32 | $('table[id="search-results"] td:nth-child(2) a').each(function() {
33 | var img = chrome.runtime.getURL('/images/content_icon.png');
34 | var href = $(this).attr('href');
35 | var link = '
';
36 | $(this).before(link);
37 | $(this).parent().find('a[class="addSABnzbd"]').first().click(addToSABnzbdFromAnimezb);
38 | });
39 | }
40 |
41 | function addDownloadAllButton() {
42 | $('div[class=row] form button[class*="btn-sm"]').each(function() {
43 | var img = chrome.runtime.getURL('/images/content_icon.png');
44 | var href = $(this).attr('href');
45 | var link = '';
46 | $(this).after(link);
47 | $(this).parent().find('input[id="addMultiSABnzbd"]').first().click(addMultiToSABnzbdFromAnimezb);
48 | });
49 | }
50 |
51 | Initialize( 'animezb', null, function() {
52 | handleAllDownloadLinks();
53 | addDownloadAllButton();
54 | });
55 |
--------------------------------------------------------------------------------
/scripts/pages/newznab-check.js:
--------------------------------------------------------------------------------
1 | String.prototype.trim = function(){return this.replace(/^\s+|\s+$/g, '');};
2 |
3 | chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
4 | if (changeInfo.status == 'complete') {
5 | var found_nab = false;
6 | var newznab_urls_pre = store.get('provider_newznab');
7 | if (typeof newznab_urls_pre == 'undefined')
8 | return;
9 | var newznab_urls = newznab_urls_pre.split(',');
10 | var parsedurl = $.url.parse( tab.url );
11 | var host = (parsedurl.host.match(/([^.]+)\.\w{2,3}(?:\.\w{2})?$/) || [])[0]
12 | for (var i = 0; i < newznab_urls.length; i++) {
13 | var newznab_url = newznab_urls[i].trim();
14 | if (newznab_url.length > 0 && tab.url.match('https?://.*' + newznab_url + '.*')) {
15 | chrome.tabs.executeScript(tabId, {file: "third_party/jquery/jquery-1.12.4.min.js"});
16 | chrome.tabs.executeScript(tabId, {file: "scripts/content/common.js"});
17 | chrome.tabs.executeScript(tabId, {file: "third_party/webtoolkit/webtoolkit.base64.js"});
18 | chrome.tabs.executeScript(tabId, {file: "scripts/content/newznab.js"});
19 | chrome.tabs.insertCSS(tabId, {file: "css/newznab.css"});
20 | if ( store.get( 'nabignore.' + host ) === false )
21 | store.set( 'nabignore.' + host );
22 | found_nab = true;
23 | break;
24 | }
25 | }
26 | if ( (!found_nab) && (tab.url.indexOf('http') == 0) ) {
27 | var nabenabled = store.get( 'nabignore.' + host );
28 | var nabdetection = store.get('config_enable_automatic_detection');
29 | if ( nabdetection && !nabenabled ) {
30 | chrome.tabs.executeScript(tabId, {file: "third_party/jquery/jquery-1.12.4.min.js"});
31 | chrome.tabs.executeScript(tabId, {file: "third_party/jquery/jquery.notify.js"});
32 | chrome.tabs.executeScript(tabId, {file: "scripts/content/common.js"});
33 | chrome.tabs.executeScript(tabId, {file: "scripts/pages/newznab-autoadd.js"});
34 | chrome.tabs.insertCSS(tabId, {file: "css/nabnotify.css"});
35 | }
36 | if ( nabenabled === false )
37 | store.set( 'nabignore.' + host );
38 | }
39 | }
40 | });
41 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/lib/store.js:
--------------------------------------------------------------------------------
1 | // Store implementation compatible with fancy-settings
2 | // Uses chrome.storage.sync for Manifest V3 compatibility
3 |
4 | function Store(name, defaults, storage, callback) {
5 | this.name = name;
6 | this.defaults = defaults || {};
7 | this.storage = storage;
8 | this.data = {};
9 | this.isReady = false;
10 | this.readyCallbacks = [];
11 |
12 | if (callback) {
13 | this.readyCallbacks.push(callback);
14 | }
15 |
16 | this.init();
17 | }
18 |
19 | Store.prototype.init = function() {
20 | var self = this;
21 | chrome.storage.local.get(null, function(result) {
22 | self.data = Object.assign({}, self.defaults, result);
23 | if (result.profiles) {
24 | }
25 | self.isReady = true;
26 | self.readyCallbacks.forEach(function(callback) {
27 | callback();
28 | });
29 | self.readyCallbacks = [];
30 | });
31 | };
32 |
33 | Store.prototype.get = function(key) {
34 | var value = this.data[key];
35 | if (key === 'profiles') {
36 | }
37 | return value;
38 | };
39 |
40 | Store.prototype.set = function(key, value) {
41 | if (key === 'profiles') {
42 | }
43 | this.data[key] = value;
44 | var obj = {};
45 | obj[key] = value;
46 | chrome.storage.local.set(obj, function() {
47 | if (chrome.runtime.lastError) {
48 | console.error('Error saving to storage:', chrome.runtime.lastError);
49 | } else {
50 | if (key === 'profiles') {
51 | }
52 | }
53 | });
54 | };
55 |
56 | Store.prototype.fromObject = function(obj) {
57 | this.data = Object.assign({}, obj);
58 | chrome.storage.local.set(obj);
59 | };
60 |
61 | Store.prototype.toObject = function(callback) {
62 | if (callback) {
63 | callback(this.data);
64 | }
65 | return this.data;
66 | };
67 |
68 | Store.prototype.clear = function() {
69 | this.data = {};
70 | chrome.storage.sync.clear();
71 | };
72 |
73 | // Sync version that uses chrome.storage.sync
74 | function StoreSync(name, defaults, storage, callback) {
75 | return new Store(name, defaults, storage, callback);
76 | }
77 |
78 | // Export for different module systems
79 | if (typeof module !== 'undefined' && module.exports) {
80 | module.exports = { Store: Store, StoreSync: StoreSync };
81 | } else {
82 | window.Store = Store;
83 | window.StoreSync = StoreSync;
84 | }
--------------------------------------------------------------------------------
/third_party/jquery/jquery.notify.js:
--------------------------------------------------------------------------------
1 | (function ($) {
2 | $.fn.extend({
3 | notify: function (options) {
4 | var settings = $.extend({ type: 'sticky', speed: 500, onDemandButtonHeight: 35 }, options);
5 | return this.each(function () {
6 | var wrapper = $(this);
7 | var ondemandBtn = $('.ondemand-button');
8 | var dh = -35;
9 | var w = wrapper.outerWidth() - ondemandBtn.outerWidth();
10 | ondemandBtn.css('left', w).css('margin-top', dh + "px" );
11 | var h = -wrapper.outerHeight();
12 | wrapper.addClass(settings.type).css('margin-top', h).addClass('visible').removeClass('hide');
13 | if (settings.type != 'ondemand') {
14 | wrapper.stop(true, false).animate({ marginTop: 0 }, settings.speed);
15 | }
16 | else {
17 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed);
18 | }
19 |
20 | var closeBtn = $('.close', wrapper);
21 | closeBtn.click(function () {
22 | if (settings.type == 'ondemand') {
23 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () {
24 | wrapper.removeClass('visible').addClass('hide');
25 | ondemandBtn.stop(true, false).animate({ marginTop: 0 }, settings.speed);
26 | });
27 | }
28 | else {
29 | wrapper.stop(true, false).animate({ marginTop: h }, settings.speed, function () {
30 | wrapper.removeClass('visible').addClass('hide');
31 | });
32 | }
33 | });
34 | if (settings.type == 'floated') {
35 | $(document).scroll(function (e) {
36 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed);
37 | }).resize(function (e) {
38 | wrapper.stop(true, false).animate({ top: $(document).scrollTop() }, settings.speed);
39 | });
40 | }
41 | else if (settings.type == 'ondemand') {
42 | ondemandBtn.click(function () {
43 | $(this).animate({ marginTop: dh }, settings.speed, function () {
44 | wrapper.removeClass('hide').addClass('visible').animate({ marginTop: 0 }, settings.speed, function () {
45 |
46 | });
47 | })
48 | });
49 | }
50 |
51 | });
52 |
53 | }
54 | });
55 | })(jQuery);
56 |
57 |
--------------------------------------------------------------------------------
/scripts/pages/common.js:
--------------------------------------------------------------------------------
1 | // profiles will be initialized by the calling page
2 | var profiles = null;
3 | var StoreClass = StoreSync;
4 | // Background function no longer needed in Manifest V3
5 | // Service workers handle background functionality directly
6 | function background()
7 | {
8 | // For backwards compatibility, return null
9 | // Callers should use chrome.runtime.sendMessage instead
10 | return null;
11 | }
12 |
13 | function activeProfile()
14 | {
15 | if (!profiles) {
16 | console.error('ProfileManager not initialized');
17 | return null;
18 | }
19 | var profile = profiles.getActiveProfile();
20 | return profile ? profile.values : null;
21 | }
22 |
23 | // Ensure functions are available globally
24 | window.activeProfile = activeProfile;
25 |
26 | function setPref(key, value) {
27 | // Store temporarily in chrome.storage.local
28 | chrome.storage.local.set({ [key]: value });
29 |
30 | if (key == 'refresh_rate') {
31 | chrome.runtime.sendMessage({ action: 'refreshRateChanged' });
32 | }
33 | }
34 |
35 | // Note: getPref is now async due to chrome.storage
36 | // Use chrome.storage.local.get directly for new code
37 | function getPref(key, callback) {
38 | chrome.storage.local.get([key], function(result) {
39 | var v = result[key];
40 | if (v == 'true') v = true;
41 | if (v == 'false') v = false;
42 | if (callback) callback(v);
43 | });
44 | }
45 |
46 | function checkEndSlash(input) {
47 | if (input.charAt(input.length-1) == '/') {
48 | return input;
49 | } else {
50 | var output = input+'/';
51 | return output;
52 | }
53 | }
54 |
55 | function constructApiUrl( profileValues ) {
56 | var profile = profileValues || activeProfile();
57 | if (!profile || !profile.url) {
58 | return null;
59 | }
60 | return checkEndSlash( profile.url ) + 'api';
61 | }
62 |
63 | function constructApiPost( profileValues ) {
64 | var profile = profileValues || activeProfile();
65 | var data = {};
66 |
67 | if (!profile) {
68 | return data;
69 | }
70 |
71 | var apikey = profile.api_key;
72 | if( apikey ) {
73 | data.apikey = apikey;
74 | }
75 |
76 | var username = profile.username;
77 | if( username ) {
78 | data.ma_username = username;
79 | }
80 |
81 | var password = profile.password;
82 | if( password ) {
83 | data.ma_password = password;
84 | }
85 |
86 | return data;
87 | }
88 |
89 | function getRefreshRate()
90 | {
91 | return parseInt( background().store.get( 'config_refresh_rate' ) ) * 1000;
92 | }
93 |
94 | /// Used to merge two associative arrays.
95 | function combine( dst, src )
96 | {
97 | for( var property in src ) {
98 | if( src.hasOwnProperty( property ) ) {
99 | dst[property] = src[property];
100 | }
101 | }
102 |
103 | return dst;
104 | }
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/i18n.js:
--------------------------------------------------------------------------------
1 | // SAMPLE
2 | this.i18n = {
3 | "settings": {
4 | "en": "Settings",
5 | "de": "Optionen"
6 | },
7 | "search": {
8 | "en": "Search",
9 | "de": "Suche"
10 | },
11 | "nothing-found": {
12 | "en": "No matches were found.",
13 | "de": "Keine Übereinstimmungen gefunden."
14 | },
15 |
16 |
17 |
18 | "information": {
19 | "en": "Information",
20 | "de": "Information"
21 | },
22 | "login": {
23 | "en": "Login",
24 | "de": "Anmeldung"
25 | },
26 | "username": {
27 | "en": "Username:",
28 | "de": "Benutzername:"
29 | },
30 | "password": {
31 | "en": "Password:",
32 | "de": "Passwort:"
33 | },
34 | "x-characters": {
35 | "en": "6 - 12 characters",
36 | "de": "6 - 12 Zeichen"
37 | },
38 | "x-characters-pw": {
39 | "en": "10 - 18 characters",
40 | "de": "10 - 18 Zeichen"
41 | },
42 | "description": {
43 | "en": "This is a description. You can write any text inside of this.
\
44 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut\
45 | labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores\
46 | et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem\
47 | ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et\
48 | dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.\
49 | Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.",
50 |
51 | "de": "Das ist eine Beschreibung. Du kannst hier beliebigen Text einfügen.
\
52 | Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut\
53 | labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores\
54 | et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem\
55 | ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et\
56 | dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.\
57 | Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
58 | },
59 | "logout": {
60 | "en": "Logout",
61 | "de": "Abmeldung"
62 | },
63 | "enable": {
64 | "en": "Enable",
65 | "de": "Aktivieren"
66 | },
67 | "disconnect": {
68 | "en": "Disconnect:",
69 | "de": "Trennen:"
70 | }
71 | };
72 |
--------------------------------------------------------------------------------
/scripts/pages/newznab-autoadd.js:
--------------------------------------------------------------------------------
1 | (function () { // Encapsulate
2 |
3 | /*
4 | New indexes are frequently requested in sabconnectplusplus
5 | of which the majority are supported as they are Newznab.
6 | Lets add support to automatically notify the user their
7 | popular indexer is supported, as well as add it to the
8 | config automatically if requested.
9 | */
10 |
11 | var newznabDetection = function(){
12 | // Lets try and wrap all detection functions to a simplistic block
13 | var runFound = false;
14 |
15 | // most sites detect here
16 | if ( ($('[name=RSSTOKEN]').filter(':first').length) &&
17 | ($('input.nzb_multi_operations_cart').filter(':first').length) )
18 | { return true; }
19 |
20 | // some additionals
21 | $('#browsetable tr:gt(0)').filter(':first').each(function() {
22 | if ( $(this).find('td.item label').filter(':first').length )
23 | runFound = true;
24 | });
25 | if ( runFound )
26 | return true;
27 |
28 | // If all else fails, we are not sab ;)
29 | return false;
30 | }
31 |
32 | // Lets restrict our movements to pages that are newznab, logged in and displaying triggerable data
33 | if ( newznabDetection() ) {
34 | var thishost = (window.location.hostname.match(/([^.]+)\.\w{2,3}(?:\.\w{2})?$/) || [])[0];
35 | $('body').prepend(
36 | $('').addClass('notification autonabSticky hide').prepend(
37 | $('
SABconnect++ says: Would you like to enable one-click "Send to SAB" buttons on this site?
').append(
38 | '
Enable |
Ignore'
39 | ),
40 | $('
×')
41 | )
42 | );
43 | $('.notification.autonabSticky').notify();
44 | $('#autonabIgnore').click(function(){
45 | var request = {
46 | action: 'set_setting',
47 | setting: 'nabignore.' + thishost,
48 | value: true
49 | };
50 | chrome.runtime.sendMessage( request );
51 | $('a.close').click();
52 | });
53 | $('#autonabEnable').click(function(){
54 | var request = {
55 | action: 'get_setting',
56 | setting: 'provider_newznab'
57 | };
58 | chrome.runtime.sendMessage( request, function( response ) {
59 | var request = {
60 | action: 'set_setting',
61 | setting: 'provider_newznab',
62 | value: response.value + ', ' + thishost
63 | };
64 | chrome.runtime.sendMessage( request, function() {
65 | location.reload();
66 | });
67 | });
68 | });
69 | }
70 | })();
71 |
--------------------------------------------------------------------------------
/scripts/content/binsearch.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | var useNiceName;
3 |
4 | function addToSABnzbdFromBinsearch() {
5 | //grab all checked boxes on page
6 | $(".xMenuT input:checked[type=checkbox]").each(function() {
7 | addOne($(this).siblings("a.addSABnzbd"));
8 | });
9 |
10 | return false;
11 | }
12 |
13 | function oneClick() {
14 | addOne(this);
15 | return false;
16 | }
17 |
18 | function addOne(addLink) {
19 | var $addLink = $(addLink);
20 | // Set the image to an in-progress image
21 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
22 | if ($addLink.find('img').length > 0) {
23 | $addLink.find('img').attr("src", img);
24 | } else {
25 | $addLink.css('background-image', 'url('+img+')');
26 | }
27 |
28 | var tr = $addLink.parents("tr").first();
29 | $tds = $(tr).children("td");
30 | var categories = $tds[4].innerText;
31 | if (categories.indexOf('\n') != -1) {
32 | var category = categories.substr(0, categories.indexOf('\n'));
33 | } else {
34 | var category = categories;
35 | }
36 | if( useNiceName ) {
37 | var nice_name = document.getElementsByName("q")[0].value;
38 | if (!nice_name.length) {
39 | nice_name = $tds[2].getElementsByTagName('span')[0].innerText;
40 | }
41 | }
42 | addToSABnzbd($addLink, '/?action=nzb&' + $("input", $tds[1]).attr("name") + '=1', "addurl", nice_name, category);
43 | }
44 |
45 | function handleAllDownloadLinks() {
46 | var img = chrome.runtime.getURL('/images/content_icon.png');
47 |
48 | $(".xMenuT input[type=checkbox]").each(function() {
49 | var href = '/?action=nzb&' + $(this).attr("name") + '=1';
50 | var link = $('

');
51 | $(this).before(link);
52 | link.click(oneClick);
53 | });
54 |
55 | $('input[name$="watchlist"]').each(function() {
56 | // add button to h3 to move checked in to SABConnect
57 | var link = $('
');
58 | link.css({
59 | "background-image": "url("+img+")",
60 | "background-repeat": "no-repeat",
61 | "background-position": "3px 1px",
62 | "padding-left": 25,
63 | "cursor": "pointer"
64 | });
65 | $(this).after(link);
66 | $(this).siblings('input[class="b addSABnzbd"]').click(addToSABnzbdFromBinsearch);
67 | });
68 | }
69 |
70 | function RefreshSettings()
71 | {
72 | GetSetting( 'use_name_binsearch', function( state ) {
73 | useNiceName = state;
74 | });
75 | }
76 |
77 | Initialize( 'binsearch', RefreshSettings, function() {
78 | console.log('SABconnect++ Binsearch: Script initialized');
79 | handleAllDownloadLinks();
80 |
81 | // Fallback if no icons were added
82 | if ($('a.addSABnzbd').length === 0) {
83 | console.log('SABconnect++ Binsearch: No icons added, trying fallback...');
84 | addIconsWithFallback({
85 | linkSelector: 'a[href*="action=nzb"], input[type=checkbox][name^="m_"]',
86 | iconClass: 'addSABnzbd',
87 | clickHandler: oneClick
88 | });
89 | }
90 | });
91 | })(jQuery);
--------------------------------------------------------------------------------
/third_party/webtoolkit/webtoolkit.base64.js:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * Base64 encode / decode
4 | * http://www.webtoolkit.info/
5 | *
6 | **/
7 |
8 | var Base64 = {
9 |
10 | // private property
11 | _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
12 |
13 | // public method for encoding
14 | encode : function (input) {
15 | var output = "";
16 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
17 | var i = 0;
18 |
19 | input = Base64._utf8_encode(input);
20 |
21 | while (i < input.length) {
22 |
23 | chr1 = input.charCodeAt(i++);
24 | chr2 = input.charCodeAt(i++);
25 | chr3 = input.charCodeAt(i++);
26 |
27 | enc1 = chr1 >> 2;
28 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
29 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
30 | enc4 = chr3 & 63;
31 |
32 | if (isNaN(chr2)) {
33 | enc3 = enc4 = 64;
34 | } else if (isNaN(chr3)) {
35 | enc4 = 64;
36 | }
37 |
38 | output = output +
39 | this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
40 | this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
41 |
42 | }
43 |
44 | return output;
45 | },
46 |
47 | // public method for decoding
48 | decode : function (input) {
49 | var output = "";
50 | var chr1, chr2, chr3;
51 | var enc1, enc2, enc3, enc4;
52 | var i = 0;
53 |
54 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
55 |
56 | while (i < input.length) {
57 |
58 | enc1 = this._keyStr.indexOf(input.charAt(i++));
59 | enc2 = this._keyStr.indexOf(input.charAt(i++));
60 | enc3 = this._keyStr.indexOf(input.charAt(i++));
61 | enc4 = this._keyStr.indexOf(input.charAt(i++));
62 |
63 | chr1 = (enc1 << 2) | (enc2 >> 4);
64 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
65 | chr3 = ((enc3 & 3) << 6) | enc4;
66 |
67 | output = output + String.fromCharCode(chr1);
68 |
69 | if (enc3 != 64) {
70 | output = output + String.fromCharCode(chr2);
71 | }
72 | if (enc4 != 64) {
73 | output = output + String.fromCharCode(chr3);
74 | }
75 |
76 | }
77 |
78 | output = Base64._utf8_decode(output);
79 |
80 | return output;
81 |
82 | },
83 |
84 | // private method for UTF-8 encoding
85 | _utf8_encode : function (string) {
86 | string = string.replace(/\r\n/g,"\n");
87 | var utftext = "";
88 |
89 | for (var n = 0; n < string.length; n++) {
90 |
91 | var c = string.charCodeAt(n);
92 |
93 | if (c < 128) {
94 | utftext += String.fromCharCode(c);
95 | }
96 | else if((c > 127) && (c < 2048)) {
97 | utftext += String.fromCharCode((c >> 6) | 192);
98 | utftext += String.fromCharCode((c & 63) | 128);
99 | }
100 | else {
101 | utftext += String.fromCharCode((c >> 12) | 224);
102 | utftext += String.fromCharCode(((c >> 6) & 63) | 128);
103 | utftext += String.fromCharCode((c & 63) | 128);
104 | }
105 |
106 | }
107 |
108 | return utftext;
109 | },
110 |
111 | // private method for UTF-8 decoding
112 | _utf8_decode : function (utftext) {
113 | var string = "";
114 | var i = 0;
115 | var c = c1 = c2 = 0;
116 |
117 | while ( i < utftext.length ) {
118 |
119 | c = utftext.charCodeAt(i);
120 |
121 | if (c < 128) {
122 | string += String.fromCharCode(c);
123 | i++;
124 | }
125 | else if((c > 191) && (c < 224)) {
126 | c2 = utftext.charCodeAt(i+1);
127 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
128 | i += 2;
129 | }
130 | else {
131 | c2 = utftext.charCodeAt(i+1);
132 | c3 = utftext.charCodeAt(i+2);
133 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
134 | i += 3;
135 | }
136 |
137 | }
138 |
139 | return string;
140 | }
141 |
142 | }
--------------------------------------------------------------------------------
/scripts/profile.js:
--------------------------------------------------------------------------------
1 | function ProfileManager()
2 | {
3 | }
4 |
5 | ProfileManager.prototype.count = function()
6 | {
7 | return store.get( 'profiles' ).length;
8 | }
9 |
10 | ProfileManager.prototype.add = function( profileName, values )
11 | {
12 | var profiles = store.get( 'profiles' );
13 | if(typeof profiles == "object" && profiles.hasOwnProperty( profileName ) ) {
14 | throw 'already_exists';
15 | }
16 |
17 | profiles[profileName] = values;
18 | this.saveProfiles(profiles);
19 | }
20 |
21 | ProfileManager.prototype.edit = function( profileName, values, newProfileName )
22 | {
23 | var profiles = store.get( 'profiles' );
24 |
25 | if( !profiles[profileName] ) {
26 | throw 'profile_missing';
27 | }
28 |
29 | if( profileName != newProfileName ) {
30 | if( profiles.hasOwnProperty( newProfileName ) ) {
31 | throw 'renamed_exists';
32 | }
33 |
34 | delete profiles[profileName];
35 | profileName = newProfileName;
36 | }
37 |
38 | profiles[profileName] = values;
39 | this.saveProfiles(profiles);
40 | }
41 |
42 | ProfileManager.prototype.remove = function( profileName )
43 | {
44 | var profiles = store.get( 'profiles' );
45 | if( !profiles.hasOwnProperty( profileName ) ) {
46 | throw 'profile_missing';
47 | }
48 |
49 | delete profiles[profileName];
50 | this.saveProfiles(profiles);
51 |
52 | var newActive = first( profiles );
53 | this.setActiveProfile( newActive );
54 | return newActive;
55 | }
56 |
57 | ProfileManager.prototype.setProfile = function( profileData )
58 | {
59 | var profiles = store.get( 'profiles' );
60 | profiles[profileData.name] = profileData.values;
61 | this.saveProfiles(profiles);
62 | }
63 |
64 | ProfileManager.prototype.getProfile = function( profileName )
65 | {
66 | if( !profileName ) {
67 | return null;
68 | }
69 |
70 | var profiles = store.get( 'profiles' );
71 | var profile = profiles[profileName];
72 |
73 | if( !profile ) {
74 | return null;
75 | }
76 |
77 | var password = getPref("profile_pass" + profileName);
78 | if(password === null || password === "null" || typeof password == "undefined") {
79 | password = "";
80 | }
81 | profiles[profileName]["password"] = password;
82 |
83 | return {
84 | 'name': profileName,
85 | 'values': profiles[profileName]
86 | };
87 | }
88 |
89 | ProfileManager.prototype.getActiveProfile = function()
90 | {
91 | var profileName = getPref( 'active_profile' );
92 | return this.getProfile( profileName );
93 | }
94 |
95 | ProfileManager.prototype.getFirstProfile = function()
96 | {
97 | var profileName = first( store.get( 'profiles' ) );
98 | return this.getProfile( profileName );
99 | }
100 |
101 | ProfileManager.prototype.setActiveProfile = function( profileName )
102 | {
103 | setPref( 'active_profile', profileName );
104 | }
105 |
106 | ProfileManager.prototype.contains = function( profileName )
107 | {
108 | var profiles = store.get( 'profiles' );
109 | return profiles.hasOwnProperty( profileName );
110 | }
111 |
112 | ProfileManager.prototype.saveProfiles = function(profiles) {
113 |
114 | // Create a deep copy to avoid modifying the original object
115 | var profilesToSave = {};
116 | for(var profileName in profiles) {
117 | var profile = profiles[profileName];
118 | profilesToSave[profileName] = {
119 | url: profile.url || '',
120 | api_key: profile.api_key || '',
121 | username: profile.username || ''
122 | };
123 |
124 | // Handle password separately - don't include it in the profiles object
125 | if(profile.hasOwnProperty("password")) {
126 | setPref("profile_pass" + profileName, profile.password);
127 | }
128 | }
129 |
130 | store.set( 'profiles', profilesToSave );
131 | }
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/README.md:
--------------------------------------------------------------------------------
1 | # [Fancy Settings 1.2](https://github.com/frankkohlhepp/fancy-settings)
2 | *Create fancy, chrome-look-alike settings for your Chrome or Safari extension in minutes!*
3 |
4 | ### Howto
5 | Welcome to Fancy Settings! Are you ready for tabs, groups, search, good style?
6 | Let's get started, it only takes a few minutes...
7 |
8 | Settings can be of different types: text input, checkbox, slider, etc. Some "settings" are not actual settings but provide functionality that is relevant to the options page: description (which is simply a block of text), button.
9 |
10 | Settings are defined in the manifest.js file as JavaScript objects. Each setting is defined by specifying a number of parameters. All types of settings are configured with the string parameters tab, group, name and type.
11 |
12 | ###Basic example:
13 | ```javascript
14 | {
15 | "tab": "Tab 1",
16 | "group": "Group 1",
17 | "name": "checkbox1",
18 | "type": "checkbox"
19 | }
20 | ```
21 |
22 | "name" is used as a part of the key when storing the setting's value in localStorage.
23 | If it's missing, nothing will be saved.
24 |
25 | ###Additionally, all types of settings are configured with their own custom parameters:
26 |
27 | ###Description ("type": "description")
28 |
29 | text (string) the block of text, which can include HTML tags. You can continue multiple lines of text by putting a \ at the end of a line, just as with any JavaScript file.
30 |
31 | ####
32 | Button ("type": "button")
33 | ```
34 | Label (string) text shown in front of the button
35 |
36 | Text (string) text shown on the button
37 | ```
38 |
39 | ####Text ("type": "text")
40 | ```
41 | label (string) text shown in front of the text field
42 |
43 | text (string) text shown in the text field when empty
44 |
45 | masked (boolean) indicates a password field
46 | ```
47 |
48 | ####Checkbox ("type": "checkbox")
49 | ```
50 | label (string) text shown behind the checkbox
51 | ```
52 |
53 | ####Slider ("type": "slider")
54 | ```
55 | label (string) text shown in front of the slider
56 |
57 | max (number) maximal value of the slider
58 |
59 | min (number) minimal value of the slider
60 |
61 | step (number) steps between two values
62 |
63 | display (boolean) indicates whether to show the slider display
64 |
65 | displayModifier (function) a function to modify the value shown in the display
66 | ```
67 |
68 | ####PopupButton ("type": "popupButton"), ListBox ("type": "listBox") & RadioButtons ("type": "radioButtons")
69 | ```
70 | label (string) text shown in front of the options
71 |
72 | options (array of options)
73 |
74 | where an option can be one of the following formats:
75 | ```
76 |
77 | ####"value"
78 | ```
79 | ["value", "displayed text"]
80 |
81 | {value: "value", text: "displayed text"}
82 | ```
83 | The "displayed text" field is optional and is displayed to the user when you don't want to display the internal value directly to the user.
84 |
85 | #### You can also group options so that the user can easily choose among them (groups may only be applied to popupButtons):
86 |
87 | ```javascript
88 | "options": {
89 | "groups": [
90 | "Hot", "Cold",
91 | ],
92 | "values": [
93 | {
94 | "value": "hot",
95 | "text": "Very hot",
96 | "group": "Hot",
97 | },
98 | {
99 | "value": "Medium",
100 | "group": 1,
101 | },
102 | {
103 | "value": "Cold",
104 | "group": 2,
105 | },
106 | ["Non-existing"]
107 | ],
108 | },
109 |
110 | ```
111 |
112 | ### License
113 | Fancy Settings is licensed under the **LGPL 2.1**.
114 | For details see *LICENSE.txt*
115 |
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "manifest_version": 3,
3 | "name": "SABconnect++",
4 | "version": "2.0.0",
5 | "description": "SABnzbd extension for Google Chrome.",
6 | "minimum_chrome_version": "88.0",
7 | "background": {
8 | "service_worker": "scripts/background-sw.js"
9 | },
10 | "options_page": "settings.html",
11 | "action": {
12 | "default_icon": "images/content_icon.png",
13 | "default_title": "SABconnect++",
14 | "default_popup": "popup.html"
15 | },
16 | "icons": {
17 | "16": "images/content_icon.png",
18 | "48": "images/sab_48.png",
19 | "128": "images/sab_128.png"
20 | },
21 | "web_accessible_resources": [{
22 | "resources": [
23 | "images/content_icon.png",
24 | "images/content_icon_error.png",
25 | "images/content_icon_fetching.png",
26 | "images/content_icon_success.png",
27 | "css/common.css"
28 | ],
29 | "matches": ["
"]
30 | }],
31 | "permissions": [
32 | "tabs",
33 | "notifications",
34 | "contextMenus",
35 | "storage",
36 | "alarms",
37 | "scripting"
38 | ],
39 | "host_permissions": [
40 | "*://*/*"
41 | ],
42 | "content_scripts": [
43 | {
44 | "matches": [
45 | "*://*.nzbclub.com/*",
46 | "*://*.bintube.com/*",
47 | "*://*.binsearch.info/*",
48 | "*://*.binsearch.net/*",
49 | "*://*.binsearch.co.uk/*",
50 | "*://*.binsear.ch/*",
51 | "*://*.nzbindex.com/*",
52 | "*://*.nzbindex.nl/*",
53 | "*://*.fanzub.com/*",
54 | "*://*.animezb.com/*",
55 | "*://animenzb.com/*",
56 | "*://*.animenzb.com/*",
57 | "*://*.dognzb.cr/*",
58 | "*://*.yubse.com/*",
59 | "*://*.omgwtfnzbs.org/*",
60 | "*://*.nzb-rss.com/*",
61 | "*://*.usenet4ever.info/*"
62 | ],
63 | "js": [
64 | "third_party/jquery/jquery-1.12.4.min.js",
65 | "scripts/content/common.js",
66 | "third_party/webtoolkit/webtoolkit.base64.js"
67 | ],
68 | "all_frames": true
69 | },
70 |
71 | {
72 | "matches": [ "*://*.nzbclub.com/*" ],
73 | "js": [ "scripts/content/nzbclub.js" ],
74 | "all_frames": true
75 | },
76 | {
77 | "matches": [ "*://*.bintube.com/*" ],
78 | "js": [ "scripts/content/bintube.js" ],
79 | "all_frames": true
80 | },
81 | {
82 | "matches": [
83 | "*://*.binsearch.info/*",
84 | "*://*.binsearch.net/*",
85 | "*://*.binsearch.co.uk/*",
86 | "*://*.binsear.ch/*"
87 | ],
88 | "js": [ "scripts/content/binsearch.js" ],
89 | "all_frames": true
90 | },
91 | {
92 | "matches" : [
93 | "*://*.nzbindex.com/*",
94 | "*://*.nzbindex.nl/*"
95 | ],
96 | "js": [ "scripts/content/nzbindex.js" ],
97 | "all_frames": true
98 | },
99 | {
100 | "matches": [ "*://*.fanzub.com/*" ],
101 | "js": [ "scripts/content/fanzub.js" ],
102 | "all_frames": true
103 | },
104 | {
105 | "matches": [ "*://*.animezb.com/*" ],
106 | "js": [ "scripts/content/animezb.js" ],
107 | "all_frames": true
108 | },
109 | {
110 | "matches": [
111 | "*://animenzb.com/*",
112 | "*://*.animenzb.com/*"
113 | ],
114 | "js": [ "scripts/content/animenzb.js" ],
115 | "all_frames": true
116 | },
117 | {
118 | "matches": [ "*://*.dognzb.cr/*" ],
119 | "js": [ "scripts/content/dognzb.js" ],
120 | "all_frames": true
121 | },
122 | {
123 | "matches": [ "*://*.yubse.com/*" ],
124 | "js": [ "scripts/content/yubse.js" ],
125 | "all_frames": true
126 | },
127 | {
128 | "matches": [ "*://omgwtfnzbs.org/*" ],
129 | "js": [ "scripts/content/omgwtfnzbs.js" ],
130 | "all_frames": true
131 | },
132 | {
133 | "matches": [ "*://*.nzb-rss.com/*" ],
134 | "js": [ "scripts/content/nzbrss.js" ],
135 | "all_frames": true
136 | },
137 | {
138 | "all_frames": true,
139 | "js": [ "scripts/content/usenet4ever.js" ],
140 | "matches": [ "*://*.usenet4ever.info/*" ]
141 | }
142 |
143 | ]
144 | }
145 |
--------------------------------------------------------------------------------
/scripts/content/dognzb.js:
--------------------------------------------------------------------------------
1 | var nzburl;
2 | var addLink;
3 | var category = null;
4 |
5 | function findNZBId(elem) {
6 | nzbid = $(elem).attr('id');
7 | url = '/fetch/' + nzbid;
8 | return url;
9 | }
10 |
11 | function addToSABnzbdFromDognzb() {
12 | var rss_hash = $('input[name="rsstoken"]').val();
13 | if (this.nodeName.toUpperCase() == 'INPUT') {
14 | this.value = "Sending...";
15 | $(this).css('color', 'green');
16 |
17 | $('table.data input:checked').each(function() {
18 | var tr = $(this).parent().parent();
19 | var a = tr.find('a[title="Send to SABnzbd"]');
20 |
21 | // Find the nzb id from the href
22 | nzburl = findNZBId(a);
23 | if (nzburl) {
24 | category = tr.find('span[class~="labelstyle-444444"]').text();
25 |
26 | addLink = a;
27 |
28 | // Add the authentication to the link about to be fetched
29 | nzburl += '/' + rss_hash;
30 |
31 | addToSABnzbd(addLink, nzburl, "addurl", null, category);
32 | }
33 | });
34 | this.value = 'Sent to SAB!';
35 | $(this).css('color', 'red');
36 | sendToSabButton = this;
37 |
38 | setTimeout(function(){ sendToSabButton.value = 'Send to SAB'; $(sendToSabButton).css('color', '#888'); }, 4000);
39 |
40 | return false;
41 | } else {
42 | // Find the newzbin id from the href
43 | nzburl = findNZBId(this);
44 | if (nzburl) {
45 | // Set the image to an in-progress image
46 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
47 | $(this).css('background-image', 'url('+img+')');
48 |
49 | var tr = $(this).parent().parent();
50 | category = tr.find('span[class~="labelstyle-444444"]').text();
51 |
52 | addLink = this;
53 |
54 |
55 | // Add the authentication to the link about to be fetched
56 | nzburl += '/' + rss_hash;
57 |
58 | addToSABnzbd(addLink, nzburl, "addurl", null, category);
59 |
60 | return false;
61 | }
62 | }
63 | }
64 |
65 | function handleAllDownloadLinks() {
66 | //XXX: Needs to be fixed with new site layout
67 | // List view: add a button above the list to send selected NZBs to SAB
68 | //$('input[class="nzb_multi_operations_sab"]').each(function() {
69 | // $(this).css('display', 'inline-block');
70 | // $(this).click(addToSABnzbdFromDognzb);
71 | //});
72 |
73 | $('div[class="dog-icon-download"]').not('.sabcpp-fake-godnzb-marker').each(function() {
74 | // Change the title to "Send to SABnzbd"
75 | newlink = $('').attr("title", "Send to SABnzbd");
76 | newlink.addClass('dog-icon-download').addClass('sabcpp-fake-dognzb-marker');
77 |
78 | // Change the nzb download image
79 | var img = chrome.runtime.getURL('images/content_icon.png');
80 | newlink.css('background-image', 'url('+img+')');
81 | newlink.css('background-position', '0 0');
82 | newlink.css('background-size', 'inherit');
83 |
84 | // Extract NZB id from onClick and set to ID attribute
85 |
86 | var nzbid = $(this).attr('onClick');
87 | var nzbid = nzbid.split('\'')[1];
88 | newlink.attr("id", nzbid);
89 |
90 | // Change the on click handler to send to sabnzbd
91 | // this is the
92 | //$(this).removeAttr("onClick");
93 | newlink.click(addToSABnzbdFromDognzb);
94 | $(this).replaceWith(newlink);
95 | });
96 | }
97 |
98 | Initialize( 'dognzb', null, function() {
99 | console.log('SABconnect++ DogNZB: Script initialized');
100 | handleAllDownloadLinks();
101 | sabcppDogCheck = function(){
102 | if($('div[class="dog-icon-download"]').not('.sabcpp-fake-godnzb-marker').length >= 1) {
103 | handleAllDownloadLinks();
104 | }
105 |
106 | // Fallback if no icons were added
107 | if ($('a.addSABnzbd').length === 0) {
108 | console.log('SABconnect++ DogNZB: No icons added, trying fallback...');
109 | addIconsWithFallback({
110 | linkSelector: 'a[href*="/getnzb/"], a[href*="/details/"]',
111 | iconClass: 'addSABnzbd',
112 | clickHandler: oneClick
113 | });
114 | }
115 |
116 | setTimeout(sabcppDogCheck, 1000);
117 | };
118 | setTimeout(sabcppDogCheck, 1000);
119 | });
120 |
--------------------------------------------------------------------------------
/scripts/content/usenet4ever.js:
--------------------------------------------------------------------------------
1 | function getHeaderFromURL(url)
2 | {
3 | var regex = new RegExp("[?&]q=([^]*)"),
4 | results = regex.exec(url);
5 | return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
6 | }
7 |
8 | function handleAllDownloadLinks()
9 | {
10 | $('body').on('click', 'img[src="http://usenet4ever.info/vb4/posten/images/nzb_link1.png"]', function(e) {
11 | e.preventDefault();
12 | $("#sabnzbdoverlay").remove();
13 |
14 | var url = $(this).parent('a').attr('href');
15 | if(url.substr(url.length - 4) == ".nzb")
16 | {
17 | addToSABnzbd($(this), url, "addurl", "", "usenet4ever");
18 | }
19 | else
20 | {
21 | var div = $(this).parent().parent();
22 | div.css('position','relative');
23 | div.append('');
24 |
25 | var title = $(this).parents('div.content').find('h1').text();
26 | //console.log('title: '+title);
27 |
28 | //if(div.text().match(/.*(passwor).*/)) alert("pass!!");
29 | if(div.text().match(/.*(asswor).*/g))
30 | {
31 | var pass = "";
32 | if (window.getSelection)
33 | {
34 | pass = window.getSelection().toString();
35 | } else if (document.selection && document.selection.type != "Control")
36 | {
37 | pass = document.selection.createRange().text;
38 | }
39 | title = title+"{{"+pass+"}}";
40 | //console.log("add passwort to the title: " + title);
41 | }
42 |
43 |
44 | var header = url.match("[\\?&]q=([^]*)");
45 | header = header[1];
46 |
47 | var binsearch = "http://binsearch.info/?q="+header+"&max=10&adv_age=&server #r2";
48 | var nzbindex = "http://nzbindex.nl/search/?q="+header+"&sort=sizedesc";
49 |
50 | $.get(nzbindex, function(data)
51 | {
52 | var results = $(data).find('#results').find('input[type="checkbox"]');
53 |
54 | if(results.length > 0)
55 | {
56 | results.each(function() {
57 | var desc = $(this).parent().next().find('label').text()+$(this).parent().next().next().text();
58 | $('#sabnzbdoverlay').append('
');
59 | });
60 | } else {
61 | $('#sabnzbdoverlay').append('');
62 | }
63 |
64 | /* binsearch
65 | $(data).find('input[type="checkbox"]').each(function() {
66 | var desc = $(this).parent().parent().find('span.d').html();
67 | $('#sabnzbdoverlay').append('');
68 | });
69 | */
70 | });
71 | }
72 |
73 | });
74 | /*
75 | $('img[src="http://usenet4ever.info/vb4/posten/images/nzb_link1.png"]').each(function() {
76 | // add button to send item to SABConnect
77 | var img = chrome.runtime.getURL('/images/content_icon.png');
78 | var link = '
';
79 | $(this).parent('a').before(link);
80 | //$(this).parent('a').next('a.addSABnzbd').click(addToSABnzbdFromUsenet4ever);
81 | });
82 | */
83 |
84 | // Change the onclick handler to send to sabnzbd
85 | $('body').on('click','.addToSABnzbd', function() {
86 | var title = $(this).attr("title");
87 | var url = $(this).attr("rel");
88 | addToSABnzbd($(this), url, "addurl", title, "usenet4ever");
89 | });
90 | }
91 |
92 | Initialize( 'usenet4ever', null, function() {
93 | handleAllDownloadLinks();
94 | });
--------------------------------------------------------------------------------
/scripts/content/yubse.js:
--------------------------------------------------------------------------------
1 | var useNiceName;
2 |
3 | function addAllToSABnzbdFromYubse() {
4 | // Set the image to an in-progress image
5 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
6 | if ($(this).find('img').length > 0) {
7 | $(this).find('img').attr("src", img);
8 | } else {
9 | $(this).css('background-image', 'url('+img+')');
10 | }
11 | var addLink = this;
12 |
13 | //grab all checked boxes on page
14 | $("#tableResult input:checked").each(function(){
15 | var nice_name = "";
16 | if( useNiceName ) {
17 | nice_name_element_parent = $(this).parents()[5];
18 | nice_name = $(nice_name_element_parent).find('#file_detail').text();
19 | var regnN = new RegExp('"[^""\r\n]*"');
20 | var nN = regnN.exec(nice_name);
21 | if (nN !=null)
22 | {
23 | nice_name = "Yubse_" + nN[0].replace("\"","").replace("\"","");
24 | }
25 | }
26 | addToSABnzbd(addLink, 'http://www.yubse.com/serv/YubseSearch.svc/Download/' + this.name, "addurl",nice_name, null);
27 | });
28 | return false;
29 | }
30 |
31 | function addToSABnzbdFromYubse(node) {
32 | // Set the image to an in-progress image
33 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
34 | if ($(node).find('a[class="addSABnzbd"]').find('img').length > 0) {
35 | $(node).find('a[class="addSABnzbd"]').find('img').attr("src", img);
36 | } else {
37 | $(node).find('a[class="addSABnzbd"]').css('background-image', 'url('+img+')');
38 | }
39 | var addLink = this;
40 |
41 | var nice_name = "";
42 | if( useNiceName ) {
43 | nice_name = $(node).find('#file_detail').text();
44 | var regnN = new RegExp('"[^""\r\n]*"');
45 | var nN = regnN.exec(nice_name);
46 | if (nN !=null)
47 | {
48 | nice_name = "Yubse_" + nN[0].replace("\"","").replace("\"","");
49 | }
50 | }
51 | addToSABnzbd(addLink, 'http://www.yubse.com/serv/YubseSearch.svc/Download/' +$(node).find('input').first().attr('name'), "addurl",nice_name, null);
52 | return false;
53 | }
54 |
55 | function handleAllDownloadLinks() {
56 | $('.header_btn a:last-child').after(function() {
57 | var img = chrome.runtime.getURL('/images/content_icon.png');
58 | var href = $(this).attr('href');
59 | var link = '
';
60 | $(this).after(link);
61 | $(this).parent().find('a[class="addSABnzbd"]').first().click(addAllToSABnzbdFromYubse);
62 | });
63 |
64 | }
65 |
66 | function handleSingleDownloadLink(node) {
67 | if ($(node).find('a[class="addSABnzbd"]').length==0){
68 | $(node).find('input').each(function() {
69 | var img = chrome.runtime.getURL('/images/content_icon.png');
70 | var href = $(this).attr('href');
71 | var link = ' | ';
72 | $(this).parent().parent().find('td:last').after(link);
73 | $(this).parent().parent().find('a[class="addSABnzbd"]').first().click(function(){ addToSABnzbdFromYubse($(node))});
74 | });
75 | }
76 | }
77 |
78 | function RefreshSettings()
79 | {
80 | GetSetting( 'use_name_yubse', function( state ) {
81 | useNiceName = state;
82 | });
83 | }
84 |
85 | $(document).ready(function() {
86 | Initialize( 'yubse', RefreshSettings(), function() {
87 | handleAllDownloadLinks();
88 | $("#tableResult tr").each(function(){
89 | handleSingleDownloadLink(this);
90 | });
91 | });
92 |
93 | // Replace deprecated DOMNodeInserted with MutationObserver
94 | var tableElement = document.getElementById('tableResult');
95 | if (tableElement) {
96 | var observer = new MutationObserver(function(mutations) {
97 | mutations.forEach(function(mutation) {
98 | if (mutation.type === 'childList') {
99 | mutation.addedNodes.forEach(function(node) {
100 | if (node.nodeType === Node.ELEMENT_NODE) {
101 | if (node.nodeName === 'THEAD') {
102 | // Detect the THEAD construction in the table for create button one time
103 | handleAllDownloadLinks();
104 | } else if (node.nodeName === 'TR') {
105 | // Detect all the tr in the table and create button for each
106 | handleSingleDownloadLink(node);
107 | }
108 | }
109 | });
110 | }
111 | });
112 | });
113 |
114 | // Start observing
115 | observer.observe(tableElement, {
116 | childList: true,
117 | subtree: true
118 | });
119 | }
120 | });
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/default.manifest.js:
--------------------------------------------------------------------------------
1 | // SAMPLE
2 | this.manifest = {
3 | "name": "My Extension",
4 | "icon": "icon.png",
5 | "settings": [
6 | {
7 | "tab": i18n.get("information"),
8 | "group": i18n.get("login"),
9 | "name": "username",
10 | "type": "text",
11 | "label": i18n.get("username"),
12 | "text": i18n.get("x-characters")
13 | },
14 | {
15 | "tab": i18n.get("information"),
16 | "group": i18n.get("login"),
17 | "name": "password",
18 | "type": "text",
19 | "label": i18n.get("password"),
20 | "text": i18n.get("x-characters-pw"),
21 | "masked": true
22 | },
23 | {
24 | "tab": i18n.get("information"),
25 | "group": i18n.get("login"),
26 | "name": "myDescription",
27 | "type": "description",
28 | "text": i18n.get("description")
29 | },
30 | {
31 | "tab": i18n.get("information"),
32 | "group": i18n.get("logout"),
33 | "name": "myCheckbox",
34 | "type": "checkbox",
35 | "label": i18n.get("enable")
36 | },
37 | {
38 | "tab": i18n.get("information"),
39 | "group": i18n.get("logout"),
40 | "name": "myButton",
41 | "type": "button",
42 | "label": i18n.get("disconnect"),
43 | "text": i18n.get("logout")
44 | },
45 | {
46 | "tab": "Details",
47 | "group": "Sound",
48 | "name": "noti_volume",
49 | "type": "slider",
50 | "label": "Notification volume:",
51 | "max": 1,
52 | "min": 0,
53 | "step": 0.01,
54 | "display": true,
55 | "displayModifier": function (value) {
56 | return (value * 100).floor() + "%";
57 | }
58 | },
59 | {
60 | "tab": "Details",
61 | "group": "Sound",
62 | "name": "sound_volume",
63 | "type": "slider",
64 | "label": "Sound volume:",
65 | "max": 100,
66 | "min": 0,
67 | "step": 1,
68 | "display": true,
69 | "displayModifier": function (value) {
70 | return value + "%";
71 | }
72 | },
73 | {
74 | "tab": "Details",
75 | "group": "Food",
76 | "name": "myPopupButton",
77 | "type": "popupButton",
78 | "label": "Soup 1 should be:",
79 | "options": {
80 | "groups": [
81 | "Hot", "Cold",
82 | ],
83 | "values": [
84 | {
85 | "value": "hot",
86 | "text": "Very hot",
87 | "group": "Hot",
88 | },
89 | {
90 | "value": "Medium",
91 | "group": 1,
92 | },
93 | {
94 | "value": "Cold",
95 | "group": 2,
96 | },
97 | ["Non-existing"]
98 | ],
99 | },
100 | },
101 | {
102 | "tab": "Details",
103 | "group": "Food",
104 | "name": "myListBox",
105 | "type": "listBox",
106 | "label": "Soup 2 should be:",
107 | "options": [
108 | ["hot", "Hot and yummy"],
109 | ["cold"]
110 | ]
111 | },
112 | {
113 | "tab": "Details",
114 | "group": "Food",
115 | "name": "myRadioButtons",
116 | "type": "radioButtons",
117 | "label": "Soup 3 should be:",
118 | "options": [
119 | ["hot", "Hot and yummy"],
120 | ["cold"]
121 | ]
122 | },
123 | {
124 | "tab": "Details",
125 | "group": "Food",
126 | "name": "deserts",
127 | "type": "number",
128 | "min": 0,
129 | "max": 5,
130 | "label": "Number of deserts"
131 | },
132 | ],
133 | "alignment": [
134 | [
135 | "username",
136 | "password"
137 | ],
138 | [
139 | "noti_volume",
140 | "sound_volume"
141 | ]
142 | ]
143 | };
144 |
--------------------------------------------------------------------------------
/third_party/jquery/jquery.urldecoder.min.js:
--------------------------------------------------------------------------------
1 | /** --------------------------------------------------------------------------
2 | * jQuery URL Decoder
3 | * Version 1.0
4 | * Parses URL and return its components. Can also build URL from components
5 | *
6 | * ---------------------------------------------------------------------------
7 | * HOW TO USE:
8 | *
9 | * $.url.decode('http://username:password@hostname/path?arg1=value%40+1&arg2=touch%C3%A9#anchor')
10 | * // returns
11 | * // http://username:password@hostname/path?arg1=value@ 1&arg2=touché#anchor
12 | * // Note: "%40" is replaced with "@", "+" is replaced with " " and "%C3%A9" is replaced with "é"
13 | *
14 | * $.url.encode('file.htm?arg1=value1 @#456&arg2=value2 touché')
15 | * // returns
16 | * // file.htm%3Farg1%3Dvalue1%20%40%23456%26arg2%3Dvalue2%20touch%C3%A9
17 | * // Note: "@" is replaced with "%40" and "é" is replaced with "%C3%A9"
18 | *
19 | * $.url.parse('http://username:password@hostname/path?arg1=value%40+1&arg2=touch%C3%A9#anchor')
20 | * // returns
21 | * {
22 | * source: 'http://username:password@hostname/path?arg1=value%40+1&arg2=touch%C3%A9#anchor',
23 | * protocol: 'http',
24 | * authority: 'username:password@hostname',
25 | * userInfo: 'username:password',
26 | * user: 'username',
27 | * password: 'password',
28 | * host: 'hostname',
29 | * port: '',
30 | * path: '/path',
31 | * directory: '/path',
32 | * file: '',
33 | * relative: '/path?arg1=value%40+1&arg2=touch%C3%A9#anchor',
34 | * query: 'arg1=value%40+1&arg2=touch%C3%A9',
35 | * anchor: 'anchor',
36 | * params: {
37 | * 'arg1': 'value@ 1',
38 | * 'arg2': 'touché'
39 | * }
40 | * }
41 | *
42 | * $.url.build({
43 | * protocol: 'http',
44 | * username: 'username',
45 | * password: 'password',
46 | * host: 'hostname',
47 | * path: '/path',
48 | * query: 'arg1=value%40+1&arg2=touch%C3%A9',
49 | * // or
50 | * //params: {
51 | * // 'arg1': 'value@ 1',
52 | * // 'arg2': 'touché'
53 | * //}
54 | * anchor: 'anchor',
55 | * })
56 | * // returns
57 | * // http://username:password@hostname/path?arg1=value%40+1&arg2=touch%C3%A9#anchor
58 | *
59 | * ---------------------------------------------------------------------------
60 | * OTHER PARTIES' CODE:
61 | *
62 | * Parser based on the Regex-based URI parser by Steven Levithan.
63 | * For more information visit http://blog.stevenlevithan.com/archives/parseuri
64 | *
65 | * Deparam taken from jQuery BBQ by Ben Alman. Dual licensed under the MIT and GPL licenses (http://benalman.com/about/license/)
66 | * http://benalman.com/projects/jquery-bbq-plugin/
67 | *
68 | * ---------------------------------------------------------------------------
69 |
70 | */
71 | jQuery.url = function() { function l(a) { for(var b = "", c = 0, f = 0, d = 0;c < a.length;) { f = a.charCodeAt(c); if(f < 128) { b += String.fromCharCode(f); c++ }else if(f > 191 && f < 224) { d = a.charCodeAt(c + 1); b += String.fromCharCode((f & 31) << 6 | d & 63); c += 2 }else { d = a.charCodeAt(c + 1); c3 = a.charCodeAt(c + 2); b += String.fromCharCode((f & 15) << 12 | (d & 63) << 6 | c3 & 63); c += 3 } }return b } function m(a, b) { var c = {}, f = {"true":true, "false":false, "null":null}; $.each(a.replace(/\+/g, " ").split("&"), function(d, j) { var e = j.split("="); d = k(e[0]); j = c; var i = 0, g = d.split("]["), h = g.length - 1; if(/\[/.test(g[0]) && /\]$/.test(g[h])) { g[h] = g[h].replace(/\]$/, ""); g = g.shift().split("[").concat(g); h = g.length - 1 }else h = 0; if(e.length === 2) { e = k(e[1]); if(b)e = e && !isNaN(e) ? +e : e === "undefined" ? undefined : f[e] !== undefined ? f[e] : e; if(h)for(;i <= h;i++) { d = g[i] === "" ? j.length : g[i]; j = j[d] = i < h ? j[d] || (g[i + 1] && isNaN(g[i + 1]) ? {} : []) : e }else if($.isArray(c[d]))c[d].push(e); else c[d] = c[d] !== undefined ? [c[d], e] : e }else if(d)c[d] = b ? undefined : "" }); return c } function n(a) { a = a || window.location; var b = ["source", "protocol", "authority", "userInfo", "user", "password", "host", "port", "relative", "path", "directory", "file", "query", "anchor"]; a = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(a); for(var c = {}, f = b.length;f--;)c[b[f]] = a[f] || ""; if(c.query)c.params = m(c.query, true); return c } function o(a) { if(a.source)return encodeURI(a.source); var b = []; if(a.protocol)if(a.protocol == "file")b.push("file:///"); else a.protocol == "mailto" ? b.push("mailto:") : b.push(a.protocol + "://"); if(a.authority)b.push(a.authority); else { if(a.userInfo)b.push(a.userInfo + "@"); else if(a.user) { b.push(a.user); a.password && b.push(":" + a.password); b.push("@") }if(a.host) { b.push(a.host); a.port && b.push(":" + a.port) } }if(a.path)b.push(a.path); else { a.directory && b.push(a.directory); a.file && b.push(a.file) }if(a.query)b.push("?" + a.query); else a.params && b.push("?" + $.param(a.params)); a.anchor && b.push("#" + a.anchor); return b.join("") } function p(a) { return encodeURIComponent(a) } function k(a) { a = a || window.location.toString(); return l(unescape(a.replace(/\+/g, " "))) } return{encode:p, decode:k, parse:n, build:o} }();
--------------------------------------------------------------------------------
/scripts/content/omgwtfnzbs.js:
--------------------------------------------------------------------------------
1 | function getNzbId(elem) {
2 | var match = /\?id=([0-9a-zA-Z]{5,6})/i.exec(elem);
3 |
4 | if (typeof match != 'undefined' && match != null) {
5 | var nzbId = match[1];
6 | return nzbId;
7 | } else {
8 | return null;
9 | }
10 | }
11 |
12 | function getUserName() {
13 | var protocol = 'http';
14 |
15 | if (window.location.href.indexOf('https') == 0) {
16 | protocol = 'https';
17 | }
18 |
19 | var apiHtml = $.ajax({url: protocol + "://omgwtfnzbs.org/account.php?action=api", async: false}).responseText;
20 | var username = apiHtml.match(/(.*)<\/sabuser>/)[1];
21 | return username;
22 | }
23 |
24 | function getApiKey() {
25 | var protocol = 'http';
26 |
27 | if (window.location.href.indexOf('https') == 0) {
28 | protocol = 'https';
29 | }
30 |
31 | var apiHtml = $.ajax({url: protocol + "://omgwtfnzbs.org/account.php?action=api", async: false}).responseText;
32 | var apiKey = apiHtml.match(/(.*)<\/sabapikey>/)[1];
33 |
34 | if (apiKey != null) {
35 | return apiKey;
36 | } else {
37 | return null;
38 | }
39 | }
40 |
41 | function addMany(e) {
42 | var category = null;
43 |
44 |
45 | $("#sendToSabButton").prop('value', 'Sending...');
46 |
47 | $("input:checkbox").each(function() {
48 | if ( $(this).attr('name') == "cartcbitems[]" && this.checked) {
49 | category = $.trim($(this).attr('category').match(/^\s*([^:]+)/)[1]);
50 | addOne($(this).val(),category);
51 | }
52 | });
53 |
54 | $("#sendToSabButton").prop('value', 'Sent to SABnzbd');
55 |
56 | setTimeout(function() {
57 | $("#sendToSabButton").prop('value', 'Send to SABnzbd');
58 | }, 4000);
59 |
60 | return false;
61 | }
62 |
63 | function addOne(nzbid,cat) {
64 | var addLink = this;
65 | var url = "https://api.omgwtfnzbs.org/nzb/?";
66 |
67 | // Build up the URL to the API for direct downloading by getting the NZB Id, Username and API Key
68 | url = url + 'id=' + nzbid + '&user=' + getUserName() + '&api=' + getApiKey();
69 |
70 | // Get the category
71 | var category = cat;
72 |
73 | if (category === null) {
74 | category = "default";
75 | }
76 |
77 | addToSABnzbd(
78 | addLink,
79 | url,
80 | "addurl",
81 | null,
82 | category);
83 |
84 | return false;
85 |
86 | }
87 |
88 | function addToSABnzbdFromOmgwtfnzbs() {
89 | // Set the image to an in-progress image
90 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
91 | $(this).find('img').attr("src", img);
92 |
93 | var nzburl = $(this).attr('href');
94 | var addLink = this;
95 | var url = "https://api.omgwtfnzbs.org/nzb/?";
96 |
97 | if (nzburl.indexOf('http://') == 0) {
98 | url = "http://api.omgwtfnzbs.org/nzb/?";
99 | }
100 |
101 | // Build up the URL to the API for direct downloading by getting the NZB Id, Username and API Key
102 | url = url + 'id=' + getNzbId(nzburl) + '&user=' + getUserName() + '&api=' + getApiKey();
103 |
104 | // Get the category
105 | var category = null;
106 | // find the category for the browse.php page
107 | if ($.trim($(this).parents('.nzbt_row').html())) {
108 | category = $.trim($(this).parents('.nzbt_row').html());
109 | category = category.match(/(.*)<\/sabcategory>/)[1];
110 | }
111 | // find the category for the details.php page
112 | else if ($( "#category" ).length != 0)
113 | {
114 | category = $.trim($('#category').html());
115 | category = category.match(/(.*)<\/sabcategory>/)[1];
116 | }
117 | // find the category for the trends.php page
118 | else if ($(this).parents('.flag_float:first').children('.small_middle').children('.bmtip.cat_class').html()) {
119 | category = $.trim($(this).parents('.flag_float:first').html());
120 | category = category.match(/(.*)<\/sabcategory>/)[1];
121 | }
122 |
123 | if (category === null) {
124 | category = "default";
125 | }
126 |
127 | // Send the NZB to SABnzbd
128 | addToSABnzbd(
129 | addLink,
130 | url,
131 | "addurl",
132 | null,
133 | category);
134 |
135 | return false;
136 | }
137 |
138 | function handleAllDownloadLinks() {
139 | $('img[src="pics/dload.gif"]').each(function() {
140 | var href = $(this).parent().attr('href');
141 | var img = chrome.runtime.getURL('/images/content_icon.png');
142 | var link_mini = '
';
143 | var link_full = '
Send to SABnzbd ';
144 |
145 | if ($(this).parent().hasClass('linky') === false) {
146 | $(this).parent().before(link_mini);
147 | } else {
148 | $(this).parent().before(link_full);
149 | }
150 | });
151 |
152 | $("#dlButton:submit").each(function() {
153 | $(this)
154 | .after(' ')
155 | ;
156 | $(document).on("click", "#sendToSabButton", function(){
157 | addMany();
158 | });
159 | });
160 |
161 | // Change the on click handler to send to sabnzbd
162 | // moved because the way it was the click was firing multiple times
163 | $('.addSABnzbd').each(function() {
164 | $(this).click(addToSABnzbdFromOmgwtfnzbs);
165 | });
166 |
167 | return;
168 | }
169 |
170 | Initialize( 'omgwtfnzbs', null, function() {
171 | handleAllDownloadLinks();
172 | });
173 |
174 |
--------------------------------------------------------------------------------
/third_party/fancy-settings-mod/css/chrome_shared.css:
--------------------------------------------------------------------------------
1 | /* Styles common to WebUI pages that share the options pages style */
2 | body {
3 | cursor: default;
4 | font-size: 13px;
5 | }
6 |
7 | a:link {
8 | color: rgb(63, 110, 194);
9 | }
10 |
11 | a:active {
12 | color: rgb(37, 64, 113);
13 | }
14 |
15 | #navbar-content-title {
16 | -webkit-padding-end: 24px;
17 | -webkit-user-select: none;
18 | color: #53637d;
19 | cursor: pointer;
20 | font-size: 200%;
21 | font-weight: normal;
22 | margin: 0;
23 | padding-bottom: 14px;
24 | padding-top: 13px;
25 | text-align: end;
26 | text-shadow: white 0 1px 2px;
27 | }
28 |
29 | #main-content {
30 | display: -webkit-box;
31 | position: absolute;
32 | left: 0;
33 | right: 0;
34 | top: 0;
35 | bottom: 0;
36 | }
37 |
38 | #navbar {
39 | margin: 0;
40 | }
41 |
42 | #navbar-container {
43 | -webkit-border-end: 1px solid #c6c9ce;
44 | background: -webkit-linear-gradient(rgba(234, 238, 243, 0.2), #eaeef3),
45 | -webkit-linear-gradient(left, #eaeef3, #eaeef3 97%, #d3d7db);
46 | position: fixed;
47 | bottom: 0;
48 | /* We set both left and right for the sake of RTL. */
49 | left: 0;
50 | right: 0;
51 | top: 0;
52 | width: 216px;
53 | z-index: 2;
54 | }
55 |
56 | html[dir='rtl'] #navbar-container {
57 | background: -webkit-linear-gradient(rgba(234, 238, 243, 0), #EAEEF3),
58 | -webkit-linear-gradient(right, #EAEEF3, #EAEEF3 97%, #D3D7DB);
59 | }
60 |
61 | html.hide-menu #navbar-container {
62 | display: none;
63 | }
64 |
65 | #navbar-container > ul {
66 | -webkit-user-select: none;
67 | list-style-type: none;
68 | margin: 0;
69 | padding: 0;
70 | }
71 |
72 | .navbar-item {
73 | border-bottom: 1px solid transparent;
74 | border-top: 1px solid transparent;
75 | color: #426dc9;
76 | cursor: pointer;
77 | display: block;
78 | font-size: 105%;
79 | outline: none;
80 | padding: 7px 0;
81 | text-align: end;
82 | text-shadow: white 0 1px 1px;
83 | -webkit-padding-end: 24px;
84 | }
85 |
86 | .navbar-item:focus {
87 | border-bottom: 1px solid #8faad9;
88 | border-top: 1px solid #8faad9;
89 | }
90 |
91 | .navbar-item-selected {
92 | -webkit-box-shadow: 0px 1px 0px #f7f7f7;
93 | background: -webkit-linear-gradient(left, #bbcee9, #bbcee9 97%, #aabedc);
94 | border-bottom: 1px solid #8faad9;
95 | border-top: 1px solid #8faad9;
96 | color: black;
97 | text-shadow: #bbcee9 0 1px 1px;
98 | }
99 |
100 | #mainview {
101 | -webkit-box-align: stretch;
102 | -webkit-padding-start: 216px;
103 | margin: 0;
104 | position: absolute;
105 | left: 0;
106 | right: 0;
107 | top: 0;
108 | bottom: 0;
109 | z-index: 1;
110 | }
111 |
112 | html.hide-menu #mainview {
113 | -webkit-padding-start: 0;
114 | }
115 |
116 | #mainview-content {
117 | min-height: 100%;
118 | position: relative;
119 | }
120 |
121 | #page-container {
122 | box-sizing: border-box;
123 | max-width: 888px;
124 | min-width: 600px;
125 | padding: 0 24px;
126 | }
127 |
128 | div.checkbox,
129 | div.radio {
130 | margin: 5px 0;
131 | }
132 |
133 | div.disabled {
134 | color: #888;
135 | }
136 |
137 | /* TEXT */
138 | input[type='password'],
139 | input[type='text'],
140 | input[type='url'],
141 | input:not([type]) {
142 | -webkit-border-radius: 2px;
143 | border: 1px solid #aaa;
144 | font-size: inherit;
145 | padding: 3px;
146 | }
147 |
148 | /* CHECKBOX, RADIO */
149 | input[type=checkbox],
150 | input[type=radio] {
151 | margin-left: 0;
152 | margin-right: 0;
153 | position: relative;
154 | top: 1px;
155 | }
156 |
157 | /* Checkbox and radio buttons have different sizes on different platforms. The
158 | * following rules have platform specific tweaks.
159 | * TODO(arv): Test the vertical position on Linux and CrOS as well.
160 | */
161 |
162 | label > input[type=checkbox],
163 | label > input[type=radio] {
164 | margin-top: 1px;
165 | }
166 |
167 | html[os=mac] label > input[type=checkbox],
168 | html[os=mac] label > input[type=radio] {
169 | margin-top: 2px;
170 | }
171 |
172 | html[os=chromeos] label > input[type=checkbox],
173 | html[os=chromeos] label > input[type=radio] {
174 | top: 2px;
175 | }
176 |
177 | /* This will 'disable' the label associated with any input whose next sibling is
178 | * the span containing the label (usually a checkbox or radio).
179 | */
180 | label > input[disabled] ~ span {
181 | color: #888;
182 | }
183 |
184 | /* Elements that need to be LTR even in an RTL context, but should align
185 | * right. (Namely, URLs, search engine names, etc.)
186 | */
187 | html[dir='rtl'] .weakrtl {
188 | direction: ltr;
189 | text-align: right;
190 | }
191 |
192 | /* Input fields in search engine table need to be weak-rtl. Since those input
193 | * fields are generated for all cr.ListItem elements (and we only want weakrtl
194 | * on some), the class needs to be on the enclosing div.
195 | */
196 | html[dir='rtl'] div.weakrtl input {
197 | direction: ltr;
198 | text-align: right;
199 | }
200 |
201 | html[dir='rtl'] .favicon-cell.weakrtl {
202 | -webkit-padding-end: 22px;
203 | -webkit-padding-start: 0;
204 | }
205 |
206 | /* weakrtl for selection drop downs needs to account for the fact that
207 | * Webkit does not honor the text-align attribute for the select element.
208 | * (See Webkit bug #40216)
209 | */
210 | html[dir='rtl'] select.weakrtl {
211 | direction: rtl;
212 | }
213 |
214 | html[dir='rtl'] select.weakrtl option {
215 | direction: ltr;
216 | }
217 |
218 | /* WebKit does not honor alignment for text specified via placeholder attrib.
219 | * This CSS is a workaround. Please remove once WebKit bug is fixed.
220 | * https://bugs.webkit.org/show_bug.cgi?id=63367
221 | */
222 | html[dir='rtl'] input.weakrtl::-webkit-input-placeholder,
223 | html[dir='rtl'] .weakrtl input::-webkit-input-placeholder {
224 | direction: rtl;
225 | }
226 |
227 | .page h1 {
228 | -webkit-padding-end: 24px;
229 | -webkit-user-select: none;
230 | border-bottom: 1px solid #eeeeee;
231 | color: #53637d;
232 | font-size: 200%;
233 | font-weight: normal;
234 | margin: 0;
235 | padding-bottom: 4px;
236 | padding-top: 13px;
237 | text-shadow: white 0 1px 2px;
238 | }
239 |
240 |
241 |
--------------------------------------------------------------------------------
/scripts/content/common.js:
--------------------------------------------------------------------------------
1 | var ignoreCats;
2 |
3 | function onResponseAdd( response, addLink )
4 | {
5 | switch( response.ret ) {
6 | case 'error' :
7 | alert("Could not contact SABnzbd \n Check it is running and your settings are correct");
8 | var img = chrome.runtime.getURL('images/content_icon_error.png');
9 | if ($(addLink).find('img').length > 0) {
10 | $(addLink).find('img').attr("src", img);
11 | } else {
12 | // Prevent updating the background image of Bootstrap buttons
13 | if ($(addLink).hasClass('btn') == false) {
14 | $(addLink).css('background-image', 'url('+img+')');
15 | }
16 | }
17 | break;
18 | case 'success':
19 | // If there was an error of some type, report it to the user and abort!
20 | if (response.data.error) {
21 | alert(response.data.error);
22 | var img = chrome.runtime.getURL('images/content_icon_error.png');
23 | if ($(addLink).find('img').length > 0) {
24 | $(addLink).find('img').attr("src", img);
25 | } else {
26 | // Prevent updating the background image of Bootstrap buttons
27 | if ($(addLink).hasClass('btn') == false) {
28 | $(addLink).css('background-image', 'url('+img+')');
29 | }
30 | }
31 | return;
32 | }
33 | var img = chrome.runtime.getURL('images/content_icon_success.png');
34 | if ($(addLink).find('img').length > 0) {
35 | $(addLink).find('img').attr("src", img);
36 | } else if (addLink.nodeName && addLink.nodeName.toUpperCase() == 'INPUT' && addLink.value == 'Sent to SABnzbd!') {
37 | // Nothing; handled in nzbsorg.js
38 | } else {
39 | // Prevent updating the background image of Bootstrap buttons
40 | if ($(addLink).hasClass('btn') == false) {
41 | $(addLink).css('background-image', 'url('+img+')');
42 | }
43 | }
44 | break;
45 | default:
46 | alert("SABconnect: Oops! Something went wrong. Try again.");
47 | }
48 | }
49 |
50 | function addToSABnzbd(addLink, nzburl, mode, nice_name, category) {
51 |
52 | if(nzburl.substring(0, 1) == "/") {
53 | var locHost = window.location.host;
54 | if (locHost == "dognzb.cr") {
55 | locHost = "dl.dognzb.cr";
56 | }
57 | nzburl = window.location.protocol + "//" + locHost + nzburl;
58 | }
59 |
60 | var request = {
61 | action: 'addToSABnzbd',
62 | nzburl: nzburl,
63 | mode: mode
64 | };
65 |
66 | if (typeof nice_name != 'undefined' && nice_name != null) {
67 | request.nzbname = nice_name;
68 | }
69 |
70 | // Always pass the category from the site if provided
71 | // The background script will decide whether to use it or the hard-coded one
72 | if (typeof category != 'undefined' && category != null) {
73 | request.category = category;
74 | }
75 |
76 | chrome.runtime.sendMessage(
77 | request,
78 | function(response) { onResponseAdd( response, addLink ) }
79 | );
80 | }
81 |
82 | function GetSetting( setting, callback )
83 | {
84 | var request = {
85 | action: 'get_setting',
86 | setting: setting
87 | }
88 |
89 | chrome.runtime.sendMessage( request, function( response ) {
90 | var value = response.value;
91 |
92 | if( typeof value == 'undefined' || value == null ) {
93 | throw 'GetSetting(): ' + setting + ' could not be found.';
94 | }
95 | else {
96 | callback( value );
97 | }
98 | });
99 | }
100 |
101 | var refresh_func = null;
102 |
103 | function CallRefreshFunction()
104 | {
105 | if( refresh_func ) {
106 | refresh_func();
107 | }
108 | }
109 |
110 | function Initialize( provider, refresh_function, callback )
111 | {
112 | var request = {
113 | action: 'initialize',
114 | provider: provider
115 | }
116 |
117 | chrome.runtime.sendMessage( request, function( response ) {
118 | console.log('SABconnect++ Initialize: Received response:', response);
119 | if( response && response.enabled ) {
120 | callback();
121 | }
122 | else {
123 | console.info( 'SABconnect: 1-click functionality for this site is disabled or no response received' );
124 | }
125 | });
126 |
127 | refresh_func = refresh_function
128 | CallRefreshFunction();
129 | }
130 |
131 | function OnRequest( request, sender, onResponse )
132 | {
133 | switch( request.action ) {
134 | case 'refresh_settings':
135 | CallRefreshFunction();
136 | break;
137 | }
138 | };
139 |
140 | chrome.runtime.onMessage.addListener( OnRequest );
141 |
142 | // Fallback function to find and add icons to download links
143 | function addIconsWithFallback(options) {
144 | options = options || {};
145 | var linkSelector = options.linkSelector || 'a[href*="/download/"], a[href*=".nzb"], a[href*="/getnzb/"]';
146 | var iconClass = options.iconClass || 'addSABnzbd';
147 | var processedAttr = options.processedAttr || 'x-sab-processed';
148 | var iconStyle = options.iconStyle || 'margin-right: 5px;';
149 | var clickHandler = options.clickHandler || function(e) {
150 | e.preventDefault();
151 | e.stopPropagation();
152 | var $link = $(this);
153 | var href = $link.attr('href');
154 | addToSABnzbd(this, href, 'addurl', null, null);
155 | return false;
156 | };
157 |
158 | var oneClickImgTag = '
';
159 | var addedCount = 0;
160 |
161 | $(linkSelector).each(function() {
162 | var $link = $(this);
163 |
164 | // Skip if already processed
165 | if ($link.attr(processedAttr) === 'true' || $link.hasClass(iconClass)) {
166 | return;
167 | }
168 |
169 | // Skip if parent already has our icon
170 | if ($link.parent().find('.' + iconClass).length > 0) {
171 | return;
172 | }
173 |
174 | // Mark as processed
175 | $link.attr(processedAttr, 'true');
176 |
177 | // Create and insert icon
178 | var $iconLink = $('' + oneClickImgTag + '');
179 | $link.before($iconLink);
180 | $iconLink.on('click', clickHandler);
181 |
182 | addedCount++;
183 | });
184 |
185 | if (addedCount > 0) {
186 | console.log('SABconnect++ Fallback: Added ' + addedCount + ' icons');
187 | } else {
188 | // If still no links found, log what we see
189 | console.log('SABconnect++ Fallback: No matching links found with selector:', linkSelector);
190 | console.log('SABconnect++ Fallback: Total anchors on page:', $('a').length);
191 |
192 | // Log first few link hrefs for debugging
193 | $('a').slice(0, 5).each(function(i) {
194 | console.log('SABconnect++ Fallback: Link ' + i + ':', $(this).attr('href'));
195 | });
196 | }
197 |
198 | return addedCount;
199 | }
200 |
--------------------------------------------------------------------------------
/scripts/compatibility/globals.js:
--------------------------------------------------------------------------------
1 | // Global compatibility layer for settings.js
2 |
3 | // MooTools-style $ function
4 | window.$ = function(id) {
5 | var element = document.getElementById(id);
6 | if (element && !element.set) {
7 | // Add MooTools-style methods
8 | element.set = function(property, value) {
9 | if (property === 'html') {
10 | element.innerHTML = value;
11 | } else if (property === 'text') {
12 | element.textContent = value;
13 | } else if (property === 'class') {
14 | element.className = value;
15 | } else {
16 | element.setAttribute(property, value);
17 | }
18 | return element;
19 | };
20 |
21 | element.setStyle = function(property, value) {
22 | element.style[property] = value;
23 | return element;
24 | };
25 |
26 | element.inject = function(parent, position) {
27 | position = position || 'bottom';
28 | if (position === 'bottom') {
29 | parent.appendChild(element);
30 | } else if (position === 'top') {
31 | parent.insertBefore(element, parent.firstChild);
32 | }
33 | return element;
34 | };
35 |
36 | element.addEvent = function(event, handler) {
37 | element.addEventListener(event, handler);
38 | return element;
39 | };
40 | }
41 | return element;
42 | };
43 |
44 | // MooTools-style Element constructor
45 | window.Element = function(tag, attributes) {
46 | var element = document.createElement(tag);
47 |
48 | if (attributes) {
49 | for (var attr in attributes) {
50 | if (attr === 'text') {
51 | element.textContent = attributes[attr];
52 | } else if (attr === 'html') {
53 | element.innerHTML = attributes[attr];
54 | } else {
55 | element.setAttribute(attr, attributes[attr]);
56 | }
57 | }
58 | }
59 |
60 | // Add MooTools-style methods
61 | element.set = function(property, value) {
62 | if (property === 'html') {
63 | element.innerHTML = value;
64 | } else if (property === 'text') {
65 | element.textContent = value;
66 | } else if (property === 'class') {
67 | element.className = value;
68 | } else {
69 | element.setAttribute(property, value);
70 | }
71 | return element;
72 | };
73 |
74 | element.setStyle = function(property, value) {
75 | element.style[property] = value;
76 | return element;
77 | };
78 |
79 | element.inject = function(parent, position) {
80 | position = position || 'bottom';
81 | if (position === 'bottom') {
82 | parent.appendChild(element);
83 | } else if (position === 'top') {
84 | parent.insertBefore(element, parent.firstChild);
85 | }
86 | return element;
87 | };
88 |
89 | element.addEvent = function(event, handler) {
90 | element.addEventListener(event, handler);
91 | return element;
92 | };
93 |
94 | element.dispose = function() {
95 | if (element.parentNode) {
96 | element.parentNode.removeChild(element);
97 | }
98 | return element;
99 | };
100 |
101 | return element;
102 | };
103 |
104 | // MooTools-style Class system
105 | window.Class = function(definition) {
106 | function ClassConstructor() {
107 | if (this.initialize) {
108 | return this.initialize.apply(this, arguments);
109 | }
110 | }
111 |
112 | for (var key in definition) {
113 | ClassConstructor.prototype[key] = definition[key];
114 | }
115 |
116 | return ClassConstructor;
117 | };
118 |
119 | // MooTools-style Array.each
120 | if (!Array.prototype.each) {
121 | Array.prototype.each = function(fn, bind) {
122 | for (var i = 0; i < this.length; i++) {
123 | fn.call(bind || this, this[i], i, this);
124 | }
125 | return this;
126 | };
127 | }
128 |
129 | // MooTools-style Object.each
130 | if (!Object.each) {
131 | Object.each = function(obj, fn, bind) {
132 | for (var key in obj) {
133 | if (obj.hasOwnProperty(key)) {
134 | fn.call(bind || obj, obj[key], key, obj);
135 | }
136 | }
137 | return obj;
138 | };
139 | }
140 |
141 | // MooTools-style typeOf function
142 | window.typeOf = function(obj) {
143 | if (obj == null) return 'null';
144 | if (obj === undefined) return 'undefined';
145 |
146 | var type = typeof obj;
147 | if (type === 'object') {
148 | if (Array.isArray(obj)) return 'array';
149 | if (obj.constructor === Date) return 'date';
150 | if (obj.constructor === RegExp) return 'regexp';
151 | return 'object';
152 | }
153 | return type;
154 | };
155 |
156 | // StoreClass alias for Store
157 | window.StoreClass = Store;
158 |
159 | // Global profiles object - will be initialized by settings.js
160 | window.profiles = null;
161 |
162 | // Global preferences functions (compatibility)
163 | window.getPref = function(name) {
164 | return store.get(name);
165 | };
166 |
167 | window.setPref = function(name, value) {
168 | return store.set(name, value);
169 | };
170 |
171 | // Initialize profiles system when DOM is ready
172 | document.addEventListener('DOMContentLoaded', function() {
173 | // Initialize profiles manager
174 | window.profiles = new ProfileManager();
175 |
176 | // Initialize default profiles if none exist - but only after store is ready
177 | function initializeDefaultProfiles() {
178 | if (!window.sabconnectStore || !window.sabconnectStore.isReady) {
179 | // Store not ready yet, wait
180 | setTimeout(initializeDefaultProfiles, 50);
181 | return;
182 | }
183 |
184 | var existingProfiles = store.get('profiles');
185 |
186 | if (!existingProfiles || Object.keys(existingProfiles).length === 0) {
187 | store.set('profiles', {
188 | 'Default': {
189 | url: '',
190 | api_key: '',
191 | username: '',
192 | password: ''
193 | }
194 | });
195 | setPref('active_profile', 'Default');
196 | }
197 | }
198 |
199 | // Start the initialization process
200 | initializeDefaultProfiles();
201 | });
--------------------------------------------------------------------------------
/third_party/jqplot/jquery.jqplot.css:
--------------------------------------------------------------------------------
1 | /*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/
2 | .jqplot-target {
3 | position: relative;
4 | color: #666666;
5 | font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
6 | font-size: 1em;
7 | /* height: 300px;
8 | width: 400px;*/
9 | }
10 |
11 | /*rules applied to all axes*/
12 | .jqplot-axis {
13 | font-size: 0.75em;
14 | }
15 |
16 | .jqplot-xaxis {
17 | margin-top: 10px;
18 | }
19 |
20 | .jqplot-x2axis {
21 | margin-bottom: 10px;
22 | }
23 |
24 | .jqplot-yaxis {
25 | margin-right: 10px;
26 | }
27 |
28 | .jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis {
29 | margin-left: 10px;
30 | margin-right: 10px;
31 | }
32 |
33 | /*rules applied to all axis tick divs*/
34 | .jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick {
35 | position: absolute;
36 | white-space: pre;
37 | }
38 |
39 |
40 | .jqplot-xaxis-tick {
41 | top: 0px;
42 | /* initial position untill tick is drawn in proper place */
43 | left: 15px;
44 | /* padding-top: 10px;*/
45 | vertical-align: top;
46 | }
47 |
48 | .jqplot-x2axis-tick {
49 | bottom: 0px;
50 | /* initial position untill tick is drawn in proper place */
51 | left: 15px;
52 | /* padding-bottom: 10px;*/
53 | vertical-align: bottom;
54 | }
55 |
56 | .jqplot-yaxis-tick {
57 | right: 0px;
58 | /* initial position untill tick is drawn in proper place */
59 | top: 15px;
60 | /* padding-right: 10px;*/
61 | text-align: right;
62 | }
63 |
64 | .jqplot-yaxis-tick.jqplot-breakTick {
65 | right: -20px;
66 | margin-right: 0px;
67 | padding:1px 5px 1px 5px;
68 | /*background-color: white;*/
69 | z-index: 2;
70 | font-size: 1.5em;
71 | }
72 |
73 | .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick {
74 | left: 0px;
75 | /* initial position untill tick is drawn in proper place */
76 | top: 15px;
77 | /* padding-left: 10px;*/
78 | /* padding-right: 15px;*/
79 | text-align: left;
80 | }
81 |
82 | .jqplot-yMidAxis-tick {
83 | text-align: center;
84 | white-space: nowrap;
85 | }
86 |
87 | .jqplot-xaxis-label {
88 | margin-top: 10px;
89 | font-size: 11pt;
90 | position: absolute;
91 | }
92 |
93 | .jqplot-x2axis-label {
94 | margin-bottom: 10px;
95 | font-size: 11pt;
96 | position: absolute;
97 | }
98 |
99 | .jqplot-yaxis-label {
100 | margin-right: 10px;
101 | /* text-align: center;*/
102 | font-size: 11pt;
103 | position: absolute;
104 | }
105 |
106 | .jqplot-yMidAxis-label {
107 | font-size: 11pt;
108 | position: absolute;
109 | }
110 |
111 | .jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label {
112 | /* text-align: center;*/
113 | font-size: 11pt;
114 | margin-left: 10px;
115 | position: absolute;
116 | }
117 |
118 | .jqplot-meterGauge-tick {
119 | font-size: 0.75em;
120 | color: #999999;
121 | }
122 |
123 | .jqplot-meterGauge-label {
124 | font-size: 1em;
125 | color: #999999;
126 | }
127 |
128 | table.jqplot-table-legend {
129 | margin-top: 12px;
130 | margin-bottom: 12px;
131 | margin-left: 12px;
132 | margin-right: 12px;
133 | }
134 |
135 | table.jqplot-table-legend, table.jqplot-cursor-legend {
136 | background-color: rgba(255,255,255,0.6);
137 | border: 1px solid #cccccc;
138 | position: absolute;
139 | font-size: 0.75em;
140 | }
141 |
142 | td.jqplot-table-legend {
143 | vertical-align:middle;
144 | }
145 |
146 | /*
147 | These rules could be used instead of assigning
148 | element styles and relying on js object properties.
149 | */
150 |
151 | /*
152 | td.jqplot-table-legend-swatch {
153 | padding-top: 0.5em;
154 | text-align: center;
155 | }
156 |
157 | tr.jqplot-table-legend:first td.jqplot-table-legend-swatch {
158 | padding-top: 0px;
159 | }
160 | */
161 |
162 | td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active {
163 | cursor: pointer;
164 | }
165 |
166 | .jqplot-table-legend .jqplot-series-hidden {
167 | text-decoration: line-through;
168 | }
169 |
170 | div.jqplot-table-legend-swatch-outline {
171 | border: 1px solid #cccccc;
172 | padding:1px;
173 | }
174 |
175 | div.jqplot-table-legend-swatch {
176 | width:0px;
177 | height:0px;
178 | border-top-width: 5px;
179 | border-bottom-width: 5px;
180 | border-left-width: 6px;
181 | border-right-width: 6px;
182 | border-top-style: solid;
183 | border-bottom-style: solid;
184 | border-left-style: solid;
185 | border-right-style: solid;
186 | }
187 |
188 | .jqplot-title {
189 | top: 0px;
190 | left: 0px;
191 | padding-bottom: 0.5em;
192 | font-size: 1.2em;
193 | }
194 |
195 | table.jqplot-cursor-tooltip {
196 | border: 1px solid #cccccc;
197 | font-size: 0.75em;
198 | }
199 |
200 |
201 | .jqplot-cursor-tooltip {
202 | border: 1px solid #cccccc;
203 | font-size: 0.75em;
204 | white-space: nowrap;
205 | background: rgba(208,208,208,0.5);
206 | padding: 1px;
207 | }
208 |
209 | .jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
210 | border: 1px solid #cccccc;
211 | font-size: 0.75em;
212 | white-space: nowrap;
213 | background: rgba(208,208,208,0.5);
214 | padding: 1px;
215 | }
216 |
217 | .jqplot-point-label {
218 | font-size: 0.75em;
219 | z-index: 2;
220 | }
221 |
222 | td.jqplot-cursor-legend-swatch {
223 | vertical-align: middle;
224 | text-align: center;
225 | }
226 |
227 | div.jqplot-cursor-legend-swatch {
228 | width: 1.2em;
229 | height: 0.7em;
230 | }
231 |
232 | .jqplot-error {
233 | /* Styles added to the plot target container when there is an error go here.*/
234 | text-align: center;
235 | }
236 |
237 | .jqplot-error-message {
238 | /* Styling of the custom error message div goes here.*/
239 | position: relative;
240 | top: 46%;
241 | display: inline-block;
242 | }
243 |
244 | div.jqplot-bubble-label {
245 | font-size: 0.8em;
246 | /* background: rgba(90%, 90%, 90%, 0.15);*/
247 | padding-left: 2px;
248 | padding-right: 2px;
249 | color: rgb(20%, 20%, 20%);
250 | }
251 |
252 | div.jqplot-bubble-label.jqplot-bubble-label-highlight {
253 | background: rgba(90%, 90%, 90%, 0.7);
254 | }
255 |
256 | div.jqplot-noData-container {
257 | text-align: center;
258 | background-color: rgba(96%, 96%, 96%, 0.3);
259 | }
260 |
--------------------------------------------------------------------------------
/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
25 |
26 |
27 |
28 |
29 |
30 |

31 |
58 |
59 |
62 |
65 |
66 |
67 |
68 |
69 | PROFILE
70 |
71 |
72 |
73 |
82 |
83 |
84 |
85 |
86 |
87 |
91 |
92 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
111 |
112 |
113 |
114 |
115 |
122 |
123 |
124 |
125 |
126 |
208 |
209 | ?
210 |
211 |
212 |
213 |
--------------------------------------------------------------------------------
/scripts/content/nzbindex.js:
--------------------------------------------------------------------------------
1 | var use_nice_name_nzbindex;
2 |
3 | function addToSABnzbdFromNzbindex() {
4 | var addLink = this;
5 |
6 | // Set the image to an in-progress image
7 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
8 | if ($(this).find('img').length > 0) {
9 | $(this).find('img').attr("src", img);
10 | var nzburl = $(this).attr('href');
11 |
12 | // Try to get category from the modern site structure
13 | var category = null;
14 | var nice_name = null;
15 |
16 | // Find the table row containing this link
17 | var $row = $(this).closest('tr');
18 | if ($row.length > 0) {
19 | // Look for group/category information in the row
20 | var groupLinks = $row.find('a[href*="groups="]');
21 | if (groupLinks.length > 0) {
22 | category = $(groupLinks[0]).text().trim();
23 | }
24 |
25 | // Try to get nice name from the title link
26 | var titleLink = $row.find('a.font-medium, a[href*="/collection/"]');
27 | if (titleLink.length > 0 && use_nice_name_nzbindex == '1') {
28 | nice_name = titleLink.text().trim();
29 | // Clean up the name
30 | if (nice_name.length > 100) {
31 | nice_name = nice_name.substring(0, 100) + '...';
32 | }
33 | }
34 | }
35 |
36 | console.log('SABconnect++ NZBIndex: Sending to SABnzbd:', nzburl, 'category:', category, 'name:', nice_name);
37 | addToSABnzbd(addLink, nzburl, "addurl", nice_name, category);
38 | } else {
39 | $(this).css('background-image', 'url(' + img + ')');
40 |
41 | //grab all checked boxes on page for batch download
42 | var checkedBoxes = $('input[type="checkbox"]:checked');
43 | console.log('SABconnect++ NZBIndex: Found checked boxes:', checkedBoxes.length);
44 |
45 | checkedBoxes.each(function() {
46 | var $checkbox = $(this);
47 | var $row = $checkbox.closest('tr');
48 | if ($row.length > 0) {
49 | var downloadLink = $row.find('a[href*="/download/"][href$=".nzb"]');
50 | if (downloadLink.length > 0) {
51 | var nzburl = downloadLink.attr('href');
52 |
53 | // Get category from group links
54 | var category = null;
55 | var groupLinks = $row.find('a[href*="groups="]');
56 | if (groupLinks.length > 0) {
57 | category = $(groupLinks[0]).text().trim();
58 | }
59 |
60 | // Get nice name if enabled
61 | var nice_name = null;
62 | var titleLink = $row.find('a.font-medium, a[href*="/collection/"]');
63 | if (titleLink.length > 0 && use_nice_name_nzbindex == '1') {
64 | nice_name = titleLink.text().trim();
65 | if (nice_name.length > 100) {
66 | nice_name = nice_name.substring(0, 100) + '...';
67 | }
68 | }
69 |
70 | console.log('SABconnect++ NZBIndex: Batch sending to SABnzbd:', nzburl);
71 | addToSABnzbd(addLink, nzburl, "addurl", nice_name, category);
72 | }
73 | }
74 | });
75 | }
76 |
77 | return false;
78 | }
79 |
80 | function handleAllDownloadLinks() {
81 | console.log('SABconnect++ NZBIndex: Running handleAllDownloadLinks...');
82 |
83 | $('input[value="Create NZB"]').each(function () {
84 | console.log('SABconnect++ NZBIndex: Found "Create NZB" button');
85 | if ($(this).attr('x-nzbpatched') !== 'true') {
86 | $(this).attr('x-nzbpatched', 'true');
87 | // add button to send checked items to SABConnect
88 | var img = chrome.runtime.getURL('/images/content_icon.png');
89 | var link = '';
90 | $(this).after(link);
91 | $(this).parent().find('input[class="addSABnzbd"]').first().click(addToSABnzbdFromNzbindex);
92 | }
93 | });
94 |
95 | // Check for both escaped and unescaped slashes in the URL
96 | $('table a[href*="nzbindex.nl/download/"], table a[href*="nzbindex.nl\\/download\\/"]').each(function () {
97 | console.log('SABconnect++ NZBIndex: Found nzbindex.nl download link:', $(this).attr('href'));
98 | if ($(this).attr('x-nzbpatched') !== 'true') {
99 | $(this).attr('x-nzbpatched', 'true');
100 | var img = chrome.runtime.getURL('/images/content_icon.png');
101 | var href = $(this).attr('href');
102 | var link = $('
');
103 | $(this).before(link);
104 | $(link).click(function(e) {
105 | e.preventDefault();
106 | e.stopPropagation();
107 | addToSABnzbdFromNzbindex.call(this);
108 | return false;
109 | });
110 | }
111 | });
112 |
113 | // Check for both escaped and unescaped slashes in the URL
114 | $('table a[href*="nzbindex.com/download/"], table a[href*="nzbindex.com\\/download\\/"]').each(function () {
115 | console.log('SABconnect++ NZBIndex: Found nzbindex.com download link:', $(this).attr('href'));
116 | if ($(this).attr('x-nzbpatched') !== 'true') {
117 | $(this).attr('x-nzbpatched', 'true');
118 | var img = chrome.runtime.getURL('/images/content_icon.png');
119 | var href = $(this).attr('href');
120 | var link = $('
');
121 | $(this).before(link);
122 | $(link).click(function(e) {
123 | e.preventDefault();
124 | e.stopPropagation();
125 | addToSABnzbdFromNzbindex.call(this);
126 | return false;
127 | });
128 | }
129 | });
130 |
131 | // Also try without table restriction - modern NZBIndex uses UUID-based .nzb links
132 | $('a[href*="/download/"][href$=".nzb"]').each(function () {
133 | var href = $(this).attr('href');
134 | console.log('SABconnect++ NZBIndex: Found modern download link:', href);
135 | if ($(this).attr('x-nzbpatched') !== 'true' && !$(this).hasClass('addSABnzbdOnClick')) {
136 | $(this).attr('x-nzbpatched', 'true');
137 | var img = chrome.runtime.getURL('/images/content_icon.png');
138 | var link = $('
');
139 | $(this).before(link);
140 | $(link).click(function(e) {
141 | e.preventDefault();
142 | e.stopPropagation();
143 | addToSABnzbdFromNzbindex.call(this);
144 | return false;
145 | });
146 | }
147 | });
148 | }
149 |
150 | function RefreshSettings() {
151 | GetSetting('use_name_nzbindex', function (value) {
152 | use_nice_name_nzbindex = value;
153 | });
154 | }
155 |
156 | Initialize('nzbindex', RefreshSettings, function () {
157 | console.log('SABconnect++ NZBIndex: Script initialized');
158 |
159 | let insertionInProgress = false;
160 |
161 | function DOMNodeInserted() {
162 | if (!insertionInProgress) {
163 | insertionInProgress = true;
164 | handleAllDownloadLinks();
165 |
166 | // Fallback: Look for any download links if no icons were added
167 | if ($('a.addSABnzbdOnClick').length === 0 && $('input.addSABnzbd').length === 0) {
168 | console.log('SABconnect++ NZBIndex: No icons added with standard selectors. Trying fallback...');
169 |
170 | // Debug: Show what links are on the page
171 | console.log('SABconnect++ NZBIndex: All links:', $('a').length);
172 | console.log('SABconnect++ NZBIndex: Links with /download/:', $('a[href*="/download/"]').length);
173 | console.log('SABconnect++ NZBIndex: Links with .nzb:', $('a[href*=".nzb"]').length);
174 | console.log('SABconnect++ NZBIndex: Links with download text:', $('a:contains("Download")').length);
175 |
176 | // Try broader selectors - modern NZBIndex uses UUID-based .nzb links
177 | var downloadLinks = $('a[href*="/download/"][href$=".nzb"], a[href*=".nzb"], a[href*="get?"], a[title*="Download"], a:contains("Download")');
178 | console.log('SABconnect++ NZBIndex: Found potential download links:', downloadLinks.length);
179 |
180 | if (downloadLinks.length > 0) {
181 | console.log('SABconnect++ NZBIndex: First download link href:', downloadLinks.first().attr('href'));
182 | }
183 |
184 | // Modern NZBIndex uses UUID-based download links like /download/uuid.nzb
185 | addIconsWithFallback({
186 | linkSelector: 'a[href*="/download/"][href$=".nzb"], a[href*="get?"]',
187 | iconClass: 'addSABnzbdOnClick',
188 | processedAttr: 'x-nzbpatched',
189 | clickHandler: addToSABnzbdFromNzbindex
190 | });
191 |
192 | console.log('SABconnect++ NZBIndex: Total icons added:', $('a.addSABnzbdOnClick').length);
193 | }
194 |
195 | insertionInProgress = false;
196 | }
197 | }
198 | // Initial run
199 | DOMNodeInserted();
200 |
201 | // Use MutationObserver instead of deprecated DOMNodeInserted
202 | var observer = new MutationObserver(function(mutations) {
203 | DOMNodeInserted();
204 | });
205 |
206 | // Start observing
207 | observer.observe(document.body, {
208 | childList: true,
209 | subtree: true
210 | });
211 |
212 | // Also try after page is fully loaded
213 | $(window).on('load', function() {
214 | setTimeout(function() {
215 | console.log('SABconnect++ NZBIndex: Checking after window load...');
216 | DOMNodeInserted();
217 | }, 500);
218 | });
219 |
220 | // And periodically for dynamic content
221 | var retryCount = 0;
222 | var retryInterval = setInterval(function() {
223 | if (retryCount++ > 10) {
224 | clearInterval(retryInterval);
225 | return;
226 | }
227 | if ($('a.addSABnzbdOnClick').length === 0) {
228 | console.log('SABconnect++ NZBIndex: Retry #' + retryCount);
229 | DOMNodeInserted();
230 | } else {
231 | clearInterval(retryInterval);
232 | }
233 | }, 1000);
234 | });
--------------------------------------------------------------------------------
/scripts/pages/manifest.js:
--------------------------------------------------------------------------------
1 | window.manifest = {
2 | 'name': 'SABConnect++ Settings',
3 | 'icon': 'images/addon_icon.svg',
4 | 'alignment': [
5 | [
6 | //'profile',
7 | 'profile_name',
8 | 'sabnzbd_url',
9 | 'sabnzbd_api_key',
10 | 'sabnzbd_username',
11 | 'sabnzbd_password'
12 | ],
13 | [
14 | 'config_hard_coded_category',
15 | 'config_default_category'
16 | ],
17 | [
18 | 'config_refresh_rate',
19 | 'config_notification_timeout'
20 | ],
21 | // Provider alignment pairs for 2-column layout
22 | [
23 | 'provider_binsearch',
24 | 'provider_bintube'
25 | ],
26 | [
27 | 'provider_dognzb',
28 | 'provider_fanzub'
29 | ],
30 | [
31 | 'provider_animezb',
32 | 'provider_animenzb'
33 | ],
34 | [
35 | 'provider_nzbclub',
36 | 'provider_nzbindex'
37 | ],
38 | [
39 | 'provider_yubse',
40 | 'provider_omgwtfnzbs'
41 | ],
42 | [
43 | 'provider_nzbrss',
44 | 'provider_usenet4ever'
45 | ],
46 | ],
47 | 'settings': [
48 | // Connections Tab
49 | {
50 | 'tab': 'Connection',
51 | 'group': 'Connection Information',
52 | 'name': 'profile_popup',
53 | 'type': 'popupButton',//'listBox',
54 | 'label': 'Connection Profile:'
55 | },
56 | {
57 | 'tab': 'Connection',
58 | 'group': 'Connection Information',
59 | 'name': 'profile_create',
60 | 'type': 'button',
61 | 'text': 'Create'
62 | },
63 | {
64 | 'tab': 'Connection',
65 | 'group': 'Connection Information',
66 | 'name': 'profile_duplicate',
67 | 'type': 'button',
68 | 'text': 'Duplicate'
69 | },
70 | {
71 | 'tab': 'Connection',
72 | 'group': 'Connection Information',
73 | 'name': 'profile_delete',
74 | 'type': 'button',
75 | 'text': 'Delete'
76 | },
77 | {
78 | 'tab': 'Connection',
79 | 'group': 'Connection Information',
80 | 'name': 'profile_name',
81 | 'type': 'text',
82 | 'label': 'Profile Name:'
83 | },
84 | {
85 | 'tab': 'Connection',
86 | 'group': 'Connection Information',
87 | 'name': 'sabnzbd_url',
88 | 'type': 'text',
89 | 'label': 'SABnzbd URL:'
90 | },
91 | {
92 | 'tab': 'Connection',
93 | 'group': 'Connection Information',
94 | 'name': 'sabnzbd_api_key',
95 | 'type': 'text',
96 | 'label': 'SABnzbd API Key:'
97 | },
98 | {
99 | 'tab': 'Connection',
100 | 'group': 'Connection Information',
101 | 'name': 'sabnzbd_username',
102 | 'type': 'text',
103 | 'label': 'SABnzbd Username:'
104 | },
105 | {
106 | 'tab': 'Connection',
107 | 'group': 'Connection Information',
108 | 'name': 'sabnzbd_password',
109 | 'type': 'text',
110 | 'label': 'SABnzbd Password:',
111 | 'masked': true
112 | },
113 | {
114 | 'tab': 'Connection',
115 | 'group': 'Connection Information',
116 | 'name': 'test_connection',
117 | 'type': 'button',
118 | 'text': 'Test Connection'
119 | },
120 |
121 | // Providers Tab, NZB downloading section
122 | {
123 | 'tab': 'Providers',
124 | 'group': '1-Click NZB downloading',
125 | 'name': 'provider_description',
126 | 'type': 'description',
127 | 'sync': true,
128 | 'text':
129 | 'This is a list of currently supported providers. For each provider that is\
130 | enabled, 1-click NZB downloading will be supported. This sends the NZB to SABnzbd\
131 | so it can be downloaded.'
132 | },
133 | {
134 | 'tab': 'Providers',
135 | 'group': '1-Click NZB downloading',
136 | 'name': 'provider_binsearch',
137 | 'type': 'checkbox',
138 | 'sync': true,
139 | 'label': 'binsearch.info'
140 | },
141 | {
142 | 'tab': 'Providers',
143 | 'group': '1-Click NZB downloading',
144 | 'name': 'provider_bintube',
145 | 'type': 'checkbox',
146 | 'sync': true,
147 | 'label': 'bintube.com'
148 | },
149 | {
150 | 'tab': 'Providers',
151 | 'group': '1-Click NZB downloading',
152 | 'name': 'provider_dognzb',
153 | 'type': 'checkbox',
154 | 'sync': true,
155 | 'label': 'dognzb.cr'
156 | },
157 | {
158 | 'tab': 'Providers',
159 | 'group': '1-Click NZB downloading',
160 | 'name': 'provider_fanzub',
161 | 'type': 'checkbox',
162 | 'sync': true,
163 | 'label': 'fanzub.com'
164 | },
165 | {
166 | 'tab': 'Providers',
167 | 'group': '1-Click NZB downloading',
168 | 'name': 'provider_animezb',
169 | 'type': 'checkbox',
170 | 'sync': true,
171 | 'label': 'animezb.com'
172 | },
173 | {
174 | 'tab': 'Providers',
175 | 'group': '1-Click NZB downloading',
176 | 'name': 'provider_animenzb',
177 | 'type': 'checkbox',
178 | 'sync': true,
179 | 'label': 'animenzb.com'
180 | },
181 | {
182 | 'tab': 'Providers',
183 | 'group': '1-Click NZB downloading',
184 | 'name': 'provider_nzbclub',
185 | 'type': 'checkbox',
186 | 'sync': true,
187 | 'label': 'nzbclub.com'
188 | },
189 | {
190 | 'tab': 'Providers',
191 | 'group': '1-Click NZB downloading',
192 | 'name': 'provider_nzbindex',
193 | 'type': 'checkbox',
194 | 'sync': true,
195 | 'label': 'nzbindex.com'
196 | },
197 | {
198 | 'tab': 'Providers',
199 | 'group': '1-Click NZB downloading',
200 | 'name': 'provider_yubse',
201 | 'type': 'checkbox',
202 | 'sync': true,
203 | 'label': 'yubse.com'
204 | },
205 | {
206 | 'tab': 'Providers',
207 | 'group': '1-Click NZB downloading',
208 | 'name': 'provider_omgwtfnzbs',
209 | 'type': 'checkbox',
210 | 'sync': true,
211 | 'label': 'omgwtfnzbs.org'
212 | },
213 | {
214 | 'tab': 'Providers',
215 | 'group': '1-Click NZB downloading',
216 | 'name': 'provider_nzbrss',
217 | 'type': 'checkbox',
218 | 'sync': true,
219 | 'label': 'nzb-rss.com'
220 | },
221 | {
222 | 'tab': 'Providers',
223 | 'group': '1-Click NZB downloading',
224 | 'name': 'provider_usenet4ever',
225 | 'type': 'checkbox',
226 | 'sync': true,
227 | 'label': 'usenet4ever.info'
228 | },
229 | {
230 | 'tab': 'Providers',
231 | 'group': '1-Click NZB downloading',
232 | 'name': 'provider_newznab',
233 | 'type': 'text',
234 | 'sync': true,
235 | 'label': 'Newznab Providers (comma-separated list of hostnames):'
236 | },
237 |
238 | // Providers Tab, display section
239 | {
240 | 'tab': 'Providers',
241 | 'group': 'Display Options',
242 | 'name': 'use_name_binsearch',
243 | 'type': 'checkbox',
244 | 'sync': true,
245 | 'label': 'binsearch.info: Use display name instead of NZB filename.'
246 | },
247 | {
248 | 'tab': 'Providers',
249 | 'group': 'Display Options',
250 | 'name': 'use_name_nzbindex',
251 | 'type': 'checkbox',
252 | 'sync': true,
253 | 'label': 'nzbindex.com: Use display name instead of NZB filename.'
254 | },
255 | {
256 | 'tab': 'Providers',
257 | 'group': 'Display Options',
258 | 'name': 'use_name_yubse',
259 | 'type': 'checkbox',
260 | 'sync': true,
261 | 'label': 'yubse.com: Use display name instead of NZB filename.'
262 | },
263 |
264 | // Configuration Tab, General section
265 | {
266 | 'tab': 'Configuration',
267 | 'group': 'General',
268 | 'name': 'config_refresh_rate',
269 | 'type': 'popupButton',
270 | 'label': 'Refresh Rate:',
271 | 'options':
272 | [
273 | [ 0, 'Disabled' ],
274 | [ 1, '1 second' ],
275 | [ 2, '2 seconds' ],
276 | [ 4, '4 seconds' ],
277 | [ 8, '8 seconds' ],
278 | [ 15, '15 seconds' ],
279 | [ 30, '30 seconds' ],
280 | [ 60, '1 minute' ],
281 | [ 120, '2 minutes' ],
282 | [ 300, '5 minutes' ],
283 | [ 900, '15 minutes' ],
284 | [ 1800, '30 minutes' ],
285 | [ 3600, '1 hour' ],
286 | [ 7200, '2 hours' ],
287 | [ 14400, '4 hours' ]
288 | ]
289 | },
290 | {
291 | 'tab': 'Configuration',
292 | 'group': 'General',
293 | 'name': 'config_notification_timeout',
294 | 'type': 'popupButton',
295 | 'label': 'Notification Timeout:',
296 | 'options':
297 | [
298 | [ 0, 'Disabled' ],
299 | [ 1, '1 second' ],
300 | [ 2, '2 seconds' ],
301 | [ 3, '3 seconds' ],
302 | [ 5, '5 seconds' ],
303 | [ 10, '10 seconds' ],
304 | [ 15, '15 seconds' ],
305 | [ 20, '20 seconds' ],
306 | [ 30, '30 seconds' ],
307 | [ 45, '45 seconds' ],
308 | [ 60, '1 minute' ],
309 | [ 120, '2 minutes' ],
310 | [ 300, '5 minutes' ],
311 | [ 1800, '30 minutes' ],
312 | [ 3600, '1 hour' ]
313 | ]
314 | },
315 | {
316 | 'tab': 'Configuration',
317 | 'group': 'General',
318 | 'name': 'config_enable_graph',
319 | 'type': 'checkbox',
320 | 'label': 'Enable Graph'
321 | },
322 | {
323 | 'tab': 'Configuration',
324 | 'group': 'General',
325 | 'name': 'config_enable_context_menu',
326 | 'type': 'checkbox',
327 | 'label': 'Enable Context Menu'
328 | },
329 | {
330 | 'tab': 'Configuration',
331 | 'group': 'General',
332 | 'name': 'config_enable_notifications',
333 | 'type': 'checkbox',
334 | 'label': 'Enable Notifications'
335 | },
336 | {
337 | 'tab': 'Configuration',
338 | 'group': 'General',
339 | 'name': 'config_enable_automatic_authentication',
340 | 'type': 'checkbox',
341 | 'label': 'Enable Automatic Authentication (insecure)'
342 | },
343 | {
344 | 'tab': 'Configuration',
345 | 'group': 'General',
346 | 'name': 'config_enable_automatic_detection',
347 | 'type': 'checkbox',
348 | 'label': 'Enable Automatic Newznab Detection (can slow down some sites)'
349 | },
350 | {
351 | 'tab': 'Configuration',
352 | 'group': 'General',
353 | 'name': 'config_reset',
354 | 'type': 'button',
355 | 'label': 'Click to reset all settings to default:',
356 | 'text': 'Reset'
357 | },
358 |
359 | // Configuration Tab, Categories section
360 | {
361 | 'tab': 'Configuration',
362 | 'group': 'Categories',
363 | 'name': 'config_category_summary',
364 | 'type': 'description',
365 | 'text':
366 | 'Below are the category settings provided by SABconnect++. For more information regarding categories,\
367 | please read the wiki page.'
368 | },
369 | {
370 | 'tab': 'Configuration',
371 | 'group': 'Categories',
372 | 'name': 'config_ignore_categories',
373 | 'type': 'checkbox',
374 | 'label': 'Do not attempt to pass category names, forcing SABnzbd to use group names in the NZB instead.\
375 | This will ignore all of the following category options. Note that some indexers (newznab, etc.)\
376 | embed category names in the nzb itself.'
377 | },
378 | {
379 | 'tab': 'Configuration',
380 | 'group': 'Categories',
381 | 'name': 'config_use_user_categories',
382 | 'type': 'checkbox',
383 | 'label': 'Override the category with the one chosen from the menu. The list of categories is loaded by SABnzbd.'
384 | },
385 | {
386 | 'tab': 'Configuration',
387 | 'group': 'Categories',
388 | 'name': 'config_use_category_header',
389 | 'type': 'checkbox',
390 | 'label': 'Use X-DNZB-Category HTTP header instead of auto-categorization\
391 | on supported sites (NZBs.org, Newzbin).'
392 | },
393 | {
394 | 'tab': 'Configuration',
395 | 'group': 'Categories',
396 | 'name': 'config_hard_coded_category',
397 | 'type': 'text',
398 | 'label': 'Hard-coded Category:'
399 | },
400 | {
401 | 'tab': 'Configuration',
402 | 'group': 'Categories',
403 | 'name': 'config_category_desc1',
404 | 'type': 'description',
405 | 'text':
406 | 'Will use this category for all downloads sent to SABnzbd.'
407 | },
408 | {
409 | 'tab': 'Configuration',
410 | 'group': 'Categories',
411 | 'name': 'config_default_category',
412 | 'type': 'text',
413 | 'label': 'Default Category:'
414 | },
415 | {
416 | 'tab': 'Configuration',
417 | 'group': 'Categories',
418 | 'name': 'config_category_desc2',
419 | 'type': 'description',
420 | 'text':
421 | 'Will use this category if no category can be obtained from the NZB index site.'
422 | },
423 | ]
424 | };
425 |
--------------------------------------------------------------------------------
/third_party/jqplot/jqplot.pointLabels.js:
--------------------------------------------------------------------------------
1 | /**
2 | * jqPlot
3 | * Pure JavaScript plotting plugin using jQuery
4 | *
5 | * Version: 1.0.9
6 | * Revision: d96a669
7 | *
8 | * Copyright (c) 2009-2016 Chris Leonello
9 | * jqPlot is currently available for use in all personal or commercial projects
10 | * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
11 | * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
12 | * choose the license that best suits your project and use it accordingly.
13 | *
14 | * Although not required, the author would appreciate an email letting him
15 | * know of any substantial use of jqPlot. You can reach the author at:
16 | * chris at jqplot dot com or see http://www.jqplot.com/info.php .
17 | *
18 | * If you are feeling kind and generous, consider supporting the project by
19 | * making a donation at: http://www.jqplot.com/donate.php .
20 | *
21 | * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
22 | *
23 | * version 2007.04.27
24 | * author Ash Searle
25 | * http://hexmen.com/blog/2007/03/printf-sprintf/
26 | * http://hexmen.com/js/sprintf.js
27 | * The author (Ash Searle) has placed this code in the public domain:
28 | * "This code is unrestricted: you are free to use it however you like."
29 | *
30 | */
31 | (function($) {
32 |
33 | /**
34 | * Class: $.jqplot.PointLabels
35 | * Plugin for putting labels at the data points.
36 | *
37 | * To use this plugin, include the js
38 | * file in your source:
39 | *
40 | * >
41 | *
42 | * By default, the last value in the data ponit array in the data series is used
43 | * for the label. For most series renderers, extra data can be added to the
44 | * data point arrays and the last value will be used as the label.
45 | *
46 | * For instance,
47 | * this series:
48 | *
49 | * > [[1,4], [3,5], [7,2]]
50 | *
51 | * Would, by default, use the y values in the labels.
52 | * Extra data can be added to the series like so:
53 | *
54 | * > [[1,4,'mid'], [3 5,'hi'], [7,2,'low']]
55 | *
56 | * And now the point labels would be 'mid', 'low', and 'hi'.
57 | *
58 | * Options to the point labels and a custom labels array can be passed into the
59 | * "pointLabels" option on the series option like so:
60 | *
61 | * > series:[{pointLabels:{
62 | * > labels:['mid', 'hi', 'low'],
63 | * > location:'se',
64 | * > ypadding: 12
65 | * > }
66 | * > }]
67 | *
68 | * A custom labels array in the options takes precendence over any labels
69 | * in the series data. If you have a custom labels array in the options,
70 | * but still want to use values from the series array as labels, set the
71 | * "labelsFromSeries" option to true.
72 | *
73 | * By default, html entities (<, >, etc.) are escaped in point labels.
74 | * If you want to include actual html markup in the labels,
75 | * set the "escapeHTML" option to false.
76 | *
77 | */
78 | $.jqplot.PointLabels = function(options) {
79 | // Group: Properties
80 | //
81 | // prop: show
82 | // show the labels or not.
83 | this.show = $.jqplot.config.enablePlugins;
84 | // prop: location
85 | // compass location where to position the label around the point.
86 | // 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
87 | this.location = 'n';
88 | // prop: labelsFromSeries
89 | // true to use labels within data point arrays.
90 | this.labelsFromSeries = false;
91 | // prop: seriesLabelIndex
92 | // array index for location of labels within data point arrays.
93 | // if null, will use the last element of the data point array.
94 | this.seriesLabelIndex = null;
95 | // prop: labels
96 | // array of arrays of labels, one array for each series.
97 | this.labels = [];
98 | // actual labels that will get displayed.
99 | // needed to preserve user specified labels in labels array.
100 | this._labels = [];
101 | // prop: stackedValue
102 | // true to display value as stacked in a stacked plot.
103 | // no effect if labels is specified.
104 | this.stackedValue = false;
105 | // prop: ypadding
106 | // vertical padding in pixels between point and label
107 | this.ypadding = 6;
108 | // prop: xpadding
109 | // horizontal padding in pixels between point and label
110 | this.xpadding = 6;
111 | // prop: escapeHTML
112 | // true to escape html entities in the labels.
113 | // If you want to include markup in the labels, set to false.
114 | this.escapeHTML = true;
115 | // prop: edgeTolerance
116 | // Number of pixels that the label must be away from an axis
117 | // boundary in order to be drawn. Negative values will allow overlap
118 | // with the grid boundaries.
119 | this.edgeTolerance = -5;
120 | // prop: formatter
121 | // A class of a formatter for the tick text. sprintf by default.
122 | this.formatter = $.jqplot.DefaultTickFormatter;
123 | // prop: formatString
124 | // string passed to the formatter.
125 | this.formatString = '';
126 | // prop: hideZeros
127 | // true to not show a label for a value which is 0.
128 | this.hideZeros = false;
129 | this._elems = [];
130 |
131 | $.extend(true, this, options);
132 | };
133 |
134 | var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
135 | var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
136 | var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
137 |
138 | // called with scope of a series
139 | $.jqplot.PointLabels.init = function (target, data, seriesDefaults, opts, plot){
140 | var options = $.extend(true, {}, seriesDefaults, opts);
141 | options.pointLabels = options.pointLabels || {};
142 | if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal' && !options.pointLabels.location) {
143 | options.pointLabels.location = 'e';
144 | }
145 | // add a pointLabels attribute to the series plugins
146 | this.plugins.pointLabels = new $.jqplot.PointLabels(options.pointLabels);
147 | this.plugins.pointLabels.setLabels.call(this);
148 | };
149 |
150 | // called with scope of series
151 | $.jqplot.PointLabels.prototype.setLabels = function() {
152 | var p = this.plugins.pointLabels;
153 | var labelIdx;
154 | if (p.seriesLabelIndex != null) {
155 | labelIdx = p.seriesLabelIndex;
156 | }
157 | else if (this.renderer.constructor === $.jqplot.BarRenderer && this.barDirection === 'horizontal') {
158 | labelIdx = (this._plotData[0].length < 3) ? 0 : this._plotData[0].length -1;
159 | }
160 | else {
161 | labelIdx = (this._plotData.length === 0) ? 0 : this._plotData[0].length -1;
162 | }
163 | p._labels = [];
164 | if (p.labels.length === 0 || p.labelsFromSeries) {
165 | if (p.stackedValue) {
166 | if (this._plotData.length && this._plotData[0].length){
167 | // var idx = p.seriesLabelIndex || this._plotData[0].length -1;
168 | for (var i=0; i scr || elb + et > scb) {
360 | elem.remove();
361 | }
362 |
363 | elem = null;
364 | helem = null;
365 | }
366 |
367 | // finally, animate them if the series is animated
368 | // if (this.renderer.animation && this.renderer.animation._supported && this.renderer.animation.show && plot._drawCount < 2) {
369 | // var sel = '.jqplot-point-label.jqplot-series-'+this.index;
370 | // $(sel).hide();
371 | // $(sel).fadeIn(1000);
372 | // }
373 |
374 | }
375 | };
376 |
377 | $.jqplot.postSeriesInitHooks.push($.jqplot.PointLabels.init);
378 | $.jqplot.postDrawSeriesHooks.push($.jqplot.PointLabels.draw);
379 | })(jQuery);
380 |
--------------------------------------------------------------------------------
/scripts/content/newznab.js:
--------------------------------------------------------------------------------
1 |
2 | (function() { // Encapsulate
3 |
4 | var queryString = '',
5 | oneClickImgTag = '
',
6 | ignoreCats,
7 | linkRelAlternate = $('link[rel=alternate]').attr('href');
8 |
9 | // Debug authentication parameters
10 | var uid = $('[name=UID]').val();
11 | var rsstoken = $('[name=RSSTOKEN]').val();
12 | console.log('SABconnect++ Newznab: Authentication debug:');
13 | console.log('SABconnect++ Newznab: UID found:', uid ? 'Yes (' + uid + ')' : 'No');
14 | console.log('SABconnect++ Newznab: RSSTOKEN found:', rsstoken ? 'Yes (' + rsstoken.substring(0, 10) + '...)' : 'No');
15 | console.log('SABconnect++ Newznab: Link rel=alternate:', linkRelAlternate);
16 |
17 | // Build authentication queryString only if we have valid authentication
18 | if (uid && rsstoken) {
19 | queryString = '?i=' + uid + '&r=' + rsstoken + '&del=1';
20 | console.log('SABconnect++ Newznab: Using standard form auth');
21 | } else if (linkRelAlternate) {
22 | var found = linkRelAlternate.match(/([\?&]i=.+$)/);
23 | if (found) {
24 | queryString = '?' + found[0];
25 | console.log('SABconnect++ Newznab: Using queryString from link rel=alternate:', queryString);
26 | }
27 | } else {
28 | console.log('SABconnect++ Newznab: Standard auth not found, trying alternatives...');
29 |
30 | // Try to find auth in meta tags
31 | var metaUID = $('meta[name="uid"]').attr('content');
32 | var metaToken = $('meta[name="rsstoken"]').attr('content');
33 |
34 | // Try to find auth in data attributes
35 | var dataUID = $('[data-uid]').attr('data-uid');
36 | var dataToken = $('[data-rsstoken]').attr('data-rsstoken');
37 |
38 | // Try to find auth in the page source/scripts
39 | var scriptUID = null, scriptToken = null;
40 | $('script').each(function() {
41 | var scriptText = $(this).text();
42 | var uidMatch = scriptText.match(/uid['":\s]*['"]([^'"]+)['"]/i);
43 | var tokenMatch = scriptText.match(/(?:rsstoken|apikey|token)['":\s]*['"]([^'"]+)['"]/i);
44 | if (uidMatch) scriptUID = uidMatch[1];
45 | if (tokenMatch) scriptToken = tokenMatch[1];
46 | });
47 |
48 | console.log('SABconnect++ Newznab: Alternative auth search:');
49 | console.log('SABconnect++ Newznab: Meta UID:', metaUID);
50 | console.log('SABconnect++ Newznab: Meta Token:', metaToken);
51 | console.log('SABconnect++ Newznab: Data UID:', dataUID);
52 | console.log('SABconnect++ Newznab: Data Token:', dataToken);
53 | console.log('SABconnect++ Newznab: Script UID:', scriptUID);
54 | console.log('SABconnect++ Newznab: Script Token:', scriptToken);
55 |
56 | // Use alternative auth if found
57 | if (metaUID && metaToken) {
58 | queryString = '?i=' + metaUID + '&r=' + metaToken + '&del=1';
59 | console.log('SABconnect++ Newznab: Using meta auth');
60 | } else if (dataUID && dataToken) {
61 | queryString = '?i=' + dataUID + '&r=' + dataToken + '&del=1';
62 | console.log('SABconnect++ Newznab: Using data auth');
63 | } else if (scriptUID && scriptToken) {
64 | queryString = '?i=' + scriptUID + '&r=' + scriptToken + '&del=1';
65 | console.log('SABconnect++ Newznab: Using script auth');
66 | } else {
67 | console.log('SABconnect++ Newznab: No authentication found - will use URLs as-is');
68 | }
69 | }
70 |
71 | console.log('SABconnect++ Newznab: Final queryString:', queryString);
72 |
73 | GetSetting('config_ignore_categories', function( value ) {
74 | ignoreCats = value;
75 | });
76 |
77 | function addMany(e) {
78 |
79 | var $button = $(this);
80 |
81 | $button.val("Sending...");
82 |
83 | $('#browsetable ' + e.data.selector).each(function() {
84 | addOne($(this).closest('tr'));
85 | });
86 |
87 | $button.val('Sent to SABnzbd!');
88 |
89 | setTimeout(function() {
90 | $button.val('Send to SABnzbd');
91 | }, 4000);
92 |
93 | return false;
94 | }
95 |
96 | // $tr is a table row from #browsetable (one nzb)
97 | // http://nzbs.org/getnzb/abef39dde2baaad865adecb95e5eb26d
98 | function addOne($tr) {
99 |
100 | var $anchor = $tr.find('a.addSABnzbd');
101 |
102 | // Set the image to an in-progress image
103 | var img = chrome.runtime.getURL('images/content_icon_fetching.png');
104 |
105 | if ($($anchor.get(0)).find('img').length > 0) {
106 | $($anchor.get(0)).find('img').attr("src", img);
107 | }
108 |
109 | var category = null;
110 | if (!ignoreCats) {
111 | $tr.parent().find('tr:nth-child(1)').find('th').each(function(i) {
112 | if (!category && $.trim($(this).text().toLowerCase()) === 'category') {
113 | category = $.trim($tr.find(`td:nth-child(${i + 1}) a`).text().match(/^\s*([^> -]+)/)[1]);
114 | }
115 | });
116 | }
117 |
118 | addToSABnzbd(
119 | $anchor.get(0),
120 | $anchor.attr('href') + queryString,
121 | 'addurl',
122 | null,
123 | category
124 | );
125 | }
126 |
127 | Initialize('newznab', null, function() {
128 | console.log('SABconnect++ Newznab: Script initialized');
129 | console.log('SABconnect++ Newznab: Found existing addSABnzbd links:', $('a.addSABnzbd').length);
130 |
131 | if ($('a.addSABnzbd').length == 0) {
132 | // Cover view: Loop through each #coverstable and #browselongtable row and add a one click link next to the download link
133 | console.log('SABconnect++ Newznab: Checking cover view tables');
134 | console.log('SABconnect++ Newznab: #coverstable rows:', $('#coverstable > tbody > tr:gt(0)').length);
135 | console.log('SABconnect++ Newznab: #browselongtable rows:', $('#browselongtable > tbody > tr:gt(0)').length);
136 | $.merge(
137 | $('#coverstable > tbody > tr:gt(0)'),
138 | $('#browselongtable > tbody > tr:gt(0)')
139 | ).each(function() {
140 | var $tr = $(this);
141 |
142 | $("div.icon_nzb", $tr).each(function() {
143 | var href = $("a", this).attr("href");
144 | $(this).before('')
145 | });
146 |
147 | $tr.find('a.addSABnzbd')
148 | .on('click', function() {
149 | addOne($(this).closest('tr'));
150 | return false;
151 | })
152 | ;
153 | });
154 | }
155 |
156 | if ($('a.addSABnzbd').length == 0) {
157 | // List view: Loop through all the td.check items and add a one-click link next the nearby title
158 | console.log('SABconnect++ Newznab: Checking list view');
159 | console.log('SABconnect++ Newznab: td.check elements:', $('td.check').length);
160 | $('td.check').each(function() {
161 | var $tr = $(this).parent(),
162 | href = $tr.find('.icon_nzb a').attr('href') || $tr.find('a.icon_nzb').attr('href');
163 |
164 | $tr.find('a.title').parent()
165 | .prepend('' + oneClickImgTag + '')
166 | ;
167 |
168 | $tr.find('a.addSABnzbd')
169 | .on('click', function() {
170 | addOne($(this).closest('tr'));
171 | return false;
172 | })
173 | ;
174 | });
175 | }
176 |
177 | if ($('a.addSABnzbd').length == 0) {
178 | // Details view (etc.)
179 | console.log('SABconnect++ Newznab: Checking details view');
180 | console.log('SABconnect++ Newznab: div.icon_nzb elements:', $('div.icon_nzb').length);
181 | $('div.icon_nzb').each(function() {
182 | var $tr = $(this),
183 | href = $(this).children("a").attr('href');
184 |
185 | $tr
186 | .before('')
187 | ;
188 |
189 | $tr.parent().find('a.addSABnzbd')
190 | .on('click', function() {
191 | addOne($(this).closest('tr'));
192 | return false;
193 | })
194 | ;
195 | });
196 | }
197 |
198 | if ($('a.addSABnzbdDetails').length == 0) {
199 | // Details view: Find the download buttons, and prepend a sabnzbd button
200 | $('table#detailstable .icon_nzb').parents('td').each(function() {
201 | var $tdWithButtons = $(this),
202 | href = $tdWithButtons.find('.icon_nzb a').attr('href'),
203 | oneClickButton = '';
204 |
205 | $('#infohead').append(oneClickButton);
206 |
207 | $tdWithButtons.prepend(oneClickButton)
208 | .find('a.addSABnzbdDetails')
209 | .add('#infohead .addSABnzbdDetails')
210 | .on('click', function() {
211 | var category = null;
212 | if ($('table#detailstable a[href^="/browse?t="]')) {
213 | category = $.trim($('table#detailstable a[href^="/browse?t="]').text().match(/^\s*([^< -]+)/)[1]);
214 | }
215 | addToSABnzbd(
216 | this,
217 | $(this).attr('href') + queryString,
218 | 'addurl',
219 | null,
220 | category
221 | );
222 | return false;
223 | })
224 | ;
225 | });
226 | }
227 |
228 | if ($('[value="Send to SABnzbd"]').length == 0) {
229 | // List view: add a button above the list to send selected NZBs to SAB
230 | $('input.nzb_multi_operations_cart')
231 | .after(' ')
232 | .siblings('input.multiDownload')
233 | .on('click', {selector: 'td input:checked'}, addMany)
234 | ;
235 | }
236 |
237 | // Cart page: add a button above the list to send all NZBs to SAB
238 | if ($('#main h1').text() === 'My Cart' || $('.container h1').text() === 'My Cart') {
239 | $('.nzb_multi_operations')
240 | .append('')
241 | .find('input.cartDownload')
242 | .on('click', {selector: 'tr:gt(0)'}, addMany)
243 | ;
244 | }
245 |
246 | // Fallback: Try to find download links more broadly if no icons were added
247 | if ($('a.addSABnzbd').length == 0 && $('a.addSABnzbdDetails').length == 0) {
248 | console.log('SABconnect++ Newznab: No icons added with standard selectors. Trying fallback...');
249 | console.log('SABconnect++ Newznab: Current hostname:', window.location.hostname);
250 | console.log('SABconnect++ Newznab: Current URL:', window.location.href);
251 | console.log('SABconnect++ Newznab: Looking for links with selectors: a[href*="/getnzb/"], a[href*=".nzb"]');
252 | console.log('SABconnect++ Newznab: Found links:', $('a[href*="/getnzb/"], a[href*=".nzb"]').length);
253 |
254 | // Try multiple selector patterns common to different Newznab sites
255 | var selectorPatterns = [
256 | 'a[href*="/getnzb/"], a[href*=".nzb"]', // Standard patterns
257 | 'a[href*="/download/"], a[href*="get="]', // Alternative patterns
258 | 'a[href*="/api"][href*="t=get"]', // API-based downloads
259 | 'a[title*="Download"], a[title*="Get NZB"]', // Title-based
260 | 'a[href*="/nzb/"], a[href*="nzb="]', // NZB parameter patterns
261 | 'a[href*="download"][href*="nzb"]' // Generic download+nzb pattern
262 | ];
263 |
264 | var addedCount = 0;
265 |
266 | // Try each pattern until we find matching links
267 | for (var i = 0; i < selectorPatterns.length && addedCount === 0; i++) {
268 | var pattern = selectorPatterns[i];
269 | var foundLinks = $(pattern).length;
270 | console.log('SABconnect++ Newznab: Trying pattern', i + 1, ':', pattern, '- Found', foundLinks, 'links');
271 |
272 | if (foundLinks > 0) {
273 | addedCount = addIconsWithFallback({
274 | linkSelector: pattern,
275 | iconClass: 'addSABnzbd',
276 | clickHandler: function(e) {
277 | e.preventDefault();
278 | var originalUrl = $(this).attr('href');
279 | var finalUrl = queryString ? originalUrl + queryString : originalUrl;
280 |
281 | // For problematic sites like nzbstars.com, try downloading the file directly
282 | if (window.location.hostname.includes('nzbstars.com')) {
283 | e.preventDefault();
284 | e.stopPropagation();
285 |
286 | fetch(originalUrl)
287 | .then(response => {
288 | if (!response.ok) {
289 | throw new Error(`HTTP error! status: ${response.status}`);
290 | }
291 |
292 | const responseHeaders = response.headers;
293 | return response.text().then(text => ({ text, responseHeaders }));
294 | })
295 | .then(({ text, responseHeaders }) => {
296 | // Validate that this is actually an NZB file
297 | if (!text.includes(']*subject="([^"]*)"[^>]*>/);
320 | if (fileMatch && fileMatch[1]) {
321 | nzbFilename = fileMatch[1]
322 | .replace(/[<>:"/\\|?*]/g, '_')
323 | .replace(/^Re:\s*/i, '')
324 | .substring(0, 100)
325 | .trim();
326 | if (!nzbFilename.endsWith('.nzb')) {
327 | nzbFilename += '.nzb';
328 | }
329 | }
330 | }
331 |
332 | // Fallback: try to extract from URL (for nzbstars.com messageid)
333 | if (nzbFilename === 'download.nzb') {
334 | const urlParams = new URLSearchParams(originalUrl.split('?')[1]);
335 | const messageId = urlParams.get('messageid');
336 | if (messageId) {
337 | nzbFilename = `nzbstars_${messageId.replace(/[<>:"/\\|?*@]/g, '_')}.nzb`;
338 | }
339 | }
340 |
341 | // Convert to blob for upload
342 | const blob = new Blob([text], { type: 'application/x-nzb' });
343 |
344 | // Convert blob to base64 for transmission to service worker
345 | const reader = new FileReader();
346 | reader.onload = function() {
347 | const base64data = reader.result.split(',')[1];
348 |
349 | // Send the NZB file data to service worker
350 | chrome.runtime.sendMessage({
351 | action: 'uploadNZBFile',
352 | fileData: base64data,
353 | filename: nzbFilename
354 | });
355 | };
356 | reader.readAsDataURL(blob);
357 | })
358 | .catch(error => {
359 | // Fallback to regular approach
360 | var $row = $(this).closest('tr');
361 | if ($row.length > 0) {
362 | addOne($row);
363 | } else {
364 | addToSABnzbd(this, finalUrl, 'addurl', null, null);
365 | }
366 | });
367 | return false;
368 | }
369 |
370 | var $row = $(this).closest('tr');
371 | if ($row.length > 0) {
372 | addOne($row);
373 | } else {
374 | // If not in a table row, handle differently
375 | addToSABnzbd(
376 | this,
377 | finalUrl,
378 | 'addurl',
379 | null,
380 | null
381 | );
382 | }
383 | return false;
384 | }
385 | });
386 |
387 | if (addedCount > 0) {
388 | console.log('SABconnect++ Newznab: Successfully added', addedCount, 'icons using pattern:', pattern);
389 | break;
390 | }
391 | }
392 | }
393 |
394 | // Debug info only if still no icons
395 | if (addedCount === 0) {
396 | console.log('SABconnect++ Newznab: Debug info - still no icons added:');
397 | console.log('SABconnect++ Newznab: All tables:', $('table').length);
398 | console.log('SABconnect++ Newznab: All links with .nzb in href:', $('a[href*=".nzb"]').length);
399 | console.log('SABconnect++ Newznab: All links with /getnzb/ in href:', $('a[href*="/getnzb/"]').length);
400 |
401 | // Sample some links to understand the site structure
402 | console.log('SABconnect++ Newznab: Sample links on page:');
403 | $('a').slice(0, 20).each(function(index, element) {
404 | var href = $(element).attr('href');
405 | var title = $(element).attr('title');
406 | var text = $(element).text().trim();
407 | if (href && (href.indexOf('download') !== -1 || href.indexOf('nzb') !== -1 || href.indexOf('get') !== -1)) {
408 | console.log('SABconnect++ Newznab: Potential download link:', { href: href, title: title, text: text });
409 | }
410 | });
411 | }
412 |
413 | console.log('SABconnect++ Newznab: FINAL - Total SAB icons added:', $('a.addSABnzbd').length);
414 | } else {
415 | console.log('SABconnect++ Newznab: Successfully added icons:', $('a.addSABnzbd').length);
416 | }
417 | });
418 |
419 | })();
420 |
--------------------------------------------------------------------------------
/scripts/pages/background.js:
--------------------------------------------------------------------------------
1 | // List of sites that send the X-DNZB-Category HTTP header
2 | //var category_header_sites = ['nzbs.org', 'newzbin2.es', 'newzxxx.com'];
3 | var category_header_sites = [];
4 |
5 | var defaultSettings = {
6 | sabnzbd_url: 'http://localhost:8080/',
7 | sabnzbd_api_key: '',
8 | sabnzbd_username: '',
9 | provider_binsearch: true,
10 | provider_bintube: true,
11 | provider_dognzb: true,
12 | provider_fanzub: true,
13 | provider_animezb: true,
14 | provider_animenzb: true,
15 | provider_nzbclub: true,
16 | provider_nzbindex: true,
17 | provider_yubse: true,
18 | provider_omgwtfnzbs: true,
19 | provider_nzbrss: true,
20 | provider_newznab: 'your_newznab.com, some_other_newznab.com',
21 | provider_usenet4ever: true,
22 | use_name_binsearch: true,
23 | use_name_nzbindex: true,
24 | use_name_yubse: true,
25 | config_refresh_rate: 15,
26 | config_enable_graph: true,
27 | config_enable_context_menu: true,
28 | config_enable_notifications: true,
29 | config_notification_timeout: 10,
30 | config_ignore_categories: false,
31 | config_use_user_categories: false,
32 | config_use_category_header: false,
33 | config_hard_coded_category: '',
34 | config_default_category: '',
35 | config_enable_automatic_authentication: true,
36 | config_enable_automatic_detection: true,
37 | profiles: {},
38 | first_profile_initialized: false,
39 | active_category: '*',
40 | settings_synced: false
41 | };
42 |
43 | var notification_container = [];
44 |
45 | var store = new StoreClass( 'settings', defaultSettings, undefined, storeReady_background );
46 |
47 | function storeReady_background() {
48 | startTimer();
49 |
50 | initializeBackgroundPage();
51 |
52 | //context_menu.js
53 | SetupContextMenu();
54 | }
55 |
56 | function resetSettings()
57 | {
58 | store.fromObject( defaultSettings );
59 | }
60 |
61 | //file size formatter - takes an input in bytes
62 | function fileSizes(value, decimals)
63 | {
64 | if(decimals == null) decimals = 2;
65 | var kb = value / 1024;
66 | var mb = value / 1048576;
67 | var gb = value / 1073741824;
68 | var tb = value / 1099511627776; // 1024^4
69 |
70 | if (tb >= 1) {
71 | return tb.toFixed(decimals) + "TB";
72 | } else if (gb >= 1) {
73 | return gb.toFixed(decimals) + "GB";
74 | } else if (mb >= 1) {
75 | return mb.toFixed(decimals) + "MB";
76 | } else {
77 | return kb.toFixed(decimals) + "KB";
78 | }
79 | }
80 |
81 | function updateBadge( data )
82 | {
83 | if( data ) {
84 | var slots = data.queue.noofslots;
85 | var badge = {};
86 | if( !slots ) {
87 | badge.text = '';
88 | } else {
89 | badge.text = slots.toString();
90 | }
91 | chrome.browserAction.setBadgeText(badge);
92 | }
93 | }
94 |
95 | function isDownloading( kbpersec )
96 | {
97 | return kbpersec && parseFloat( kbpersec ) > 1;
98 | }
99 |
100 | function updateBackground( data )
101 | {
102 | if( data ) {
103 | var badgeColor = {}
104 | if( isDownloading( data.queue.kbpersec ) ) {
105 | badgeColor.color = new Array(0, 213, 7, 100);
106 | } else {
107 | badgeColor.color = new Array(255, 0, 0, 100);
108 | }
109 |
110 | chrome.browserAction.setBadgeBackgroundColor(badgeColor)
111 | }
112 | }
113 |
114 | function updateSpeedLog( data )
115 | {
116 | var speedlog = [];
117 |
118 | var speedlogData = getPref( 'speedlog' );
119 | if( speedlogData )
120 | {
121 | speedlog = JSON.parse(getPref('speedlog'));
122 |
123 | // Only allow 10 values, if at our limit, remove the first value (oldest)
124 | while( speedlog.length >= 10 ) {
125 | speedlog.shift();
126 | }
127 | }
128 |
129 | speedlog.push( data ? parseFloat( data.queue.kbpersec ) : 0 );
130 | setPref( 'speedlog', JSON.stringify( speedlog ) );
131 | }
132 |
133 | function displayNotificationCallback( data )
134 | {
135 | // Return early if data is null, which can happen if we
136 | // have invalid connection information in settings and
137 | // we actually can't establish a connection with sabnzbd.
138 | if( !data || data.error ) {
139 | return;
140 | }
141 |
142 | data.history.slots.forEach(function(entry)
143 | {
144 | // console.log(entry);
145 | var key = 'past_dl-' + entry.name + '-' + entry.bytes;
146 | if (typeof localStorage[key] == 'undefined')
147 | {
148 |
149 | // Only notify when post-processing is complete
150 | if (entry.action_line == '')
151 | {
152 | chrome.notifications.onButtonClicked.addListener(function(notId, buttonIndex) {
153 | });
154 | if (entry.fail_message != '')
155 | {
156 | var fail_msg = entry.fail_message.split('<')[0];
157 | var notification = chrome.notifications.create(
158 | entry.name,
159 | {
160 | type: 'basic',
161 | iconUrl: 'images/addon_icon.svg',
162 | title: 'Download Failed',
163 | message: entry.name + ': ' + fail_msg,
164 | buttons: [{ title: entry.storage }]
165 | },
166 | );
167 | } else {
168 | var notification = chrome.notifications.create(
169 | entry.name,
170 | {
171 | type: 'basic',
172 | iconUrl: 'images/addon_icon.svg',
173 | title: 'Download Complete',
174 | message: entry.name,
175 | buttons: [{ title: entry.storage }]
176 | },
177 | );
178 | }
179 |
180 | localStorage[key] = true;
181 | }
182 | }
183 | });
184 | }
185 |
186 | function displayNotificationCycle ( )
187 | {
188 | var al = notification_container.length, notification_temp = [];
189 | for ( var i = 0; i < al; i++ )
190 | {
191 | var notif = notification_container[i];
192 | var notification = notif.shift();
193 | var timeout = notif.shift();
194 | if ( timeout <= 1 )
195 | {
196 | console.log( "Notification timeout reached, killing popup" );
197 | notification.cancel();
198 | }
199 | else
200 | {
201 | var notif_temp = [ notification, timeout - 1 ];
202 | notification_temp.push( notif_temp );
203 | }
204 | }
205 | notification_container = notification_temp;
206 | setTimeout( displayNotificationCycle, 1000 );
207 | }
208 | setTimeout( displayNotificationCycle, 1000 );
209 |
210 |
211 | function fetchInfoSuccess( data, quickUpdate, callback )
212 | {
213 | if( !data || data.error ) {
214 | setPref( 'error', data ? data.error : 'Success with no data?' );
215 |
216 | if( callback ) {
217 | callback();
218 | }
219 |
220 | return;
221 | }
222 |
223 | // This will remove the error
224 | // Will cause problems if the error pref is used elsewhere to report other errors
225 | setPref('error', '');
226 | setPref('timeleft', data ? data.queue.timeleft : '0' );
227 | if(data) {
228 | // Convert to bytes
229 | var bytesPerSec = parseFloat(data.queue.kbpersec)*1024;
230 | var speed = data.queue.speed + 'B/s';
231 | } else {
232 | var speed = '-';
233 | }
234 | setPref('speed', speed);
235 |
236 | // Do not run this on a quickUpdate (unscheduled refresh)
237 | if( !quickUpdate ) {
238 | updateSpeedLog( data );
239 | }
240 |
241 | var queueSize = '';
242 | if( data && data.queue.mbleft > 0 ) {
243 | // Convert to bytes
244 | var bytesInMegabyte = 1048576;
245 | var bytesLeft = data.queue.mbleft * bytesInMegabyte;
246 | var queueSize = fileSizes(bytesLeft);
247 | }
248 | setPref('sizeleft', queueSize);
249 |
250 | setPref('queue', data ? JSON.stringify(data.queue.slots) : '' );
251 |
252 | setPref( 'status', data ? data.queue.status : '' );
253 | setPref( 'paused', data ? data.queue.paused : '' );
254 | if(data.queue.paused) {
255 | setPref("pause_int", data.queue.pause_int);
256 | }
257 |
258 | updateBadge( data );
259 | updateBackground( data );
260 |
261 | if( callback ) {
262 | callback();
263 | }
264 | }
265 |
266 | function fetchInfoError( XMLHttpRequest, textStatus, errorThrown, callback ) {
267 | setPref('error', 'Could not connect to SABnzbd - Check it is running, the details in this plugin\'s settings are correct and that you are running at least SABnzbd version 0.5!');
268 |
269 | if( callback ) {
270 | callback();
271 | }
272 | }
273 |
274 | function testConnection( profileValues, callback )
275 | {
276 | fetchInfo( true, callback, profileValues );
277 | }
278 |
279 | /**
280 | * quickUpdate
281 | * If set to true, will not update the graph ect, currently used when a queue item has been moved/deleted in order to refresh the queue list
282 | */
283 | function fetchInfo( quickUpdate, callback, profileValues )
284 | {
285 | var params = {
286 | mode: 'queue',
287 | limit: '5'
288 | };
289 |
290 | sendSabRequest(
291 | params,
292 | function(data) { fetchInfoSuccess( data, quickUpdate, callback ) },
293 | function(_1, _2, _3) { fetchInfoError( _1, _2, _3, callback ) },
294 | profileValues
295 | );
296 | }
297 |
298 | function displayNotifications()
299 | {
300 | if( store.get('config_enable_notifications') === true ) {
301 | var params = {
302 | mode: 'history',
303 | limit: '10'
304 | };
305 |
306 | sendSabRequest( params, displayNotificationCallback );
307 | }
308 | }
309 |
310 | function setMaxSpeed( speed, success_callback, error_callback )
311 | {
312 | var params = {
313 | mode: 'config',
314 | name: 'speedlimit',
315 | value: speed
316 | };
317 |
318 | sendSabRequest( params, success_callback, error_callback );
319 | }
320 |
321 | function getMaxSpeed( success_callback )
322 | {
323 | var params = {
324 | mode: 'config',
325 | name: 'get_speedlimit'
326 | };
327 |
328 | sendSabRequest( params, success_callback );
329 | }
330 |
331 | function sendSabRequest( params, success_callback, error_callback, profileValues )
332 | {
333 | var profile = profileValues || activeProfile();
334 |
335 | var sabApiUrl = constructApiUrl( profile );
336 | var data = constructApiPost( profile );
337 | data.output = 'json';
338 |
339 | $.ajax({
340 | type: "GET",
341 | url: sabApiUrl,
342 | data: combine( data, params ),
343 | username: profile.username,
344 | password: profile.password,
345 | dataType: 'json',
346 | success: success_callback,
347 | error: error_callback
348 | });
349 | }
350 |
351 | function updatePopup()
352 | {
353 | var views = chrome.runtime.getViews({ type: "popup" });
354 | if( views.length == 1 )
355 | {
356 | var popup = views[0];
357 | popup.reDrawPopup();
358 | }
359 | }
360 |
361 | function refresh( quick, callback )
362 | {
363 | if( !callback ) {
364 | callback = updatePopup;
365 | }
366 |
367 | fetchInfo( quick, callback );
368 |
369 | if( !quick ) {
370 | displayNotifications();
371 | }
372 | }
373 |
374 | var gTimer;
375 |
376 | function restartTimer()
377 | {
378 | if( gTimer ) {
379 | clearInterval( gTimer );
380 | }
381 |
382 | startTimer();
383 | }
384 |
385 | function startTimer()
386 | {
387 | var refreshRate = getRefreshRate();
388 | if( refreshRate > 0 ) {
389 | gTimer = setInterval( refresh, refreshRate );
390 | }
391 | else {
392 | console.log("Will NOT refresh from SABnzbd automatically (refresh disabled in options).");
393 | }
394 | }
395 |
396 | function DoesSiteSupportCatHeader( nzburl )
397 | {
398 | var supported = false;
399 | for (var i=0; i