├── .gitignore ├── README.md ├── WHATauto.py ├── button.user.js ├── changelog.txt ├── cookies └── bitmetv.cookie ├── credentials.conf.example ├── custom.conf.example ├── db.py ├── filters.conf.example ├── globals.py ├── irclib.py ├── regex.conf ├── reports.conf.example ├── setup.conf.example └── torrentparser.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | __pycache__ 21 | 22 | # Installer logs 23 | pip-log.txt 24 | 25 | # Unit test / coverage reports 26 | .coverage 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | 38 | ## pyWhatAuto specifics: 39 | *.db 40 | logs/ 41 | cookies/ 42 | 43 | ## pyWhatAuto config files 44 | credentials.conf 45 | custom.conf 46 | filters.conf 47 | reports.conf 48 | setup.conf 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyWhatAuto 2 | 3 | An IRC bot that can auto-download torrents, download on command, and provides a download button via a browser userscript. 4 | 5 | Desperately in need of a rewrite. 6 | 7 | # Setup instructions 8 | 9 | ## Get the files 10 | 11 | ### Git 12 | 13 | Just use `git clone https://github.com/jimrollenhagen/pywhatauto.git` at the location you want to run your bot from. 14 | 15 | ### Zip 16 | 17 | Grab the zip file from `https://github.com/jimrollenhagen/pywhatauto/archive/master.zip`. 18 | 19 | #### Extract the files 20 | 21 | If you are on a linux machine just run `unzip master.zip` and you'll end up with a directory called `pywhatauto-master`. 22 | 23 | ## Edit the configuration files 24 | 25 | ### setup.conf 26 | 27 | Copy setup.conf.example to setup.conf 28 | 29 | Setup the watch directory: 30 | 31 | torrentDir=/home/username/watch/ 32 | 33 | Setup the drive you want to show freespace on (usually the dir you download to): 34 | 35 | drive=/home/username/downloads/ 36 | 37 | Set the size of your download drive (in GB): 38 | 39 | limit=500 40 | 41 | Set the percentage of your storage to use (stops downloading torrents when drive is this full): 42 | 43 | freePercent=5 44 | 45 | Set log to 0 if you don't want to keep logs: 46 | 47 | log=0 48 | 49 | Set chatter to 0 if you don't want to see channel traffic: 50 | 51 | chatter=0 52 | 53 | Under [sites], enable the networks you want to watch, 1 is on, 0 is off. 54 | 55 | `whatcd=0` to `whatcd=1` for example. 56 | 57 | If you want to use the download button make sure to change the password and port for the web interface. For a more in-depth explanation of the download button read the "Download button" section below. 58 | 59 | ### credentials.conf 60 | 61 | Copy credentials.conf.example to credentials.conf 62 | 63 | In this file you'll have to add the credentials to the various sites and setup your bot on the IRC network. We'll take a look on how this'll look for What.CD: 64 | 65 | [whatcd] 66 | nickowner=99999@yourusername.yourclass.what.cd 67 | chanfilter=#whatbot 68 | username=yoursiteusername 69 | password=yoursitepassword 70 | botNick=yourusername|pyWHATbot 71 | ircKey=yourirckey 72 | nickServPass=yournickservpass 73 | watch=/home/username/watch/what 74 | 75 | `nickowner` 76 | Run /whois yourusername on the IRC network and you'll see the hostmask. That's what we need. 77 | 78 | `chanfilter` 79 | You don't have to change that. That's the channel where you can "talk" to your bot and issue commands. 80 | 81 | `username` 82 | Your What.CD username. 83 | 84 | `password` 85 | Your What.CD password. 86 | 87 | `botNick` 88 | The nick your bot will join the #whatbot channel and the announce channels. 89 | 90 | `ircKey` 91 | Your IRC key you setup in your What.CD user profile. 92 | 93 | `nickServPass` 94 | Your bot's NickServ password. You have to make sure your bot's IRC nick is registered. You can do that by changing your nick and using the `REGISTER` command like this: 95 | 96 | /nick yourusername|pyWHATbot 97 | /msg nickserv register yourpassword your@email.com 98 | /whois yourusername|pyWHATbot 99 | 100 | If you want to group your bot nick and your main account so you can use the same NickServ password for both accounts you'll have to run the following commands: 101 | 102 | /nick yourusername|pyWHATbot 103 | /nickserv group yourusername 104 | 105 | 106 | `watch` 107 | This is the directory you want the bot to store the `.torrent` files it snatched. This should also be your torrent client's watch directory. 108 | 109 | ### filters.conf 110 | 111 | Copy filters.conf.example to filters.conf 112 | 113 | This is the file where you tell your bot which releases it should grab for you. 114 | 115 | To get an idea which options are possible please read the `filters.conf` included with this release. 116 | 117 | **Examples:** 118 | 119 | Here's an example to grab all of the 100% Log/Cue Flacs from the year 2014, if you wanted to download regardless of the year, just remove the line `year=2014`: 120 | 121 | [WHAT-2014FLAC] 122 | site=whatcd 123 | filterType=music 124 | active=1 125 | source=CD 126 | quality=Lossless 127 | format=FLAC 128 | cue=1 129 | log=1 130 | logper=100 131 | year=2014 132 | watch=/home/username/watch/what/ 133 | 134 | If you are just interested in specific artists you could add them like this. Note that regex must be prefixed with `@`: 135 | 136 | [WHAT-JAMS] 137 | site=whatcd 138 | filterType=music 139 | active=1 140 | artist=Widespread Panic 141 | Phish 142 | Furthur 143 | Trey Anastasio 144 | @Umphrey(.+)s McGee 145 | @Jerry Joseph.* 146 | format=FLAC 147 | MP3 148 | year=2014 149 | watch=/home/username/watch/what/ 150 | 151 | # Start the bot 152 | 153 | To start the bot just run `python WHATauto.py` which is located in the pywhatauto directory. Make sure the python version you are using to execute it is < than 3 (Check with `python --version`). If your operating system defaults to a newer version you'll have to manually specify the version and start it like this: `python2.7 WHATauto.py` 154 | 155 | Currently your bot will close the connection to the IRC network if you close your terminal. To keep it running in the background even if you close your terminal it's recommended to use something like `screen` or `tmux`. 156 | 157 | To use screen type `screen -S nameofmyscreen` and press return. In the new screen you just opened just navigate to the place where your `WHATauto.py` is located and then run it like explained above. Now we'll have to detach the window with `ctrl` + `a` + `d`. You'll be back at the place where we started before you typed the `screen` command. If you want to list your screens running in the background just type `screen -ls` and you'll get a list of screens which'll look like this: 158 | 159 | There are screens on: 160 | 8304.test (Detached) 161 | 27582.pywhatauto (Detached) 162 | 2 Sockets in /home/dewey/.screen. 163 | 164 | To attach to a detached screen just type `screen -r 27582` and you'll be back at your bot's screen. 165 | 166 | # Sending commands to the bot 167 | 168 | If you want to get informations from your bot or issue download commands just join the same channel your bot is sitting in on the IRC net work and type `%help`. Your bot should now respond to your commands. 169 | 170 | # Download button 171 | 172 | If you want to use the pyWA download button to send torrents to your bot via your browser you'll need to install the user script located in your `pywhatauto-master` directory. It's called `button.user.js`. 173 | 174 | The older (original) version can be found at [userscripts-mirror.org](https://userscripts-mirror.org/scripts/show/85457). 175 | 176 | Once the script is installed, you will need to either visit your settings page on a gazelle based site and configure the script (All fields auto save) or change the `var weblink` line at the top of script. 177 | 178 | ![Settings preview](http://i.imgur.com/t1vPmSq.png) 179 | 180 | The port and password of the web interface are configured in the `setup.conf`. The relevant bits looks like this: 181 | 182 | 183 | ;WEBUI password 184 | password=youwouldliketoknowthisone 185 | ;WEBUI port 186 | port=1337 187 | 188 | The hostname can be an IP address or domain pointing towards your server. 189 | 190 | # Support 191 | 192 | If you are having troubles getting your bot to work just join `#whatbot` on the PTH IRC network. 193 | -------------------------------------------------------------------------------- /button.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name What.cd Whatauto link creator 3 | // @namespace test.com 4 | // @author blubbablubb 5 | // @description Userscript to add a download button next to the torrent on the following sites. 6 | // @include http*://*what.cd/* 7 | // @include http*://*broadcasthe.net/* 8 | // @include http*://*fux0r.eu/* 9 | // @include http*://*passthepopcorn.me/* 10 | // @include http*://*tehconnection.eu/* 11 | // @include https://*waffles.ch/* 12 | // @include http*://*hdbits.org/* 13 | // @include http*://*bitmetv.org/* 14 | // @include http*://*sceneaccess.eu/* 15 | // @include http*://*awesome-hd.me/* 16 | // @include http*://*bit-hdtv.com/* 17 | // @include http*://*x264.me/* 18 | // @include http*://*gazellegames.net/* 19 | // @include http*://*redacted.ch/* 20 | // @include http*://*apollo.rip/* 21 | // @include http*://*morethan.tv/* 22 | // @include http*://*notwhat.cd/* 23 | // @version 0.0.22 24 | // @date 2014-17-11 25 | // @grant GM_getValue 26 | // @grant GM_setValue 27 | // @grant GM_notification 28 | // ==/UserScript== 29 | 30 | // EDIT THE FOLLOWING LINE WITH YOUR HOST (OR IP) + PORT WHICH YOU HAVE SELECTED IN setup.conf IN pyWHATAUTO 31 | var weblink = 'http://example.com:1337/dl.pywa?pass=youwouldliketoknowthisone'; 32 | 33 | if (/https?.*?notwhat\.cd.*/.test(document.URL)) { 34 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 35 | var devider = ' | '; 36 | var site = "notwhat"; 37 | } else if (/https?.*?broadcasthe\.net.*/.test(document.URL)) { 38 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 39 | var divider = ' | '; 40 | var site = "broadcasthenet"; 41 | } else if (/https?.*?fux0r\.eu.*/.test(document.URL)) { 42 | var linkregex = /torrents.php\?action=download.*?id=(\d+).*?/i; 43 | var divider = ' : '; 44 | var site = "fux0r"; 45 | } else if (/https?.*?passthepopcorn\.me.*/.test(document.URL)) { 46 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 47 | var divider = ' | '; 48 | var site = "passthepopcorn"; 49 | } else if (/https?.*?tehconnection\.eu.*/.test(document.URL)) { 50 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 51 | var divider = ' | '; 52 | var site = "tehconnection"; 53 | } else if (/https?.*?waffles\.ch.*/.test(document.URL)) { 54 | var linkregex = /.*?download.php\/\d+\/(\d+)\/(.*?)\.torrent\?passkey.*/i; 55 | var divider = ' | '; 56 | var site = "waffles"; 57 | var includename = "2"; 58 | } else if (/https?.*?hdbits\.org.*/.test(document.URL)) { 59 | var linkregex = /download.php\?id=(\d+).*/i; 60 | var divider = ' | '; 61 | var site = "hdbits"; 62 | // var includename = "2"; 63 | } else if (/https?.*?bitmetv\.org.*/.test(document.URL)) { 64 | var linkregex = /.*?download.php\/(\d+)\/(.*?)\.torrent$/i; 65 | var divider = ' | '; 66 | var site = "bitmetv"; 67 | var includename = "2"; 68 | } else if (/https?.*?sceneaccess\.eu.*/.test(document.URL)) { 69 | var linkregex = /downloadbig.php\?id=(\d+).*?/i; 70 | var divider = ' | '; 71 | var site = "sceneaccess"; 72 | } else if (/https?.*?awesome-hd\.me.*/.test(document.URL)) { 73 | var linkregex = /torrents.php\?action=download.*?id=(\d+).*?/i; 74 | var divider = ' | '; 75 | var site = "awesomehd"; 76 | } else if (/https?.*?bit-hdtv\.com.*/.test(document.URL)) { 77 | var linkregex = /.*?download.php\/(\d+)\/(.*?)\.torrent.*/i; 78 | var divider = ' | '; 79 | var site = "bithdtv"; 80 | var includename = "2"; 81 | } else if (/https?.*?x264\.me.*/.test(document.URL)) { 82 | var linkregex = /.*?download.php\/(\d+)\/(.*?)\.torrent.*/i; 83 | var divider = ' | '; 84 | var site = "bithdtv"; 85 | var includename = "2"; 86 | } else if (/https?.*?gazellegames\.net.*/.test(document.URL)) { 87 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 88 | var divider = ' | '; 89 | var site = "gazellegames"; 90 | } else if (/https?.*?redacted\.ch.*/.test(document.URL)) { 91 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 92 | var divider = ' | '; 93 | var site = "redacted"; 94 | } else if (/https?.*?apollo\.rip.*/.test(document.URL)) { 95 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 96 | var divider = ' | '; 97 | var site = "apollo"; 98 | } else if (/https?.*?morethan\.tv.*/.test(document.URL)) { 99 | var linkregex = /torrents\.php\?action=download.*?id=(\d+).*?authkey=.*?torrent_pass=(?=([a-z0-9]+))\2(?!&)/i; 100 | var devider = ' | '; 101 | var site = "morethantv"; 102 | } else if (/https?.*?what\.cd.*/.test(document.URL)) { 103 | var linkregex = /torrents.php\?action=download.*?id=(\d+).*/i; 104 | var divider = ' | '; 105 | var site = "whatcd"; 106 | } else { 107 | alert("You have found a bug. Go and tell blubba!"); 108 | } 109 | 110 | var settings = getSettings(); 111 | var settingsPage = window.location.href.match('user.php\\?action=edit&userid='); 112 | var top10Page = window.location.href.match('top10.php'); 113 | var linkLabel = "pWA"; 114 | if (top10Page) { 115 | linkLabel = "[" + linkLabel + "]"; 116 | } 117 | 118 | if ((settings.pass && settings.url && settings.port) || checkWeblink()) { 119 | alltorrents = []; 120 | for (var i = 0; i < document.links.length; i++) { 121 | alltorrents.push(document.links[i]); 122 | } 123 | 124 | for (var i = 0; i < alltorrents.length; i++) { 125 | if (linkregex.exec(alltorrents[i])) { 126 | if (includename == 1) { 127 | id = RegExp.$2; 128 | name = RegExp.$1; 129 | } else if (includename == 2) { 130 | id = RegExp.$1; 131 | name = RegExp.$2; 132 | } else { 133 | id = RegExp.$1; 134 | } 135 | createLink(alltorrents[i], id, name); 136 | } 137 | } 138 | } 139 | 140 | if (settingsPage) { 141 | appendSettings(); 142 | document.getElementById('pywhatauto_settings_pass').addEventListener('change', saveSettings, false); 143 | document.getElementById('pywhatauto_settings_url').addEventListener('change', saveSettings, false); 144 | document.getElementById('pywhatauto_settings_port').addEventListener('change', saveSettings, false); 145 | } 146 | 147 | if (!settings && !settingsPage && !checkWeblink()) { 148 | GM_notification({ 149 | text: 'Missing configuration\nVisit user settings page on gazelle site or edit weblink line at the top of script to configure', 150 | title: 'pyWHATauto:', 151 | timeout: 10000, 152 | }); 153 | } 154 | 155 | function createLink(linkelement, id, name) { 156 | var link = document.createElement("pyWA"); 157 | link.appendChild(document.createElement("a")); 158 | link.firstChild.appendChild(document.createTextNode(linkLabel)); 159 | link.appendChild(document.createTextNode(divider)); 160 | 161 | if (name) { 162 | if(checkWeblink()) { 163 | link.firstChild.href=weblink+"&name="+name+"&site="+site+"&id="+id; 164 | } else { 165 | link.firstChild.href = settings.url + ":" + settings.port + "/dl.pywa?pass=" + settings.pass + "&name=" + name + "&site=" + site + "&id=" + id; 166 | } 167 | } else { 168 | if(checkWeblink()) { 169 | link.firstChild.href=weblink+"&site="+site+"&id="+id; 170 | } else { 171 | link.firstChild.href = settings.url + ":" + settings.port + "/dl.pywa?pass=" + settings.pass + "&site=" + site + "&id=" + id; 172 | } 173 | } 174 | link.firstChild.target = "_blank"; 175 | link.firstChild.title = "Direct download to pyWHATauto"; 176 | linkelement.parentNode.insertBefore(link, linkelement); 177 | } 178 | 179 | function appendSettings() { 180 | var container = document.getElementsByClassName('main_column')[0]; 181 | var lastTable = container.lastElementChild; 182 | var settingsHTML = '\n\n'; 183 | settingsHTML += '\n\n'; 184 | settingsHTML += '\n'; 185 | settingsHTML += '\n'; 186 | settingsHTML += '\n'; 187 | settingsHTML += '\n
pyWHATauto Settings (autosaved)
Password
Hostname
Port
'; 188 | lastTable.insertAdjacentHTML('afterend', settingsHTML); 189 | 190 | var sectionsElem = document.querySelectorAll('#settings_sections > ul')[0]; 191 | sectionsHTML = '

pyWHATauto

'; 192 | var li = document.createElement('li'); 193 | li.innerHTML = sectionsHTML; 194 | sectionsElem.insertBefore(li, document.querySelectorAll('#settings_sections > ul > li:nth-child(10)')[0]); 195 | } 196 | 197 | function getSettings() { 198 | var pass = GM_getValue('pass', ''); 199 | var port = GM_getValue('port', ''); 200 | var url = GM_getValue('url', ''); 201 | if (pass && url && port) { 202 | return { 203 | pass: pass, 204 | url: url, 205 | port: port 206 | }; 207 | } else { 208 | return false; 209 | } 210 | } 211 | 212 | function saveSettings() { 213 | var elem = document.getElementById(this.id); 214 | var setting = this.id.replace('pywhatauto_settings_', ''); 215 | var border = elem.style.border; 216 | GM_setValue(setting, elem.value); 217 | if (GM_getValue(setting) === elem.value) { 218 | elem.style.border = '1px solid green'; 219 | setTimeout(function () { 220 | elem.style.border = border; 221 | }, 2000); 222 | } else { 223 | elem.style.border = '1px solid red'; 224 | } 225 | } 226 | 227 | function checkWeblink() { 228 | if(weblink && weblink !== 'http://example.com:1337/dl.pywa?pass=youwouldliketoknowthisone'){ 229 | return true; 230 | } 231 | return false; 232 | } 233 | -------------------------------------------------------------------------------- /changelog.txt: -------------------------------------------------------------------------------- 1 | Changelog: 2 | 3 | 4.7.14 Version 1.74 4 | *Change user agent to be unique to pywa 5 | 6 | 1.18.14 Version 1.73 7 | *Change user agent to fix what.cd http 8 | *Require passkey for PTP login 9 | 10 | 8.14.11 Version 1.72 11 | *Complete rewrite of the download functions. Much more efficient, download speed increased significantly. Download function is now properly threaded. If anything breaks let me know. 12 | *Download function know returns more specific error cases. 13 | *Webserver now runs multithreaded. This speeds up simoultaneous downloads significantly. 14 | *Webserver gives more info on downloads. 15 | *If no webserver password is set, the bot now sets a random one. 16 | *Removed: Webserver database output, since its broken. If anyone actually used this, let me know! 17 | *File web.py is now obsolete. The webserver is integrated in WHATauto.py 18 | *Download function for hdbits changed. Username and password are only required for downloading, no passkey needed. Requires setting 'Lock session to IP' to No under 'Profile'-'Security' on hdbits. 19 | *Fix: Automatic downloads which do not match the size filter or have raised a download error increase the downloaded count in %stats. 20 | *Fix: reconnect function fails under certain conditions 21 | *Exceptions are now logged to file for easier debugging 22 | *Changed output formatting for easier readability 23 | *The logfile for the most recent session is being written into logs/pyWALog.txt (as well as to the date formated path) to allow for quicker access. It gets overwritten on each restart. 24 | *Fix: the reports are not logged properly 25 | *Added message level 'warning', for important but messages which are not errors 26 | 27 | 8.10.11 Version 1.71 28 | *Quick bugfix of some typos 29 | 30 | 8.10.11 Version 1.7 31 | *Blubba's first major release 32 | *Probably half of the code has been rewritten 33 | *The bots _should_ now be stable - they detect and handle network errors and timeouts now (no more need to %disconnect and %connect because the bot had silently timed out) 34 | *Two new functions, %whoisall and %whoami: %whoisall sends a whois to all announce bots on all currently connected networks and returns the findings, checking their idents with those found in regex.conf. This should simplify the process of determining faults in the config file. %whoami sends a whois on each connected network for the botnick. It checks if the bot is currently in the announce channel, and reports back to the user. 35 | *Most user functions have been rewriten, mostly to stop running into Exceptions. If you find a way to break the bot, let me know! 36 | *A lot of sites where added. The connect functions are now checking more closely if enough variables in credentials.conf are set. 37 | *the keys for each site in credentials.conf have changed for quite a few sites. If you want to use a site properly, you will need to fill them all out. 38 | *Fixed a major security issue. 39 | *Runs on python 2.5, 2.6 and 2.7 40 | *Option to bind the webserver to a specific ip. This is set under 'webserverip' in setup.conf 41 | *Lots of other things have changed. 42 | 43 | 9.9.10 Version 1.29 44 | *Added a fun command. %ragequit 45 | *Fixed the waffles download url. Waffles will NOT work unless you upgrade to 1.29! 46 | *Spruced up the download routine a bit to handle filenames passed by the GM script 47 | *Updated TC's regex. You must add the "irckey" field in credentials.conf for it to work. 48 | *You no longer need filtertype= on sites that only have one announce type. For now, the only site that requires filtertype is what.cd 49 | *Bitgamer login fixed. 50 | *Fixed the version checking before %update. 51 | *Added capturing of naughty announcements that don't fit my regex. The only way this will help is if your bot is ALSO on the what network! 52 | *Fixed PTP's regex, and switched to #announce since HAL isn't in #announce-ssl anymore. Also, the available filter options have changed, since they are using a new announce format. 53 | 54 | 9.6.10 Version 1.28 55 | *Changed the behavior of threads being created/deleted for parsing announcements. Nothing should be missed now! (Thanks to mavin for pointing it out!). 56 | *Fixed the "size" filter to handle torrents that are created in a non-traditional way (BMTV!). 57 | *Changed the "size" output level. It is now at "info" and not "filter". 58 | *Added the sqliteDB for creating a history of announcements! 59 | *Added a website (VERY basic) that shows that last 100 announcements for every site. http://:/index.pywa 60 | *Added support for the Unicode in many circumstances. 61 | *Added the ability to have the bot download a torrent via http://:/dl.pywa?password=&site=&id= 62 | *The above, integrated with a Greasemonkey script, allows you to one-click download to your torrent box ;) 63 | *Added deli.sh. 64 | *Added awesome-hd. 65 | *Re-Added karagarga. 66 | 67 | 8.12.10 Version 1.27 68 | *Fix for What.cd's mis-spelling of "attachment" so that torrent names are back to names and not numbers. 69 | *Fix for BTN's regex. "releasename" is now a filter option (not all BTN's releases have it though). 70 | *Fixed the all_tags filter option. It should work now! HOWEVER, all_tags DOES NOT use regex since it uses a special matching comparison. In all_tags, using "cience" WILL match "science". DO NOT PREPEND ANY TAGS IN ALL_TAGS WITH A @! 71 | *Added the SIZE limiter to ALL sites! (Thanks paperk!). Use like "size=small limit, upper limit". THIS IS IN MEGABYTES. If you use GB in any other filters, like on SCC, you have to convert to MB! 72 | 73 | 8.1.10 Version 1.26: 74 | *Fixed a bug that %sites to crash the bot when using custom.conf (ty pred!) 75 | *STR regex fixed. 76 | *Added karagarga. 77 | *Started adding in the freepercent routine to stop downloads. Not finished. 78 | *Added in a fix for what.cd to include REFERER when logging in. 79 | *Added in the USER-AGENT when logging in. Just for kicks, right? 80 | 81 | 7.9.10 Version 1.25: 82 | *Changed when pyWA uses credentials to connect to an irc server. If, under credentials, you specify ircusesignon=1 then pyWA will send the username/password combo when connecting to the server (usually used for bouncers). If it is not present, then it will just connect anonymously. 83 | *Added the ability for pyWA to handle several torrent sites all on the same physical IRC network. For this to work, all the sites MUST specify the EXACT SAME irc server in the address field. However, there are a few quirks with this implementation. If both sites specify the same channel to join in #chanfilter, then it will respond to commands twice! 84 | *Added TheBox, TheEmpire, (both require a preset cookie) and HDBits (Thanks predakanga!) For this to work you need to add the sites to setup.conf, and the relevant sections to credentials.conf! 85 | *Fixed a bug when disconnecting a network when not using the alias name. (Thanks predakanga!) 86 | 87 | 7.7.10 Version 1.24: 88 | *Updated TL's regex to add 'group' as a filter option. 89 | *Changed PTP's announce channel and format. 90 | *%version now shows pyWA and regex.conf versions. 91 | *Waffles support added, which required re-tooling the download routines. 92 | 93 | 6.16.10 Version 1.23: 94 | *Fixed a bug in loading site info, and sped it up. 95 | *Added error checking for incorrect cookie formats. 96 | *Fixed PTP regex 97 | *Fixed BMTV regex 98 | *Added the %update command to update your regex.conf file quickly. This does version checks as well to make sure you are using a compatible version of pyWA. 99 | *Re-organized how pubMSGs are handled so I can debug easier. 100 | *Added some more attitude to the bot. 101 | *Added catches in several places for blank nickowners. If a nickowner is blank, it will warn you at startup, and won't respond on that network. 102 | *Hopefully fixed an issue where toggling filters manually would cause tons of new threads to open and never close. (TY PHERSICK!) 103 | 104 | 6.8.10 Version 1.22: 105 | *Fixed the whois command so it clears previous lookups 106 | *Fixed the manual download command when dealing with aliases/custom.conf. How did I miss this? 107 | *Fixed another bug in man downloading across sites. 108 | *Fixed HFU auto-downloading 109 | *Fixed a LOCK.release() bug on manual downloads that failed. 110 | *Re-wrote how authentication is configured. A new conf option in regex.conf called "authstring" specifies the line to use to request access to the announce channel. This can be used in custom.conf to add your own networks. 111 | *Changed the regex.conf for PTP 112 | *Fixed where the credentials for irc servers that require username/password is pulled from. 113 | *Added a socket timeout, hopefully to stop pyWA from waiting forever for a site to respond and keeping open tons of threads. This is just a shot in the dark. 114 | 115 | 5.25.10 Version 1.21: 116 | *Fixed toggle option matching 117 | *Added cross-network channel joins/parts, as well as automatic pre-pending of the "#" if it is missing. 118 | *Fixed the delay on downloads to only apply to automatic downloads. Somewhere along the line I switched the comparison. 119 | *Added a cross-network WHOIS command so you can see if you are in a channel or not. 120 | 121 | 5.18.10 Version 1.2: 122 | *Fixed BTN. Nickowner changed. 123 | *Added the TITLE tag for BTN. It is the scene release title, but is NOT always included in all announcements. 124 | *Logging does not make one huge file anymore. It will create a new log every 24 hours. 125 | *Until further notice, there are no automatic "rule exceptions" for log/logper/cue. This means that if you put log/logper/cue in your filter, it will ALWAYS be required, even if you have format=MP3. This means you'll have to create copies for your FLAC filters! 126 | *You will receive a log error if you've set up notifications on a network that the bot is currently not connected to. 127 | *You can now put your custom sites in CUSTOM.CONF and won't have to re-add them everytime I make major changes to regex.conf/etc. A sample site is there as an example. 128 | *You can now type "%status" as well as "%stats". 129 | 130 | 5.3.10 Version 1.14159: 131 | *Manual downloads shouldn't notify you with an email anymore. 132 | *Automatic downloads however, will. Sorry, had that reversed before, obviously. 133 | *Private messages from a nickowner using Regex can control the bot again. 134 | *Fixed the help for filters, and a crash when using '%filter help'. 135 | *Every filter can now be set to email=1 or email=0 to enable/disable (respectively) emailing for that filter. This overrides the global setting. 136 | *The option to PM a user on a successful download is now working! 137 | *Every filter can now be set to notify=1 or notify=0 to enable/disable (respectively) notifying you on IRC for that filter. This overrides the global setting. 138 | *Added support for username/pass combos when connecting to an IRC server (for bouncers, mainly. Thanks SeveredCross) 139 | *Thanks to Phersick for pointing out broken not_filter options, the not_ filter options have been revamped. 140 | *'tags' uses a re.search regex, while all other filter options use re.match. This means that you don't have to worry about regex ("V.*") matching the middle of a value ("Hive"). 141 | *Filter values now have a new format. Pre-pending the @ symbol to any value will force the bot to match using regex, otherwise it defaults to literal matching. 142 | *The default filter matching technique is now LITERAL MATCHING, not regex. 143 | *Fixed a bug that caused pyWA running under 2.5 to crash when I pondered. 144 | *Added a 1 sec timer between identifying and joining channels. This should cut down on join spam. (At the request of Blubba :) 145 | 146 | 4.28.10 Version 1.1415: 147 | *Made it so SCC packs capturing with a completely blank filter did not cause matches with announcements that aren't packs. 148 | *Added full SciHD support 149 | *Added the ability to send a gmail when pyWA auto-downloads 150 | *Added the configuration options for IRC notification when pyWA auto-downloads. This will be added in a future release. 151 | *Added full STR support 152 | *Fixed a really bad bug that caused the entire bot to quit gracefully if a single network/site decided to disconnect the bot. 153 | *Did some work on BTN's announce/capturing 154 | *Changed the way matching works with filters. You no longer have to worry about things like: group=V.* matching "HelloVHAHAH". It will only match things that START with V. 155 | *I am actually using my own bot again, so development should pick up ;) 156 | 157 | 4.25.10 Version 1.141: 158 | *Fixed a bug that required not_tags to be formatted differently than all the other tags. This is no longer the case. 159 | *Fixed BTN's announce format 160 | *Regex can now be used in the nickowner field. 161 | *Fixed/added SCC packs announcements 162 | 163 | 4.8.10 Version 1.14: 164 | *Fixed a bug in the "tags" filter option that was making it act like "all_tags" 165 | *Added filter info output when "all_tags" is the cause for failure 166 | 167 | 4.7.10: Version 1.1: 168 | *Fixed the P&TB routine again. IT WILL WORK. 169 | *Fixed a crash when trying to exit but logging was turned off. 170 | *Added 2.5 support with a bunch of help from Yots! w00t! 171 | *Changed BTN to their new bot/channel join system. 172 | *Added FILTER disable/enable support! 173 | *Fixed a bug where setting limit=0 and issuing a %free command caused a crash. 174 | 175 | 4.2.10: Version 1: 176 | *Added TehConnection.eu support! 177 | *Added Animebyt.es support! 178 | *Added TheGFT.org support! 179 | *Added IPTorrent support! (Requires pre-set cookie) 180 | *Added fux0r.eu support! 181 | *Torrentleech support! (Requires pre-set cookie) 182 | *BitGamer support! 183 | *Added ScienceHD manual download support. 184 | *Added the ability to write to logs. Everything show in the output window will be written. NOTE: THIS CAN GET HUGE VERY FAST, especially if verbosity:debug is on! 185 | *Added better error checking for .torrent files. If the file is not at least 100bytes, then we know the torrent is bad, so pyWA will try to download it again up to 2 more times. 186 | *Error handling if the drive accidentally becomes full 187 | *Multiple entries added into the %help menu 188 | *Added the setup option "limit" for shared seedboxes, as well as a better routine for gathering information on how much space is left in shared environments. If this option is left blank, pyWA will try to determine how much space you have left (in shared environments it WILL be wrong!). 189 | *When specifying cue/log, you now still need to specify that you want FLAC as the format since some releases are MP3 and have cues/log. 190 | *Re-tooled the "size" filter option. The format is now size=lower limit, upper limit (in GB). Example: size=0.2,20 191 | *CapITal LetTErS in nickowner don't matter anymore. (ty Brenex) 192 | *Fixed the notification when you are forced joined into a channel. 193 | *Added error handling when a torrent doesn't exist and the site returns a 404 error. 194 | *Added error handling when someone deletes a torrent just as you are requesting it. 195 | *Added error handling for sites that require pre-set cookies and don't have username/password in credentials.conf. 196 | *Removed a bunch of extraneous code that probably just slowed things down. 197 | *Completely revamped stripping IRC codes out, so hopefully colored announces won't be such a problem. 198 | *Fixed the P&tB routine. I need to have my fun! 199 | 200 | 3.25.10: Release Candidate 3: 201 | *REMOVED waffles.fm support because of the download routine overhaul. I could not, for the life of me, figure out how to get python+cookies to work correctly with waffles. ALL other websites work fine with this mechanism. If you can figure out how to download a torrent with a preset cookie in python on waffles.fm, then show me so I can implement it. Until then, they are no longer supported. 202 | *Added SCC and BMTV support! (Requires making your own cookies!) 203 | *Your bot ABSOLUTELY MUST be registered with nickserv for this new version to work. This alleviates many headaches when requesting for channel access and ident routines haven't finished. ALL requests are now only made after a successful ident! 204 | *When starting pyWHATauto, the version number is printed so you know what you're running. 205 | *Sending a %disconnect from networkA no longer causes an exception 206 | *Fixed several bugs that caused pyWHATauto to crash after issuing an improper %disconnect command. 207 | *The %disconnect command now properly removes that network from %stats (and some background stuff) 208 | *You can now send a raw command at startup. I implemented it to enable vhosts at connection, but you could use it for anything (as long as the format is right!) It is the credential.conf option cmd=command. 209 | *All output messages are prepended with the network that it came from using aliases if possible. 210 | *Cleaned up filter output quite a bit. Definitely less-spammy. 211 | *Cleaned up the entire output window a ton in the spirit of making it actually readable! 212 | *DEBUG mode now spits out less useless information, and will also show the header information passed during a download negotiation. 213 | *All websites now re-use cookies and try to login with them when downloading torrents. This is much faster! 214 | *Fixed a bug that caused network disconnects when connecting to 4+ networks. Also, connecting is much, much faster. 215 | *Change %sites to show all networks and their nicknames that pyWHATauto supports, seperated by enabled and disabled. 216 | *Added a %connect command. There is very little error-checking, but it works at least! 217 | *eBooks on WHAT.cd work again. 218 | *Removed all time.sleep() routines, so the bot is never waiting for something to happen causing timeouts. Everything is event driven. 219 | *A bunch of minor bug fixes and a somet a little extra just for fun ;) 220 | 221 | 3.22.10: Release Candidate 2: 222 | *Fixed a bug where just sending "%download" would cause a crash. 223 | *Full support for passthepopcorn.net. The bot that runs announces has changed ident twice since I added support, which caused breakages. Looking at how to fix this and still keep the script secure. 224 | *Full Waffles.fm support! 225 | *Full TorrentVault support. 226 | *The bot will now auto-ghost after connecting, and re-take it's name. 227 | *Added the commands %nick and %ghost to help with nickname issues. 228 | *Added the universal %cmd to send commands through the bot. To use it, follow these guidelines: http://en.wikipedia.org/wiki/List_of_Internet_Relay_Chat_commands. It is best to use the built-in pyWHATauto commands if possible, otherwise use this. 229 | *Fixed the filter option nomenclature on BTN thanks to qbic. Scene/fasttorrent are now a toggle options, and "source" is as it should be. 230 | *pyWHATauto now uses the #btn-whatuto channel on BTN, and captures the new announcement format in that channel. It still joins #btn-announce for ident/authorization. Until they add filenames to the new announcements, the tag option "filename" has been removed. 231 | *Fixed a bug where the bot would crash if you sent it a blank %join or %part command. 232 | *Added an %uptime command. 233 | *Added a %statsreset command. 234 | *If a filter option is left blank, it should no longer cause a failed match for that filter. 235 | *Filter options that just use 0 or 1 as toggle options will no longer use not_option as well. Instead, 0 will mean "don't have this" and 1 will mean "have this". Ex: scene=1 means only scene releases, and scene=0 means no scene releases. 236 | *PMing commands to the bot works now. 237 | *Manual downloads across multiple sites from the same channel work correctly now. 238 | *Moved the toggles for each network back to setup.conf so you can more easily and quickly toggle networks on and off. 239 | *Did somet tricky to keep all you people from changing the only code in there that's fun for me ;) 240 | *You can now have the bot download from sites that the bot isn't currently connected to. For example, you can disable BTN in setup.conf, but still manually download torrents from it using %download btn id. The username/password information must be setup in credentials! 241 | *Added more error checking for manually downloading torrents. If a torrent id is invalid and the site uses gazelle, then you will get a notification that the torrent doesn't exist. The other sites will have more error checking soon as well. 242 | *Added the ability to have 1 alias per network! Wooohoo! So now you can use "btn" for broadcasthenet, or whatever you set up. Change it in setup.conf. This ONLY works for commands sent in the IRC channel, not for anything in the confs. 243 | 244 | 3.15.10: Release Candidate 1: 245 | *Fixed a recursion bug that would cause it to crash after sitting for a long time. 246 | *Watch folders in the filters will now be used if set, otherwise the default will be used. 247 | *All custom watch folders in credentials/filters will be checked to make sure they exist on startup and on %reload. Tons of error handling/checking on these folders have been added. Many bugs introduced, and then fixed. 248 | *Fixed a bug where if the nickowner sent a message that started with a space it would crash the bot. 249 | *Fixed the bug where %stats would stop responding after a long period of running. 250 | *Implemented the ability to change how to connect to a server based on the regex.conf toggle options "port" and "ssl" 251 | *Added in the ability to watch all irc events not explicitly handled by pyWHATauto by using the verbosity:debug option. Warning: THIS WILL SPAM YOU 252 | *The bot will now automatically change it's nickname if the nick it is using is already taken on connect. 253 | *If the irc server sends a line termination notice (irc type of 'error') the bot will try to reconnect 5 seconds later. 254 | *Error checking has been introduced in many places where users could input CapiTAl LEttErs In THEiR conFiGS. Much more needed. 255 | *Fixed the %free/%drive command on linux. 256 | *Finished making pyWHATauto site-agnostic. It now fully works on BTN! I just need information about the other sites to edit the REGEX.conf to support them. 257 | *Output of the DEBUG lines should be more useful as it includes what server sent the information. 258 | *You should receive only one DEBUG message per error now, instead of one for every network currently connected. 259 | *Tons more error checking and exception catches. Not perfect, but it's better! 260 | *Initial support for theplace.bz. Might need access to verify how downloads are done and change some code in the bot, not sure yet. 261 | 262 | 3.11.10: Alpha r3: 263 | *Multiple values for filters can now be separated by both newlines or comma. A space when using commas is not required, but it makes for easier readability. 264 | -- Note that if you decide to use newlines, every new line must be tabbed in! 265 | *A graceful error message will notify you if you are not using the correct version of python. 2.6 is required. 266 | *Added support for the %current cmd. It will only PM you. 267 | *Added some error checking in a few spots, hopefully to give you information about what happened. 268 | *Updated the example filters.conf to show the new options 269 | *Cleaned up the help replies a bunch. 270 | *Fixed the %join/%part commands. They now properly work like in the help. 271 | *Added the %disconnect command. 272 | *The quit command now disconnects from all sites and closes all threads. 273 | *Cleaned up the threads a bunch. There should now only be 4 threads + messaging processing threads, instead of the many I had before. 274 | 275 | 3.10.10: Alpha r2: 276 | *Added version/date information. 277 | *Completely revamped how IRC commands are handled, and finished them! Use %help to learn how to use your new bot. 278 | *Fixed a bug where matches would not be made because of case-sensitive 'chanfilter' values. Case does not matter anymore. 279 | *Fixed multiple game-breaking bugs that stopped torrents from being downloaded successfully. 280 | *Filters will now only match log/cue/logper if the release is in FLAC. 281 | *Now handles connecting to multiple sites at the same time, which means: 282 | *It is now properly threaded! W00000T! 283 | *Many setup options have been moved to credentials. Nickowner is different for every network, so it's added there, as well as which channels to autojoin. 284 | *Added a toggle option in setup to disable showing random chatter from each network. 285 | *Support for announcements that are multi-line. 286 | *Started adding support for broadcasthe.net. 287 | *Changes to regex.conf to handle the different ways other networks require authorization 288 | 289 | 290 | 3.9.10: Alpha r1 - Original release! 291 | 292 | 293 | For the future: 294 | *Add cross-network channel joins IE %join $network #channel 295 | *Add a nick auto-register routine. 296 | *Redirect nick-highlights/pms directed at bot to the nickowner 297 | *Remove leading zeroes from filters/announcements (example: season 01 episode 01) 298 | *Add email support. 299 | *Add the ability to send all messages to a certain nick on a network through DCC chat 300 | *Add a bot "flood protection" scheme so it can't spam a channel. 301 | *Implement RSS feeds 302 | *Have the bot reply to VERSION queries. 303 | *If the connection drops (internet loss, etc), the bot needs to reconnect when the internet is back. 304 | *Implement the ability to toggle filters from the chatroom. 305 | *Put in error-checking if the site id is wrong or the username/password to login to the site is wrong 306 | *A GUI option.. maybe.. someday. 307 | *Add notifications if the bot gets kicked/glined etc. 308 | *Add color to the IRC output 309 | *Add a filter matching tool. 310 | *Implement a %register command to register with ident. 311 | *Use the option freepercent to stop downloading if the drive starts getting full. 312 | -------------------------------------------------------------------------------- /cookies/bitmetv.cookie: -------------------------------------------------------------------------------- 1 | #LWP-Cookies-1.0 2 | Set-Cookie3: pass=PASS; path="/"; domain=www.bitme.tv; path_spec; discard; version=0 3 | Set-Cookie3: uid=UID; path="/"; domain=www.bitme.tv; path_spec; discard; version=0 -------------------------------------------------------------------------------- /credentials.conf.example: -------------------------------------------------------------------------------- 1 | ;WHATauto credentials.conf 2 | 3 | ;nickowner= the ident (i.e. 123@username.class.what.cd, or 123@username.*.what.cd to match all userclasses) 4 | ;username= your site username 5 | ;password= your site password 6 | ;botNick= the nick you want your bot to use 7 | ;ircKey= your gazelle-based key login 8 | ;nickServPass= your nickServPass password 9 | ;cmd= using the "cmd" option during startup you can send a raw command to the server, in most case the toggle for a vhost 10 | ;watch= place where manual downloads go 11 | ;ircusesignon= set to 1 if the irc server requires a password to be sent 12 | ;ircpassword= put the password for the irc server here (useful for bnc's) 13 | ;channelpassword= if the announce channel requires a password 14 | ;chanfilter= channels to join automatically 15 | 16 | ; 17 | ;The following networks are currently supported by pyWHATauto 18 | ; 19 | 20 | [animebytes] 21 | nickowner= 22 | username= 23 | password= 24 | botnick= 25 | nickservpass= 26 | irckey= 27 | chanfilter= 28 | 29 | [apollo] 30 | nickowner= 31 | username= 32 | password= 33 | botnick= 34 | nickservpass= 35 | irckey= 36 | chanfilter= 37 | 38 | [awesomehd] 39 | nickowner= 40 | username= 41 | password= 42 | botnick= 43 | nickservpass= 44 | irckey= 45 | chanfilter= 46 | 47 | [baconbits] 48 | nickowner= 49 | username= 50 | password= 51 | botnick= 52 | nickservpass= 53 | chanfilter= 54 | 55 | [bd25] 56 | nickowner= 57 | username= 58 | password= 59 | ;irc currently not set up due to missing announce channel. 60 | 61 | [bibliotik] 62 | nickowner= 63 | username= 64 | password= 65 | ;irc currently not set up due to missing announce channel 66 | 67 | [bitgamer] 68 | nickowner= 69 | username= 70 | password= 71 | botnick= 72 | nickservpass= 73 | chanfilter= 74 | 75 | [bithdtv] 76 | nickowner= 77 | username= 78 | password= 79 | botnick= 80 | nickservpass= 81 | channelpassword= 82 | chanfilter= 83 | 84 | [bitme] 85 | nickowner= 86 | username= 87 | password= 88 | botnick= 89 | irckey= 90 | nickservpass= 91 | chanfilter= 92 | 93 | [bitmetv] 94 | nickowner= 95 | username= 96 | password= 97 | botnick= 98 | irckey= 99 | nickservpass= 100 | chanfilter= 101 | cmd= 102 | ;watch= 103 | 104 | [broadcasthenet] 105 | ;BTN has a wierd irckey system - you will need to identify to the bot once manually. 106 | nickowner= 107 | chanfilter=#whatbot 108 | username= 109 | password= 110 | botnick= 111 | nickservpass= 112 | ;watch= 113 | 114 | [brokenstones] 115 | nickowner= 116 | username= 117 | password= 118 | botnick= 119 | nickservpass= 120 | chanfilter= 121 | watch= 122 | 123 | [delish] 124 | nickowner= 125 | username= 126 | password= 127 | botnick= 128 | nickservpass= 129 | irckey= 130 | ;watch= 131 | 132 | [digitalhive] 133 | nickowner= 134 | username= 135 | password= 136 | botnick= 137 | nickservpass= 138 | chanfilter= 139 | 140 | [fux0r] 141 | nickowner= 142 | ;chanfilter= 143 | username= 144 | password= 145 | botnick= 146 | nickservpass= 147 | ;watch= 148 | 149 | [gazellegames] 150 | nickowner= 151 | username= 152 | password= 153 | botnick= 154 | nickservpass= 155 | irckey= 156 | 157 | [hdbits] 158 | ;hdbits downloading requires having the passkey set below! 159 | nickowner= 160 | username= 161 | password= 162 | botnick= 163 | irckey= 164 | nickservpass= 165 | passkey= 166 | chanfilter=#blubba 167 | ;watch= 168 | 169 | [iptorrents] 170 | nickowner= 171 | botnick= 172 | username= 173 | nickservpass= 174 | 175 | [karagarga] 176 | nickowner= 177 | botnick= 178 | username= 179 | password= 180 | nickservpass= 181 | chanfilter= 182 | 183 | [lztr] 184 | nickowner= 185 | botnick= 186 | username= 187 | password= 188 | nickservpass= 189 | irckey= 190 | 191 | [morethantv] 192 | nickowner= 193 | username= 194 | password= 195 | botnick= 196 | irckey= 197 | nickservpass= 198 | chanfilter=#announce 199 | ;watch= 200 | 201 | [notwhat] 202 | nickowner= 203 | username= 204 | password= 205 | botnick= 206 | nickservpass= 207 | irckey= 208 | chanfilter=#notwhat.cd-announce 209 | ;watch= 210 | 211 | [thegft] 212 | nickowner= 213 | chanfilter= 214 | username= 215 | password= 216 | botnick= 217 | irckey= 218 | nickservpass= 219 | 220 | [redacted] 221 | nickowner= 222 | chanfilter=#whatbot 223 | username= 224 | password= 225 | botnick= 226 | irckey= 227 | nickservpass= 228 | ;watch= 229 | 230 | [passthepopcorn] 231 | nickowner= 232 | username= 233 | password= 234 | passkey= 235 | botnick= 236 | nickservpass= 237 | chanfilter=#whatbot 238 | ;watch= 239 | 240 | [pianosheets] 241 | ;No irc announce channel (afaik) 242 | nickowner= 243 | username= 244 | password= 245 | 246 | [pretome] 247 | nickowner= 248 | chanfilter= 249 | username= 250 | password= 251 | botnick= 252 | nickservpass= 253 | ;watch= 254 | 255 | [piratethenet] 256 | nickowner= 257 | username= 258 | password= 259 | botnick= 260 | irckey= 261 | nickservpass= 262 | 263 | [pwnnetwork] 264 | nickowner= 265 | username= 266 | password= 267 | botnick= 268 | nickServPass= 269 | irckey= 270 | ;watch= 271 | 272 | [sceneaccess] 273 | nickowner= 274 | username= 275 | botnick= 276 | nickservpass= 277 | passkey= 278 | chanfilter= 279 | cmd= 280 | ;watch= 281 | 282 | [scenehd] 283 | nickowner= 284 | username= 285 | password= 286 | botnick= 287 | nickservpass= 288 | irckey= 289 | chanfilter=#blubba 290 | ;watch= 291 | 292 | [sciencehd] 293 | nickowner= 294 | username= 295 | password= 296 | botnick= 297 | nickServPass= 298 | irckey= 299 | ;watch= 300 | 301 | [sharetheremote] 302 | nickowner= 303 | username= 304 | password= 305 | botNick= 306 | nickServPass= 307 | chanfilter= 308 | ;watch= 309 | 310 | [shellife] 311 | nickowner= 312 | username= 313 | password= 314 | 315 | [stopthepresses] 316 | nickowner= 317 | username= 318 | password= 319 | botNick= 320 | nickServPass= 321 | chanfilter=#whatbot 322 | irckey= 323 | ;watch= 324 | [tehconnection] 325 | nickowner= 326 | username= 327 | password= 328 | botnick= 329 | nickservpass= 330 | irckey= 331 | chanfilter= 332 | ;watch= 333 | 334 | [theempire] 335 | nickowner= 336 | username= 337 | password= 338 | botNick= 339 | irckey= 340 | nickServPass= 341 | chanfilter= 342 | ;watch= 343 | 344 | [thebox] 345 | nickowner= 346 | username= 347 | password= 348 | botNick= 349 | irckey= 350 | nickServPass= 351 | ;watch= 352 | 353 | [theplace] 354 | nickowner= 355 | username= 356 | password= 357 | botNick= 358 | irckey= 359 | nickServPass= 360 | chanfilter= 361 | ;watch= 362 | 363 | [theswarm] 364 | nickowner= 365 | username= 366 | password= 367 | irckey= 368 | botNick= 369 | nickServPass= 370 | 371 | [tophos] 372 | nickowner= 373 | username= 374 | password= 375 | botNick= 376 | nickServPass= 377 | chanfilter= 378 | ;watch= 379 | 380 | [torrentbytes] 381 | nickowner= 382 | username= 383 | password= 384 | irckey= 385 | botnick= 386 | nickservpass= 387 | 388 | [torrentleech] 389 | nickowner= 390 | username= 391 | botNick= 392 | nickservpass= 393 | ;watch= 394 | 395 | [torrentvault] 396 | nickowner= 397 | chanfilter= 398 | username= 399 | password= 400 | botNick= 401 | nickServPass= 402 | ;watch= 403 | 404 | [undergroundgamer] 405 | nickowner= 406 | username= 407 | password= 408 | botnick= 409 | nickservpass= 410 | 411 | [waffles] 412 | ;waffles doesn't have an irc announce channel anymore. 413 | username= 414 | password= 415 | 416 | [whatcd] 417 | nickowner= 418 | chanfilter=#whatbot 419 | username= 420 | password= 421 | botnick= 422 | irckey= 423 | nickservpass= 424 | ;watch= 425 | 426 | [x264] 427 | nickowner= 428 | username= 429 | password= 430 | botnick= 431 | nickservpass= 432 | channelpassword= 433 | -------------------------------------------------------------------------------- /custom.conf.example: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimrollenhagen/pywhatauto/f93d5742041275c92ea9bd704a9c4c07714e62ae/custom.conf.example -------------------------------------------------------------------------------- /db.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Created on Aug 13, 2010 3 | 4 | @author: JohnnyFive 5 | ''' 6 | 7 | import sqlite3, os, sys#, Queue 8 | from threading import Thread 9 | 10 | def main(): 11 | sq = sqlDB(os.path.realpath(os.path.dirname(sys.argv[0]))) 12 | print sq 13 | 14 | class sqlDB( Thread ): 15 | 16 | def __init__(self, loadloc, queue): 17 | Thread.__init__(self) 18 | self.q = queue 19 | self.loadloc = loadloc 20 | self.verified = list() 21 | 22 | def run(self): 23 | self.conn = sqlite3.connect(os.path.join(self.loadloc, 'example.db')) 24 | # self.conn = sqlite3.connect(":memory:") 25 | self.c = self.conn.cursor() 26 | while True: 27 | info = self.q.get() 28 | self.addAnnounce(info[0],info[1],info[2]) 29 | 30 | def verifyTable(self, site): 31 | #test to see if the table exists already 32 | self.c.execute("SELECT name FROM sqlite_master WHERE name=?", (site,)) 33 | #if it doesn't, create it 34 | if self.c.fetchone() is None: 35 | self.c.execute("CREATE TABLE %s (token numeric, id integer PRIMARY KEY, announcement text, downloadid integer)" %site) 36 | 37 | self.verified.append(site) 38 | 39 | def addAnnounce(self, site, announcement, downloadid): 40 | #verify that we've already created a table for this site 41 | if site not in self.verified: 42 | self.verifyTable(site) 43 | 44 | var = (site,) 45 | #test to see if any values exist in the table, and if anything exists grab the current token so we know where to start adding/replacing new announcements. 46 | self.c.execute("SELECT * from %s WHERE token='1'"%site) 47 | #if nothing exists, start adding things 48 | x = self.c.fetchone() 49 | if x is None: 50 | var = (announcement, downloadid) 51 | self.c.execute("insert into %s values ('1', '1', ?, ?)"%site,var) 52 | else: 53 | #REPLACE the line with the token and remove the token (set to 0) 54 | self.c.execute('UPDATE %s SET token=0 WHERE token=1'%site) 55 | 56 | #INSERT OR REPLACE the incoming announcement into the table 57 | currID = x[1] 58 | #Revolve back to 1 if we're at 100 already 59 | if currID == 100: 60 | currID = 1 61 | else: 62 | currID += 1 63 | 64 | #Create a new line or replace the current one with the new announce 65 | var = (currID, unicode(announcement,('utf-8'),errors='ignore'), downloadid) 66 | self.c.execute("INSERT OR REPLACE INTO %s VALUES ('1', ?, ?, ?)"%site,var) 67 | self.conn.commit() 68 | 69 | if __name__ == '__main__': 70 | main() -------------------------------------------------------------------------------- /filters.conf.example: -------------------------------------------------------------------------------- 1 | [BTN_720p_HDTV_scene] 2 | site=broadcasthenet 3 | filterType=video 4 | active=0 5 | resolution=720p 6 | scene=1 7 | source=hdtv 8 | series=NCIS 9 | NCIS: Los Angeles 10 | The Big Bang Theory 11 | Stargate Universe 12 | Dexter 13 | The Simpsons 14 | The Walking Dead 15 | Grey's Anatomy 16 | Gossip Girl 17 | V (2009) 18 | Top Gear 19 | Breaking In 20 | The Daily Show with Jon Stewart 21 | Alphas 22 | Pawn Stars 23 | size=0,2000 24 | 25 | [BTN_720p_web] 26 | site=broadcasthenet 27 | filterType=video 28 | active=0 29 | resolution=720p 30 | source=WEB 31 | series=Community 32 | Doctor Who (2005) 33 | Breaking In 34 | Top Gear 35 | Alphas 36 | size=0,3000 37 | 38 | [BTN_720p_HDTV_any] 39 | site=broadcasthenet 40 | filterType=video 41 | active=0 42 | resolution=720p 43 | source=hdtv 44 | series=Game of Thrones 45 | 46 | [BTN_1080p_HDTV_noscene] 47 | site=broadcasthenet 48 | filterType=video 49 | active=0 50 | resolution=720p 51 | scene=0 52 | source=hdtv 53 | series=Game of Thrones 54 | 55 | [hdb_720p_webdl_buffer] 56 | site=hdbits 57 | active=0 58 | filterType=hd 59 | category=TV 60 | title=@.*720p.*(?:Web.?DL|HDTV).* 61 | size=0,2000 62 | 63 | [hdb_specificmovies] 64 | site=hdbits 65 | active=0 66 | filterType=hd 67 | category=movie 68 | title=@(?:Source.?Code|The.?Adjustment.?Bureau|Wild.?Wild.?West|Sucker.?Punch|Limitless|Unknown|Rango|Fast.?Five|Paul|Les.?Diaboliques|Drive.?Angry|True.?Grit|Battle.*Los.?Angeles|The.?People.?vs.?Larry.?Flynt|Solyaris|I.?Saw.?The.?Devil|Blue.?Valentine|Papillion|Philadelphia|American.?Graffiti|Just.?Go.?With.?It|Chugyeogja).* 69 | 70 | [hdb_specificseries] 71 | site=hdbits 72 | active=0 73 | filterType=hd 74 | category=TV 75 | title=@(?:Game.?of.?Thrones|South.?Park|Doctor.?Who).* 76 | 77 | [hdb_specificporn] 78 | site=hdbits 79 | active=0 80 | filterType=hd 81 | category=xxx 82 | title=@X.?Art.* 83 | 84 | [hdb_allporn] 85 | site=hdbits 86 | active=0 87 | filterType=hd 88 | category=xxx 89 | not_title=@Creampie.?Angels.* 90 | size=0,20000 91 | 92 | [thebox_buffer] 93 | site=thebox 94 | filterType=misc 95 | active=0 96 | name=@^(?:Coronation Street|Coach Trip|Hollyoaks|The Lock Up|Emmerdale|Doctors|Lunch Monkeys - S02|EastEnders).* 97 | 98 | [RED_500MB_to_5GB] 99 | site=redacted 100 | filterType=music 101 | active=1 102 | size=500,5000 103 | -------------------------------------------------------------------------------- /globals.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | import threading, sys, os, Queue#, thread 3 | 4 | #This is an internal flag I use to test announcements. 5 | TESTING = False 6 | #Should we log everything? 7 | LOG = False 8 | 9 | #What version of REGEX.conf are we using? Loaded from the file itself on startup 10 | REGVERSION = 0 11 | 12 | 13 | NETWORKS = dict() 14 | #the dictionary of networks and their information that have a section in credentials.conf or custom.conf 15 | RUNNING = dict() 16 | #The dictionary of running bots 17 | TOSTART = dict() 18 | #Dictionary of initial 'sites' keys in setup and custom.conf 19 | TOALIAS = dict() 20 | #Take a sitename and go to the alias 21 | ALIASLENGTH = 0 22 | #Length of the longest alias 23 | FROMALIAS = dict() 24 | #take an alias and go to the sitename 25 | REPORTS = dict() 26 | #the reports of seen/downloaded for each site 27 | FILTERS_CHANGED= dict() 28 | #A dictionary of filters that have been manually changed and their states 29 | FILTERS= dict() 30 | #A dictionary of filters and their original states 31 | STARTTIME = datetime.now() 32 | #The time at which the bot was started 33 | LOCK = threading.RLock() 34 | #the threading LOCK. Come on people. 35 | EXIT = False 36 | #Do we want to exit? 37 | sys.tracebacklimit = 20 38 | #How far should I allow traceback? 39 | OWNER = ['34038@johnnyfive','3808@johnnyfive','191067@blubba','1868@blubba'] 40 | #I own this shit, yo 41 | CD = ['inline; filename=','inline; Filename=','attachment; Filename=','attachment; filename=', 'attachement; filename='] 42 | #ContentDispositions 43 | SCRIPTDIR=os.path.realpath(os.path.dirname(sys.argv[0])) 44 | #where was this script loaded from? 45 | 46 | Q = Queue.Queue() 47 | #The Queue object used to send the DB new announcements 48 | -------------------------------------------------------------------------------- /irclib.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 1999--2002 Joel Rosdahl 2 | # 3 | # This library is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU Lesser General Public 5 | # License as published by the Free Software Foundation; either 6 | # version 2.1 of the License, or (at your option) any later version. 7 | # 8 | # This library is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # Lesser General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU Lesser General Public 14 | # License along with this library; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 | # 17 | # keltus 18 | # 19 | # $Id: irclib.py,v 1.47 2008/09/25 22:00:59 keltus Exp $ 20 | 21 | """irclib -- Internet Relay Chat (IRC) protocol client library. 22 | 23 | This library is intended to encapsulate the IRC protocol at a quite 24 | low level. It provides an event-driven IRC client framework. It has 25 | a fairly thorough support for the basic IRC protocol, CTCP, DCC chat, 26 | but DCC file transfers is not yet supported. 27 | 28 | In order to understand how to make an IRC client, I'm afraid you more 29 | or less must understand the IRC specifications. They are available 30 | here: [IRC specifications]. 31 | 32 | The main features of the IRC client framework are: 33 | 34 | * Abstraction of the IRC protocol. 35 | * Handles multiple simultaneous IRC server connections. 36 | * Handles server PONGing transparently. 37 | * Messages to the IRC server are done by calling methods on an IRC 38 | connection object. 39 | * Messages from an IRC server triggers events, which can be caught 40 | by event handlers. 41 | * Reading from and writing to IRC server sockets are normally done 42 | by an internal select() loop, but the select()ing may be done by 43 | an external main loop. 44 | * Functions can be registered to execute at specified times by the 45 | event-loop. 46 | * Decodes CTCP tagging correctly (hopefully); I haven't seen any 47 | other IRC client implementation that handles the CTCP 48 | specification subtilties. 49 | * A kind of simple, single-server, object-oriented IRC client class 50 | that dispatches events to instance methods is included. 51 | 52 | Current limitations: 53 | 54 | * The IRC protocol shines through the abstraction a bit too much. 55 | * Data is not written asynchronously to the server, i.e. the write() 56 | may block if the TCP buffers are stuffed. 57 | * There are no support for DCC file transfers. 58 | * The author haven't even read RFC 2810, 2811, 2812 and 2813. 59 | * Like most projects, documentation is lacking... 60 | 61 | .. [IRC specifications] http://www.irchelp.org/irchelp/rfc/ 62 | """ 63 | 64 | import bisect 65 | import re 66 | import select 67 | import socket 68 | import string 69 | import sys 70 | import time 71 | import types 72 | if sys.version_info >= (2, 6): 73 | import ssl as SSL 74 | 75 | VERSION = 0, 4, 8 76 | DEBUG = 0 77 | 78 | # TODO 79 | # ---- 80 | # (maybe) thread safety 81 | # (maybe) color parser convenience functions 82 | # documentation (including all event types) 83 | # (maybe) add awareness of different types of ircds 84 | # send data asynchronously to the server (and DCC connections) 85 | # (maybe) automatically close unused, passive DCC connections after a while 86 | 87 | # NOTES 88 | # ----- 89 | # connection.quit() only sends QUIT to the server. 90 | # ERROR from the server triggers the error event and the disconnect event. 91 | # dropping of the connection triggers the disconnect event. 92 | 93 | class IRCError(Exception): 94 | """Represents an IRC exception.""" 95 | pass 96 | 97 | 98 | class IRC: 99 | """Class that handles one or several IRC server connections. 100 | 101 | When an IRC object has been instantiated, it can be used to create 102 | Connection objects that represent the IRC connections. The 103 | responsibility of the IRC object is to provide an event-driven 104 | framework for the connections and to keep the connections alive. 105 | It runs a select loop to poll each connection's TCP socket and 106 | hands over the sockets with incoming data for processing by the 107 | corresponding connection. 108 | 109 | The methods of most interest for an IRC client writer are server, 110 | add_global_handler, remove_global_handler, execute_at, 111 | execute_delayed, process_once and process_forever. 112 | 113 | Here is an example: 114 | 115 | irc = irclib.IRC() 116 | server = irc.server() 117 | server.connect(\"irc.some.where\", 6667, \"my_nickname\") 118 | server.privmsg(\"a_nickname\", \"Hi there!\") 119 | irc.process_forever() 120 | 121 | This will connect to the IRC server irc.some.where on port 6667 122 | using the nickname my_nickname and send the message \"Hi there!\" 123 | to the nickname a_nickname. 124 | """ 125 | 126 | def __init__(self, fn_to_add_socket=None, 127 | fn_to_remove_socket=None, 128 | fn_to_add_timeout=None): 129 | """Constructor for IRC objects. 130 | 131 | Optional arguments are fn_to_add_socket, fn_to_remove_socket 132 | and fn_to_add_timeout. The first two specify functions that 133 | will be called with a socket object as argument when the IRC 134 | object wants to be notified (or stop being notified) of data 135 | coming on a new socket. When new data arrives, the method 136 | process_data should be called. Similarly, fn_to_add_timeout 137 | is called with a number of seconds (a floating point number) 138 | as first argument when the IRC object wants to receive a 139 | notification (by calling the process_timeout method). So, if 140 | e.g. the argument is 42.17, the object wants the 141 | process_timeout method to be called after 42 seconds and 170 142 | milliseconds. 143 | 144 | The three arguments mainly exist to be able to use an external 145 | main loop (for example Tkinter's or PyGTK's main app loop) 146 | instead of calling the process_forever method. 147 | 148 | An alternative is to just call ServerConnection.process_once() 149 | once in a while. 150 | """ 151 | 152 | if fn_to_add_socket and fn_to_remove_socket: 153 | self.fn_to_add_socket = fn_to_add_socket 154 | self.fn_to_remove_socket = fn_to_remove_socket 155 | else: 156 | self.fn_to_add_socket = None 157 | self.fn_to_remove_socket = None 158 | 159 | self.fn_to_add_timeout = fn_to_add_timeout 160 | self.connections = [] 161 | self.handlers = {} 162 | self.delayed_commands = [] # list of tuples in the format (time, function, arguments) 163 | 164 | self.add_global_handler("ping", _ping_ponger, -42) 165 | 166 | def server(self): 167 | """Creates and returns a ServerConnection object.""" 168 | 169 | c = ServerConnection(self) 170 | self.connections.append(c) 171 | return c 172 | 173 | def process_data(self, sockets): 174 | """Called when there is more data to read on connection sockets. 175 | 176 | Arguments: 177 | 178 | sockets -- A list of socket objects. 179 | 180 | See documentation for IRC.__init__. 181 | """ 182 | for s in sockets: 183 | for c in self.connections: 184 | if s == c._get_socket(): 185 | c.process_data() 186 | 187 | def process_timeout(self): 188 | """Called when a timeout notification is due. 189 | 190 | See documentation for IRC.__init__. 191 | """ 192 | t = time.time() 193 | while self.delayed_commands: 194 | if t >= self.delayed_commands[0][0]: 195 | self.delayed_commands[0][1](*self.delayed_commands[0][2]) 196 | del self.delayed_commands[0] 197 | else: 198 | break 199 | 200 | def process_once(self, timeout=0): 201 | """Process data from connections once. 202 | 203 | Arguments: 204 | 205 | timeout -- How long the select() call should wait if no 206 | data is available. 207 | 208 | This method should be called periodically to check and process 209 | incoming data, if there are any. If that seems boring, look 210 | at the process_forever method. 211 | """ 212 | sockets = map(lambda x: x._get_socket(), self.connections) 213 | sockets = filter(lambda x: x != None, sockets) 214 | if sockets: 215 | i = select.select(sockets, [], [], timeout)[0] 216 | self.process_data(i) 217 | else: 218 | time.sleep(timeout) 219 | self.process_timeout() 220 | 221 | def process_forever(self, timeout=0.2): 222 | """Run an infinite loop, processing data from connections. 223 | 224 | This method repeatedly calls process_once. 225 | 226 | Arguments: 227 | 228 | timeout -- Parameter to pass to process_once. 229 | """ 230 | while 1: 231 | self.process_once(timeout) 232 | 233 | def disconnect_all(self, message=""): 234 | """Disconnects all connections.""" 235 | for c in self.connections: 236 | c.disconnect(message) 237 | 238 | def add_global_handler(self, event, handler, priority=0): 239 | """Adds a global handler function for a specific event type. 240 | 241 | Arguments: 242 | 243 | event -- Event type (a string). Check the values of the 244 | numeric_events dictionary in irclib.py for possible event 245 | types. 246 | 247 | handler -- Callback function. 248 | 249 | priority -- A number (the lower number, the higher priority). 250 | 251 | The handler function is called whenever the specified event is 252 | triggered in any of the connections. See documentation for 253 | the Event class. 254 | 255 | The handler functions are called in priority order (lowest 256 | number is highest priority). If a handler function returns 257 | \"NO MORE\", no more handlers will be called. 258 | """ 259 | if not event in self.handlers: 260 | self.handlers[event] = [] 261 | bisect.insort(self.handlers[event], ((priority, handler))) 262 | 263 | def remove_global_handler(self, event, handler): 264 | """Removes a global handler function. 265 | 266 | Arguments: 267 | 268 | event -- Event type (a string). 269 | 270 | handler -- Callback function. 271 | 272 | Returns 1 on success, otherwise 0. 273 | """ 274 | if not event in self.handlers: 275 | return 0 276 | for h in self.handlers[event]: 277 | if handler == h[1]: 278 | self.handlers[event].remove(h) 279 | return 1 280 | 281 | def execute_at(self, at, function, arguments=()): 282 | """Execute a function at a specified time. 283 | 284 | Arguments: 285 | 286 | at -- Execute at this time (standard \"time_t\" time). 287 | 288 | function -- Function to call. 289 | 290 | arguments -- Arguments to give the function. 291 | """ 292 | self.execute_delayed(at-time.time(), function, arguments) 293 | 294 | def execute_delayed(self, delay, function, arguments=()): 295 | """Execute a function after a specified time. 296 | 297 | Arguments: 298 | 299 | delay -- How many seconds to wait. 300 | 301 | function -- Function to call. 302 | 303 | arguments -- Arguments to give the function. 304 | """ 305 | bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments)) 306 | if self.fn_to_add_timeout: 307 | self.fn_to_add_timeout(delay) 308 | 309 | def dcc(self, dcctype="chat"): 310 | """Creates and returns a DCCConnection object. 311 | 312 | Arguments: 313 | 314 | dcctype -- "chat" for DCC CHAT connections or "raw" for 315 | DCC SEND (or other DCC types). If "chat", 316 | incoming data will be split in newline-separated 317 | chunks. If "raw", incoming data is not touched. 318 | """ 319 | c = DCCConnection(self, dcctype) 320 | self.connections.append(c) 321 | return c 322 | 323 | def _handle_event(self, connection, event): 324 | """[Internal]""" 325 | h = self.handlers 326 | for handler in h.get("all_events", []) + h.get(event.eventtype(), []): 327 | if handler[1](connection, event) == "NO MORE": 328 | return 329 | 330 | def _remove_connection(self, connection): 331 | """[Internal]""" 332 | self.connections.remove(connection) 333 | if self.fn_to_remove_socket: 334 | self.fn_to_remove_socket(connection._get_socket()) 335 | 336 | _rfc_1459_command_regexp = re.compile("^(:(?P[^ ]+) +)?(?P[^ ]+)( *(?P .+))?") 337 | 338 | class Connection: 339 | """Base class for IRC connections. 340 | 341 | Must be overridden. 342 | """ 343 | def __init__(self, irclibobj): 344 | self.irclibobj = irclibobj 345 | 346 | def _get_socket(self): 347 | raise IRCError, "Not overridden" 348 | 349 | ############################## 350 | ### Convenience wrappers. 351 | 352 | def execute_at(self, at, function, arguments=()): 353 | self.irclibobj.execute_at(at, function, arguments) 354 | 355 | def execute_delayed(self, delay, function, arguments=()): 356 | self.irclibobj.execute_delayed(delay, function, arguments) 357 | 358 | 359 | class ServerConnectionError(IRCError): 360 | pass 361 | 362 | class ServerNotConnectedError(ServerConnectionError): 363 | pass 364 | 365 | 366 | # Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to 367 | # use \n as message separator! :P 368 | _linesep_regexp = re.compile("\r?\n") 369 | 370 | class ServerConnection(Connection): 371 | """This class represents an IRC server connection. 372 | 373 | ServerConnection objects are instantiated by calling the server 374 | method on an IRC object. 375 | """ 376 | 377 | def __init__(self, irclibobj): 378 | Connection.__init__(self, irclibobj) 379 | self.connected = 0 # Not connected yet. 380 | self.socket = None 381 | self.ssl = None 382 | 383 | def connect(self, server, port, nickname, password=None, username=None, 384 | ircname=None, localaddress="", localport=0, ssl=False, ipv6=False): 385 | """Connect/reconnect to a server. 386 | 387 | Arguments: 388 | 389 | server -- Server name. 390 | 391 | port -- Port number. 392 | 393 | nickname -- The nickname. 394 | 395 | password -- Password (if any). 396 | 397 | username -- The username. 398 | 399 | ircname -- The IRC name ("realname"). 400 | 401 | localaddress -- Bind the connection to a specific local IP address. 402 | 403 | localport -- Bind the connection to a specific local port. 404 | 405 | ssl -- Enable support for ssl. 406 | 407 | ipv6 -- Enable support for ipv6. 408 | 409 | This function can be called to reconnect a closed connection. 410 | 411 | Returns the ServerConnection object. 412 | """ 413 | if self.connected: 414 | self.disconnect("Changing servers") 415 | 416 | self.previous_buffer = "" 417 | self.handlers = {} 418 | self.real_server_name = "" 419 | self.real_nickname = nickname 420 | self.server = server 421 | self.port = port 422 | self.nickname = nickname 423 | self.username = username or nickname 424 | self.ircname = ircname or nickname 425 | self.password = password 426 | self.localaddress = localaddress 427 | self.localport = localport 428 | self.localhost = socket.gethostname() 429 | if ipv6: 430 | self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 431 | else: 432 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 433 | try: 434 | self.socket.bind((self.localaddress, self.localport)) 435 | self.socket.connect((self.server, self.port)) 436 | if ssl: 437 | if sys.version_info >= (2, 6): 438 | self.ssl = SSL.wrap_socket(self.socket) 439 | else: 440 | self.ssl = socket.ssl(self.socket) 441 | except socket.error, x: 442 | self.socket.close() 443 | self.socket = None 444 | raise ServerConnectionError, "Couldn't connect to socket: %s" % x 445 | self.connected = 1 446 | if self.irclibobj.fn_to_add_socket: 447 | self.irclibobj.fn_to_add_socket(self.socket) 448 | 449 | # Log on... 450 | if self.password: 451 | self.pass_(self.password) 452 | self.nick(self.nickname) 453 | self.user(self.username, self.ircname) 454 | return self 455 | 456 | def close(self): 457 | """Close the connection. 458 | 459 | This method closes the connection permanently; after it has 460 | been called, the object is unusable. 461 | """ 462 | 463 | self.disconnect("Closing object") 464 | self.irclibobj._remove_connection(self) 465 | 466 | def _get_socket(self): 467 | """[Internal]""" 468 | return self.socket 469 | 470 | def get_server_name(self): 471 | """Get the (real) server name. 472 | 473 | This method returns the (real) server name, or, more 474 | specifically, what the server calls itself. 475 | """ 476 | 477 | if self.real_server_name: 478 | return self.real_server_name 479 | else: 480 | return "" 481 | 482 | def get_nickname(self): 483 | """Get the (real) nick name. 484 | 485 | This method returns the (real) nickname. The library keeps 486 | track of nick changes, so it might not be the nick name that 487 | was passed to the connect() method. """ 488 | 489 | return self.real_nickname 490 | 491 | def process_data(self): 492 | """[Internal]""" 493 | 494 | try: 495 | if self.ssl: 496 | new_data = self.ssl.read(2**14) 497 | else: 498 | new_data = self.socket.recv(2**14) 499 | except socket.error: 500 | # The server hung up. 501 | self.disconnect("Connection reset by peer") 502 | return 503 | if not new_data: 504 | # Read nothing: connection must be down. 505 | self.disconnect("Connection reset by peer") 506 | return 507 | 508 | lines = _linesep_regexp.split(self.previous_buffer + new_data) 509 | 510 | # Save the last, unfinished line. 511 | self.previous_buffer = lines.pop() 512 | 513 | for line in lines: 514 | if DEBUG: 515 | print "FROM SERVER:", line 516 | 517 | if not line: 518 | continue 519 | 520 | prefix = None 521 | command = None 522 | arguments = None 523 | self._handle_event(Event("all_raw_messages", 524 | self.get_server_name(), 525 | None, 526 | [line])) 527 | 528 | m = _rfc_1459_command_regexp.match(line) 529 | if m.group("prefix"): 530 | prefix = m.group("prefix") 531 | if not self.real_server_name: 532 | self.real_server_name = prefix 533 | 534 | if m.group("command"): 535 | command = m.group("command").lower() 536 | 537 | if m.group("argument"): 538 | a = m.group("argument").split(" :", 1) 539 | arguments = a[0].split() 540 | if len(a) == 2: 541 | arguments.append(a[1]) 542 | 543 | # Translate numerics into more readable strings. 544 | if command in numeric_events: 545 | command = numeric_events[command] 546 | 547 | if command == "nick": 548 | if nm_to_n(prefix) == self.real_nickname: 549 | self.real_nickname = arguments[0] 550 | elif command == "welcome": 551 | # Record the nickname in case the client changed nick 552 | # in a nicknameinuse callback. 553 | self.real_nickname = arguments[0] 554 | 555 | if command in ["privmsg", "notice"]: 556 | target, message = arguments[0], arguments[1] 557 | messages = _ctcp_dequote(message) 558 | 559 | if command == "privmsg": 560 | if is_channel(target): 561 | command = "pubmsg" 562 | else: 563 | if is_channel(target): 564 | command = "pubnotice" 565 | else: 566 | command = "privnotice" 567 | 568 | for m in messages: 569 | if type(m) is types.TupleType: 570 | if command in ["privmsg", "pubmsg"]: 571 | command = "ctcp" 572 | else: 573 | command = "ctcpreply" 574 | 575 | m = list(m) 576 | if DEBUG: 577 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 578 | command, prefix, target, m) 579 | self._handle_event(Event(command, prefix, target, m)) 580 | if command == "ctcp" and m[0] == "ACTION": 581 | self._handle_event(Event("action", prefix, target, m[1:])) 582 | else: 583 | if DEBUG: 584 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 585 | command, prefix, target, [m]) 586 | self._handle_event(Event(command, prefix, target, [m])) 587 | else: 588 | target = None 589 | 590 | if command == "quit": 591 | arguments = [arguments[0]] 592 | elif command == "ping": 593 | target = arguments[0] 594 | else: 595 | target = arguments[0] 596 | arguments = arguments[1:] 597 | 598 | if command == "mode": 599 | if not is_channel(target): 600 | command = "umode" 601 | 602 | if DEBUG: 603 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 604 | command, prefix, target, arguments) 605 | self._handle_event(Event(command, prefix, target, arguments)) 606 | 607 | def _handle_event(self, event): 608 | """[Internal]""" 609 | self.irclibobj._handle_event(self, event) 610 | if event.eventtype() in self.handlers: 611 | for fn in self.handlers[event.eventtype()]: 612 | fn(self, event) 613 | 614 | def is_connected(self): 615 | """Return connection status. 616 | 617 | Returns true if connected, otherwise false. 618 | """ 619 | return self.connected 620 | 621 | def add_global_handler(self, *args): 622 | """Add global handler. 623 | 624 | See documentation for IRC.add_global_handler. 625 | """ 626 | self.irclibobj.add_global_handler(*args) 627 | 628 | def remove_global_handler(self, *args): 629 | """Remove global handler. 630 | 631 | See documentation for IRC.remove_global_handler. 632 | """ 633 | self.irclibobj.remove_global_handler(*args) 634 | 635 | def action(self, target, action): 636 | """Send a CTCP ACTION command.""" 637 | self.ctcp("ACTION", target, action) 638 | 639 | def admin(self, server=""): 640 | """Send an ADMIN command.""" 641 | self.send_raw(" ".join(["ADMIN", server]).strip()) 642 | 643 | def ctcp(self, ctcptype, target, parameter=""): 644 | """Send a CTCP command.""" 645 | ctcptype = ctcptype.upper() 646 | self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or "")) 647 | 648 | def ctcp_reply(self, target, parameter): 649 | """Send a CTCP REPLY command.""" 650 | self.notice(target, "\001%s\001" % parameter) 651 | 652 | def disconnect(self, message=""): 653 | """Hang up the connection. 654 | 655 | Arguments: 656 | 657 | message -- Quit message. 658 | """ 659 | if not self.connected: 660 | return 661 | 662 | self.connected = 0 663 | 664 | self.quit(message) 665 | 666 | try: 667 | self.socket.close() 668 | except socket.error: 669 | pass 670 | self.socket = None 671 | self._handle_event(Event("disconnect", self.server, "", [message])) 672 | 673 | def globops(self, text): 674 | """Send a GLOBOPS command.""" 675 | self.send_raw("GLOBOPS :" + text) 676 | 677 | def info(self, server=""): 678 | """Send an INFO command.""" 679 | self.send_raw(" ".join(["INFO", server]).strip()) 680 | 681 | def invite(self, nick, channel): 682 | """Send an INVITE command.""" 683 | self.send_raw(" ".join(["INVITE", nick, channel]).strip()) 684 | 685 | def ison(self, nicks): 686 | """Send an ISON command. 687 | 688 | Arguments: 689 | 690 | nicks -- List of nicks. 691 | """ 692 | self.send_raw("ISON " + " ".join(nicks)) 693 | 694 | def join(self, channel, key=""): 695 | """Send a JOIN command.""" 696 | self.send_raw("JOIN %s%s" % (channel, (key and (" " + key)))) 697 | 698 | def kick(self, channel, nick, comment=""): 699 | """Send a KICK command.""" 700 | self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment)))) 701 | 702 | def links(self, remote_server="", server_mask=""): 703 | """Send a LINKS command.""" 704 | command = "LINKS" 705 | if remote_server: 706 | command = command + " " + remote_server 707 | if server_mask: 708 | command = command + " " + server_mask 709 | self.send_raw(command) 710 | 711 | def list(self, channels=None, server=""): 712 | """Send a LIST command.""" 713 | command = "LIST" 714 | if channels: 715 | command = command + " " + ",".join(channels) 716 | if server: 717 | command = command + " " + server 718 | self.send_raw(command) 719 | 720 | def lusers(self, server=""): 721 | """Send a LUSERS command.""" 722 | self.send_raw("LUSERS" + (server and (" " + server))) 723 | 724 | def mode(self, target, command): 725 | """Send a MODE command.""" 726 | self.send_raw("MODE %s %s" % (target, command)) 727 | 728 | def motd(self, server=""): 729 | """Send an MOTD command.""" 730 | self.send_raw("MOTD" + (server and (" " + server))) 731 | 732 | def names(self, channels=None): 733 | """Send a NAMES command.""" 734 | self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or "")) 735 | 736 | def nick(self, newnick): 737 | """Send a NICK command.""" 738 | self.send_raw("NICK " + newnick) 739 | 740 | def notice(self, target, text): 741 | """Send a NOTICE command.""" 742 | # Should limit len(text) here! 743 | self.send_raw("NOTICE %s :%s" % (target, text)) 744 | 745 | def oper(self, nick, password): 746 | """Send an OPER command.""" 747 | self.send_raw("OPER %s %s" % (nick, password)) 748 | 749 | def part(self, channels, message=""): 750 | """Send a PART command.""" 751 | if type(channels) == types.StringType: 752 | self.send_raw("PART " + channels + (message and (" " + message))) 753 | else: 754 | self.send_raw("PART " + ",".join(channels) + (message and (" " + message))) 755 | 756 | def pass_(self, password): 757 | """Send a PASS command.""" 758 | self.send_raw("PASS " + password) 759 | 760 | def ping(self, target, target2=""): 761 | """Send a PING command.""" 762 | self.send_raw("PING %s%s" % (target, target2 and (" " + target2))) 763 | 764 | def pong(self, target, target2=""): 765 | """Send a PONG command.""" 766 | self.send_raw("PONG %s%s" % (target, target2 and (" " + target2))) 767 | 768 | def privmsg(self, target, text): 769 | """Send a PRIVMSG command.""" 770 | # Should limit len(text) here! 771 | self.send_raw("PRIVMSG %s :%s" % (target, text)) 772 | 773 | def privmsg_many(self, targets, text): 774 | """Send a PRIVMSG command to multiple targets.""" 775 | # Should limit len(text) here! 776 | self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text)) 777 | 778 | def quit(self, message=""): 779 | """Send a QUIT command.""" 780 | # Note that many IRC servers don't use your QUIT message 781 | # unless you've been connected for at least 5 minutes! 782 | self.send_raw("QUIT" + (message and (" :" + message))) 783 | 784 | def send_raw(self, string): 785 | """Send raw string to the server. 786 | 787 | The string will be padded with appropriate CR LF. 788 | """ 789 | if self.socket is None: 790 | raise ServerNotConnectedError, "Not connected." 791 | try: 792 | if self.ssl: 793 | self.ssl.write(string + "\r\n") 794 | else: 795 | self.socket.send(string + "\r\n") 796 | if DEBUG: 797 | print "TO SERVER:", string 798 | except socket.error: 799 | # Ouch! 800 | self.disconnect("Connection reset by peer.") 801 | 802 | def squit(self, server, comment=""): 803 | """Send an SQUIT command.""" 804 | self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment))) 805 | 806 | def stats(self, statstype, server=""): 807 | """Send a STATS command.""" 808 | self.send_raw("STATS %s%s" % (statstype, server and (" " + server))) 809 | 810 | def time(self, server=""): 811 | """Send a TIME command.""" 812 | self.send_raw("TIME" + (server and (" " + server))) 813 | 814 | def topic(self, channel, new_topic=None): 815 | """Send a TOPIC command.""" 816 | if new_topic is None: 817 | self.send_raw("TOPIC " + channel) 818 | else: 819 | self.send_raw("TOPIC %s :%s" % (channel, new_topic)) 820 | 821 | def trace(self, target=""): 822 | """Send a TRACE command.""" 823 | self.send_raw("TRACE" + (target and (" " + target))) 824 | 825 | def user(self, username, realname): 826 | """Send a USER command.""" 827 | self.send_raw("USER %s 0 * :%s" % (username, realname)) 828 | 829 | def userhost(self, nicks): 830 | """Send a USERHOST command.""" 831 | self.send_raw("USERHOST " + ",".join(nicks)) 832 | 833 | def users(self, server=""): 834 | """Send a USERS command.""" 835 | self.send_raw("USERS" + (server and (" " + server))) 836 | 837 | def version(self, server=""): 838 | """Send a VERSION command.""" 839 | self.send_raw("VERSION" + (server and (" " + server))) 840 | 841 | def wallops(self, text): 842 | """Send a WALLOPS command.""" 843 | self.send_raw("WALLOPS :" + text) 844 | 845 | def who(self, target="", op=""): 846 | """Send a WHO command.""" 847 | self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o"))) 848 | 849 | def whois(self, targets): 850 | """Send a WHOIS command.""" 851 | self.send_raw("WHOIS " + ",".join(targets)) 852 | 853 | def whowas(self, nick, max="", server=""): 854 | """Send a WHOWAS command.""" 855 | self.send_raw("WHOWAS %s%s%s" % (nick, 856 | max and (" " + max), 857 | server and (" " + server))) 858 | 859 | class DCCConnectionError(IRCError): 860 | pass 861 | 862 | 863 | class DCCConnection(Connection): 864 | """This class represents a DCC connection. 865 | 866 | DCCConnection objects are instantiated by calling the dcc 867 | method on an IRC object. 868 | """ 869 | def __init__(self, irclibobj, dcctype): 870 | Connection.__init__(self, irclibobj) 871 | self.connected = 0 872 | self.passive = 0 873 | self.dcctype = dcctype 874 | self.peeraddress = None 875 | self.peerport = None 876 | 877 | def connect(self, address, port): 878 | """Connect/reconnect to a DCC peer. 879 | 880 | Arguments: 881 | address -- Host/IP address of the peer. 882 | 883 | port -- The port number to connect to. 884 | 885 | Returns the DCCConnection object. 886 | """ 887 | self.peeraddress = socket.gethostbyname(address) 888 | self.peerport = port 889 | self.socket = None 890 | self.previous_buffer = "" 891 | self.handlers = {} 892 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 893 | self.passive = 0 894 | try: 895 | self.socket.connect((self.peeraddress, self.peerport)) 896 | except socket.error, x: 897 | raise DCCConnectionError, "Couldn't connect to socket: %s" % x 898 | self.connected = 1 899 | if self.irclibobj.fn_to_add_socket: 900 | self.irclibobj.fn_to_add_socket(self.socket) 901 | return self 902 | 903 | def listen(self): 904 | """Wait for a connection/reconnection from a DCC peer. 905 | 906 | Returns the DCCConnection object. 907 | 908 | The local IP address and port are available as 909 | self.localaddress and self.localport. After connection from a 910 | peer, the peer address and port are available as 911 | self.peeraddress and self.peerport. 912 | """ 913 | self.previous_buffer = "" 914 | self.handlers = {} 915 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 916 | self.passive = 1 917 | try: 918 | self.socket.bind((socket.gethostbyname(socket.gethostname()), 0)) 919 | self.localaddress, self.localport = self.socket.getsockname() 920 | self.socket.listen(10) 921 | except socket.error, x: 922 | raise DCCConnectionError, "Couldn't bind socket: %s" % x 923 | return self 924 | 925 | def disconnect(self, message=""): 926 | """Hang up the connection and close the object. 927 | 928 | Arguments: 929 | 930 | message -- Quit message. 931 | """ 932 | if not self.connected: 933 | return 934 | 935 | self.connected = 0 936 | try: 937 | self.socket.close() 938 | except socket.error: 939 | pass 940 | self.socket = None 941 | self.irclibobj._handle_event( 942 | self, 943 | Event("dcc_disconnect", self.peeraddress, "", [message])) 944 | self.irclibobj._remove_connection(self) 945 | 946 | def process_data(self): 947 | """[Internal]""" 948 | 949 | if self.passive and not self.connected: 950 | conn, (self.peeraddress, self.peerport) = self.socket.accept() 951 | self.socket.close() 952 | self.socket = conn 953 | self.connected = 1 954 | if DEBUG: 955 | print "DCC connection from %s:%d" % ( 956 | self.peeraddress, self.peerport) 957 | self.irclibobj._handle_event( 958 | self, 959 | Event("dcc_connect", self.peeraddress, None, None)) 960 | return 961 | 962 | try: 963 | new_data = self.socket.recv(2**14) 964 | except socket.error: 965 | # The server hung up. 966 | self.disconnect("Connection reset by peer") 967 | return 968 | if not new_data: 969 | # Read nothing: connection must be down. 970 | self.disconnect("Connection reset by peer") 971 | return 972 | 973 | if self.dcctype == "chat": 974 | # The specification says lines are terminated with LF, but 975 | # it seems safer to handle CR LF terminations too. 976 | chunks = _linesep_regexp.split(self.previous_buffer + new_data) 977 | 978 | # Save the last, unfinished line. 979 | self.previous_buffer = chunks[-1] 980 | if len(self.previous_buffer) > 2**14: 981 | # Bad peer! Naughty peer! 982 | self.disconnect() 983 | return 984 | chunks = chunks[:-1] 985 | else: 986 | chunks = [new_data] 987 | 988 | command = "dccmsg" 989 | prefix = self.peeraddress 990 | target = None 991 | for chunk in chunks: 992 | if DEBUG: 993 | print "FROM PEER:", chunk 994 | arguments = [chunk] 995 | if DEBUG: 996 | print "command: %s, source: %s, target: %s, arguments: %s" % ( 997 | command, prefix, target, arguments) 998 | self.irclibobj._handle_event( 999 | self, 1000 | Event(command, prefix, target, arguments)) 1001 | 1002 | def _get_socket(self): 1003 | """[Internal]""" 1004 | return self.socket 1005 | 1006 | def privmsg(self, string): 1007 | """Send data to DCC peer. 1008 | 1009 | The string will be padded with appropriate LF if it's a DCC 1010 | CHAT session. 1011 | """ 1012 | try: 1013 | self.socket.send(string) 1014 | if self.dcctype == "chat": 1015 | self.socket.send("\n") 1016 | if DEBUG: 1017 | print "TO PEER: %s\n" % string 1018 | except socket.error: 1019 | # Ouch! 1020 | self.disconnect("Connection reset by peer.") 1021 | 1022 | class SimpleIRCClient: 1023 | """A simple single-server IRC client class. 1024 | 1025 | This is an example of an object-oriented wrapper of the IRC 1026 | framework. A real IRC client can be made by subclassing this 1027 | class and adding appropriate methods. 1028 | 1029 | The method on_join will be called when a "join" event is created 1030 | (which is done when the server sends a JOIN messsage/command), 1031 | on_privmsg will be called for "privmsg" events, and so on. The 1032 | handler methods get two arguments: the connection object (same as 1033 | self.connection) and the event object. 1034 | 1035 | Instance attributes that can be used by sub classes: 1036 | 1037 | ircobj -- The IRC instance. 1038 | 1039 | connection -- The ServerConnection instance. 1040 | 1041 | dcc_connections -- A list of DCCConnection instances. 1042 | """ 1043 | def __init__(self): 1044 | self.ircobj = IRC() 1045 | self.connection = self.ircobj.server() 1046 | self.dcc_connections = [] 1047 | self.ircobj.add_global_handler("all_events", self._dispatcher, -10) 1048 | self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10) 1049 | 1050 | def _dispatcher(self, c, e): 1051 | """[Internal]""" 1052 | m = "on_" + e.eventtype() 1053 | if hasattr(self, m): 1054 | getattr(self, m)(c, e) 1055 | 1056 | def _dcc_disconnect(self, c, e): 1057 | self.dcc_connections.remove(c) 1058 | 1059 | def connect(self, server, port, nickname, password=None, username=None, 1060 | ircname=None, localaddress="", localport=0, ssl=False, ipv6=False): 1061 | """Connect/reconnect to a server. 1062 | 1063 | Arguments: 1064 | 1065 | server -- Server name. 1066 | 1067 | port -- Port number. 1068 | 1069 | nickname -- The nickname. 1070 | 1071 | password -- Password (if any). 1072 | 1073 | username -- The username. 1074 | 1075 | ircname -- The IRC name. 1076 | 1077 | localaddress -- Bind the connection to a specific local IP address. 1078 | 1079 | localport -- Bind the connection to a specific local port. 1080 | 1081 | ssl -- Enable support for ssl. 1082 | 1083 | ipv6 -- Enable support for ipv6. 1084 | 1085 | This function can be called to reconnect a closed connection. 1086 | """ 1087 | self.connection.connect(server, port, nickname, 1088 | password, username, ircname, 1089 | localaddress, localport, ssl, ipv6) 1090 | 1091 | def dcc_connect(self, address, port, dcctype="chat"): 1092 | """Connect to a DCC peer. 1093 | 1094 | Arguments: 1095 | 1096 | address -- IP address of the peer. 1097 | 1098 | port -- Port to connect to. 1099 | 1100 | Returns a DCCConnection instance. 1101 | """ 1102 | dcc = self.ircobj.dcc(dcctype) 1103 | self.dcc_connections.append(dcc) 1104 | dcc.connect(address, port) 1105 | return dcc 1106 | 1107 | def dcc_listen(self, dcctype="chat"): 1108 | """Listen for connections from a DCC peer. 1109 | 1110 | Returns a DCCConnection instance. 1111 | """ 1112 | dcc = self.ircobj.dcc(dcctype) 1113 | self.dcc_connections.append(dcc) 1114 | dcc.listen() 1115 | return dcc 1116 | 1117 | def start(self): 1118 | """Start the IRC client.""" 1119 | self.ircobj.process_forever() 1120 | 1121 | 1122 | class Event: 1123 | """Class representing an IRC event.""" 1124 | def __init__(self, eventtype, source, target, arguments=None): 1125 | """Constructor of Event objects. 1126 | 1127 | Arguments: 1128 | 1129 | eventtype -- A string describing the event. 1130 | 1131 | source -- The originator of the event (a nick mask or a server). 1132 | 1133 | target -- The target of the event (a nick or a channel). 1134 | 1135 | arguments -- Any event specific arguments. 1136 | """ 1137 | self._eventtype = eventtype 1138 | self._source = source 1139 | self._target = target 1140 | if arguments: 1141 | self._arguments = arguments 1142 | else: 1143 | self._arguments = [] 1144 | 1145 | def eventtype(self): 1146 | """Get the event type.""" 1147 | return self._eventtype 1148 | 1149 | def source(self): 1150 | """Get the event source.""" 1151 | return self._source 1152 | 1153 | def target(self): 1154 | """Get the event target.""" 1155 | return self._target 1156 | 1157 | def arguments(self): 1158 | """Get the event arguments.""" 1159 | return self._arguments 1160 | 1161 | _LOW_LEVEL_QUOTE = "\020" 1162 | _CTCP_LEVEL_QUOTE = "\134" 1163 | _CTCP_DELIMITER = "\001" 1164 | 1165 | _low_level_mapping = { 1166 | "0": "\000", 1167 | "n": "\n", 1168 | "r": "\r", 1169 | _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE 1170 | } 1171 | 1172 | _low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)") 1173 | 1174 | def mask_matches(nick, mask): 1175 | """Check if a nick matches a mask. 1176 | 1177 | Returns true if the nick matches, otherwise false. 1178 | """ 1179 | nick = irc_lower(nick) 1180 | mask = irc_lower(mask) 1181 | mask = mask.replace("\\", "\\\\") 1182 | for ch in ".$|[](){}+": 1183 | mask = mask.replace(ch, "\\" + ch) 1184 | mask = mask.replace("?", ".") 1185 | mask = mask.replace("*", ".*") 1186 | r = re.compile(mask, re.IGNORECASE) 1187 | return r.match(nick) 1188 | 1189 | _special = "-[]\\`^{}" 1190 | nick_characters = string.ascii_letters + string.digits + _special 1191 | _ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^", 1192 | string.ascii_lowercase + "{}|~") 1193 | 1194 | def irc_lower(s): 1195 | """Returns a lowercased string. 1196 | 1197 | The definition of lowercased comes from the IRC specification (RFC 1198 | 1459). 1199 | """ 1200 | return s.translate(_ircstring_translation) 1201 | 1202 | def _ctcp_dequote(message): 1203 | """[Internal] Dequote a message according to CTCP specifications. 1204 | 1205 | The function returns a list where each element can be either a 1206 | string (normal message) or a tuple of one or two strings (tagged 1207 | messages). If a tuple has only one element (ie is a singleton), 1208 | that element is the tag; otherwise the tuple has two elements: the 1209 | tag and the data. 1210 | 1211 | Arguments: 1212 | 1213 | message -- The message to be decoded. 1214 | """ 1215 | 1216 | def _low_level_replace(match_obj): 1217 | ch = match_obj.group(1) 1218 | 1219 | # If low_level_mapping doesn't have the character as key, we 1220 | # should just return the character. 1221 | return _low_level_mapping.get(ch, ch) 1222 | 1223 | if _LOW_LEVEL_QUOTE in message: 1224 | # Yup, there was a quote. Release the dequoter, man! 1225 | message = _low_level_regexp.sub(_low_level_replace, message) 1226 | 1227 | if _CTCP_DELIMITER not in message: 1228 | return [message] 1229 | else: 1230 | # Split it into parts. (Does any IRC client actually *use* 1231 | # CTCP stacking like this?) 1232 | chunks = message.split(_CTCP_DELIMITER) 1233 | 1234 | messages = [] 1235 | i = 0 1236 | while i < len(chunks)-1: 1237 | # Add message if it's non-empty. 1238 | if len(chunks[i]) > 0: 1239 | messages.append(chunks[i]) 1240 | 1241 | if i < len(chunks)-2: 1242 | # Aye! CTCP tagged data ahead! 1243 | messages.append(tuple(chunks[i+1].split(" ", 1))) 1244 | 1245 | i = i + 2 1246 | 1247 | if len(chunks) % 2 == 0: 1248 | # Hey, a lonely _CTCP_DELIMITER at the end! This means 1249 | # that the last chunk, including the delimiter, is a 1250 | # normal message! (This is according to the CTCP 1251 | # specification.) 1252 | messages.append(_CTCP_DELIMITER + chunks[-1]) 1253 | 1254 | return messages 1255 | 1256 | def is_channel(string): 1257 | """Check if a string is a channel name. 1258 | 1259 | Returns true if the argument is a channel name, otherwise false. 1260 | """ 1261 | return string and string[0] in "#&+!" 1262 | 1263 | def ip_numstr_to_quad(num): 1264 | """Convert an IP number as an integer given in ASCII 1265 | representation (e.g. '3232235521') to an IP address string 1266 | (e.g. '192.168.0.1').""" 1267 | n = long(num) 1268 | p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF, 1269 | n >> 8 & 0xFF, n & 0xFF])) 1270 | return ".".join(p) 1271 | 1272 | def ip_quad_to_numstr(quad): 1273 | """Convert an IP address string (e.g. '192.168.0.1') to an IP 1274 | number as an integer given in ASCII representation 1275 | (e.g. '3232235521').""" 1276 | p = map(long, quad.split(".")) 1277 | s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]) 1278 | if s[-1] == "L": 1279 | s = s[:-1] 1280 | return s 1281 | 1282 | def nm_to_n(s): 1283 | """Get the nick part of a nickmask. 1284 | 1285 | (The source of an Event is a nickmask.) 1286 | """ 1287 | return s.split("!")[0] 1288 | 1289 | def nm_to_uh(s): 1290 | """Get the userhost part of a nickmask. 1291 | 1292 | (The source of an Event is a nickmask.) 1293 | """ 1294 | return s.split("!")[1] 1295 | 1296 | def nm_to_h(s): 1297 | """Get the host part of a nickmask. 1298 | 1299 | (The source of an Event is a nickmask.) 1300 | """ 1301 | return s.split("@")[1] 1302 | 1303 | def nm_to_u(s): 1304 | """Get the user part of a nickmask. 1305 | 1306 | (The source of an Event is a nickmask.) 1307 | """ 1308 | s = s.split("!")[1] 1309 | return s.split("@")[0] 1310 | 1311 | def parse_nick_modes(mode_string): 1312 | """Parse a nick mode string. 1313 | 1314 | The function returns a list of lists with three members: sign, 1315 | mode and argument. The sign is \"+\" or \"-\". The argument is 1316 | always None. 1317 | 1318 | Example: 1319 | 1320 | >>> irclib.parse_nick_modes(\"+ab-c\") 1321 | [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]] 1322 | """ 1323 | 1324 | return _parse_modes(mode_string, "") 1325 | 1326 | def parse_channel_modes(mode_string): 1327 | """Parse a channel mode string. 1328 | 1329 | The function returns a list of lists with three members: sign, 1330 | mode and argument. The sign is \"+\" or \"-\". The argument is 1331 | None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\". 1332 | 1333 | Example: 1334 | 1335 | >>> irclib.parse_channel_modes(\"+ab-c foo\") 1336 | [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]] 1337 | """ 1338 | 1339 | return _parse_modes(mode_string, "bklvo") 1340 | 1341 | def _parse_modes(mode_string, unary_modes=""): 1342 | """[Internal]""" 1343 | modes = [] 1344 | arg_count = 0 1345 | 1346 | # State variable. 1347 | sign = "" 1348 | 1349 | a = mode_string.split() 1350 | if len(a) == 0: 1351 | return [] 1352 | else: 1353 | mode_part, args = a[0], a[1:] 1354 | 1355 | if mode_part[0] not in "+-": 1356 | return [] 1357 | for ch in mode_part: 1358 | if ch in "+-": 1359 | sign = ch 1360 | elif ch == " ": 1361 | pass 1362 | #collecting_arguments = 1 1363 | elif ch in unary_modes: 1364 | if len(args) >= arg_count + 1: 1365 | modes.append([sign, ch, args[arg_count]]) 1366 | arg_count = arg_count + 1 1367 | else: 1368 | modes.append([sign, ch, None]) 1369 | else: 1370 | modes.append([sign, ch, None]) 1371 | return modes 1372 | 1373 | def _ping_ponger(connection, event): 1374 | """[Internal]""" 1375 | connection.pong(event.target()) 1376 | 1377 | # Numeric table mostly stolen from the Perl IRC module (Net::IRC). 1378 | numeric_events = { 1379 | "001": "welcome", 1380 | "002": "yourhost", 1381 | "003": "created", 1382 | "004": "myinfo", 1383 | "005": "featurelist", # XXX 1384 | "200": "tracelink", 1385 | "201": "traceconnecting", 1386 | "202": "tracehandshake", 1387 | "203": "traceunknown", 1388 | "204": "traceoperator", 1389 | "205": "traceuser", 1390 | "206": "traceserver", 1391 | "207": "traceservice", 1392 | "208": "tracenewtype", 1393 | "209": "traceclass", 1394 | "210": "tracereconnect", 1395 | "211": "statslinkinfo", 1396 | "212": "statscommands", 1397 | "213": "statscline", 1398 | "214": "statsnline", 1399 | "215": "statsiline", 1400 | "216": "statskline", 1401 | "217": "statsqline", 1402 | "218": "statsyline", 1403 | "219": "endofstats", 1404 | "221": "umodeis", 1405 | "231": "serviceinfo", 1406 | "232": "endofservices", 1407 | "233": "service", 1408 | "234": "servlist", 1409 | "235": "servlistend", 1410 | "241": "statslline", 1411 | "242": "statsuptime", 1412 | "243": "statsoline", 1413 | "244": "statshline", 1414 | "250": "luserconns", 1415 | "251": "luserclient", 1416 | "252": "luserop", 1417 | "253": "luserunknown", 1418 | "254": "luserchannels", 1419 | "255": "luserme", 1420 | "256": "adminme", 1421 | "257": "adminloc1", 1422 | "258": "adminloc2", 1423 | "259": "adminemail", 1424 | "261": "tracelog", 1425 | "262": "endoftrace", 1426 | "263": "tryagain", 1427 | "265": "n_local", 1428 | "266": "n_global", 1429 | "300": "none", 1430 | "301": "away", 1431 | "302": "userhost", 1432 | "303": "ison", 1433 | "305": "unaway", 1434 | "306": "nowaway", 1435 | "311": "whoisuser", 1436 | "312": "whoisserver", 1437 | "313": "whoisoperator", 1438 | "314": "whowasuser", 1439 | "315": "endofwho", 1440 | "316": "whoischanop", 1441 | "317": "whoisidle", 1442 | "318": "endofwhois", 1443 | "319": "whoischannels", 1444 | "321": "liststart", 1445 | "322": "list", 1446 | "323": "listend", 1447 | "324": "channelmodeis", 1448 | "329": "channelcreate", 1449 | "331": "notopic", 1450 | "332": "currenttopic", 1451 | "333": "topicinfo", 1452 | "341": "inviting", 1453 | "342": "summoning", 1454 | "346": "invitelist", 1455 | "347": "endofinvitelist", 1456 | "348": "exceptlist", 1457 | "349": "endofexceptlist", 1458 | "351": "version", 1459 | "352": "whoreply", 1460 | "353": "namreply", 1461 | "361": "killdone", 1462 | "362": "closing", 1463 | "363": "closeend", 1464 | "364": "links", 1465 | "365": "endoflinks", 1466 | "366": "endofnames", 1467 | "367": "banlist", 1468 | "368": "endofbanlist", 1469 | "369": "endofwhowas", 1470 | "371": "info", 1471 | "372": "motd", 1472 | "373": "infostart", 1473 | "374": "endofinfo", 1474 | "375": "motdstart", 1475 | "376": "endofmotd", 1476 | "377": "motd2", # 1997-10-16 -- tkil 1477 | "381": "youreoper", 1478 | "382": "rehashing", 1479 | "384": "myportis", 1480 | "391": "time", 1481 | "392": "usersstart", 1482 | "393": "users", 1483 | "394": "endofusers", 1484 | "395": "nousers", 1485 | "401": "nosuchnick", 1486 | "402": "nosuchserver", 1487 | "403": "nosuchchannel", 1488 | "404": "cannotsendtochan", 1489 | "405": "toomanychannels", 1490 | "406": "wasnosuchnick", 1491 | "407": "toomanytargets", 1492 | "409": "noorigin", 1493 | "411": "norecipient", 1494 | "412": "notexttosend", 1495 | "413": "notoplevel", 1496 | "414": "wildtoplevel", 1497 | "421": "unknowncommand", 1498 | "422": "nomotd", 1499 | "423": "noadmininfo", 1500 | "424": "fileerror", 1501 | "431": "nonicknamegiven", 1502 | "432": "erroneusnickname", # Thiss iz how its speld in thee RFC. 1503 | "433": "nicknameinuse", 1504 | "436": "nickcollision", 1505 | "437": "unavailresource", # "Nick temporally unavailable" 1506 | "441": "usernotinchannel", 1507 | "442": "notonchannel", 1508 | "443": "useronchannel", 1509 | "444": "nologin", 1510 | "445": "summondisabled", 1511 | "446": "usersdisabled", 1512 | "451": "notregistered", 1513 | "461": "needmoreparams", 1514 | "462": "alreadyregistered", 1515 | "463": "nopermforhost", 1516 | "464": "passwdmismatch", 1517 | "465": "yourebannedcreep", # I love this one... 1518 | "466": "youwillbebanned", 1519 | "467": "keyset", 1520 | "471": "channelisfull", 1521 | "472": "unknownmode", 1522 | "473": "inviteonlychan", 1523 | "474": "bannedfromchan", 1524 | "475": "badchannelkey", 1525 | "476": "badchanmask", 1526 | "477": "nochanmodes", # "Channel doesn't support modes" 1527 | "478": "banlistfull", 1528 | "481": "noprivileges", 1529 | "482": "chanoprivsneeded", 1530 | "483": "cantkillserver", 1531 | "484": "restricted", # Connection is restricted 1532 | "485": "uniqopprivsneeded", 1533 | "491": "nooperhost", 1534 | "492": "noservicehost", 1535 | "501": "umodeunknownflag", 1536 | "502": "usersdontmatch", 1537 | } 1538 | 1539 | generated_events = [ 1540 | # Generated events 1541 | "dcc_connect", 1542 | "dcc_disconnect", 1543 | "dccmsg", 1544 | "disconnect", 1545 | "ctcp", 1546 | "ctcpreply", 1547 | ] 1548 | 1549 | protocol_events = [ 1550 | # IRC protocol events 1551 | "error", 1552 | "join", 1553 | "kick", 1554 | "mode", 1555 | "part", 1556 | "ping", 1557 | "privmsg", 1558 | "privnotice", 1559 | "pubmsg", 1560 | "pubnotice", 1561 | "quit", 1562 | "invite", 1563 | "pong", 1564 | ] 1565 | 1566 | all_events = generated_events + protocol_events + numeric_events.values() 1567 | -------------------------------------------------------------------------------- /regex.conf: -------------------------------------------------------------------------------- 1 | ;WHATauto regex.conf 2 | ;DO NOT EDIT THIS FILE 3 | 4 | [version] 5 | version=100 6 | 7 | ;;downloadTypes: 8 | ;1 = use the downloadID, and expect the filename in response 9 | ;2 = use the downloadID followed by '/downloadID.torrent' to request the torrent. 10 | 11 | ;;requiresAuth types: 12 | ;0 = No auth 13 | ;1 = Send auth string to botname 14 | 15 | 16 | [whatcd] 17 | server: irc.what-network.net 18 | port: 6697 19 | ssl: 1 20 | 21 | ;nick of the identify bot, also the nick of the announce bot if announcebotname is not set. 22 | botname: drone 23 | 24 | ;what is the /who of the bot? (for security) 25 | botwho: drone@ircop.what-network.net 26 | 27 | ;What channel should I be watching announcements in? Also, this is autojoined by messaging botname and asking to enter 28 | announcechannel: #what.cd-announce 29 | 30 | ;Does the network require logging into a bot to hide my ident? 31 | requiresauth: 1 32 | 33 | ;What is the format for the authorization string? 34 | authstring: enter $authchan $username $irckey 35 | 36 | ;What channel should I ask to join while authorizing against the bot? 37 | authchan: #what.cd-announce 38 | 39 | ;What are the names (in this config) of the announcement regex's? 40 | announces: music, ebook 41 | 42 | ;How many lines does the bot use to make those announcements? 43 | announcelines: 1 44 | 45 | ;The regex format for the music announcement, prefaced by the amount of lines the announcement takes 46 | music: (.+?) - (.+) \[(\d+)\] \[([^\]]+)\] - (MP3|FLAC|Ogg|AAC|AC3|DTS|Ogg Vorbis) / ((?:24bit)?(?: ?Lossless)?(?:[\d|~|\.xVq|\s]*(?:AAC|APX|APS|Mixed|Auto|VBR)?(?: LC)?)?(?: ?(?:\(VBR\)|\(?ABR\)?|[K|k][b|p]{1,2}s)?)?)(?: / (Log))?(?: / ([-0-9\.]+)\%)?(?: / (Cue))?(?: / (CD|DVD|Vinyl|Soundboard|SACD|Cassette|DAT|WEB|Blu-ray))(?: / (Scene))?(?: / (Freeleech!))? - https://what\.cd/torrents\.php\?id=(\d+) / https://what\.cd/torrents\.php\?action=download&id=(\d+) - ?(.*) 47 | ;The regex format for the ebook announcement, prefaced by the amount of lines the announcement takes 48 | ebook: ^((?:(?!MP3|FLAC|Ogg|AAC|AC3|DTS|Ogg Vorbis).)*) - https://what.cd/torrents.php\?id=(\d+) / https://what.cd/torrents.php\?action=download&id=(\d+) - (.*) 49 | 50 | loginURL: https://what.cd/login.php 51 | 52 | ;Specifies how the torrents should be requested. 53 | downloadType: 1 54 | 55 | ;The url used to download the torrents 56 | downloadURL: https://what.cd/torrents.php?action=download&id= 57 | 58 | ;The allowed tags that can be used in the filters for this site 59 | tags: artist, title, album, tags, year, type, log, logper, cue, scene, freeleech, format, quality, source, all_tags, groupID 60 | 61 | ;The allowed not_tags that can be used in the filters for this site 62 | not_tags: not_artist, not_title, not_album, not_tags, not_year, not_type, not_format, not_quality, not_source, not_groupID 63 | 64 | ;The regex group matches in order for the music announcment 65 | musicFormat: artist, album, year, type, format, quality, log, logper, cue, source, scene, freeleech, groupID, downloadID, tags 66 | 67 | ;The regex group matches in order for the ebook announcement 68 | ebookFormat: title, groupID, downloadID, tags 69 | 70 | 71 | [3dtorrents] 72 | ;3dtorrents.org has no announce channel (afaik) 73 | presetcookie: 1 74 | downloadtype: 4 75 | urlending: &f= 76 | downloadURL: http://www.3dtorrents.org/download.php?id= 77 | 78 | [alphaomega] 79 | server:ssl.alphaomega.me 80 | port:7000 81 | ssl:1 82 | botname:cerberus 83 | botwho:Cerberus@alphaomega.me 84 | announcechannel: #AO.announce 85 | requiresauth:0 86 | announcelines:1 87 | announces:misc 88 | misc: \[New Release\]-\[([^\]]*)\]-\[([^\]]*)\]-\[URL\]-\[ http://www.alphaomega.me/torrents.php\?id=\d+ \]-\[ (\d+) \] 89 | downloadURL:http://www.alphaomega.me/torrents.php?action=download&id= 90 | downloadType: 1 91 | loginURL:http://www.alphaomega.me/login.php 92 | tags: group, title 93 | not_tags: not_group, not_title 94 | miscFormat: group, title, downloadID 95 | 96 | [animebytes] 97 | server: irc.animebyt.es 98 | port: 7000 99 | ssl: 1 100 | botname: Satsuki 101 | botwho: 75@galador.Developer.AnimeBytes 102 | announcechannel: #announce 103 | announcelines: 1 104 | authchan: #announce 105 | requiresauth: 1 106 | authstring: enter $authchan $username $irckey 107 | loginURL: https://yuki.animebyt.es/login.php 108 | announces: anime 109 | anime: (.*) - \[(\d+)\] - (TV Series|Movie|DVD Special|ONA|OVA|TV Special) (DVD|HD DVD|Bluray|TV|LD|VHS|Web) / (\w+) / (\w+) / (\w+) / ([^/]+)(?:/ (Dual Audio) )?(?:/ (Softsubs|RAW|Hardsubs) )?(?:(?!/ Hentai)/ ([^/\|]+)(?!/ Hentai) )?(?:/ (Hentai) )?(?:/ (Freeleech!) )?\|\| Page: http://animebyt\.es/torrents\.php\?id=[\d]+ \|\| SSL Page: https://yuki\.animebyt\.es/torrents\.php\?id=[\d]+ \|\| Download: http://animebyt\.es/torrents\.php\?action=download&id=([\d]+) \|\| ([^\|]+) (?:\|\| Uploaded By: (\w+))? 110 | downloadtype: 1 111 | downloadURL: https://yuki.animebyt.es/torrents.php?action=download&id= 112 | tags: title, year, type, source, container, codec, resolution, audio, dual_audio, subs, sub_group, hentai, freeleech, tags, uploader 113 | not_tags: title, year, type, source, container, codec, resolution, audio, subs, tags, uploader 114 | animeFormat: title, year, type, source, container, codec, resolution, audio, dual_audio, subs, sub_group, hentai, freeleech, downloadID, tags, uploader 115 | 116 | [apollo] 117 | server: irc.apollo.rip 118 | port: 7000 119 | ssl: 1 120 | botname: APOLLO 121 | botwho: APOLLO@apollo.apollo.rip 122 | announcechannel: #announce 123 | requiresauth: 1 124 | authstring: enter $authchan $username $irckey 125 | authchan: #announce 126 | announces: music 127 | announcelines: 1 128 | music: TORRENT: (.+?) - (.+) \[(\d+)\] \[([^\]]+)\] - (MP3|FLAC|Ogg|AAC|AC3|DTS|Ogg Vorbis) / ((?:24bit)?(?: ?Lossless)?(?:[\d|~|\.xVq|\s]*(?:AAC|APX|APS|Mixed|Auto|VBR)?(?: LC)?)?(?: ?(?:\(VBR\)|\(?ABR\)?|[K|k][b|p]{1,2}s)?)?)(?: / (Log))?(?: / ([-0-9\.]+)\%)?(?: / (Cue))?(?: / (CD|DVD|Vinyl|Soundboard|SACD|Cassette|DAT|WEB|Blu-ray))(?: / (Scene))?(?: / (Freeleech!))? - ?(.*) - https://apollo\.rip/torrents\.php\?id=(\d+) / https://apollo\.rip/torrents\.php\?action=download&id=(\d+) 129 | loginURL: https://apollo.rip/login.php 130 | downloadType: 1 131 | downloadURL: https://apollo.rip/torrents.php?action=download&id= 132 | tags: artist, album, year, type, format, quality, log, logper, cue, source, scene, freeleech, tags, groupID 133 | not_tags: not_artist, not_album, not_year, not_type, not_format, not_quality, not_log, not_logper, not_cue, not_source, not_scene, not_freeleech, not_tags, not_groupID 134 | musicFormat: artist, album, year, type, format, quality, log, logper, cue, source, scene, freeleech, tags, groupID, downloadID 135 | 136 | [awesomehd] 137 | server: irc.awesome-hd.me 138 | port: 7000 139 | ssl: 1 140 | botname: ahdbot 141 | botwho: AHDBot@awesome-hd.me 142 | announcechannel: #ahd-announce 143 | requiresauth: 1 144 | announcelines: 1 145 | announces: misc 146 | misc: (TV-SHOW|MOVIE|AUDIO-TRACK): ?([^\[]+)? \[(\d*)\] - ([^/]*) / ([^/]*) / ([^/]*) / ([^/]*) / ([^/]*)(?: / (AC-3|AAC|AVC|DTS|DTS-Express|DTSHD-HRA|DTSHD-MA|FLAC|PCM|PGS|TrueHD|UTF-8|Vorbis)?)?(?: / (Scene))?(?: / (AHD Gold))?(?: / (DXVA))?(?: / (Freeleech!))?(?: / (\d+)% Freeleech)?(?: / (\d+)x Upload)? - https://awesome-hd.net/torrents.php\?id=\d+ / https://awesome-hd.net/torrents.php\?action=download&id=(\d+)(?: - (.*))? 147 | ;intro: @.*http:.*http:.* 148 | loginURL: https://awesome-hd.me/login.php 149 | downloadURL: https://awesome-hd.me/torrents.php?action=download&id= 150 | downloadType: 1 151 | authstring: enter $announcechan $username $irckey 152 | tags: type, title, year, group, source, resolution, container, codec, audio, scene, ahd, dxva, freeleech, freeleechpercent, uploadmultiplier, all_tags 153 | not_tags: not_type, not_title, not_year, not_group, not_source, not_resolution, not_container, not_codec, not_audio, not_scene, not_ahd, not_dxva, not_freeleech, not_freeleechpercent, not_uploadmultiplier, not_tags 154 | miscFormat: type, title, year, group, source, resolution, container, codec, audio, scene, ahd, dxva, freeleech, freeleechpercent, uploadmultiplier, downloadID, tags 155 | 156 | [baconbits] 157 | server: irc.baconbits.org 158 | port: 6697 159 | ssl: 1 160 | loginURL: https://baconbits.org/login.php 161 | downloadURL: https://baconbits.org/torrents.php?action=download&id= 162 | downloadType: 1 163 | botname: BaconBot 164 | botwho: bot@baconbits.org 165 | announcechannel: #torrents 166 | requiresauth: 0 167 | announcelines: 1 168 | announces: music, misc 169 | misc: (\S+) uploaded: (.*) - https://baconbits\.org/torrents\.php\?id=(\d+)&torrentid=(\d+) - ?(.*)$ 170 | music: (\S+) uploaded: (.+?) - (.+) \[(\d+)\] - (MP3|FLAC|Ogg|AAC|AC3|DTS|Ogg Vorbis) / ((?:24bit)? ?Lossless|[Vxq\.0-9|APS]{1,5}(?: \(VBR\))?) / (Log)? ?/? ?(Cue)? ?/? ?(CD|DVD|Vinyl|Soundboard|SACD|Cassette|DAT|WEB|Blu-ray) ?/? ?(Scene)? - https://baconbits\.org/torrents\.php\?id=(\d+)&torrentid=(\d+) - ?(.*)$ 171 | intro: @(\S+) uploaded: 172 | video: (\S+) uploaded: (.+) \[(\d+)\] - (.+) \/ (.+) \/ (.+) \/ (.+) \/ (.+) - https://baconbits\.org/torrents\.php\?id=(\d+)&torrentid=(\d+) - ?(.*)$ 173 | tags: uploader, title, artist, album, year, format, quality, log, cue, source, scene, groupID, encoding, audio, container, resolution, all_tags 174 | not_tags: not_uploader, not_title, not_artist, not_album, not_year, not_format, not_quality, not_log, not_cue, not_source, not_scene, not_groupID, not_encoding, not_audio, not_container, not_resolution, not_tags 175 | musicFormat: uploader, artist, album, year, format, quality, log, cue, source, scene, groupID, downloadID, tags 176 | miscFormat: uploader, title, groupID, downloadID, tags 177 | videoFormat: uploader, title, year, source, encoding, audio, container, resolution, groupID, downloadID, tags 178 | 179 | [bd25] 180 | downloadURL: http://bd25.org/download.php?id= 181 | loginURL: http://bd25.org/index.php?page=login 182 | loginuserpost: uid 183 | loginpasswordpost: pwd 184 | login200: 1 185 | downloadType: 4 186 | urlending: &f= 187 | 188 | [bibliotik] 189 | loginURL: http://bibliotik.org/login 190 | downloadURL: http://bibliotik.org/torrents 191 | downloadType: 3 192 | urlending: download 193 | 194 | [bitgamer] 195 | server: irc.bitgamer.su 196 | port: 7777 197 | ssl: 1 198 | botname: Bob 199 | botwho: BobSaget@Illest.MF.in.a.Cardigan.Sweater 200 | announcechannel: #bitgamer 201 | requiresauth: 0 202 | announcelines: 1 203 | announces: games 204 | games:New torrent: \((.+)\) (.*) \[([^/]+)?/?([^/]+)?/?(\d+)?.*\] (.*) http://www\.bitgamer\.su/details\.php\?id=(\d+) 205 | intro:New torrent: 206 | downloadURL: http://www.bitgamer.su/download.php?id= 207 | downloadType: 1 208 | loginURL: http://www.bitgamer.su/login.php 209 | tags: platform, title, region, genre, year, size 210 | not_tags: not_platform, not_title, not_region, not_genre, not_year, not_size 211 | gamesFormat: platform, title, region, genre, year, size, downloadID 212 | 213 | [bithdtv] 214 | server: irc.p2p-network.net 215 | port: 6697 216 | ssl: 1 217 | botname: BHD-bot 218 | announcechannel:#bit-hdtv 219 | announcelines: 1 220 | requiresauth: 2 221 | botwho: ~BHD-bot@P2PNET-D572632F.bit-hdtv.com 222 | announces: tv 223 | tv: New torrent: ([^\|]+) \| Cat.: ?([^\|]*) \| ?([^\|]*) \| ?([^\|]*) \|([^\|]+) \| Uploader:([^\|]+) \| http.*id=(\d+) 224 | intro: New torrent: 225 | downloadType: 2 226 | downloadURL: http://www.bit-hdtv.com/download.php 227 | tags: title, category, codec, unknown, resolution, uploader 228 | not_tags: not_title, not_category, not_codec, not_unknown, not_resolution, not_uploader 229 | tvFormat: title, category, codec, unknown, resolution, uploader, downloadID 230 | 231 | [bitme] 232 | server: irc.lostirc.org 233 | port: 6697 234 | ssl: 1 235 | botname: BitMe 236 | announcechannel: #bitme.announce 237 | announcelines: 1 238 | requiresauth: 1 239 | authstring: !invite $username $irckey 240 | botwho: eggdrop@Lost-1E91F4D5.net 241 | announces: misc 242 | presetcookie: 1 243 | misc: BitMe\.ORG Category: \[ (.*) \] Torrents: \[ (.*) \] Size: \[ (.*) \] Details: \[ http.*id=(\d+)&hit=1 \] Torrent: \[ .* \] 244 | intro: BitMe.ORG Category: 245 | downloadType: 2 246 | downloadURL: http://www.bitme.org/download.php 247 | tags: category, title, size 248 | not_tags: not_category, not_title, not_size 249 | miscFormat: category, title, size, downloadID 250 | 251 | [bitmetv] 252 | server: irc.lostirc.org 253 | port: 6697 254 | ssl: 1 255 | botname: bitmetv 256 | announcechannel: #bitmetv.announce 257 | announcelines: 1 258 | authchan: #bitmetv.announce 259 | requiresauth: 1 260 | authstring: !invite $username $irckey 261 | botwho: eggdrop@bitmetv.org 262 | announces: tv 263 | presetcookie: 1 264 | tv: BitMeTV\.ORG Torrents: \[ (.*?) \] Size: \[ (.*?) \] Link: \[.*?id=(.*?)&hit=1 \] 265 | intro: BitMeTV.ORG Torrents 266 | downloadType: 2 267 | downloadURL: https://www.bitmetv.org/download.php 268 | tags: title, size 269 | not_tags: not_title, not_size 270 | tvFormat: title, size, downloadID 271 | 272 | [broadcasthenet] 273 | server: irc.broadcasthe.net 274 | port: 6697 275 | ssl: 1 276 | botname: CableGuy 277 | botwho: Cable@CableGuy.Bot.BroadcasThe.Net 278 | announcebotname: Barney 279 | announcebotwho: 4505@Barney.Bot.BroadcasThe.Net 280 | announcechannel: #btn-whatauto 281 | announcelines: 1 282 | announces: video 283 | downloadType: 1 284 | video: ([^\|]*) \| (?:AutoFillFail|S(?:eason )?(\d+)(?:E(\d+))?|[^\|]*) \| (Episode|Season) \| (\d+) \| (AVI|MKV|VOB|MPEG|MP4|ISO|WMV|TS|M4V|M2TS|---) \| (XViD|x264-Hi10P|MPEG2|DiVX|DVDR|VC-1|x264|H.264|H.265|WMV|BD|---) \| (HDTV|PDTV|DSR|DVDRip|TVRip|VHSRip|Bluray|BDRip|BRRip|DVD5|DVD9|HDDVD|WEBRip|WEB-DL|BD5|BD9|BD25|BD50|Mixed|Unknown|---) \| (SD|720p|1080p|1080i|2160p|Portable Device|---) \| (Yes|No) \| (Yes|No) \| (\d+) \| ([^\|]*) \| ([^\|]*) (?:\| (.*))? 285 | loginURL: https://broadcasthe.net/login.php 286 | morepostdata: 'keeplogged' : '1' 287 | downloadURL: https://broadcasthe.net/torrents.php?action=download&id= 288 | tags: series, year, container, source, uploader, encoding, resolution, season, episode, type, scene, fasttorrent, title, language, releasename 289 | not_tags: not_series, not_year, not_container, not_source, not_uploader, not_encoding, not_resolution, not_season, not_episode, not_type, not_title, not_language, not_releasename 290 | videoFormat: series, season, episode, type, year, container, encoding, source, resolution, scene, fasttorrent, downloadID, uploader, language, releasename 291 | 292 | [brokenstones] 293 | server: irc.p2p-network.net 294 | port: 6697 295 | ssl: 1 296 | botname: Lisa 297 | botwho: ~Lisa@most.gorgeous.bot 298 | announcechannel: #brokenstones 299 | requiresauth: 0 300 | announces: misc 301 | announcelines: 1 302 | misc: \[BrokenStones\] (.*) - http://brokenstones.me/details.php\?id=(\d+) 303 | intro: [BrokenStones] 304 | presetcookie: 1 305 | downloadType: 2 306 | downloadURL: https://brokenstones.me/download.php 307 | tags: title 308 | not_tags: not_title 309 | miscFormat: title, downloadID 310 | 311 | [delish] 312 | server: irc.deli.sh 313 | port: 6697 314 | ssl: 1 315 | botname: Delish 316 | botwho: Delish@bot.deli.sh 317 | announcechannel: #deli.sh-announce 318 | requiresauth: 1 319 | authstring: enter $authchan $username $irckey 320 | authchan: #deli.sh-announce 321 | announces: food 322 | announcelines: 1 323 | food: ^(\[.*\]) :: (.*) :: (.*) :: http://deli\.sh/torrents\.php\?id=\d+ / http://deli\.sh/torrents\.php\?action=download&id=(\d+).* 324 | loginURL: http://deli.sh/login.php 325 | downloadType: 1 326 | downloadURL: https://ssl.deli.sh/torrents.php?action=download&id= 327 | tags: category, title, tags 328 | not_tags: not_category, not_title, not_tags 329 | foodFormat: category, title, tags, downloadID 330 | 331 | [digitalhive] 332 | server: irc.corrupt-net.org 333 | port: 7000 334 | ssl: 1 335 | botname: DH 336 | botwho: bot@dh.org 337 | announcechannel: #dh.announce 338 | announcelines: 1 339 | requiresauth: 0 340 | announces: misc 341 | misc: New in(?: ([\S]*) )?- (.*?) (?:\(Uploaded (.+) After Pre\.\))?\s*\(http://www.digitalhive.org/details.php\?id=(\d+)\) 342 | intro: New in 343 | downloadURL: https://www.digitalhive.org/download.php?id= 344 | downloadType: 1 345 | loginURL: https://www.digitalhive.org/takelogin.php 346 | tags: group, pretime, title 347 | not_tags: not_group, not_pretime, not_title 348 | miscFormat: group, title, pretime, downloadID 349 | 350 | [fux0r] 351 | server: irc.corrupt-net.org 352 | port: 6697 353 | ssl: 1 354 | botname: XXX 355 | botwho: Announce@hey.fux0r.eu 356 | announcechannel: #fux0r 357 | announcelines: 1 358 | requiresauth: 0 359 | loginURL: https://hey.fux0r.eu/login.php 360 | announces: porn 361 | porn: \s*New Filth \( (.*) \) Formats \( ([^/]*) / ([^/]*) / ([^/]*)(?: / (720p|1080p))?(?: / (Scene))?(?: / (Freeleech))?(?: / ([/ Disc \d]*?))?(?: / (Exclusive))? \) URL \( http://hey\.fux0r\.eu/torrents\.php\?id=\d+ / https://ssl\.fux0r\.eu/torrents\.php\?id=(\d+) \) TID \( (\d+) \) 362 | downloadtype: 1 363 | downloadURL: https://hey.fux0r.eu/torrents.php?action=download&id= 364 | tags: title, container, codec, source, hd, scene, freeleech, discnumber, exclusive, groupID 365 | not_tags: not_title, not_container, not_codec, not_source, not_hd, not_discnumber, not_groupID 366 | pornFormat: title, container, codec, source, hd, scene, freeleech, discnumber, exclusive, groupID, downloadID 367 | 368 | [gazellegames] 369 | ;You have to set #announce as a Auto-Join IRC Channel in your profile as this is the only way to enter channels. 370 | server: irc.gazellegames.net 371 | port: 7000 372 | ssl: 1 373 | botname: Vertigo 374 | botwho: Vertigo@gazellegames.net 375 | announcechannel: #announce 376 | announcebotname: Vertigo 377 | announcebotwho: Vertigo@gazellegames.net 378 | requiresauth: 1 379 | authstring: enter $username $irckey 380 | ;authchan: #announce 381 | announcelines: 1 382 | announces: games 383 | games: (.+?) - Uploaded: \|\|(?:[ ])(Board Game|Mac|iOS|Android|DOS|Windows|XBLA|Xbox|Xbox 360|Xbox on 360|DSiWare|Game Boy|Game Boy|DSiWare|Game Boy|Game Boy Advance|Game Boy Color|NES|Nintendo 64|Nintendo 3DS|Nintendo DS|Nintendo GameCube|Super NES|Virtual Boy|Wii|WiiVC|WiiWare|PlayStation|PlayStation 2|PlayStation 3|PlayStation Portable|PlayStation Vita|PS on PSP|Dreamcast|Game Gear|Master System|Mega Drive|Saturn|Atari|Atari 2600|Atari 5200|Atari 7800|Atari Jaguar|Atari Lynx|Amstrad CPC|Spectravideo|MSX|MSX 2|Bandai WonderSwan|Bandai WonderSwan Color|Board Game|Card Game|Colecovision|ex-bG Collections|ex-bG Fullsets|ex-bG Goldens|Fairchild Channel F|General Computer Vectrex|Interactive DVD|Linux|Mattel Intellivision|Memotech MTX|Miles Gordon Sam Coupe|Phone/PDA|SNK NeoGeo Pocket|Taito Type X|Tandy Color Computer|Tangerine Oric|Thomson MO5|TurboGrafx|Watara Supervision)?(?: \- )?(.+)? in (.+) \[-(\d+)-\] - \((.+), (.*)\) - http://gazellegames.net/torrents.php\?id=(\d+) -OR- http://gazellegames.net/torrents.php\?action=download&id=(\d+) - (.*) 384 | gamesFormat: uploader, platform, uploadTitle, gameTitle, year, language, scene, groupID, downloadID, tags 385 | tags: platform, gameTitle, year, language, scene, groupID, tags, all_tags 386 | not_tags: not_platform, not_gameTitle, not_year, not_language, not_groupID, not_tags 387 | downloadURL: https://gazellegames.net/torrents.php?action=download&id= 388 | downloadType: 1 389 | loginURL: https://gazellegames.net/login.php 390 | 391 | 392 | [gettorrents] 393 | server: irc.gettorrents.org 394 | port: 6697 395 | ssl: 1 396 | botname: Gettorrents 397 | botwho: Gettorrent@gettorrents.org 398 | announcechannel: #gettorrents 399 | requiresauth: 0 400 | announcelines: 1 401 | announces: music 402 | music: New torrent was uploaded -> Name: ([^:]*) \.:\. Requested: (no|yes) \.:\. Category: ([^\.]*) \.:\. Size: [^:]*:\. Uploaded By: ([^\s]*) \.:\. URL: https://gettorrents.org/details.php\?id=([\d]+) 403 | tags: name, requested, category, uploader 404 | not_tags: not_name, not_requested, not_category, not_uploader 405 | musicFormat: name, requested, category, uploader, downloadID 406 | downloadURL: https://www.gettorrents.org/download.php 407 | downloadType: 2 408 | loginURL: https://gettorrents.org/takelogin.php 409 | 410 | [hdbits] 411 | server: irc.p2p-network.net 412 | port: 6697 413 | ssl: 1 414 | requiresAuth: 1 415 | botname: midgards 416 | botwho: ~johndoe@hdbits.org 417 | authstring: invite $irckey 418 | announcechannel: #hdbits 419 | announces: hd 420 | announceLines: 1 421 | hd: New Torrent: (.*) - Type: (.*?)(?: \((.+),(.+)\))? - Uploaded by: (\w+) - http://hdbits.org/details.php\?id=(\d+)&hit=1 422 | hdFormat: title, category, codec, source, uploader, downloadID 423 | intro: New Torrent: 424 | tags: title, category, codec, source, uploader 425 | not_tags: not_title, not_category, not_codec, not_source, not_uploader 426 | downloadType: 1 427 | loginURL: https://hdbits.org/takelogon.php 428 | loginuserpost: uname 429 | downloadURL: https://hdbits.org/download.php/1.torrent?id= 430 | 431 | [iptorrents] 432 | server: irc.iptorrents.com 433 | port: 6697 434 | ssl: 1 435 | botname: IPT 436 | botwho: IPT@THE.BOT 437 | announcechannel: #ipt.announce 438 | announcelines: 1 439 | requiresauth: 0 440 | announces: misc 441 | presetcookie: 1 442 | misc: \[([^\]]*)\] (.*) - http://www.iptorrents.com/details.php\?id=(\d+)(?: - (.*))? 443 | downloadtype: 2 444 | downloadURL: http://on.iptorrents.com/download.php 445 | tags: group, title, sizeString 446 | not_tags: not_group, not_title, not_sizeString 447 | miscFormat: group, title, downloadID, sizeString 448 | 449 | [karagarga] 450 | server: irc.brokensphere.net 451 | port: 6697 452 | ssl: 1 453 | botname: Hermes[RSS] 454 | botwho: Hermes@karagarga.net 455 | announcechannel: #karagarga 456 | requiresauth: 0 457 | announcelines: 4 458 | announces: movies 459 | movies: ^(?: Now available.*? Tracker| --> http://karagarga.net/details.php\?id=\d+)(.*) - (.*) \((.*\d+.*)\) \[(.*)\] --> http://karagarga.net/details.php\?id=(\d+)(?:.* - .* \(.*\d+.*\) \[.*\]| =+) 460 | intro: Now available on 461 | @^ ?--> http.*?\d+ ?[^=]+ .* http.*?\d+ =+$ 462 | downloadURL: http://karagarga.net/down.php 463 | downloadType: 2 464 | loginURL: http://karagarga.net/takelogin.php 465 | tags: director, title, year, genre 466 | not_tags: not_director, not_title, not_year, not_genre 467 | moviesFormat: director, title, year, genre, downloadID 468 | 469 | [lztr] 470 | server: hub.tik-t0k.net 471 | port: 6697 472 | ssl: 1 473 | botname: Scarlett 474 | botwho: Scarlett@lztr.us 475 | announcechannel: #lztr 476 | requiresauth: 1 477 | authstring: enter #lztr $username $irckey 478 | announcelines: 1 479 | announces: music 480 | music: (.*) - (.*) \[(\d+)\] - (FLAC|MP3|Ogg|M4A) / ((?:24Bit Lossless|Lossless|V0|V2|320k|256k|192k|256|q8)(?: \(VBR\))?)(?: / (Log))?(?: / ([\d\.]*)%)?(?: / (Cue))? / (CD|WEB|SACD|Vinyl|iTunes)(?: / (Scene))?(?: / (Super Awesome Upload))?(?: /(Exclusive| SSD))? - http://www.lztr.us/.*ts.php\?id=(\d+) / .*d&id=(\d+) - (.*) 481 | downloadURL: http://lztr.us/torrents.php?action=download&id= 482 | downloadType: 1 483 | loginURL: http://lztr.us/login.php 484 | tags: artist, album, year, format, quality, log, logper, cue, source, scene, superawesomeupload, exclusive, groupID, all_tags 485 | not_tags: not_artist, not_album, not_year, not_format, not_quality, not_log, not_logper, not_cue, not_source, not_scene, not_exclusive, not_groupID, not_tags 486 | musicFormat: artist, album, year, format, quality, log, logper, cue, source, scene, superawesomeupload, exclusive, groupID, downloadID, tags 487 | 488 | [morethantv] 489 | server: irc.morethan.tv 490 | port: 6669 491 | ssl: 1 492 | botname: X 493 | botwho: X@netadmin.irc.morethan.tv 494 | announcechannel: #announce 495 | requiresauth: 1 496 | authstring: enter $username $irckey $authchan 497 | authchan: #morethan.tv 498 | announces: video 499 | announcelines: 1 500 | video: (.*) - (x264|XviD|DivX|h264|DVD5|DVD9|BD25|BD50|MPEG2|Other) / (SD|720p|1080p|1080i|Portable_Device|4k|Other) / (HDTV|TVrip|Web-DL|Webrip|DSR|BluRay|DVD|DVD9|HDDVD|VHSrip|PDTV|Other) - https://www\.morethan\.tv/torrents\.php\?id=(\d+) / https://www\.morethan\.tv/torrents\.php\?action=download&id=(\d+) - ?(.*) 501 | loginURL: https://www.morethan.tv/login.php 502 | downloadType: 1 503 | downloadURL: https://www.morethan.tv/torrents.php?action=download&id= 504 | tags: releasename, container, resolution, source, groupID, tags 505 | not_tags: not_releasename, not_container, not_resolution, not_source, not_groupID, not_tags 506 | videoFormat: releasename, container, resolution, source, groupID, downloadID, tags 507 | 508 | [thegft] 509 | server: irc.thegft.org 510 | port: 6697 511 | ssl: 1 512 | botname: Spannage 513 | botwho: Spannage@GFT.Whore 514 | announcechannel: #gftracker-spam 515 | announcelines: 1 516 | requiresauth: 1 517 | authstring: invite $irckey 518 | presetcookie: 1 519 | announces: misc 520 | misc: NEW :: ([^:]+) :: ([^:]+) :: http://www\.thegft\.org/details\.php\?id=([\d]+) :: .* 521 | miscFormat: title, group, downloadID 522 | downloadtype: 1 523 | downloadURL: http://www.thegft.org/download.php?id= 524 | tags: title, group 525 | not_tags: not_title, not_group 526 | 527 | [tophos] 528 | server: irc.corrupt-net.org 529 | port: 6697 530 | ssl: 1 531 | botname: Tophos 532 | botwho: TopHos@Corrupt-FA371758.xzibition.com 533 | announcechannel: #tophos.spam 534 | announcelines: 5 535 | requiresauth: 0 536 | presetcookie: 1 537 | announces: misc 538 | misc: Name: ?(.*)Category: ?(.*)Pretime: ?(.*)Size: ?(.*)URL: http://www.tophos.org/details.php\?id=(\d+) 539 | miscFormat: title, category, pretime, sizeString, downloadID 540 | intro: Name: 541 | downloadtype: 1 542 | downloadURL: https://www.tophos.org/download.php?torrent= 543 | tags: title, category, pretime, sizeString 544 | not_tags: not_title, not_category, not_pretime, not_sizeString 545 | 546 | [notwhat] 547 | server: irc.notwhat.cd 548 | port: 6697 549 | ssl: 1 550 | botname: che 551 | botwho: che@irc.notwhat.services 552 | announcechannel: #notwhat 553 | requiresauth: 1 554 | authstring: enter $authchan $username $irckey 555 | authchan: #notwhat.cd-announce 556 | announces: music 557 | announcelines: 1 558 | music: (.+?) - (.+) \[(\d+)\] \[([^\]]+)\] - (MP3|FLAC|Ogg|AAC|AC3|DTS|Ogg Vorbis) / ((?:24bit)?(?: ?Lossless)?(?:[\d|~|\.xVq|\s]*(?:AAC|APX|APS|Mixed|Auto|VBR)?(?: LC)?)?(?: ?(?:\(VBR\)|\(?ABR\)?|[K|k][b|p]{1,2}s)?)?)(?: / (Log))?(?: / ([-0-9\.]+)\%)?(?: / (Cue))?(?: / (CD|DVD|Vinyl|Soundboard|SACD|Cassette|DAT|WEB|Blu-ray))(?: / (Scene))?(?: / (Freeleech!))? - https://notwhat\.cd/torrents\.php\?id=(\d+) / https://notwhat\.cd/torrents\.php\?action=download&id=(\d+) - ?(.*) 559 | loginURL: https://notwhat.cd/login.php 560 | downloadType: 1 561 | downloadURL: https://notwhat.cd/torrents.php?action=download&id= 562 | tags: artist, album, year, type, format, quality, log, logper, cue, source, scene, freeleech, groupID, downloadID, tags 563 | not_tags: not_artist, not_album, not_year, not_type, not_format, not_quality, not_log, not_logper, not_cue, not_source, not_scene, not_freeleech, not_groupID, not_downloadID, not_tags 564 | musicFormat: artist, album, year, type, format, quality, log, logper, cue, source, scene, freeleech, groupID, downloadID, tags 565 | 566 | [redacted] 567 | server: irc.scratch-network.net 568 | port: 6697 569 | ssl: 1 570 | botname: Drone 571 | botwho: Drone@127.0.0.1 572 | announcechannel: #red-announce 573 | requiresauth: 1 574 | authstring: enter $authchan $username $irckey 575 | authchan: #red-announce 576 | announces: music 577 | announcelines: 1 578 | music: (.+?) - (.+) \[(\d+)\] \[([^\]]+)\] - (MP3|FLAC|Ogg|AAC|AC3|DTS|Ogg Vorbis) / ((?:24bit)?(?: ?Lossless)?(?:[\d|~|\.xVq|\s]*(?:AAC|APX|APS|Mixed|Auto|VBR)?(?: LC)?)?(?: ?(?:\(VBR\)|\(?ABR\)?|[K|k][b|p]{1,2}s)?)?)(?: / (Log))?(?: / ([-0-9\.]+)\%)?(?: / (Cue))?(?: / (CD|DVD|Vinyl|Soundboard|SACD|Cassette|DAT|WEB|Blu-ray))(?: / (Scene))?(?: / (Freeleech!))? - https://redacted\.ch/torrents\.php\?id=(\d+) / https://redacted\.ch/torrents\.php\?action=download&id=(\d+) - ?(.*) 579 | loginURL: https://redacted.ch/login.php 580 | downloadType: 1 581 | downloadURL: https://redacted.ch/torrents.php?action=download&id= 582 | tags: artist, title, album, tags, year, type, log, logper, cue, scene, freeleech, format, quality, source, all_tags, groupID 583 | not_tags: not_artist, not_title, not_album, not_tags, not_year, not_type, not_format, not_quality, not_source, not_groupID 584 | musicFormat: artist, album, year, type, format, quality, log, logper, cue, source, scene, freeleech, groupID, downloadID, tags 585 | 586 | [passthepopcorn] 587 | server: irc.passthepopcorn.me 588 | port: 7000 589 | ssl: 1 590 | botname: Hummingbird 591 | announcechannel: #ptp-announce 592 | announcelines: 1 593 | botwho: nectar@ircop.passthepopcorn.me 594 | requiresauth: 1 595 | authstring: ENTER $username $irckey $announcechan 596 | announces: video 597 | video: (.*) \[(\d+)\](?: by (.*))? - ([^\/]*) / ([^\/]*) / ([^\/]*) / ([^\/]*)(?: / (Scene))?(?: / (Freeleech!))? - https?://passthepopcorn\.me/torrents\.php\?id=(\d+)&torrentid=(\d+) / https?://passthepopcorn\.me/torrents.php\?action=download&id=\d+ - ?(.*) 598 | loginURL: https://passthepopcorn.me/ajax.php?action=login 599 | downloadURL: https://passthepopcorn.me/torrents.php?action=download&id= 600 | downloadType: 1 601 | loginjson: 1 602 | tags: title, year, director, codec, source, container, resolution, scene, freeleech, groupID, tags 603 | not_tags: not_title, not_director, not_year, not_codec, not_container, not_source, not_resolution, not_groupID, not_tags 604 | videoFormat: title, year, director, codec, source, container, resolution, scene, freeleech, groupID, downloadID, tags 605 | 606 | [pianosheets] 607 | ;IRC does not seem to be implemented is not tested. 608 | downloadURL: http://www.pianosheets.org/torrents.php?action=download&id= 609 | downloadType: 1 610 | loginURL: http://www.pianosheets.org/login.php 611 | 612 | [pretome] 613 | server: irc.preto.me 614 | port: 6697 615 | ssl: 1 616 | botname: tehDude 617 | botwho: theDude@preto.me 618 | announcechannel: #announce 619 | announcelines: 1 620 | requiresauth: 0 621 | presetcookie: 1 622 | announces: video 623 | video: tehFIRE: \[(.*)\] (.*) :: preGAP: (.*) :: http.*id=(\d+) 624 | intro: tehFIRE: 625 | downloadURL: https://preto.me/download.php 626 | downloadType: 2 627 | tags: category, title, pretime 628 | not_tags: not_category, not_title, not_pretime 629 | videoFormat: category, title, pretime, downloadID 630 | 631 | [piratethenet] 632 | server: hub.tik-t0k.net 633 | port: 6697 634 | ssl: 1 635 | botname: PTNBot 636 | botwho: PTNBot@ptn.b0t 637 | announcechannel: #piratethe.net 638 | announcelines: 1 639 | requiresauth: 1 640 | authstring: enter $irckey 641 | announces: video 642 | video: New Torrent.* Name: (.*) .:. .:. Quality: (.*) .:. Size: (.*) http.*id=(\d+) 643 | intro: New Torrent Uploaded 644 | downloadURL: https://piratethe.net/download.php?torrent= 645 | downloadType: 1 646 | loginURL: https://piratethe.net/takelogin.php 647 | tags: title, quality 648 | not_tags: not_title, not_quality 649 | videoFormat: title, quality, size, downloadID 650 | 651 | [pwnnetwork] 652 | downloadURL: https://pwnnetwork.net/torrents.php?action=download&id= 653 | downloadType: 1 654 | loginURL: https://pwnnetwork.net/login.php 655 | 656 | [quorks] 657 | server: irc.chaoz-irc.org 658 | port: 7000 659 | ssl: 1 660 | botname: Quorksbot 661 | botwho: quorksbot@ist.der.Newsbot.von.quorks 662 | announcechannel: #qrt-announce 663 | announcelines: 1 664 | requiresauth: 0 665 | downloadURL: http://quorks.net/download.php 666 | downloadType: 2 667 | loginURL: https://quorks.net/takelogin.php 668 | announces: misc 669 | misc: -=\] (.*) \[=- http.*id=(\d+) ?(.*) 670 | tags: title, pretime 671 | not_tags: not_title, not_pretime 672 | miscFormat: title, downloadID, pretime 673 | 674 | 675 | [sharetheremote] 676 | server: irc.sharetheremote.org 677 | port: 6697 678 | ssl: 1 679 | botname: TVGuide 680 | botwho: ~HALv4@127.0.0.1 681 | announcechannel: #announce 682 | requiresauth: 0 683 | announcelines: 1 684 | announces: tv 685 | tv: New upload by ([\w]+) :: (.*?) S([\d]+)E([\d]+) :: ([^/]+)/([^/]+)/([^ ]+) :: http://sharetheremote.org/torrents\.php\?id=[\d]+&torrentid=([\d]+) 686 | downloadURL: https://sharetheremote.org/torrents.php?action=download&id= 687 | downloadType: 1 688 | loginURL: https://sharetheremote.org/login.php 689 | tags: uploader, title, season, episode, source, container, resolution 690 | not_tags: not_uploader, not_title, not_season, not_episode, not_source, not_container, not_resolution 691 | tvFormat: uploader, title, season, episode, source, container, resolution, downloadID 692 | 693 | [sceneaccess] 694 | ;SCC requires the passkey in credentials.conf 695 | server: irc.sceneaccess.org 696 | port: 7000 697 | ssl: 1 698 | botname: SCC 699 | botwho: ~SCC@bot.sceneaccess.org 700 | announcechannel: #announce 701 | announcelines: 1 702 | requiresauth: 0 703 | announces: misc, packs 704 | packs: NEW in (.*?[/|\\]Packs): -> (.*?) \((.*?)\) - .*?id=(\d+) 705 | misc: NEW in ([^:]+?): -> (.*?) (?:\(Uploaded (.*?) after pre\) - )?\(([^\)]+)\) - .*?id=(\d+) 706 | intro: NEW in 707 | loginURL: http://sceneaccess.org/login 708 | downloadURL: http://sceneaccess.org/download 709 | downloadType: 5 710 | nameregexp: NEW in .* -> (.*?) \(.*\).* 711 | tags: group, title, pretime, size 712 | not_tags: not_group, not_title, not_pretime, not_size 713 | miscFormat: group, title, pretime, size, downloadID 714 | packsFormat: group, title, size, downloadID 715 | 716 | [scenehd] 717 | server: irc.p2p-network.net 718 | port: 6697 719 | ssl: 1 720 | botname: SceneHD 721 | botwho: ~SceneHD@SceneHD.org 722 | announcebotname: fireBot 723 | announcebotwho: ~fireBot@P2PNET-93A39977.fiery.ca 724 | announcechannel: #SceneHD.Announce 725 | requiresauth: 1 726 | authstring: invite $irckey $announcechan 727 | announces: hd 728 | announcelines: 1 729 | hd: \[NEW\] (.*) \[(.*)\] https:.*id=(\d+) by: (.*) 730 | intro: [NEW] 731 | downloadType: 1 732 | downloadURL: https://scenehd.org/download.php?id= 733 | loginURL: https://scenehd.org/takelogin.php 734 | tags: title, category, uploader 735 | not_tags: not_title, not_category, not_uploader 736 | hdFormat: title, category, downloadID, uploader 737 | 738 | [sciencehd] 739 | server: irc.forpirates.net 740 | port: 6697 741 | ssl: 1 742 | botname: SciBorg 743 | botwho: SciBorg@sciencehd.net 744 | announcechannel: #SciHD-Announce 745 | requiresauth: 1 746 | authstring: $announcechan $username $irckey 747 | announcelines: 1 748 | announces: science 749 | science: New Upload! \[ ([\w]+) \] \[([^\]]+)\] \[([\d]+) \| ([\w]+) \| ([\w]+) \| ([\w]+)\] - http[s]?://sciencehd\.info/torrents\.php\?id=[\d]+&torrentid=([\d]+) 750 | downloadURL: https://sciencehd.info/torrents.php?action=download&id= 751 | downloadType: 1 752 | loginURL: http://sciencehd.info/login.php 753 | tags: group, title, year, container, resolution, audio 754 | not_tags: not_group, not_title, not_year, not_container, not_resolution, not_audio 755 | scienceFormat: group, title, year, container, resolution, audio, downloadID 756 | 757 | [shellife] 758 | ;Shellife.eu has no announce channel (afaik) 759 | downloadtype: 1 760 | downloadURL: http://shellife.eu/download.php?id= 761 | loginURL: http://shellife.eu/takelogin.php 762 | 763 | 764 | [stopthepresses] 765 | server: irc.stopthepress.es 766 | port: 6697 767 | ssl: 1 768 | botname: PaperBoy 769 | botwho: PaperBoy@ircop.stopthepress.es 770 | requiresauth: 1 771 | authstring: AUTH $username $irckey 772 | announcechannel: #STP-Announce 773 | announcelines: 1 774 | announces: ebook, newspaper, packs 775 | downloadType: 1 776 | ebook: (.+) - (.+) \[(\d{4})\] \[(Book)\] - (EPUB|MOBI|PDF|AZW3|DJVU|HTML|TXT|CBR|CBZ|Other) / (Web|Disc|Scan|Unknown|Other)(?: / (Retail))?(?: / (Scene))?(?: / (Freeleech!))? - .*id=(\d+) / .*id=(\d+) - (.*) 777 | ebookFormat: author, title, year, type, format, source, retail, scene, freeleech, groupID, downloadID, tags 778 | newspaper: (.+?) - (\d{2})[-]*(\d{2})? \[(\d{4})\] \[(Issue|Special Issue)\] - (EPUB|MOBI|PDF|AZW3|DJVU|HTML|TXT|CBR|CBZ|Other) / (Web|Disc|Scan|Unknown)(?: / (Retail))?(?: / (Scene))?(?: / (Freeleech\!))? - .*id=(\d+) / .*id=(\d+) - (.*) 779 | newspaperFormat: title, month, day, year, type, format, source, retail, scene, freeleech, groupID, downloadID, tags 780 | packs: (.+) \[(\d{4})\] \[(Pack)\] - (EPUB|MOBI|PDF|AZW3|DJVU|HTML|TXT|CBR|CBZ|Other) / (Web|Disc|Scan|Unknown)(?: / (Retail))?(?: / (Scene))?(?: / (Freeleech\!))? - .*id=(\d+) / .*id=(\d+) - (.*) 781 | packsFormat: title, year, type, format, source, retail, scene, freeleech, groupID, downloadID, tags 782 | loginURL: https://stopthepress.es/login.php 783 | morepostdata: 'keeplogged' : '1', 'login' : 'Login' 784 | downloadURL: https://stopthepress.es/torrents.php?action=download&id= 785 | tags: author, title, day, month, year, format, source, retail, scene, freeleech, groupID, tags, all_tags 786 | not_tags: not_author, not_title, not_year, not_format, not_source, not_retail, not_scene, not_groupID, not_tags 787 | 788 | [supertorrents] 789 | server: irc.p2pirc.org 790 | port: 7000 791 | ssl: 1 792 | botname: ST-Bot 793 | botwho: userbot@5EBD3F46.6B67753F.D9A02788.IP 794 | announcechannel: #supertorrents 795 | announcelines: 1 796 | requiresauth: 0 797 | announces: misc 798 | misc: NEW TORRENT: ([^ ]*) SECTION: ([^ ]*) PRETIME: [^:]*: http://www.supertorrents.org/details.php\?id=([\d]+)&hit=1 799 | downloadtype: 2 800 | downloadURL: http://www.supertorrents.org/download.php 801 | loginURL: http://www.supertorrents.org/takelogin.php 802 | tags: title, section 803 | not_tags: not_title, not_section 804 | miscFormat: title, section, downloadID 805 | 806 | [tehconnection] 807 | server: irc.p2p-net.eu 808 | port: 6697 809 | ssl: 1 810 | botname: Zooey 811 | botwho: Bot@TehConnection.eu 812 | announcechannel: #tehconnection 813 | requiresauth: 1 814 | authstring:invite $announcechan $username $irckey 815 | announcelines: 1 816 | announces: misc 817 | misc: New Torrent: (.*?) \((\d+)\) \[(.*?)\/(.*?)\/(.*?)\/(.*?)(?:\/(Scene)|)\] :: Uploader: (.*?) :: https://tehconnection\.eu/torrents\.php\?id=[\d]+&torrentid=(\d+) 818 | intro:New Torrent: 819 | downloadURL: https://tehconnection.eu/torrents.php?action=download&id= 820 | downloadType: 1 821 | loginURL: https://tehconnection.eu/login.php 822 | tags: title, year, codec, quality, container, source, scene, uploader 823 | not_tags: not_title, not_year, not_codec, not_quality, not_container, not_source, not_uploader 824 | miscFormat: title, year, source, quality, container, codec, scene, uploader, downloadID 825 | 826 | [torrentbytes] 827 | server: irc.p2p-network.net 828 | port: 6697 829 | ssl: 1 830 | botname: erica 831 | botwho: ~erica@torrentbytes.net 832 | announcebotwho: ~ByteMe@torrentbytes.net 833 | announcechannel: #torrentbytes-spam 834 | requiresauth: 1 835 | authstring: letmeinspam $username $irckey 836 | announces: misc 837 | announcelines: 1 838 | misc: \[TB-RLS\] \.:\. \[ (.*) \] \.:\. \[ http.*d\.php\?id=(\d+) \].*id=\d+ \](?: \.:\. \[ (.+) \])? 839 | intro: [TB-RLS] 840 | downloadType: 1 841 | downloadURL: https://www.torrentbytes.net/download.php?id= 842 | loginURL: https://www.torrentbytes.net/takelogin.php 843 | morepostdata: 'login' : 'Log in!', 'returnto' : '/' 844 | tags: title, pretime 845 | not_tags: not_title, not_pretime 846 | miscFormat: title, downloadID, pretime 847 | 848 | [torrentleech] 849 | server: irc.torrentleech.org 850 | port: 7021 851 | ssl: 1 852 | botname: _AnnounceBot_ 853 | botwho: _AnnounceB@86989CED.D067F63E.7EA4CE36.IP 854 | announcechannel: #torrentleech 855 | requiresauth: 0 856 | announcelines: 1 857 | announces: misc 858 | presetcookie: 1 859 | misc: New Torrent Announcement: <([^>]*)>[\W]*Name:'([^']*)[\W]*uploaded by '([^']*)' -\W+http://www.torrentleech.org/details.php\?id=(\d+) 860 | downloadURL: http://www.torrentleech.org/download.php 861 | downloadType: 2 862 | tags: group, title, uploader 863 | not_tags: not_group, not_title, not_uploader 864 | miscFormat: group, title, uploader, downloadID 865 | 866 | [torrentvault] 867 | server: irc.torrentvault.org 868 | port: 9022 869 | ssl: 1 870 | botname: TorrentVault 871 | announcechannel: #tv 872 | announcelines: 1 873 | authchan: #tv 874 | requiresauth: 1 875 | authstring: invite $username $irckey 876 | botwho: tv@services.torrentvault 877 | announces: misc 878 | downloadType: 1 879 | misc: (?:\w+) in (.+) -> (.+) by ([\S]+)(?: for (?:\w+))? \[(?:(?: [\w]+: [^\|\]]+)(?: [\|]?)){1,2}(?: Genre: ([\S]+) )?\] \[ https://www\.torrentvault\.org/(?:[a-z]|\?|\.|&|=)+(\d+) 880 | loginURL: http://www.torrentvault.org/login.php 881 | downloadURL: http://www.torrentvault.org/torrents.php?action=download&id= 882 | tags= group, title, uploader, genre 883 | not_tags: not_group, not_title, not_uploader, not_genre 884 | miscFormat: group, title, uploader, genre, downloadID 885 | 886 | [thebox] 887 | server: irc.theplace.bz 888 | port: 6697 889 | ssl: 1 890 | requiresAuth: 0 891 | botname: Bella 892 | botwho: Bella@tbz-750B1DDE 893 | announcechannel: #TheBox.bz 894 | announces: misc 895 | announceLines: 1 896 | misc: .*\s+->\s+'((.*)\s+\((.*)\)\s+\[(.*?)\s*\((.*)\)\])\s*'\s+@\s+http://thebox.bz/details\.php\?id=([\d]+)\s+\(\s*(.*)\s*\) 897 | presetcookie: 1 898 | downloadType: 2 899 | downloadURL: http://www.thebox.bz/download.php 900 | miscFormat: release, name, date, source, format, downloadID, category 901 | tags: release, name, date, source, format, category 902 | not_tags: release, name, date, source, format, category 903 | 904 | [theempire] 905 | server: irc.theplace.bz 906 | port: 6697 907 | ssl: 1 908 | requiresAuth: 0 909 | botname: Bella 910 | botwho: Bella@tbz-750B1DDE 911 | announcechannel: #TheEmpire.bz 912 | announces: misc 913 | announceLines: 1 914 | misc: .*\s+->\s+'((.*)\s+\((.*)\)\s+\[(.*)\s\((.*)\)\])'\s+@\s+http://theempire.bz/details\.php\?id=([\d]+)\s+\(\s*(.*)\s*\) 915 | presetcookie: 1 916 | downloadType: 2 917 | downloadURL: http://www.theempire.bz/download.php 918 | miscFormat: release, name, date, source, format, downloadID, category 919 | tags: release, name, date, source, format, category 920 | not_tags: release, name, date, source, format, category 921 | 922 | [theplace] 923 | server: irc.theplace.bz 924 | ssl: 0 925 | port: 6667 926 | botname: bella 927 | botwho: Bella@tbz-750B1DDE 928 | announcechannel: #theplace.bz 929 | announcelines: 1 930 | requiresauth: 0 931 | announces: selfhelp 932 | selfhelp: \[ThePlace\] New Upload -> '(.+)' @ http://theplace.bz/details\.php\?id=(\d+) \((.+)\) 933 | loginURL: http://theplace.bz/login.php 934 | downloadType: 2 935 | downloadURL: http://theplace.bz/download.php 936 | selfhelpFormat: title, downloadID, tags 937 | tags: title, tags 938 | not_tags: not_title, not_tags 939 | 940 | [theswarm] 941 | server: irc.swarm-network.me 942 | ssl: 1 943 | port: 6697 944 | botname: julie 945 | botwho: julie@julie.theswarm.me 946 | announcechannel: #announce 947 | announcelines: 1 948 | requiresauth: 1 949 | authstring: enter $authchan $username $irckey 950 | authchan: #announce 951 | announces: movie 952 | movie: abc(de)fg (\d+) 953 | movieFormat: title, downloadID 954 | tags: title 955 | not_tags: not_title 956 | loginURL: https://ssl.theswarm.me/login.php 957 | downloadURL: https://ssl.theswarm.me/torrents.php?action=download&id= 958 | downloadType: 1 959 | 960 | [undergroundgamer] 961 | server: irc.bitgamer.su 962 | port: 7777 963 | ssl: 1 964 | botname: Eva 965 | botwho: Eva@Servant.of.The.Supreme.Overlord 966 | announcechannel: #chat 967 | requiresauth: 0 968 | announcelines: 1 969 | announces: games 970 | games:New torrent: \((.+?)\) (.+?) \[([^/]+)?/?(\d+)?\] ?\[?(RQ)?\]? ?(.*) http://www\.underground-gamer\.com/details\.php\?id=(\d+) 971 | intro:New torrent: 972 | downloadURL: https://www.underground-gamer.com/download.php?id= 973 | downloadType: 1 974 | loginURL: https://www.underground-gamer.com/login.php 975 | tags: platform, title, genre, year, request, sizestring 976 | not_tags: not_platform, not_title, not_genre, not_year, not_request, not_sizestring 977 | gamesFormat: platform, title, genre, year, request, sizestring, downloadID 978 | 979 | [waffles] 980 | ;waffles does not have an announce channel anymore. 981 | ;server: irc.p2p-network.net 982 | ;port: 6697 983 | ;ssl: 1 984 | ;botname: Waffles 985 | ;botwho: ~bot@waffles.fm 986 | ;announcechannel: #waffles.fm-announce 987 | ;requiresauth: 1 988 | ;authstring: invite 989 | ;announcelines: 1 990 | ;announces: music 991 | ;music: \[([^\]]*)\]: ([^-]*) - ([^\[]*) \[(\d*)-([^-]*)-([^-]*)-([^-\]]*)(?:-([^-\]]*))?\] - http://waffles.fm/details.php\?id=(\d*) 992 | ;tags: genre, artist, album, year, source, format, quality, log 993 | ;not_tags: not_genre, not_artist, not_album, not_year, not_source, not_format, not_quality 994 | ;musicFormat: genre, artist, album, year, source, format, quality, log, downloadID 995 | downloadURL: https://www.waffles.fm/download.php/1 996 | downloadType: 2 997 | loginURL: https://www.waffles.fm/w/index.php?title=Special:UserLogin&action=submitlogin&type=login&returnto=Main_Page 998 | morepostdata: 'wpLoginattempt' : 'Log in' 999 | loginuserpost: wpName 1000 | loginpasswordpost: wpPassword 1001 | 1002 | [x264] 1003 | server: irc.xevion.net 1004 | port: 6670 1005 | ssl: 1 1006 | botname: x2bot 1007 | botwho: x264@Sodomy.Monster 1008 | announcechannel: #x264 1009 | requiresauth: 2 1010 | announcelines: 1 1011 | announces: movies 1012 | movies: \[ New Torrent: (.*) \| (.*) \| https:\/\/.*id=(\d+) \| .*title\/(.*)\/? \] 1013 | intro: [ New Torrent: 1014 | downloadURL: https://x264.me/download.php 1015 | downloadType: 2 1016 | loginURL: https://x264.me/takelogin.php 1017 | tags: title, category, imdbID 1018 | not_tags: not_title, not_category, not_imdbID 1019 | moviesFormat: title, category, downloadID, imdbID 1020 | 1021 | ;;BLANK TEMPLATE 1022 | [blank] 1023 | server: 1024 | port: 1025 | ssl: 1026 | botname: 1027 | botwho: 1028 | announcechannel: 1029 | requiresauth: 1030 | announcelines: 1031 | announces: 1032 | misc: 1033 | downloadURL: 1034 | downloadType: 1 1035 | loginURL: 1036 | tags: 1037 | not_tags: 1038 | miscFormat: 1039 | -------------------------------------------------------------------------------- /reports.conf.example: -------------------------------------------------------------------------------- 1 | [animebytes] 2 | seen = 0 3 | downloaded = 0 4 | 5 | [awesomehd] 6 | seen = 0 7 | downloaded = 0 8 | 9 | [baconbits] 10 | seen = 19 11 | downloaded = 0 12 | 13 | [bd25] 14 | seen = 0 15 | downloaded = 0 16 | 17 | [bibliotik] 18 | seen = 0 19 | downloaded = 0 20 | 21 | [bitgamer] 22 | seen = 0 23 | downloaded = 0 24 | 25 | [bithdtv] 26 | seen = 0 27 | downloaded = 0 28 | 29 | [bitme] 30 | seen = 0 31 | downloaded = 0 32 | 33 | [bitmetv] 34 | seen = 0 35 | downloaded = 0 36 | 37 | [bitspyder] 38 | seen = 0 39 | downloaded = 0 40 | 41 | [broadcasthenet] 42 | seen = 0 43 | downloaded = 0 44 | 45 | [brokenstones] 46 | seen = 0 47 | downloaded = 0 48 | 49 | [delish] 50 | seen = 0 51 | downloaded = 0 52 | 53 | [digitalhive] 54 | seen = 0 55 | downloaded = 0 56 | 57 | [fux0r] 58 | seen = 1 59 | downloaded = 0 60 | 61 | [gazellegames] 62 | seen = 0 63 | downloaded = 0 64 | 65 | [hdbits] 66 | seen = 0 67 | downloaded = 0 68 | 69 | [iptorrents] 70 | seen = 0 71 | downloaded = 0 72 | 73 | [karagarga] 74 | seen = 0 75 | downloaded = 0 76 | 77 | [lztr] 78 | seen = 0 79 | downloaded = 0 80 | 81 | [passthepopcorn] 82 | seen = 0 83 | downloaded = 0 84 | 85 | [pianosheets] 86 | seen = 0 87 | downloaded = 0 88 | 89 | [piratethenet] 90 | seen = 0 91 | downloaded = 0 92 | 93 | [pretome] 94 | seen = 0 95 | downloaded = 0 96 | 97 | [pwnnetwork] 98 | seen = 0 99 | downloaded = 0 100 | 101 | [sceneaccess] 102 | seen = 0 103 | downloaded = 0 104 | 105 | [scenehd] 106 | seen = 0 107 | downloaded = 0 108 | 109 | [sciencehd] 110 | seen = 0 111 | downloaded = 0 112 | 113 | [sharetheremote] 114 | seen = 0 115 | downloaded = 0 116 | 117 | [shellife] 118 | seen = 0 119 | downloaded = 0 120 | 121 | [stopthepresses] 122 | seen = 0 123 | downloaded = 0 124 | 125 | [tehconnection] 126 | seen = 0 127 | downloaded = 0 128 | 129 | [thebox] 130 | seen = 0 131 | downloaded = 0 132 | 133 | [theempire] 134 | seen = 0 135 | downloaded = 0 136 | 137 | [thegft] 138 | seen = 0 139 | downloaded = 0 140 | 141 | [theplace] 142 | seen = 0 143 | downloaded = 0 144 | 145 | [theswarm] 146 | seen = 0 147 | downloaded = 0 148 | 149 | [tophos] 150 | seen = 0 151 | downloaded = 0 152 | 153 | [torrentbytes] 154 | seen = 0 155 | downloaded = 0 156 | 157 | [torrentleech] 158 | seen = 0 159 | downloaded = 0 160 | 161 | [torrentvault] 162 | seen = 0 163 | downloaded = 0 164 | 165 | [undergroundgamer] 166 | seen = 0 167 | downloaded = 0 168 | 169 | [waffles] 170 | seen = 0 171 | downloaded = 0 172 | 173 | [whatcd] 174 | seen = 313 175 | downloaded = 0 176 | 177 | [x264] 178 | seen = 0 179 | downloaded = 0 180 | 181 | -------------------------------------------------------------------------------- /setup.conf.example: -------------------------------------------------------------------------------- 1 | ;WHATauto setup.conf 2 | [setup] 3 | ;This is the folder that all torrents will download to by default, unless over-ridden by another setting in credentials.conf/filters.conf. 4 | 5 | 6 | torrentDir= 7 | 8 | 9 | ;This is the drive that pyWHATauto will report free space on. 10 | 11 | drive= 12 | 13 | 14 | ;Most seedbox providers don't use quota, so it can be impossible to find out 15 | ;progmatically how much space is available to a user. Fill in how much space is dedicated 16 | ;to you. If you leave this blank then it will try to autofill, but unless you 17 | ;own the entire drive (and not just a subfolder) it will be wrong! 18 | ;This number should be in gigabytes. 19 | limit= 20 | 21 | ;options in increasing verbosity are: 22 | ;'error','msg','info','cmd','filter','debug' 23 | ;msg means only error messages and chatter will show up. 24 | ;by setting verbosity:msg and chatter:0, that is the same thing as verbosity:error 25 | verbosity=filter 26 | 27 | ;This will write a continual log of the output to file. 28 | ;Every time pyWA is run a new log will be created. 29 | log= 1 30 | 31 | ;Should the random chatter (messages etc) from the channels show up in your output? 32 | chatter= 0 33 | 34 | ;This will delay torrents from downloading for x many seconds after a match is made. 35 | ;This helps combat some trackers that announce before the torrent is available. 36 | ;This setting is universal, but only applies to automatic downloads. 37 | delay=3 38 | 39 | ;At what percentage should pyWA stop downloading torrents? 40 | freePercent= 5 41 | 42 | ;WEBUI password 43 | password= 44 | ;WEBUI port 45 | port= 46 | ;WEBUI IP (only required if you want to bind the server to a specific ip) 47 | webserverip= 48 | webserverssl=0 49 | ; full path to server certfile 50 | ; openssl req -new -x509 -keyout /home/pywhatauto/server.pem -out /home/pywhatauto/server.pem -days 365 -nodes 51 | certfile=/home/pywhatauto/server.pem 52 | 53 | [notification] 54 | gmail= j5@gmail.com 55 | password= xxxx 56 | email= 0 57 | 58 | server= whatcd 59 | nick= yournick 60 | message= 0 61 | 62 | [sites] 63 | ;this will enable or disable each network 64 | alphaomega= 0 65 | animebytes= 0 66 | apollo= 0 67 | awesomehd = 0 68 | baconbits = 0 69 | BD25= 0 70 | bibliotik= 0 71 | bitgamer= 0 72 | bitme= 0 73 | bitmetv= 0 74 | broadcasthenet= 0 75 | brokenstones= 0 76 | delish= 0 77 | digitalhive= 0 78 | fux0r= 0 79 | gazellegames= 0 80 | hdbits= 0 81 | iptorrents= 0 82 | karagarga= 0 83 | lztr=0 84 | morethantv=0 85 | notwhat=0 86 | redacted= 0 87 | passthepopcorn= 0 88 | pretome= 0 89 | pianosheets= 0 90 | piratethenet= 0 91 | pwnnetwork= 0 92 | quorks = 0 93 | sceneaccess= 0 94 | scenehd= 0 95 | sciencehd= 0 96 | sharetheremote= 0 97 | shellife=0 98 | stopthepresses=0 99 | tehconnection= 0 100 | thebox= 0 101 | theempire= 0 102 | thegft= 0 103 | theplace= 0 104 | theswarm = 0 105 | tophos= 0 106 | torrentbytes= 0 107 | torrentleech= 0 108 | torrentvault= 0 109 | undergroundgamer= 0 110 | waffles= 0 111 | whatcd= 0 112 | x264= 0 113 | 114 | [aliases] 115 | ;change the alias used in commands on IRC. Only 1 alias per network. 116 | animebytes= ab 117 | apollo= apl 118 | awesomehd= ahd 119 | baconbits= bb 120 | bd25= b25 121 | bibliotik= biblio 122 | bitgamer= bg 123 | bitme = bme 124 | bitmetv= bmtv 125 | broadcasthenet= btn 126 | brokenstones= bs 127 | delish= deli 128 | digitalhive= dh 129 | fux0r= hfu 130 | gazellegames= ggames 131 | hdbits= hdb 132 | iptorrents= ipt 133 | karagarga= kg 134 | lztr= lz 135 | morethantv= mtv 136 | notwhat= nwcd 137 | redacted = red 138 | passthepopcorn = ptp 139 | pretome= ptme 140 | pianosheets= piash 141 | piratethenet= ptn 142 | pwnnetwork= pwn 143 | quorks = quo 144 | sceneaccess= scc 145 | scenehd= schd 146 | sciencehd= shd 147 | sharetheremote= str 148 | shellife= shl 149 | stopthepresses=stp 150 | thebox=tbox 151 | theempire=te 152 | thegft= gft 153 | tehconnection= tc 154 | theplace= tp 155 | theswarm = ts 156 | tophos= top 157 | torrentbytes= tbytes 158 | torrentvault= tv 159 | torrentleech= tl 160 | undergroundgamer= ug 161 | waffles= waf 162 | whatcd= what 163 | 164 | [debug] 165 | ;Setting this allows for you to do even more in-depth debugging. It will ONLY connect to one network, does not use threads, and will show 166 | ;extended tracebacks during a crash. 167 | testing = 0 168 | -------------------------------------------------------------------------------- /torrentparser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: UTF-8 -*- 3 | from __future__ import with_statement 4 | from __future__ import division 5 | 6 | import re, os, hashlib 7 | 8 | def main(): 9 | dir = '/home/blubba/.config/deluge/state' 10 | print('%-50s %-3s %10.2s %-5s' %('Infohash','M','MBs','Name')) 11 | for file in os.listdir(dir): 12 | root, ext = os.path.splitext(file) 13 | if ext and ext == '.torrent': 14 | x = torrentparser(filename=os.path.join(dir,file)) 15 | if root == x.infohash(): 16 | m = 'Y' 17 | else: 18 | m = 'N' 19 | print('%-50s %-3s %10.2f %-5s' %(root,m,x.mbsize(),x.name())) 20 | 21 | class torrentparser(object): 22 | ''' 23 | Universal torrent writing and parsing class 24 | ''' 25 | def __init__(self, debug = False, filename=False, content=False): 26 | '''Takes a string with the path to the torrent file in filename, 27 | or the content of a torrent file in content (as a string). 28 | Runs the parses on it. 29 | Returns a string if the file could not be opened/read, 30 | raises SyntaxError on parsing errors.''' 31 | self.torrentfile = None 32 | self.debug = debug 33 | self.dictionary = None 34 | if filename or content: 35 | return self.parse(filename=filename, content=content) 36 | 37 | def parse(self, filename=False, content=False): 38 | 39 | if filename: 40 | try: 41 | with open(filename, 'rb') as f: 42 | self.torrentfile = f.read() 43 | except IOError: 44 | print('Cannot open file ' + filename) 45 | raise 46 | elif content: 47 | self.torrentfile = content 48 | else: 49 | return 'No torrent data given' 50 | self.decode() 51 | 52 | def decode(self): 53 | '''Decodes the raw bencoded file into self.dictionary. Raises SyntaxErrors on errors.''' 54 | reg = re.compile('^(\d+)?(?:i(\d+)e)?(l?)(d)?(e)?', re.I + re.U) #strings, ints, lists, dicts 55 | t = self.torrentfile 56 | depth = [None] 57 | l = [] 58 | i= 0 59 | 60 | while i < len(t): 61 | m = reg.match(t[i:]) 62 | if self.debug: print 'left: ' + repr(l) 63 | ty = type(depth[-1]) 64 | if m: 65 | if m.group(1): #String 66 | string = t[i + len(m.group(1)) + 1 : i + int(m.group(1)) + len(m.group(1)) + 1] 67 | if ty == type(list()): 68 | if self.debug: print('Append %s to %s' %(repr(string),repr(depth[-1]))) 69 | depth[-1].append(string) 70 | elif ty == type(dict()): 71 | if l[-1]: 72 | if l[-1] == 'pieces' and self.debug: 73 | string = 'Here are all the pieces' 74 | if self.debug: print('Map %s -> String: %s' %(l[-1],repr(string))) 75 | depth[-1].update({l[-1] : string}) 76 | l[-1] = None 77 | else: 78 | if self.debug: print('Add left: String: %s' %(repr(string))) 79 | l[-1] = string 80 | else: 81 | raise SyntaxError('Lonely String found: %s' %string) 82 | i += int(m.group(1)) + len(m.group(1)) + 1 83 | elif m.group(2): #Integer 84 | integer = int(m.group(2)) 85 | #print('Integer: ' + str(integer)) 86 | if ty == type(list()): 87 | depth[-1].append(integer) 88 | elif ty == type(dict()): 89 | if l[-1]: 90 | if self.debug: print('Map %s -> Integer: %s' %(l[-1],repr(integer))) 91 | depth[-1].update({l[-1] : integer}) 92 | l[-1] = None 93 | else: 94 | if self.debug: print('Add left: Integer: %s' %(repr(integer))) 95 | l[-1] = str(integer) 96 | else: 97 | raise SyntaxError('Lonely Integer found: %s' %str(integer)) 98 | i += len(str(m.group(2))) + 2 99 | elif m.group(3): #List 100 | if self.debug: print('List found') 101 | depth.append(list()) 102 | i+=1 103 | elif m.group(4): 104 | if self.debug: print('Dictionary found') 105 | depth.append(dict()) 106 | l.append(None) 107 | i+=1 108 | elif m.group(5): 109 | if type(depth[-2]) == type(dict()): 110 | if ty == type(dict()): 111 | if self.debug: print('Dictionary ended: Map %s -> %s' %(l[-2],repr(depth[-1]))) 112 | depth[-2].update({l[-2]: depth[-1]}) 113 | l[-2] = None 114 | del l[-1] 115 | else: 116 | if self.debug: print('List ended: Map %s -> %s' %(l[-1],repr(depth[-1]))) 117 | depth[-2].update({l[-1]: depth[-1]}) 118 | l[-1] = None 119 | elif type(depth[-2]) == type(list()): 120 | if self.debug: print('List ended: Append %s to %s' %(repr(depth[-1]),repr(depth[-2]))) 121 | depth[-2].append(depth[-1]) 122 | if ty == type(dict()): 123 | del l[-1] 124 | else: 125 | if self.debug: print repr(depth) 126 | self.dictionary = depth[-1] 127 | del depth[-1] 128 | i+=1 129 | else: 130 | if i+1 == len(t): 131 | i+=1 132 | else: 133 | raise SyntaxError('Torrent files is not formated, i: %d/%d remaining content: %s' %(i,len(t),repr(t[i:]))) 134 | else: 135 | raise SyntaxError('Torrent files is not formated, i: %d/%d remaining content: %s' %(i,len(t),repr(t[i:]))) 136 | 137 | def encode(self, dictionary=None): 138 | '''Returns the bencoded version of the dictionary''' 139 | if not dictionary: 140 | dictionary = self.dictionary 141 | ben = 'd' 142 | for key in sorted(dictionary.iterkeys(),key=str.lower): 143 | t = type(dictionary[key]) 144 | ben += "%d:%s"%(len(key),key) 145 | if t == type(dict()): 146 | ben += self.encode(dictionary[key]) 147 | elif t == type(str()): 148 | ben += '%d:%s' %(len(dictionary[key]), dictionary[key]) 149 | elif t == type(int()): 150 | ben += 'i%de' %dictionary[key] 151 | elif t == type(list()): 152 | ben += self.encodelist(dictionary[key]) 153 | else: 154 | raise SyntaxError('Mallformated dictionary as it seems.') 155 | return ben + 'e' 156 | 157 | def encodelist(self, list): 158 | '''Returns the bencoded version of the list''' 159 | ben = 'l' 160 | for key in list: 161 | t = type(key) 162 | if t == type(dict()): 163 | ben += self.encode(key) 164 | elif t == type(str()): 165 | ben += '%d:%s' %(len(key), key) 166 | elif t == type(int()): 167 | ben += 'i%de' %key 168 | elif t == type(list()): 169 | ben += self.encodelist(key) 170 | else: 171 | raise SyntaxError('Mallformated dictionary as it seems.') 172 | return ben + 'e' 173 | 174 | def pretty(self): 175 | '''Prints a pretty version of the dictionary''' 176 | import pprint 177 | pprint.pprint(self.dictionary) 178 | 179 | def files(self): 180 | '''Returns a list of all files.''' 181 | f = [] 182 | if 'files' in self.dictionary['info']: 183 | for ff in self.dictionary['info']['files']: 184 | path = self.dictionary['info']['name'] 185 | for fff in ff['path']: 186 | path = os.path.join(path,fff) 187 | f.append(path) 188 | if self.debug: print(path) 189 | elif 'name' in self.dictionary['info']: 190 | f.append(self.dictionary['info']['name']) 191 | if self.debug: print f[0] 192 | return(f) 193 | 194 | def infohash(self): 195 | '''Returns the sha1hash of the infodb''' 196 | return hashlib.sha1(self.encode(self.dictionary['info'])).hexdigest() 197 | 198 | def mbsize(self): 199 | '''Returns the size in MB''' 200 | return self.rawsize() / (1024*1024) 201 | 202 | def gbsize(self): 203 | '''Returns the size in GB''' 204 | return self.rawsize() / (1024*1024*1024) 205 | 206 | def name(self): 207 | '''Returns the name of the torrent, as specified in the info''' 208 | if 'name' in self.dictionary['info']: 209 | return self.dictionary['info']['name'] 210 | 211 | def rawsize(self): 212 | '''Returns the size in bytes''' 213 | size = 0 214 | if 'files' in self.dictionary['info']: 215 | for ff in self.dictionary['info']['files']: 216 | size += ff['length'] 217 | elif 'name' in self.dictionary['info']: 218 | size += self.dictionary['info']['length'] 219 | return size 220 | 221 | if __name__ == "__main__": 222 | main() 223 | 224 | --------------------------------------------------------------------------------