Browse Source
Added fanzub anime nzb provider. Fixed NyaaTorrents anime provider. This is in testing phase so bugs are to be expected.tags/release_0.1.0
68 changed files with 60230 additions and 424 deletions
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 3.6 KiB |
@ -0,0 +1,103 @@ |
|||
#import sickbeard |
|||
#set global $title="Config - Anime" |
|||
#set global $header="Anime" |
|||
|
|||
#set global $sbPath="../.." |
|||
|
|||
#set global $topmenu="config"# |
|||
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_top.tmpl") |
|||
|
|||
#if $varExists('header') |
|||
<h1 class="header">$header</h1> |
|||
#else |
|||
<h1 class="title">$title</h1> |
|||
#end if |
|||
|
|||
<script type="text/javascript" src="$sbRoot/js/config.js?$sbPID"></script> |
|||
|
|||
<div id="config"> |
|||
<div id="config-content"> |
|||
|
|||
<form id="configForm" action="saveAnime" method="post"> |
|||
|
|||
<div id="config-components"> |
|||
|
|||
<div id="core-component-group1" class="component-group clearfix"> |
|||
<div class="component-group-desc"> |
|||
<h3><a href="http://anidb.info" onclick="window.open(this.href, '_blank'); return false;"><img src="$sbRoot/images/providers/anidb.gif" alt="AniDB" title="AniDB" width="16" height="16" /> AniDB</a></h3> |
|||
<p>AniDB is non-profit database of anime information that is freely open to the public</p> |
|||
</div> |
|||
|
|||
<fieldset class="component-group-list"> |
|||
<div class="field-pair"> |
|||
<input type="checkbox" class="enabler" name="use_anidb" id="use_anidb" #if $sickbeard.USE_ANIDB then "checked=\"checked\"" else ""# /> |
|||
<label class="clearfix" for="use_notifo"> |
|||
<span class="component-title">Enable</span> |
|||
<span class="component-desc">Should Sick Beard use data from AniDB?</span> |
|||
</label> |
|||
</div> |
|||
|
|||
<div id="content_use_anidb"> |
|||
<div class="field-pair"> |
|||
<label class="nocheck clearfix"> |
|||
<span class="component-title">AniDB Username</span> |
|||
<input type="text" name="anidb_username" id="anidb_username" value="$sickbeard.ANIDB_USERNAME" size="35" /> |
|||
</label> |
|||
<label class="nocheck clearfix"> |
|||
<span class="component-title"> </span> |
|||
<span class="component-desc">Username of your AniDB account</span> |
|||
</label> |
|||
</div> |
|||
|
|||
<div class="field-pair"> |
|||
<label class="nocheck clearfix"> |
|||
<span class="component-title">AniDB Password</span> |
|||
<input type="password" name="anidb_password" id="anidb_password" value="$sickbeard.ANIDB_PASSWORD" size="35" /> |
|||
</label> |
|||
<label class="nocheck clearfix"> |
|||
<span class="component-title"> </span> |
|||
<span class="component-desc">Password of your AniDB account</span> |
|||
</label> |
|||
</div> |
|||
<div class="field-pair"> |
|||
<label class="nocheck clearfix"> |
|||
<span class="component-title">AniDB MyList</span> |
|||
<input type="checkbox" name="anidb_use_mylist" id="anidb_use_mylist" #if $sickbeard.ANIDB_USE_MYLIST then "checked=\"checked\"" else ""# /> |
|||
</label> |
|||
<label class="nocheck clearfix"> |
|||
<span class="component-title"> </span> |
|||
<span class="component-desc">Do you want to add the PostProcessed Episodes to the MyList ?</span> |
|||
</label> |
|||
</div> |
|||
</div> |
|||
<input type="submit" class="config_submitter" value="Save Changes" /> |
|||
</fieldset> |
|||
|
|||
</div><!-- /component-group //--> |
|||
<div id="core-component-group2" class="component-group clearfix"> |
|||
|
|||
<div class="component-group-desc"> |
|||
<h3>Look and Feel</h3> |
|||
</div> |
|||
<fieldset class="component-group-list"> |
|||
<div class="field-pair"> |
|||
<input type="checkbox" class="enabler" name="split_home" id="split_home" #if $sickbeard.ANIME_SPLIT_HOME then "checked=\"checked\"" else ""# /> |
|||
<label class="clearfix" for="use_notifo"> |
|||
<span class="component-title">Split show lists</span> |
|||
<span class="component-desc">Separate anime and normal shows in groups</span> |
|||
</label> |
|||
</div> |
|||
<input type="submit" class="config_submitter" value="Save Changes" /> |
|||
</fieldset> |
|||
</div><!-- /component-group //--> |
|||
<br/><input type="submit" class="config_submitter" value="Save Changes" /><br/> |
|||
|
|||
</div><!-- /config-components //--> |
|||
|
|||
</form> |
|||
|
|||
|
|||
</div></div> |
|||
<div class="clearfix"></div> |
|||
|
|||
#include $os.path.join($sickbeard.PROG_DIR, "gui/slick/interfaces/default/inc_bottom.tmpl") |
@ -0,0 +1,39 @@ |
|||
$(function () { |
|||
$('.showTitle a').each(function () { |
|||
match = $(this).parent().attr("id").match(/^scene_exception_(\d+)$/); |
|||
$(this).qtip({ |
|||
content: { |
|||
text: 'Loading...', |
|||
ajax: { |
|||
url: $("#sbRoot").val() + '/home/sceneExceptions', |
|||
type: 'GET', |
|||
data: { |
|||
show: match[1] |
|||
}, |
|||
success: function (data, status) { |
|||
this.set('content.text', data); |
|||
} |
|||
} |
|||
}, |
|||
show: { |
|||
solo: true |
|||
}, |
|||
position: { |
|||
viewport: $(window), |
|||
my: 'top center', |
|||
at: 'bottom center', |
|||
adjust: { |
|||
y: 3, |
|||
x: 0 |
|||
} |
|||
}, |
|||
style: { |
|||
tip: { |
|||
corner: true, |
|||
method: 'polygon' |
|||
}, |
|||
classes: 'ui-tooltip-rounded ui-tooltip-shadow ui-tooltip-sb' |
|||
} |
|||
}); |
|||
}); |
|||
}); |
@ -0,0 +1,796 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
import threading |
|||
from time import time, sleep, strftime, localtime |
|||
from types import * |
|||
|
|||
from aniDBlink import AniDBLink |
|||
from aniDBcommands import * |
|||
from aniDBerrors import * |
|||
from aniDBAbstracter import Anime, Episode |
|||
|
|||
version = 100 |
|||
|
|||
class Connection(threading.Thread): |
|||
def __init__(self, clientname='adba', server='api.anidb.info', port=9000, myport=9876, user=None, password=None, session=None, log=False, logPrivate=False, keepAlive=False): |
|||
super(Connection, self).__init__() |
|||
# setting the log function |
|||
self.logPrivate = logPrivate |
|||
if type(log) in (FunctionType, MethodType):# if we get a function or a method use that. |
|||
self.log = log |
|||
self.logPrivate = True # true means sensitive data will not be NOT be logged ... yeah i know oO |
|||
elif log:# if it something else (like True) use the own print_log |
|||
self.log = self.print_log |
|||
else:# dont log at all |
|||
self.log = self.print_log_dummy |
|||
|
|||
|
|||
self.link = AniDBLink(server, port, myport, self.log, logPrivate=self.logPrivate) |
|||
self.link.session = session |
|||
|
|||
self.clientname = clientname |
|||
self.clientver = version |
|||
|
|||
# from original lib |
|||
self.mode = 1 #mode: 0=queue,1=unlock,2=callback |
|||
|
|||
# to lock other threads out |
|||
self.lock = threading.RLock() |
|||
|
|||
# thread keep alive stuff |
|||
self.keepAlive = keepAlive |
|||
self.setDaemon(True) |
|||
self.lastKeepAliveCheck = 0 |
|||
self.lastAuth = 0 |
|||
self._username = password |
|||
self._password = user |
|||
|
|||
self._iamALIVE = False |
|||
|
|||
self.counter = 0 |
|||
self.counterAge = 0 |
|||
|
|||
def print_log(self, data): |
|||
print(strftime("%Y-%m-%d %H:%M:%S", localtime(time())) + ": " + str(data)) |
|||
|
|||
def print_log_dummy(self, data): |
|||
pass |
|||
|
|||
def stop(self): |
|||
self.logout(cutConnection=True) |
|||
|
|||
|
|||
def cut(self): |
|||
self.link.stop() |
|||
|
|||
def handle_response(self, response): |
|||
if response.rescode in ('501', '506') and response.req.command != 'AUTH': |
|||
self.log("seams like the last command got a not authed error back tring to reconnect now") |
|||
if self._reAuthenticate(): |
|||
response.req.resp = None |
|||
response = self.handle(response.req, response.req.callback) |
|||
|
|||
|
|||
def handle(self, command, callback): |
|||
|
|||
self.lock.acquire() |
|||
if self.counterAge < (time() - 120): # the last request was older then 2 min reset delay and counter |
|||
self.counter = 0 |
|||
self.link.delay = 2 |
|||
else: # something happend in the last 120 seconds |
|||
if self.counter < 5: |
|||
self.link.delay = 2 # short term "A Client MUST NOT send more than 0.5 packets per second (that's one packet every two seconds, not two packets a second!)" |
|||
elif self.counter >= 5: |
|||
self.link.delay = 6 # long term "A Client MUST NOT send more than one packet every four seconds over an extended amount of time." |
|||
|
|||
if command.command not in ('AUTH', 'PING', 'ENCRYPT'): |
|||
self.counterAge = time() |
|||
self.counter += 1 |
|||
if self.keepAlive: |
|||
self.authed() |
|||
|
|||
def callback_wrapper(resp): |
|||
self.handle_response(resp) |
|||
if callback: |
|||
callback(resp) |
|||
|
|||
self.log("handling(" + str(self.counter) + "-" + str(self.link.delay) + ") command " + str(command.command)) |
|||
|
|||
#make live request |
|||
command.authorize(self.mode, self.link.new_tag(), self.link.session, callback_wrapper) |
|||
self.link.request(command) |
|||
|
|||
#handle mode 1 (wait for response) |
|||
if self.mode == 1: |
|||
command.wait_response() |
|||
try: |
|||
command.resp |
|||
except: |
|||
self.lock.release() |
|||
if self.link.banned: |
|||
raise AniDBBannedError("User is banned") |
|||
else: |
|||
raise AniDBCommandTimeoutError("Command has timed out") |
|||
|
|||
self.handle_response(command.resp) |
|||
self.lock.release() |
|||
return command.resp |
|||
else: |
|||
self.lock.release() |
|||
|
|||
def authed(self, reAuthenticate=False): |
|||
self.lock.acquire() |
|||
authed = (self.link.session != None) |
|||
if not authed and (reAuthenticate or self.keepAlive): |
|||
self._reAuthenticate() |
|||
authed = (self.link.session != None) |
|||
self.lock.release() |
|||
return authed |
|||
|
|||
def _reAuthenticate(self): |
|||
if self._username and self._password: |
|||
self.log("auto re authenticating !") |
|||
resp = self.auth(self._username, self._password) |
|||
if resp.rescode not in ('500'): |
|||
return True |
|||
else: |
|||
return False |
|||
|
|||
def _keep_alive(self): |
|||
self.lastKeepAliveCheck = time() |
|||
self.log("auto check !") |
|||
# check every 30 minutes if the session is still valid |
|||
# if not reauthenticate |
|||
if self.lastAuth and time() - self.lastAuth > 1800: |
|||
self.log("auto uptime !") |
|||
self.uptime() # this will update the self.link.session and will refresh the session if it is still alive |
|||
|
|||
if self.authed(): # if we are authed we set the time |
|||
self.lastAuth = time() |
|||
else: # if we aren't authed and we have the user and pw then reauthenticate |
|||
self._reAuthenticate() |
|||
|
|||
# issue a ping every 20 minutes after the last package |
|||
# this ensures the connection will be kept alive |
|||
if self.link.lastpacket and time() - self.link.lastpacket > 1200: |
|||
self.log("auto ping !") |
|||
self.ping() |
|||
|
|||
|
|||
def run(self): |
|||
while self.keepAlive: |
|||
self._keep_alive() |
|||
sleep(120) |
|||
|
|||
|
|||
def auth(self, username, password, nat=None, mtu=None, callback=None): |
|||
""" |
|||
Login to AniDB UDP API |
|||
|
|||
parameters: |
|||
username - your anidb username |
|||
password - your anidb password |
|||
nat - if this is 1, response will have "address" in attributes with your "ip:port" (default:0) |
|||
mtu - maximum transmission unit (max packet size) (default: 1400) |
|||
|
|||
""" |
|||
self.log("ok1") |
|||
if self.keepAlive: |
|||
self.log("ok2") |
|||
self._username = username |
|||
self._password = password |
|||
if self.is_alive() == False: |
|||
self.log("You wanted to keep this thing alive!") |
|||
if self._iamALIVE == False: |
|||
self.log("Starting thread now...") |
|||
self.start() |
|||
self._iamALIVE = True |
|||
else: |
|||
self.log("not starting thread seams like it is already running. this must be a _reAuthenticate") |
|||
|
|||
|
|||
self.lastAuth = time() |
|||
return self.handle(AuthCommand(username, password, 3, self.clientname, self.clientver, nat, 1, 'utf8', mtu), callback) |
|||
|
|||
def logout(self, cutConnection=False, callback=None): |
|||
""" |
|||
Log out from AniDB UDP API |
|||
|
|||
""" |
|||
result = self.handle(LogoutCommand(), callback) |
|||
if(cutConnection): |
|||
self.cut() |
|||
return result |
|||
|
|||
def push(self, notify, msg, buddy=None, callback=None): |
|||
""" |
|||
Subscribe/unsubscribe to/from notifications |
|||
|
|||
parameters: |
|||
notify - Notifications about files added? |
|||
msg - Notifications about message added? |
|||
buddy - Notifications about buddy events? |
|||
|
|||
structure of parameters: |
|||
notify msg [buddy] |
|||
|
|||
""" |
|||
return self.handle(PushCommand(notify, msg, buddy), callback) |
|||
|
|||
def pushack(self, nid, callback=None): |
|||
""" |
|||
Acknowledge notification (do this when you get 271-274) |
|||
|
|||
parameters: |
|||
nid - Notification packet id |
|||
|
|||
structure of parameters: |
|||
nid |
|||
|
|||
""" |
|||
return self.handle(PushAckCommand(nid), callback) |
|||
|
|||
def notifyadd(self, aid=None, gid=None, type=None, priority=None, callback=None): |
|||
""" |
|||
Add a notification |
|||
|
|||
parameters: |
|||
aid - Anime id |
|||
gid - Group id |
|||
type - Type of notification: type=> 0=all, 1=new, 2=group, 3=complete |
|||
priority - low = 0, medium = 1, high = 2 (unconfirmed) |
|||
|
|||
structure of parameters: |
|||
[aid={int}|gid={int}]&type={int}&priority={int} |
|||
|
|||
""" |
|||
|
|||
return self.handle(NotifyAddCommand(aid, gid, type, priority), callback) |
|||
|
|||
|
|||
def notify(self, buddy=None, callback=None): |
|||
""" |
|||
Get number of pending notifications and messages |
|||
|
|||
parameters: |
|||
buddy - Also display number of online buddies |
|||
|
|||
structure of parameters: |
|||
[buddy] |
|||
|
|||
""" |
|||
return self.handle(NotifyCommand(buddy), callback) |
|||
|
|||
def notifylist(self, callback=None): |
|||
""" |
|||
List all pending notifications/messages |
|||
|
|||
""" |
|||
return self.handle(NotifyListCommand(), callback) |
|||
|
|||
def notifyget(self, type, id, callback=None): |
|||
""" |
|||
Get notification/message |
|||
|
|||
parameters: |
|||
type - (M=message, N=notification) |
|||
id - message/notification id |
|||
|
|||
structure of parameters: |
|||
type id |
|||
|
|||
""" |
|||
return self.handle(NotifyGetCommand(type, id), callback) |
|||
|
|||
def notifyack(self, type, id, callback=None): |
|||
""" |
|||
Mark message read or clear a notification |
|||
|
|||
parameters: |
|||
type - (M=message, N=notification) |
|||
id - message/notification id |
|||
|
|||
structure of parameters: |
|||
type id |
|||
|
|||
""" |
|||
return self.handle(NotifyAckCommand(type, id), callback) |
|||
|
|||
def buddyadd(self, uid=None, uname=None, callback=None): |
|||
""" |
|||
Add a user to your buddy list |
|||
|
|||
parameters: |
|||
uid - user id |
|||
uname - name of the user |
|||
|
|||
structure of parameters: |
|||
(uid|uname) |
|||
|
|||
""" |
|||
return self.handle(BuddyAddCommand(uid, uname), callback) |
|||
|
|||
def buddydel(self, uid, callback=None): |
|||
""" |
|||
Remove a user from your buddy list |
|||
|
|||
parameters: |
|||
uid - user id |
|||
|
|||
structure of parameters: |
|||
uid |
|||
|
|||
""" |
|||
return self.handle(BuddyDelCommand(uid), callback) |
|||
|
|||
def buddyaccept(self, uid, callback=None): |
|||
""" |
|||
Accept user as buddy |
|||
|
|||
parameters: |
|||
uid - user id |
|||
|
|||
structure of parameters: |
|||
uid |
|||
|
|||
""" |
|||
return self.handle(BuddyAcceptCommand(uid), callback) |
|||
|
|||
def buddydeny(self, uid, callback=None): |
|||
""" |
|||
Deny user as buddy |
|||
|
|||
parameters: |
|||
uid - user id |
|||
|
|||
structure of parameters: |
|||
uid |
|||
|
|||
""" |
|||
return self.handle(BuddyDenyCommand(uid), callback) |
|||
|
|||
def buddylist(self, startat, callback=None): |
|||
""" |
|||
Retrieve your buddy list |
|||
|
|||
parameters: |
|||
startat - number of buddy to start listing from |
|||
|
|||
structure of parameters: |
|||
startat |
|||
|
|||
""" |
|||
return self.handle(BuddyListCommand(startat), callback) |
|||
|
|||
def buddystate(self, startat, callback=None): |
|||
""" |
|||
Retrieve buddy states |
|||
|
|||
parameters: |
|||
startat - number of buddy to start listing from |
|||
|
|||
structure of parameters: |
|||
startat |
|||
|
|||
""" |
|||
return self.handle(BuddyStateCommand(startat), callback) |
|||
|
|||
def anime(self, aid=None, aname=None, amask= -1, callback=None): |
|||
""" |
|||
Get information about an anime |
|||
|
|||
parameters: |
|||
aid - anime id |
|||
aname - name of the anime |
|||
amask - a bitfield describing what information you want about the anime |
|||
|
|||
structure of parameters: |
|||
(aid|aname) [amask] |
|||
|
|||
structure of amask: |
|||
|
|||
""" |
|||
return self.handle(AnimeCommand(aid, aname, amask), callback) |
|||
|
|||
def episode(self, eid=None, aid=None, aname=None, epno=None, callback=None): |
|||
""" |
|||
Get information about an episode |
|||
|
|||
parameters: |
|||
eid - episode id |
|||
aid - anime id |
|||
aname - name of the anime |
|||
epno - number of the episode |
|||
|
|||
structure of parameters: |
|||
eid |
|||
(aid|aname) epno |
|||
|
|||
""" |
|||
return self.handle(EpisodeCommand(eid, aid, aname, epno), callback) |
|||
|
|||
def file(self, fid=None, size=None, ed2k=None, aid=None, aname=None, gid=None, gname=None, epno=None, fmask= -1, amask=0, callback=None): |
|||
""" |
|||
Get information about a file |
|||
|
|||
parameters: |
|||
fid - file id |
|||
size - size of the file |
|||
ed2k - ed2k-hash of the file |
|||
aid - anime id |
|||
aname - name of the anime |
|||
gid - group id |
|||
gname - name of the group |
|||
epno - number of the episode |
|||
fmask - a bitfield describing what information you want about the file |
|||
amask - a bitfield describing what information you want about the anime |
|||
|
|||
structure of parameters: |
|||
fid [fmask] [amask] |
|||
size ed2k [fmask] [amask] |
|||
(aid|aname) (gid|gname) epno [fmask] [amask] |
|||
|
|||
structure of fmask: |
|||
bit key description |
|||
0 - - |
|||
1 aid aid |
|||
2 eid eid |
|||
3 gid gid |
|||
4 lid lid |
|||
5 - - |
|||
6 - - |
|||
7 - - |
|||
8 state state |
|||
9 size size |
|||
10 ed2k ed2k |
|||
11 md5 md5 |
|||
12 sha1 sha1 |
|||
13 crc32 crc32 |
|||
14 - - |
|||
15 - - |
|||
16 dublang dub language |
|||
17 sublang sub language |
|||
18 quality quality |
|||
19 source source |
|||
20 audiocodec audio codec |
|||
21 audiobitrate audio bitrate |
|||
22 videocodec video codec |
|||
23 videobitrate video bitrate |
|||
24 resolution video resolution |
|||
25 filetype file type (extension) |
|||
26 length length in seconds |
|||
27 description description |
|||
28 - - |
|||
29 - - |
|||
30 filename anidb file name |
|||
31 - - |
|||
|
|||
structure of amask: |
|||
bit key description |
|||
0 gname group name |
|||
1 gshortname group short name |
|||
2 - - |
|||
3 - - |
|||
4 - - |
|||
5 - - |
|||
6 - - |
|||
7 - - |
|||
8 epno epno |
|||
9 epname ep english name |
|||
10 epromaji ep romaji name |
|||
11 epkanji ep kanji name |
|||
12 - - |
|||
13 - - |
|||
14 - - |
|||
15 - - |
|||
16 totaleps anime total episodes |
|||
17 lastep last episode nr (highest, not special) |
|||
18 year year |
|||
19 type type |
|||
20 romaji romaji name |
|||
21 kanji kanji name |
|||
22 name english name |
|||
23 othername other name |
|||
24 shortnames short name list |
|||
25 synonyms synonym list |
|||
26 categories category list |
|||
27 relatedaids related aid list |
|||
28 producernames producer name list |
|||
29 producerids producer id list |
|||
30 - - |
|||
31 - - |
|||
|
|||
""" |
|||
return self.handle(FileCommand(fid, size, ed2k, aid, aname, gid, gname, epno, fmask, amask), callback) |
|||
|
|||
def group(self, gid=None, gname=None, callback=None): |
|||
""" |
|||
Get information about a group |
|||
|
|||
parameters: |
|||
gid - group id |
|||
gname - name of the group |
|||
|
|||
structure of parameters: |
|||
(gid|gname) |
|||
|
|||
""" |
|||
return self.handle(GroupCommand(gid, gname), callback) |
|||
|
|||
def groupstatus(self, aid=None, state=None, callback=None): |
|||
""" |
|||
Returns a list of group names and ranges of episodes released by the group for a given anime. |
|||
parameters: |
|||
aid - anime id |
|||
state - If state is not supplied, groups with a completion state of 'ongoing', 'finished', or 'complete' are returned |
|||
state values: |
|||
1 -> ongoing |
|||
2 -> stalled |
|||
3 -> complete |
|||
4 -> dropped |
|||
5 -> finished |
|||
6 -> specials only |
|||
""" |
|||
return self.handle(GroupstatusCommand(aid, state), callback) |
|||
|
|||
def producer(self, pid=None, pname=None, callback=None): |
|||
""" |
|||
Get information about a producer |
|||
|
|||
parameters: |
|||
pid - producer id |
|||
pname - name of the producer |
|||
|
|||
structure of parameters: |
|||
(pid|pname) |
|||
|
|||
""" |
|||
|
|||
return self.handle(ProducerCommand(pid, pname), callback) |
|||
|
|||
def mylist(self, lid=None, fid=None, size=None, ed2k=None, aid=None, aname=None, gid=None, gname=None, epno=None, callback=None): |
|||
""" |
|||
Get information about your mylist |
|||
|
|||
parameters: |
|||
lid - mylist id |
|||
fid - file id |
|||
size - size of the file |
|||
ed2k - ed2k-hash of the file |
|||
aid - anime id |
|||
aname - name of the anime |
|||
gid - group id |
|||
gname - name of the group |
|||
epno - number of the episode |
|||
|
|||
structure of parameters: |
|||
lid |
|||
fid |
|||
size ed2k |
|||
(aid|aname) (gid|gname) epno |
|||
|
|||
""" |
|||
return self.handle(MyListCommand(lid, fid, size, ed2k, aid, aname, gid, gname, epno), callback) |
|||
|
|||
def mylistadd(self, lid=None, fid=None, size=None, ed2k=None, aid=None, aname=None, gid=None, gname=None, epno=None, edit=None, state=None, viewed=None, source=None, storage=None, other=None, callback=None): |
|||
""" |
|||
Add/Edit information to/in your mylist |
|||
|
|||
parameters: |
|||
lid - mylist id |
|||
fid - file id |
|||
size - size of the file |
|||
ed2k - ed2k-hash of the file |
|||
aid - anime id |
|||
aname - name of the anime |
|||
gid - group id |
|||
gname - name of the group |
|||
epno - number of the episode |
|||
edit - whether to add to mylist or edit an existing entry (0=add,1=edit) |
|||
state - the location of the file |
|||
viewed - whether you have watched the file (0=unwatched,1=watched) |
|||
source - where you got the file (bittorrent,dc++,ed2k,...) |
|||
storage - for example the title of the cd you have this on |
|||
other - other data regarding this file |
|||
|
|||
structure of parameters: |
|||
lid edit=1 [state viewed source storage other] |
|||
fid [state viewed source storage other] [edit] |
|||
size ed2k [state viewed source storage other] [edit] |
|||
(aid|aname) (gid|gname) epno [state viewed source storage other] |
|||
(aid|aname) edit=1 [(gid|gname) epno] [state viewed source storage other] |
|||
|
|||
structure of state: |
|||
value meaning |
|||
0 unknown - state is unknown or the user doesn't want to provide this information |
|||
1 on hdd - the file is stored on hdd |
|||
2 on cd - the file is stored on cd |
|||
3 deleted - the file has been deleted or is not available for other reasons (i.e. reencoded) |
|||
|
|||
structure of epno: |
|||
value meaning |
|||
x target episode x |
|||
0 target all episodes |
|||
-x target all episodes upto x |
|||
|
|||
""" |
|||
return self.handle(MyListAddCommand(lid, fid, size, ed2k, aid, aname, gid, gname, epno, edit, state, viewed, source, storage, other), callback) |
|||
|
|||
def mylistdel(self, lid=None, fid=None, aid=None, aname=None, gid=None, gname=None, epno=None, callback=None): |
|||
""" |
|||
Delete information from your mylist |
|||
|
|||
parameters: |
|||
lid - mylist id |
|||
fid - file id |
|||
size - size of the file |
|||
ed2k - ed2k-hash of the file |
|||
aid - anime id |
|||
aname - name of the anime |
|||
gid - group id |
|||
gname - name of the group |
|||
epno - number of the episode |
|||
|
|||
structure of parameters: |
|||
lid |
|||
fid |
|||
(aid|aname) (gid|gname) epno |
|||
|
|||
""" |
|||
return self.handle(MyListCommand(lid, fid, aid, aname, gid, gname, epno), callback) |
|||
|
|||
def myliststats(self, callback=None): |
|||
""" |
|||
Get summary information of your mylist |
|||
|
|||
""" |
|||
return self.handle(MyListStatsCommand(), callback) |
|||
|
|||
def vote(self, type, id=None, name=None, value=None, epno=None, callback=None): |
|||
""" |
|||
Rate an anime/episode/group |
|||
|
|||
parameters: |
|||
type - type of the vote |
|||
id - anime/group id |
|||
name - name of the anime/group |
|||
value - the vote |
|||
epno - number of the episode |
|||
|
|||
structure of parameters: |
|||
type (id|name) [value] [epno] |
|||
|
|||
structure of type: |
|||
value meaning |
|||
1 rate an anime (episode if you also specify epno) |
|||
2 rate an anime temporarily (you haven't watched it all) |
|||
3 rate a group |
|||
|
|||
structure of value: |
|||
value meaning |
|||
-x revoke vote |
|||
0 get old vote |
|||
100-1000 give vote |
|||
|
|||
""" |
|||
return self.handle(VoteCommand(type, id, name, value, epno), callback) |
|||
|
|||
def randomanime(self, type, callback=None): |
|||
""" |
|||
Get information of random anime |
|||
|
|||
parameters: |
|||
type - where to take the random anime |
|||
|
|||
structure of parameters: |
|||
type |
|||
|
|||
structure of type: |
|||
value meaning |
|||
0 db |
|||
1 watched |
|||
2 unwatched |
|||
3 mylist |
|||
|
|||
""" |
|||
return self.handle(RandomAnimeCommand(type), callback) |
|||
|
|||
def ping(self, callback=None): |
|||
""" |
|||
Test connectivity to AniDB UDP API |
|||
|
|||
""" |
|||
return self.handle(PingCommand(), callback) |
|||
|
|||
def encrypt(self, user, apipassword, type=None, callback=None): |
|||
""" |
|||
Encrypt all future traffic |
|||
|
|||
parameters: |
|||
user - your username |
|||
apipassword - your api password |
|||
type - type of encoding (1=128bit AES) |
|||
|
|||
structure of parameters: |
|||
user [type] |
|||
|
|||
""" |
|||
return self.handle(EncryptCommand(user, apipassword, type), callback) |
|||
|
|||
def encoding(self, name, callback=None): |
|||
""" |
|||
Change encoding used in messages |
|||
|
|||
parameters: |
|||
name - name of the encoding |
|||
|
|||
structure of parameters: |
|||
name |
|||
|
|||
comments: |
|||
DO NOT USE THIS! |
|||
utf8 is the only encoding which will support all the text in anidb responses |
|||
the responses have japanese, russian, french and probably other alphabets as well |
|||
even if you can't display utf-8 locally, don't change the server-client -connections encoding |
|||
rather, make python convert the encoding when you DISPLAY the text |
|||
it's better that way, let it go as utf8 to databases etc. because then you've the real data stored |
|||
|
|||
""" |
|||
raise AniDBStupidUserError, "pylibanidb sets the encoding to utf8 as default and it's stupid to use any other encoding. you WILL lose some data if you use other encodings, and now you've been warned. you will need to modify the code yourself if you want to do something as stupid as changing the encoding" |
|||
return self.handle(EncodingCommand(name), callback) |
|||
|
|||
def sendmsg(self, to, title, body, callback=None): |
|||
""" |
|||
Send message |
|||
|
|||
parameters: |
|||
to - name of the user you want as the recipient |
|||
title - title of the message |
|||
body - the message |
|||
|
|||
structure of parameters: |
|||
to title body |
|||
|
|||
""" |
|||
return self.handle(SendMsgCommand(to, title, body), callback) |
|||
|
|||
def user(self, user, callback=None): |
|||
""" |
|||
Retrieve user id |
|||
|
|||
parameters: |
|||
user - username of the user |
|||
|
|||
structure of parameters: |
|||
user |
|||
|
|||
""" |
|||
return self.handle(UserCommand(user), callback) |
|||
|
|||
def uptime(self, callback=None): |
|||
""" |
|||
Retrieve server uptime |
|||
|
|||
""" |
|||
return self.handle(UptimeCommand(), callback) |
|||
|
|||
def version(self, callback=None): |
|||
""" |
|||
Retrieve server version |
|||
|
|||
""" |
|||
return self.handle(VersionCommand(), callback) |
@ -0,0 +1,293 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
from time import time, sleep |
|||
import aniDBfileInfo as fileInfo |
|||
import xml.etree.cElementTree as etree |
|||
import os, re, string |
|||
from aniDBmaper import AniDBMaper |
|||
from aniDBtvDBmaper import TvDBMap |
|||
from aniDBerrors import * |
|||
|
|||
|
|||
|
|||
class aniDBabstractObject(object): |
|||
|
|||
def __init__(self, aniDB, load=False): |
|||
self.laoded = False |
|||
self.set_connection(aniDB) |
|||
if load: |
|||
self.load_data() |
|||
|
|||
def set_connection(self, aniDB): |
|||
self.aniDB = aniDB |
|||
if self.aniDB: |
|||
self.log = self.aniDB.log |
|||
else: |
|||
self.log = self._fake_log() |
|||
|
|||
def _fake_log(self, x=None): |
|||
pass |
|||
|
|||
def _fill(self, dataline): |
|||
for key in dataline: |
|||
try: |
|||
tmpList = dataline[key].split("'") |
|||
if len(tmpList) > 1: |
|||
newList = [] |
|||
for i in tmpList: |
|||
try: |
|||
newList.append(int(i)) |
|||
except: |
|||
newList.append(unicode(i, "utf-8")) |
|||
self.__dict__[key] = newList |
|||
continue |
|||
except: |
|||
pass |
|||
try: |
|||
self.__dict__[key] = int(dataline[key]) |
|||
except: |
|||
self.__dict__[key] = unicode(dataline[key], "utf-8") |
|||
key = property(lambda x: dataline[key]) |
|||
|
|||
def __getattr__(self, name): |
|||
try: |
|||
return object.__getattribute__(self, name) |
|||
except: |
|||
return None |
|||
|
|||
def _build_names(self): |
|||
names = [] |
|||
names = self._easy_extend(names, self.english_name) |
|||
names = self._easy_extend(names, self.short_name_list) |
|||
names = self._easy_extend(names, self.synonym_list) |
|||
names = self._easy_extend(names, self.other_name) |
|||
|
|||
self.allNames = names |
|||
|
|||
def _easy_extend(self, initialList, item): |
|||
if item: |
|||
if isinstance(item, list): |
|||
initialList.extend(item) |
|||
elif isinstance(item, basestring): |
|||
initialList.append(item) |
|||
|
|||
return initialList |
|||
|
|||
|
|||
def load_data(self): |
|||
return False |
|||
|
|||
def add_notification(self): |
|||
""" |
|||
type - Type of notification: type=> 0=all, 1=new, 2=group, 3=complete |
|||
priority - low = 0, medium = 1, high = 2 (unconfirmed) |
|||
|
|||
""" |
|||
if(self.aid): |
|||
self.aniDB.notifyadd(aid=self.aid, type=1, priority=1) |
|||
|
|||
|
|||
class Anime(aniDBabstractObject): |
|||
def __init__(self, aniDB, name=None, aid=None, tvdbid=None, tvrageid=None, paramsA=None, autoCorrectName=False, load=False): |
|||
|
|||
self.maper = AniDBMaper() |
|||
self.tvDBMap = TvDBMap() |
|||
self.allAnimeXML = None |
|||
|
|||
self.name = name |
|||
self.aid = aid |
|||
self.tvdb_id = tvdbid |
|||
|
|||
if self.tvdb_id and not self.aid: |
|||
self.aid = self.tvDBMap.get_anidb_for_tvdb(self.tvdb_id) |
|||
|
|||
if not (self.name or self.aid): |
|||
raise AniDBIncorrectParameterError("No aid or name available") |
|||
|
|||
if not self.aid: |
|||
self.aid = self._get_aid_from_xml(self.name) |
|||
if not self.name or autoCorrectName: |
|||
self.name = self._get_name_from_xml(self.aid) |
|||
|
|||
if not (self.name or self.aid): |
|||
raise ValueError |
|||
|
|||
if not self.tvdb_id: |
|||
self.tvdb_id = self.tvDBMap.get_tvdb_for_anidb(self.aid) |
|||
|
|||
if not paramsA: |
|||
self.bitCode = "b2f0e0fc000000" |
|||
self.params = self.maper.getAnimeCodesA(self.bitCode) |
|||
else: |
|||
self.paramsA = paramsA |
|||
self.bitCode = self.maper.getAnimeBitsA(self.paramsA) |
|||
|
|||
super(Anime, self).__init__(aniDB, load) |
|||
|
|||
def load_data(self): |
|||
"""load the data from anidb""" |
|||
|
|||
if not (self.name or self.aid): |
|||
raise ValueError |
|||
|
|||
self.rawData = self.aniDB.anime(aid=self.aid, aname=self.name, amask=self.bitCode) |
|||
if self.rawData.datalines: |
|||
self._fill(self.rawData.datalines[0]) |
|||
self._builPreSequal() |
|||
self.laoded = True |
|||
|
|||
def get_groups(self): |
|||
if not self.aid: |
|||
return [] |
|||
self.rawData = self.aniDB.groupstatus(aid=self.aid) |
|||
self.release_groups = [] |
|||
for line in self.rawData.datalines: |
|||
self.release_groups.append({"name":unicode(line["name"], "utf-8"), |
|||
"rating":line["rating"], |
|||
"range":line["episode_range"] |
|||
}) |
|||
return self.release_groups |
|||
|
|||
#TODO: refactor and use the new functions in anidbFileinfo |
|||
def _get_aid_from_xml(self, name): |
|||
if not self.allAnimeXML: |
|||
self.allAnimeXML = self._read_animetitels_xml() |
|||
|
|||
regex = re.compile('( \(\d{4}\))|[%s]' % re.escape(string.punctuation)) # remove any punctuation and e.g. ' (2011)' |
|||
#regex = re.compile('[%s]' % re.escape(string.punctuation)) # remove any punctuation and e.g. ' (2011)' |
|||
name = regex.sub('', name.lower()) |
|||
lastAid = 0 |
|||
for element in self.allAnimeXML.getiterator(): |
|||
if element.get("aid", False): |
|||
lastAid = int(element.get("aid")) |
|||
if element.text: |
|||
testname = regex.sub('', element.text.lower()) |
|||
|
|||
if testname == name: |
|||
return lastAid |
|||
return 0 |
|||
|
|||
#TODO: refactor and use the new functions in anidbFileinfo |
|||
def _get_name_from_xml(self, aid, onlyMain=True): |
|||
if not self.allAnimeXML: |
|||
self.allAnimeXML = self._read_animetitels_xml() |
|||
|
|||
for anime in self.allAnimeXML.findall("anime"): |
|||
if int(anime.get("aid", False)) == aid: |
|||
for title in anime.getiterator(): |
|||
currentLang = title.get("{http://www.w3.org/XML/1998/namespace}lang", False) |
|||
currentType = title.get("type", False) |
|||
if (currentLang == "en" and not onlyMain) or currentType == "main": |
|||
return title.text |
|||
return "" |
|||
|
|||
|
|||
def _read_animetitels_xml(self, path=None): |
|||
if not path: |
|||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "animetitles.xml") |
|||
|
|||
f = open(path, "r") |
|||
allAnimeXML = etree.ElementTree(file=f) |
|||
return allAnimeXML |
|||
|
|||
def _builPreSequal(self): |
|||
if self.related_aid_list and self.related_aid_type: |
|||
try: |
|||
for i in range(len(self.related_aid_list)): |
|||
if self.related_aid_type[i] == 2: |
|||
self.__dict__["prequal"] = self.related_aid_list[i] |
|||
elif self.related_aid_type[i] == 1: |
|||
self.__dict__["sequal"] = self.related_aid_list[i] |
|||
except: |
|||
if self.related_aid_type == 2: |
|||
self.__dict__["prequal"] = self.related_aid_list |
|||
elif self.str_related_aid_type == 1: |
|||
self.__dict__["sequal"] = self.related_aid_list |
|||
|
|||
|
|||
|
|||
class Episode(aniDBabstractObject): |
|||
|
|||
def __init__(self, aniDB, number=None, epid=None, filePath=None, fid=None, epno=None, paramsA=None, paramsF=None, load=False, calculate=False): |
|||
if not aniDB and not number and not epid and not file and not fid: |
|||
return None |
|||
|
|||
self.maper = AniDBMaper() |
|||
self.epid = epid |
|||
self.filePath = filePath |
|||
self.fid = fid |
|||
self.epno = epno |
|||
if calculate: |
|||
(self.ed2k, self.size) = self._calculate_file_stuff(self.filePath) |
|||
|
|||
|
|||
if not paramsA: |
|||
self.bitCodeA = "C000F0C0" |
|||
self.paramsA = self.maper.getFileCodesA(self.bitCodeA) |
|||
else: |
|||
self.paramsA = paramsA |
|||
self.bitCodeA = self.maper.getFileBitsA(self.paramsA) |
|||
|
|||
if not paramsF: |
|||
self.bitCodeF = "7FF8FEF8" |
|||
self.paramsF = self.maper.getFileCodesF(self.bitCodeF) |
|||
else: |
|||
self.paramsF = paramsF |
|||
self.bitCodeF = self.maper.getFileBitsF(self.paramsF) |
|||
|
|||
super(Episode, self).__init__(aniDB, load) |
|||
|
|||
def load_data(self): |
|||
"""load the data from anidb""" |
|||
if self.filePath and not (self.ed2k or self.size): |
|||
(self.ed2k, self.size) = self._calculate_file_stuff(self.filePath) |
|||
|
|||
self.rawData = self.aniDB.file(fid=self.fid, size=self.size, ed2k=self.ed2k, aid=self.aid, aname=None, gid=None, gname=None, epno=self.epno, fmask=self.bitCodeF, amask=self.bitCodeA) |
|||
self._fill(self.rawData.datalines[0]) |
|||
self._build_names() |
|||
self.laoded = True |
|||
|
|||
def add_to_mylist(self, status=None): |
|||
""" |
|||
status: |
|||
0 unknown - state is unknown or the user doesn't want to provide this information (default) |
|||
1 on hdd - the file is stored on hdd |
|||
2 on cd - the file is stored on cd |
|||
3 deleted - the file has been deleted or is not available for other reasons (i.e. reencoded) |
|||
|
|||
""" |
|||
if self.filePath and not (self.ed2k or self.size): |
|||
(self.ed2k, self.size) = self._calculate_file_stuff(self.filePath) |
|||
|
|||
try: |
|||
self.aniDB.mylistadd(size=self.size, ed2k=self.ed2k, state=status) |
|||
except Exception, e : |
|||
self.log(u"exception msg: " + str(e)) |
|||
else: |
|||
# TODO: add the name or something |
|||
self.log(u"Added the episode to anidb") |
|||
|
|||
|
|||
def _calculate_file_stuff(self, filePath): |
|||
if not filePath: |
|||
return (None, None) |
|||
self.log("Calculating the ed2k. Please wait...") |
|||
ed2k = fileInfo.get_file_hash(filePath) |
|||
size = fileInfo.get_file_size(filePath) |
|||
return (ed2k, size) |
|||
|
@ -0,0 +1,388 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
from threading import Lock |
|||
from aniDBresponses import * |
|||
from aniDBerrors import * |
|||
|
|||
class Command: |
|||
queue={None:None} |
|||
def __init__(self,command,**parameters): |
|||
self.command=command |
|||
self.parameters=parameters |
|||
self.raw=self.flatten(command,parameters) |
|||
|
|||
self.mode=None |
|||
self.callback=None |
|||
self.waiter=Lock() |
|||
self.waiter.acquire() |
|||
|
|||
def __repr__(self): |
|||
return "Command(%s,%s) %s\n%s\n"%(repr(self.tag),repr(self.command),repr(self.parameters),self.raw_data()) |
|||
|
|||
def authorize(self,mode,tag,session,callback): |
|||
self.mode=mode |
|||
self.callback=callback |
|||
self.tag=tag |
|||
self.session=session |
|||
|
|||
self.parameters['tag']=tag |
|||
self.parameters['s']=session |
|||
|
|||
def handle(self,resp): |
|||
self.resp=resp |
|||
if self.mode==1: |
|||
self.waiter.release() |
|||
elif self.mode==2: |
|||
self.callback(resp) |
|||
|
|||
def wait_response(self): |
|||
self.waiter.acquire() |
|||
|
|||
def flatten(self,command,parameters): |
|||
tmp=[] |
|||
for key,value in parameters.iteritems(): |
|||
if value==None: |
|||
continue |
|||
tmp.append("%s=%s"%(self.escape(key),self.escape(value))) |
|||
return ' '.join([command,'&'.join(tmp)]) |
|||
|
|||
def escape(self,data): |
|||
return str(data).replace('&','&') |
|||
|
|||
def raw_data(self): |
|||
self.raw=self.flatten(self.command,self.parameters) |
|||
return self.raw |
|||
|
|||
def cached(self,interface,database): |
|||
return None |
|||
|
|||
def cache(self,interface,database): |
|||
pass |
|||
|
|||
#first run |
|||
class AuthCommand(Command): |
|||
def __init__(self,username,password,protover,client,clientver,nat=None,comp=None,enc=None,mtu=None): |
|||
parameters={'user':username,'pass':password,'protover':protover,'client':client,'clientver':clientver,'nat':nat,'comp':comp,'enc':enc,'mtu':mtu} |
|||
Command.__init__(self,'AUTH',**parameters) |
|||
|
|||
class LogoutCommand(Command): |
|||
def __init__(self): |
|||
Command.__init__(self,'LOGOUT') |
|||
|
|||
#third run (at the same time as second) |
|||
class PushCommand(Command): |
|||
def __init__(self,notify,msg,buddy=None): |
|||
parameters={'notify':notify,'msg':msg,'buddy':buddy} |
|||
Command.__init__(self,'PUSH',**parameters) |
|||
|
|||
class PushAckCommand(Command): |
|||
def __init__(self,nid): |
|||
parameters={'nid':nid} |
|||
Command.__init__(self,'PUSHACK',**parameters) |
|||
|
|||
class NotifyAddCommand(Command): |
|||
def __init__(self,aid=None,gid=None,type=None,priority=None): |
|||
if not (aid or gid) or (aid and gid): |
|||
raise AniDBIncorrectParameterError,"You must provide aid OR gid for NOTIFICATIONADD command" |
|||
parameters={'aid':aid,"gid":gid,"type":type,"priority":priority} |
|||
Command.__init__(self,'NOTIFICATIONADD',**parameters) |
|||
|
|||
class NotifyCommand(Command): |
|||
def __init__(self,buddy=None): |
|||
parameters={'buddy':buddy} |
|||
Command.__init__(self,'NOTIFY',**parameters) |
|||
|
|||
class NotifyListCommand(Command): |
|||
def __init__(self): |
|||
Command.__init__(self,'NOTIFYLIST') |
|||
|
|||
class NotifyGetCommand(Command): |
|||
def __init__(self,type,id): |
|||
parameters={'type':type,'id':id} |
|||
Command.__init__(self,'NOTIFYGET',**parameters) |
|||
|
|||
class NotifyAckCommand(Command): |
|||
def __init__(self,type,id): |
|||
parameters={'type':type,'id':id} |
|||
Command.__init__(self,'NOTIFYACK',**parameters) |
|||
|
|||
class BuddyAddCommand(Command): |
|||
def __init__(self,uid=None,uname=None): |
|||
if not (uid or uname) or (uid and uname): |
|||
raise AniDBIncorrectParameterError,"You must provide <u(id|name)> for BUDDYADD command" |
|||
parameters={'uid':uid,'uname':uname.lower()} |
|||
Command.__init__(self,'BUDDYADD',**parameters) |
|||
|
|||
class BuddyDelCommand(Command): |
|||
def __init__(self,uid): |
|||
parameters={'uid':uid} |
|||
Command.__init__(self,'BUDDYDEL',**parameters) |
|||
|
|||
class BuddyAcceptCommand(Command): |
|||
def __init__(self,uid): |
|||
parameters={'uid':uid} |
|||
Command.__init__(self,'BUDDYACCEPT',**parameters) |
|||
|
|||
class BuddyDenyCommand(Command): |
|||
def __init__(self,uid): |
|||
parameters={'uid':uid} |
|||
Command.__init__(self,'BUDDYDENY',**parameters) |
|||
|
|||
class BuddyListCommand(Command): |
|||
def __init__(self,startat): |
|||
parameters={'startat':startat} |
|||
Command.__init__(self,'BUDDYLIST',**parameters) |
|||
|
|||
class BuddyStateCommand(Command): |
|||
def __init__(self,startat): |
|||
parameters={'startat':startat} |
|||
Command.__init__(self,'BUDDYSTATE',**parameters) |
|||
|
|||
#first run |
|||
class AnimeCommand(Command): |
|||
def __init__(self,aid=None,aname=None,amask=None): |
|||
if not (aid or aname): |
|||
raise AniDBIncorrectParameterError,"You must provide <a(id|name)> for ANIME command" |
|||
parameters={'aid':aid,'aname':aname,'amask':amask} |
|||
Command.__init__(self,'ANIME',**parameters) |
|||
|
|||
class EpisodeCommand(Command): |
|||
def __init__(self,eid=None,aid=None,aname=None,epno=None): |
|||
if not (eid or ((aname or aid) and epno)) or (aname and aid) or (eid and (aname or aid or epno)): |
|||
raise AniDBIncorrectParameterError,"You must provide <eid XOR a(id|name)+epno> for EPISODE command" |
|||
parameters={'eid':eid,'aid':aid,'aname':aname,'epno':epno} |
|||
Command.__init__(self,'EPISODE',**parameters) |
|||
|
|||
class FileCommand(Command): |
|||
def __init__(self,fid=None,size=None,ed2k=None,aid=None,aname=None,gid=None,gname=None,epno=None,fmask=None,amask=None): |
|||
if not (fid or (size and ed2k) or ((aid or aname) and (gid or gname) and epno)) or (fid and (size or ed2k or aid or aname or gid or gname or epno)) or ((size and ed2k) and (fid or aid or aname or gid or gname or epno)) or (((aid or aname) and (gid or gname) and epno) and (fid or size or ed2k)) or (aid and aname) or (gid and gname): |
|||
raise AniDBIncorrectParameterError,"You must provide <fid XOR size+ed2k XOR a(id|name)+g(id|name)+epno> for FILE command" |
|||
parameters={'fid':fid,'size':size,'ed2k':ed2k,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno,'fmask':fmask,'amask':amask} |
|||
Command.__init__(self,'FILE',**parameters) |
|||
|
|||
class GroupCommand(Command): |
|||
def __init__(self,gid=None,gname=None): |
|||
if not (gid or gname) or (gid and gname): |
|||
raise AniDBIncorrectParameterError,"You must provide <g(id|name)> for GROUP command" |
|||
parameters={'gid':gid,'gname':gname} |
|||
Command.__init__(self,'GROUP',**parameters) |
|||
|
|||
class GroupstatusCommand(Command): |
|||
def __init__(self,aid=None,status=None): |
|||
if not aid: |
|||
raise AniDBIncorrectParameterError,"You must provide aid for GROUPSTATUS command" |
|||
parameters={'aid':aid,'status':status} |
|||
Command.__init__(self,'GROUPSTATUS',**parameters) |
|||
|
|||
class ProducerCommand(Command): |
|||
def __init__(self,pid=None,pname=None): |
|||
if not (pid or pname) or (pid and pname): |
|||
raise AniDBIncorrectParameterError,"You must provide <p(id|name)> for PRODUCER command" |
|||
parameters={'pid':pid,'pname':pname} |
|||
Command.__init__(self,'PRODUCER',**parameters) |
|||
|
|||
def cached(self,intr,db): |
|||
pid=self.parameters['pid'] |
|||
pname=self.parameters['pname'] |
|||
|
|||
codes=('pid', 'name', 'shortname', 'othername', 'type', 'pic', 'url') |
|||
names=','.join([code for code in codes if code!='']) |
|||
ruleholder=(pid and 'pid=%s' or '(name=%s OR shortname=%s OR othername=%s)') |
|||
rulevalues=(pid and [pid] or [pname,pname,pname]) |
|||
|
|||
rows=db.select('ptb',names,ruleholder+" AND status&8",*rulevalues) |
|||
|
|||
if len(rows)>1: |
|||
raise AniDBInternalError,"It shouldn't be possible for database to return more than 1 line for PRODUCER cache" |
|||
elif not len(rows): |
|||
return None |
|||
else: |
|||
resp=ProducerResponse(self,None,'245','CACHED PRODUCER',[list(rows[0])]) |
|||
resp.parse() |
|||
return resp |
|||
|
|||
def cache(self,intr,db): |
|||
if self.resp.rescode!='245' or self.cached(intr,db): |
|||
return |
|||
|
|||
codes=('pid', 'name', 'shortname', 'othername', 'type', 'pic', 'url') |
|||
if len(db.select('ptb','pid','pid=%s',self.resp.datalines[0]['pid'])): |
|||
sets='status=status|15,'+','.join([code+'=%s' for code in codes if code!='']) |
|||
values=[self.resp.datalines[0][code] for code in codes if code!='']+[self.resp.datalines[0]['pid']] |
|||
|
|||
db.update('ptb',sets,'pid=%s',*values) |
|||
else: |
|||
names='status,'+','.join([code for code in codes if code!='']) |
|||
valueholders='0,'+','.join(['%s'for code in codes if code!='']) |
|||
values=[self.resp.datalines[0][code] for code in codes if code!=''] |
|||
|
|||
db.insert('ptb',names,valueholders,*values) |
|||
|
|||
class MyListCommand(Command): |
|||
def __init__(self,lid=None,fid=None,size=None,ed2k=None,aid=None,aname=None,gid=None,gname=None,epno=None): |
|||
if not (lid or fid or (size and ed2k) or (aid or aname)) or (lid and (fid or size or ed2k or aid or aname or gid or gname or epno)) or (fid and (lid or size or ed2k or aid or aname or gid or gname or epno)) or ((size and ed2k) and (lid or fid or aid or aname or gid or gname or epno)) or ((aid or aname) and (lid or fid or size or ed2k)) or (aid and aname) or (gid and gname): |
|||
raise AniDBIncorrectParameterError,"You must provide <lid XOR fid XOR size+ed2k XOR a(id|name)+g(id|name)+epno> for MYLIST command" |
|||
parameters={'lid':lid,'fid':fid,'size':size,'ed2k':ed2k,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno} |
|||
Command.__init__(self,'MYLIST',**parameters) |
|||
|
|||
def cached(self,intr,db): |
|||
lid=self.parameters['lid'] |
|||
fid=self.parameters['fid'] |
|||
size=self.parameters['size'] |
|||
ed2k=self.parameters['ed2k'] |
|||
aid=self.parameters['aid'] |
|||
aname=self.parameters['aname'] |
|||
gid=self.parameters['gid'] |
|||
gname=self.parameters['gname'] |
|||
epno=self.parameters['epno'] |
|||
|
|||
names=','.join([code for code in MylistResponse(None,None,None,None,[]).codetail if code!='']) |
|||
|
|||
if lid: |
|||
ruleholder="lid=%s" |
|||
rulevalues=[lid] |
|||
elif fid or size or ed2k: |
|||
resp=intr.file(fid=fid,size=size,ed2k=ed2k) |
|||
if resp.rescode!='220': |
|||
resp=NoSuchMylistResponse(self,None,'321','NO SUCH ENTRY (FILE NOT FOUND)',[]) |
|||
resp.parse() |
|||
return resp |
|||
fid=resp.datalines[0]['fid'] |
|||
|
|||
ruleholder="fid=%s" |
|||
rulevalues=[fid] |
|||
else: |
|||
resp=intr.anime(aid=aid,aname=aname) |
|||
if resp.rescode!='230': |
|||
resp=NoSuchFileResponse(self,None,'321','NO SUCH ENTRY (ANIME NOT FOUND)',[]) |
|||
resp.parse() |
|||
return resp |
|||
aid=resp.datalines[0]['aid'] |
|||
|
|||
resp=intr.group(gid=gid,gname=gname) |
|||
if resp.rescode!='250': |
|||
resp=NoSuchFileResponse(self,None,'321','NO SUCH ENTRY (GROUP NOT FOUND)',[]) |
|||
resp.parse() |
|||
return resp |
|||
gid=resp.datalines[0]['gid'] |
|||
|
|||
resp=intr.episode(aid=aid,epno=epno) |
|||
if resp.rescode!='240': |
|||
resp=NoSuchFileResponse(self,None,'321','NO SUCH ENTRY (EPISODE NOT FOUND)',[]) |
|||
resp.parse() |
|||
return resp |
|||
eid=resp.datalines[0]['eid'] |
|||
|
|||
ruleholder="aid=%s AND eid=%s AND gid=%s" |
|||
rulevalues=[aid,eid,gid] |
|||
|
|||
rows=db.select('ltb',names,ruleholder+" AND status&8",*rulevalues) |
|||
|
|||
if len(rows)>1: |
|||
#resp=MultipleFilesFoundResponse(self,None,'322','CACHED MULTIPLE FILES FOUND',/*get fids from rows, not gonna do this as you haven't got a real cache out of these..*/) |
|||
return None |
|||
elif not len(rows): |
|||
return None |
|||
else: |
|||
resp=MylistResponse(self,None,'221','CACHED MYLIST',[list(rows[0])]) |
|||
resp.parse() |
|||
return resp |
|||
|
|||
def cache(self,intr,db): |
|||
if self.resp.rescode!='221' or self.cached(intr,db): |
|||
return |
|||
|
|||
codes=MylistResponse(None,None,None,None,[]).codetail |
|||
if len(db.select('ltb','lid','lid=%s',self.resp.datalines[0]['lid'])): |
|||
sets='status=status|15,'+','.join([code+'=%s' for code in codes if code!='']) |
|||
values=[self.resp.datalines[0][code] for code in codes if code!='']+[self.resp.datalines[0]['lid']] |
|||
|
|||
db.update('ltb',sets,'lid=%s',*values) |
|||
else: |
|||
names='status,'+','.join([code for code in codes if code!='']) |
|||
valueholders='15,'+','.join(['%s' for code in codes if code!='']) |
|||
values=[self.resp.datalines[0][code] for code in codes if code!=''] |
|||
|
|||
db.insert('ltb',names,valueholders,*values) |
|||
|
|||
class MyListAddCommand(Command): |
|||
def __init__(self,lid=None,fid=None,size=None,ed2k=None,aid=None,aname=None,gid=None,gname=None,epno=None,edit=None,state=None,viewed=None,source=None,storage=None,other=None): |
|||
if not (lid or fid or (size and ed2k) or ((aid or aname) and (gid or gname))) or (lid and (fid or size or ed2k or aid or aname or gid or gname or epno)) or (fid and (lid or size or ed2k or aid or aname or gid or gname or epno)) or ((size and ed2k) and (lid or fid or aid or aname or gid or gname or epno)) or (((aid or aname) and (gid or gname)) and (lid or fid or size or ed2k)) or (aid and aname) or (gid and gname) or (lid and not edit): |
|||
raise AniDBIncorrectParameterError,"You must provide <lid XOR fid XOR size+ed2k XOR a(id|name)+g(id|name)+epno> for MYLISTADD command" |
|||
parameters={'lid':lid,'fid':fid,'size':size,'ed2k':ed2k,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno,'edit':edit,'state':state,'viewed':viewed,'source':source,'storage':storage,'other':other} |
|||
Command.__init__(self,'MYLISTADD',**parameters) |
|||
|
|||
class MyListDelCommand(Command): |
|||
def __init__(self,lid=None,fid=None,aid=None,aname=None,gid=None,gname=None,epno=None): |
|||
if not (lid or fid or ((aid or aname) and (gid or gname) and epno)) or (lid and (fid or aid or aname or gid or gname or epno)) or (fid and (lid or aid or aname or gid or gname or epno)) or (((aid or aname) and (gid or gname) and epno) and (lid or fid)) or (aid and aname) or (gid and gname): |
|||
raise AniDBIncorrectParameterError,"You must provide <lid+edit=1 XOR fid XOR a(id|name)+g(id|name)+epno> for MYLISTDEL command" |
|||
parameters={'lid':lid,'fid':fid,'aid':aid,'aname':aname,'gid':gid,'gname':gname,'epno':epno} |
|||
Command.__init__(self,'MYLISTDEL',**parameters) |
|||
|
|||
class MyListStatsCommand(Command): |
|||
def __init__(self): |
|||
Command.__init__(self,'MYLISTSTATS') |
|||
|
|||
class VoteCommand(Command): |
|||
def __init__(self,type,id=None,name=None,value=None,epno=None): |
|||
if not (id or name) or (id and name): |
|||
raise AniDBIncorrectParameterError,"You must provide <(id|name)> for VOTE command" |
|||
parameters={'type':type,'id':id,'name':name,'value':value,'epno':epno} |
|||
Command.__init__(self,'VOTE',**parameters) |
|||
|
|||
class RandomAnimeCommand(Command): |
|||
def __init__(self,type): |
|||
parameters={'type':type} |
|||
Command.__init__(self,'RANDOMANIME',**parameters) |
|||
|
|||
class PingCommand(Command): |
|||
def __init__(self): |
|||
Command.__init__(self,'PING') |
|||
|
|||
#second run |
|||
class EncryptCommand(Command): |
|||
def __init__(self,user,apipassword,type): |
|||
self.apipassword=apipassword |
|||
parameters={'user':user.lower(),'type':type} |
|||
Command.__init__(self,'ENCRYPT',**parameters) |
|||
|
|||
class EncodingCommand(Command): |
|||
def __init__(self,name): |
|||
parameters={'name':type} |
|||
Command.__init__(self,'ENCODING',**parameters) |
|||
|
|||
class SendMsgCommand(Command): |
|||
def __init__(self,to,title,body): |
|||
if len(title)>50 or len(body)>900: |
|||
raise AniDBIncorrectParameterError,"Title must not be longer than 50 chars and body must not be longer than 900 chars for SENDMSG command" |
|||
parameters={'to':to.lower(),'title':title,'body':body} |
|||
Command.__init__(self,'SENDMSG',**parameters) |
|||
|
|||
class UserCommand(Command): |
|||
def __init__(self,user): |
|||
parameters={'user':user} |
|||
Command.__init__(self,'USER',**parameters) |
|||
|
|||
class UptimeCommand(Command): |
|||
def __init__(self): |
|||
Command.__init__(self,'UPTIME') |
|||
|
|||
class VersionCommand(Command): |
|||
def __init__(self): |
|||
Command.__init__(self,'VERSION') |
|||
|
@ -0,0 +1,37 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
class AniDBError(Exception): |
|||
pass |
|||
|
|||
class AniDBIncorrectParameterError(AniDBError): |
|||
pass |
|||
|
|||
class AniDBCommandTimeoutError(AniDBError): |
|||
pass |
|||
|
|||
class AniDBMustAuthError(AniDBError): |
|||
pass |
|||
|
|||
class AniDBPacketCorruptedError(AniDBError): |
|||
pass |
|||
|
|||
class AniDBBannedError(AniDBError): |
|||
pass |
|||
|
|||
class AniDBInternalError(AniDBError): |
|||
pass |
@ -0,0 +1,75 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
from __future__ import with_statement |
|||
import hashlib |
|||
import os |
|||
import xml.etree.cElementTree as etree |
|||
|
|||
|
|||
# http://www.radicand.org/blog/orz/2010/2/21/edonkey2000-hash-in-python/ |
|||
def get_file_hash(filePath): |
|||
""" Returns the ed2k hash of a given file.""" |
|||
if not filePath: |
|||
return None |
|||
md4 = hashlib.new('md4').copy |
|||
|
|||
def gen(f): |
|||
while True: |
|||
x = f.read(9728000) |
|||
if x: yield x |
|||
else: return |
|||
|
|||
def md4_hash(data): |
|||
m = md4() |
|||
m.update(data) |
|||
return m |
|||
|
|||
with open(filePath, 'rb') as f: |
|||
a = gen(f) |
|||
hashes = [md4_hash(data).digest() for data in a] |
|||
if len(hashes) == 1: |
|||
return hashes[0].encode("hex") |
|||
else: return md4_hash(reduce(lambda a,d: a + d, hashes, "")).hexdigest() |
|||
|
|||
|
|||
def get_file_size(path): |
|||
size = os.path.getsize(path) |
|||
return size |
|||
|
|||
|
|||
|
|||
def read_anidb_xml(filePath): |
|||
if not filePath: |
|||
filePath = os.path.join(os.path.dirname(os.path.abspath( __file__ )), "animetitles.xml") |
|||
return read_xml_into_etree(filePath) |
|||
|
|||
|
|||
def read_tvdb_map_xml(filePath): |
|||
if not filePath: |
|||
filePath = os.path.join(os.path.dirname(os.path.abspath( __file__ )), "anime-list.xml") |
|||
return read_xml_into_etree(filePath) |
|||
|
|||
|
|||
def read_xml_into_etree(filePath): |
|||
if not filePath: |
|||
return None |
|||
|
|||
f = open(filePath,"r") |
|||
xmlASetree = etree.ElementTree(file = f) |
|||
return xmlASetree |
|||
|
@ -0,0 +1,218 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
import socket, sys, zlib |
|||
from time import time, sleep |
|||
import threading |
|||
from aniDBresponses import ResponseResolver |
|||
from aniDBerrors import * |
|||
|
|||
|
|||
class AniDBLink(threading.Thread): |
|||
def __init__(self, server, port, myport, logFunction, delay=2, timeout=20, logPrivate=False): |
|||
super(AniDBLink, self).__init__() |
|||
self.server = server |
|||
self.port = port |
|||
self.target = (server, port) |
|||
self.timeout = timeout |
|||
|
|||
self.myport = 0 |
|||
self.bound = self.connectSocket(myport, self.timeout) |
|||
|
|||
self.cmd_queue = {None:None} |
|||
self.resp_tagged_queue = {} |
|||
self.resp_untagged_queue = [] |
|||
self.tags = [] |
|||
self.lastpacket = time() |
|||
self.delay = delay |
|||
self.session = None |
|||
self.banned = False |
|||
self.crypt = None |
|||
|
|||
self.log = logFunction |
|||
self.logPrivate = logPrivate |
|||
|
|||
self._stop = threading.Event() |
|||
self._quiting = False |
|||
self.setDaemon(True) |
|||
self.start() |
|||
|
|||
def connectSocket(self, myport, timeout): |
|||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) |
|||
self.sock.settimeout(timeout) |
|||
portlist = [myport] + [7654] |
|||
for port in portlist: |
|||
try: |
|||
self.sock.bind(('', port)) |
|||
except: |
|||
continue |
|||
else: |
|||
self.myport = port |
|||
return True |
|||
else: |
|||
return False; |
|||
|
|||
def disconnectSocket(self): |
|||
self.sock.close() |
|||
|
|||
def stop (self): |
|||
self.log("Releasing socket and stopping link thread") |
|||
self._quiting = True |
|||
self.disconnectSocket() |
|||
self._stop.set() |
|||
|
|||
def stopped (self): |
|||
return self._stop.isSet() |
|||
|
|||
def print_log(self, data): |
|||
print data |
|||
|
|||
def print_log_dummy(self, data): |
|||
pass |
|||
|
|||
def run(self): |
|||
while not self._quiting: |
|||
try: |
|||
data = self.sock.recv(8192) |
|||
except socket.timeout: |
|||
self._handle_timeouts() |
|||
|
|||
continue |
|||
self.log("NetIO < %s" % repr(data)) |
|||
try: |
|||
for i in range(2): |
|||
try: |
|||
tmp = data |
|||
resp = None |
|||
if tmp[:2] == '\x00\x00': |
|||
tmp = zlib.decompressobj().decompress(tmp[2:]) |
|||
self.log("UnZip | %s" % repr(tmp)) |
|||
resp = ResponseResolver(tmp) |
|||
except: |
|||
sys.excepthook(*sys.exc_info()) |
|||
self.crypt = None |
|||
self.session = None |
|||
else: |
|||
break |
|||
if not resp: |
|||
raise AniDBPacketCorruptedError, "Either decrypting, decompressing or parsing the packet failed" |
|||
cmd = self._cmd_dequeue(resp) |
|||
resp = resp.resolve(cmd) |
|||
resp.parse() |
|||
if resp.rescode in ('200', '201'): |
|||
self.session = resp.attrs['sesskey'] |
|||
if resp.rescode in ('209',): |
|||
print "sorry encryption is not supported" |
|||
raise |
|||
#self.crypt=aes(md5(resp.req.apipassword+resp.attrs['salt']).digest()) |
|||
if resp.rescode in ('203', '403', '500', '501', '503', '506'): |
|||
self.session = None |
|||
self.crypt = None |
|||
if resp.rescode in ('504', '555'): |
|||
self.banned = True |
|||
print "AniDB API informs that user or client is banned:", resp.resstr |
|||
resp.handle() |
|||
if not cmd or not cmd.mode: |
|||
self._resp_queue(resp) |
|||
else: |
|||
self.tags.remove(resp.restag) |
|||
except: |
|||
sys.excepthook(*sys.exc_info()) |
|||
print "Avoiding flood by paranoidly panicing: Aborting link thread, killing connection, releasing waiters and quiting" |
|||
self.sock.close() |
|||
try:cmd.waiter.release() |
|||
except:pass |
|||
for tag, cmd in self.cmd_queue.iteritems(): |
|||
try:cmd.waiter.release() |
|||
except:pass |
|||
sys.exit() |
|||
|
|||
def _handle_timeouts(self): |
|||
willpop = [] |
|||
for tag, cmd in self.cmd_queue.iteritems(): |
|||
if not tag: |
|||
continue |
|||
if time() - cmd.started > self.timeout: |
|||
self.tags.remove(cmd.tag) |
|||
willpop.append(cmd.tag) |
|||
cmd.waiter.release() |
|||
|
|||
for tag in willpop: |
|||
self.cmd_queue.pop(tag) |
|||
|
|||
def _resp_queue(self, response): |
|||
if response.restag: |
|||
self.resp_tagged_queue[response.restag] = response |
|||
else: |
|||
self.resp_untagged_queue.append(response) |
|||
|
|||
def getresponse(self, command): |
|||
if command: |
|||
resp = self.resp_tagged_queue.pop(command.tag) |
|||
else: |
|||
resp = self.resp_untagged_queue.pop() |
|||
self.tags.remove(resp.restag) |
|||
return resp |
|||
|
|||
def _cmd_queue(self, command): |
|||
self.cmd_queue[command.tag] = command |
|||
self.tags.append(command.tag) |
|||
|
|||
def _cmd_dequeue(self, resp): |
|||
if not resp.restag: |
|||
return None |
|||
else: |
|||
return self.cmd_queue.pop(resp.restag) |
|||
|
|||
def _delay(self): |
|||
return (self.delay < 2.1 and 2.1 or self.delay) |
|||
|
|||
def _do_delay(self): |
|||
age = time() - self.lastpacket |
|||
delay = self._delay() |
|||
if age <= delay: |
|||
sleep(delay - age) |
|||
|
|||
def _send(self, command): |
|||
if self.banned: |
|||
self.log("NetIO | BANNED") |
|||
raise AniDBBannedError, "Not sending, banned" |
|||
self._do_delay() |
|||
self.lastpacket = time() |
|||
command.started = time() |
|||
data = command.raw_data() |
|||
|
|||
self.sock.sendto(data, self.target) |
|||
if command.command == 'AUTH' and self.logPrivate: |
|||
self.log("NetIO > sensitive data is not logged!") |
|||
else: |
|||
self.log("NetIO > %s" % repr(data)) |
|||
|
|||
def new_tag(self): |
|||
if not len(self.tags): |
|||
maxtag = "T000" |
|||
else: |
|||
maxtag = max(self.tags) |
|||
newtag = "T%03d" % (int(maxtag[1:]) + 1) |
|||
return newtag |
|||
|
|||
def request(self, command): |
|||
if not (self.session and command.session) and command.command not in ('AUTH', 'PING', 'ENCRYPT'): |
|||
raise AniDBMustAuthError, "You must be authed to execute commands besides AUTH and PING" |
|||
command.started = time() |
|||
self._cmd_queue(command) |
|||
self._send(command) |
@ -0,0 +1,138 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
from random import shuffle |
|||
|
|||
class AniDBMaper: |
|||
|
|||
blacklist = ('unused','retired','reserved') |
|||
|
|||
def getAnimeBitsA(self,amask): |
|||
map = self.getAnimeMapA() |
|||
return self._getBitChain(map,amask); |
|||
|
|||
def getAnimeCodesA(self,aBitChain): |
|||
amap = self.getAnimeMapA() |
|||
return self._getCodes(amap,aBitChain); |
|||
|
|||
|
|||
def getFileBitsF(self,fmask): |
|||
fmap = self.getFileMapF() |
|||
return self._getBitChain(fmap,fmask) |
|||
|
|||
def getFileCodesF(self,bitChainF): |
|||
fmap = self.getFileMapF() |
|||
return self._getCodes(fmap,bitChainF) |
|||
|
|||
|
|||
def getFileBitsA(self,amask): |
|||
amap = self.getFileMapA() |
|||
return self._getBitChain(amap,amask) |
|||
|
|||
def getFileCodesA(self,bitChainA): |
|||
amap = self.getFileMapA() |
|||
return self._getCodes(amap,bitChainA) |
|||
|
|||
|
|||
def _getBitChain(self,map,wanted): |
|||
"""Return an hex string with the correct bit set corresponding to the wanted fields in the map |
|||
""" |
|||
bit = 0 |
|||
for index,field in enumerate(map): |
|||
if field in wanted and not field in self.blacklist: |
|||
bit = bit ^ (1<<len(map)-index-1) |
|||
|
|||
bit = str(hex(bit)).lstrip("0x").rstrip("L") |
|||
bit = ''.join(["0" for unused in xrange(len(map)/4 - len(bit))])+bit |
|||
return bit |
|||
|
|||
def _getCodes(self,map,bitChain): |
|||
"""Returns a list with the corresponding fields as set in the bitChain (hex string) |
|||
""" |
|||
codeList=[] |
|||
bitChain = int(bitChain,16) |
|||
mapLength = len(map) |
|||
for i in reversed(range(mapLength)): |
|||
if bitChain&(2**i): |
|||
codeList.append(map[mapLength-i-1]) |
|||
return codeList |
|||
|
|||
def getAnimeMapA(self): |
|||
# each line is one byte |
|||
# only chnage this if the api changes |
|||
map = ['aid','unused','year','type','related_aid_list','related_aid_type','category_list','category_weight_list', |
|||
'romaji_name','kanji_name','english_name','other_name','short_name_list','synonym_list','retired','retired', |
|||
'episodes','highest_episode_number','special_ep_count','air_date','end_date','url','picname','category_id_list', |
|||
'rating','vote_count','temp_rating','temp_vote_count','average_review_rating','review_count','award_list','is_18_restricted', |
|||
'anime_planet_id','ANN_id','allcinema_id','AnimeNfo_id','unused','unused','unused','date_record_updated', |
|||
'character_id_list','creator_id_list','main_creator_id_list','main_creator_name_list','unused','unused','unused','unused', |
|||
'specials_count','credits_count','other_count','trailer_count','parody_count','unused','unused','unused'] |
|||
return map |
|||
|
|||
def getFileMapF(self): |
|||
# each line is one byte |
|||
# only chnage this if the api changes |
|||
map = ['unused','aid','eid','gid','mylist_id','list_other_episodes','IsDeprecated','state', |
|||
'size','ed2k','md5','sha1','crc32','unused','unused','reserved', |
|||
'quality','source','audio_codec_list','audio_bitrate_list','video_codec','video_bitrate','video_resolution','file_type_extension', |
|||
'dub_language','sub_language','length_in_seconds','description','aired_date','unused','unused','anidb_file_name', |
|||
'mylist_state','mylist_filestate','mylist_viewed','mylist_viewdate','mylist_storage','mylist_source','mylist_other','unused'] |
|||
return map |
|||
|
|||
def getFileMapA(self): |
|||
# each line is one byte |
|||
# only chnage this if the api changes |
|||
map = ['anime_total_episodes','highest_episode_number','year','type','related_aid_list','related_aid_type','category_list','reserved', |
|||
'romaji_name','kanji_name','english_name','other_name','short_name_list','synonym_list','retired','retired', |
|||
'epno','ep_name','ep_romaji_name','ep_kanji_name','episode_rating','episode_vote_count','unused','unused', |
|||
'group_name','group_short_name','unused','unused','unused','unused','unused','date_aid_record_updated'] |
|||
return map |
|||
|
|||
def checkMapping(self,verbos=False): |
|||
|
|||
print "------" |
|||
print "File F: "+ str(self.checkMapFileF(verbos)) |
|||
print "------" |
|||
print "File A: "+ str(self.checkMapFileA(verbos)) |
|||
|
|||
|
|||
def checkMapFileF(self,verbos=False): |
|||
getGeneralMap = lambda: self.getFileMapF() |
|||
getBits = lambda x: self.getFileBitsF(x) |
|||
getCodes = lambda x: self.getFileCodesF(x) |
|||
return self._checkMapGeneral(getGeneralMap,getBits,getCodes,verbos=verbos) |
|||
|
|||
def checkMapFileA(self,verbos=False): |
|||
getGeneralMap = lambda: self.getFileMapA() |
|||
getBits = lambda x: self.getFileBitsA(x) |
|||
getCodes = lambda x: self.getFileCodesA(x) |
|||
return self._checkMapGeneral(getGeneralMap,getBits,getCodes,verbos=verbos) |
|||
|
|||
def _checkMapGeneral(self,getGeneralMap,getBits,getCodes,verbos=False): |
|||
map = getGeneralMap() |
|||
shuffle(map) |
|||
mask = [elem for elem in map if elem not in self.blacklist][:5] |
|||
bits = getBits(mask) |
|||
mask_re = getCodes(bits) |
|||
bits_re = getBits(mask_re) |
|||
if verbos: |
|||
print mask |
|||
print mask_re |
|||
print bits |
|||
print bits_re |
|||
print "bits are:"+ str((bits_re == bits)) |
|||
print "map is :"+ str((sorted(mask_re) == sorted(mask))) |
|||
return (bits_re == bits) and sorted(mask_re) == sorted(mask) |
File diff suppressed because it is too large
@ -0,0 +1,65 @@ |
|||
#!/usr/bin/env python |
|||
# |
|||
# This file is part of aDBa. |
|||
# |
|||
# aDBa is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# aDBa is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with aDBa. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
import os |
|||
import xml.etree.cElementTree as etree |
|||
import aniDBfileInfo as fileInfo |
|||
|
|||
|
|||
|
|||
class TvDBMap(): |
|||
|
|||
def __init__(self,filePath=None): |
|||
self.xmlMap = fileInfo.read_tvdb_map_xml(filePath) |
|||
|
|||
def get_tvdb_for_anidb(self,anidb_id): |
|||
return self._get_x_for_y(anidb_id,"anidbid","tvdbid") |
|||
|
|||
def get_anidb_for_tvdb(self,tvdb_id): |
|||
return self._get_x_for_y(tvdb_id,"tvdbid","anidbid") |
|||
|
|||
|
|||
def _get_x_for_y(self,xValue,x,y): |
|||
#print("searching "+x+" with the value "+str(xValue)+" and want to give back "+y) |
|||
xValue = str(xValue) |
|||
for anime in self.xmlMap.findall("anime"): |
|||
try: |
|||
if anime.get(x,False) == xValue: |
|||
return int(anime.get(y,0)) |
|||
except ValueError, e: |
|||
continue |
|||
return 0 |
|||
|
|||
|
|||
def get_season_episode_for_anidb_absoluteNumber(self,anidb_id,absoluteNumber): |
|||
# NOTE: this cant be done without the length of each season from thetvdb |
|||
#TODO: implement |
|||
season = 0 |
|||
episode = 0 |
|||
|
|||
for anime in self.xmlMap.findall("anime"): |
|||
if int(anime.get("anidbid",False)) == anidb_id: |
|||
defaultSeason = int(anime.get("defaulttvdbseason",1)) |
|||
|
|||
|
|||
return (season,episode) |
|||
|
|||
def get_season_episode_for_tvdb_absoluteNumber(self,anidb_id,absoluteNumber): |
|||
#TODO: implement |
|||
season = 0 |
|||
episode = 0 |
|||
return (season,episode) |
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,151 @@ |
|||
# Author: Nic Wolfe <nic@wolfeden.ca> |
|||
# URL: http://code.google.com/p/sickbeard/ |
|||
# |
|||
# This file is part of Sick Beard. |
|||
# |
|||
# Sick Beard is free software: you can redistribute it and/or modify |
|||
# it under the terms of the GNU General Public License as published by |
|||
# the Free Software Foundation, either version 3 of the License, or |
|||
# (at your option) any later version. |
|||
# |
|||
# Sick Beard is distributed in the hope that it will be useful, |
|||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|||
# GNU General Public License for more details. |
|||
# |
|||
# You should have received a copy of the GNU General Public License |
|||
# along with Sick Beard. If not, see <http://www.gnu.org/licenses/>. |
|||
|
|||
import urllib |
|||
|
|||
import sickbeard |
|||
import generic |
|||
|
|||
from sickbeard import classes, show_name_helpers, helpers |
|||
|
|||
from sickbeard import exceptions, logger |
|||
from sickbeard.common import * |
|||
from sickbeard import tvcache |
|||
from lib.dateutil.parser import parse as parseDate |
|||
|
|||
class Fanzub(generic.NZBProvider): |
|||
|
|||
def __init__(self): |
|||
|
|||
generic.NZBProvider.__init__(self, "Fanzub") |
|||
|
|||
self.supportsBacklog = False |
|||
self.supportsAbsoluteNumbering = True |
|||
|
|||
self.enabled = False |
|||
|
|||
self.cache = FanzubCache(self) |
|||
|
|||
self.url = 'http://fanzub.com/' |
|||
|
|||
def isEnabled(self): |
|||
return self.enabled |
|||
|
|||
def imageName(self): |
|||
return 'fanzub.gif' |
|||
|
|||
def _checkAuth(self): |
|||
return True |
|||
|
|||
def _get_season_search_strings(self, ep_obj): |
|||
return [x for x in show_name_helpers.makeSceneSeasonSearchString(self.show, ep_obj)] |
|||
|
|||
def _get_episode_search_strings(self, ep_obj, add_string=''): |
|||
return [x for x in show_name_helpers.makeSceneSearchString(self.show, ep_obj)] |
|||
|
|||
def _doSearch(self, search_string, epcount=0, age=0): |
|||
if self.show and not self.show.is_anime: |
|||
logger.log(u"" + str(self.show.name) + " is not an anime skiping ...") |
|||
return [] |
|||
|
|||
params = { |
|||
"cat": "anime", |
|||
"q": search_string.encode('utf-8'), |
|||
"max": "100" |
|||
} |
|||
|
|||
search_url = self.url + "rss?" + urllib.urlencode(params) |
|||
|
|||
logger.log(u"Search url: " + search_url, logger.DEBUG) |
|||
|
|||
data = self.cache.getRSSFeed(search_url) |
|||
if not data: |
|||
return [] |
|||
|
|||
if 'entries' in data: |
|||
|
|||
items = data.entries |
|||
results = [] |
|||
|
|||
for curItem in items: |
|||
(title, url) = self._get_title_and_url(curItem) |
|||
|
|||
if title and url: |
|||
results.append(curItem) |
|||
else: |
|||
logger.log( |
|||
u"The data returned from the " + self.name + " is incomplete, this result is unusable", |
|||
logger.DEBUG) |
|||
|
|||
return results |
|||
|
|||
return [] |
|||
|
|||
def findPropers(self, date=None): |
|||
|
|||
results = [] |
|||
|
|||
for i in [2, 3, 4]: # we will look for a version 2, 3 and 4 |
|||
""" |
|||
because of this the proper search failed !! |
|||
well more precisly because _doSearch does not accept a dict rather then a string |
|||
params = { |
|||
"q":"v"+str(i).encode('utf-8') |
|||
} |
|||
""" |
|||
for curResult in self._doSearch("v" + str(i)): |
|||
|
|||
match = re.search('(\w{3}, \d{1,2} \w{3} \d{4} \d\d:\d\d:\d\d) [\+\-]\d{4}', curResult.findtext('pubDate')) |
|||
if not match: |
|||
continue |
|||
|
|||
dateString = match.group(1) |
|||
resultDate = parseDate(dateString).replace(tzinfo=None) |
|||
|
|||
if date == None or resultDate > date: |
|||
results.append(classes.Proper(curResult.findtext('title'), curResult.findtext('link'), resultDate)) |
|||
|
|||
return results |
|||
|
|||
class FanzubCache(tvcache.TVCache): |
|||
|
|||
def __init__(self, provider): |
|||
|
|||
tvcache.TVCache.__init__(self, provider) |
|||
|
|||
# only poll Fanzub every 20 minutes max |
|||
# we get 100 post each call ! |
|||
self.minTime = 20 |
|||
|
|||
|
|||
def _getRSSData(self): |
|||
|
|||
params = {"cat": "anime".encode('utf-8'), |
|||
"max": "100".encode('utf-8') |
|||
} |
|||
|
|||
rss_url = self.provider.url + 'rss?' + urllib.urlencode(params) |
|||
|
|||
logger.log(self.provider.name + u" cache update URL: " + rss_url, logger.DEBUG) |
|||
|
|||
return self.getRSSFeed(rss_url) |
|||
|
|||
def _checkAuth(self, data): |
|||
return self.provider._checkAuthFromData(data) |
|||
|
|||
provider = Fanzub() |
Loading…
Reference in new issue