├── 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 | 2 | 3 | -------------------------------------------------------------------------------- /images/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /images/pause-item.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /images/delete-item.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /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 |
32 |
33 |

34 |
35 |
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 |
44 |
45 |

46 |
47 |
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('

sorry bro, nix gefunden :( schau selbst

'); 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 | SABconnect++ 31 |
32 | 48 | 51 | 54 | 57 |
58 |
59 | 62 | 65 |
66 |
67 |
68 |
69 | PROFILE 70 | 71 |
72 |
73 |
74 | SPEED LIMIT 75 |
76 | 77 | 78 | % 79 | 80 |
81 |
82 | 83 |
84 |
85 |
86 | 87 |
88 |
89 |
90 |
91 | 92 |
93 |
94 | 95 |
96 |
97 | 98 | 99 |
100 | 101 |
102 | 103 |
    104 | 105 | 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