mirror of https://github.com/evilhero/mylar
FIX:(#1046) if nzbname had '+' within title, would fail to create nzb within cache (nzb only), IMP:(#1079) test notification buttons added, FIX: Changed names of functions to ResumeSeries/DeleteSeries/PauseSeries, IMP: pushbullet notification converted now using requests module, FIX:(#1088) better versioning detection when searching, enforce permissions on cache directory before writing (to ensure nzbs will be written with proper permissions), IMP: Popup dialog box on Delete Series in Comic Details page - option to delete series folder & contents avaiable as well, IMP: Try to determine local IP on startup to allow for better execution of sending nzbs to nzb client
This commit is contained in:
parent
ef037dce1e
commit
abcc4e88ec
|
@ -9,7 +9,18 @@
|
||||||
<div id="subhead_container">
|
<div id="subhead_container">
|
||||||
<div id="subhead_menu">
|
<div id="subhead_menu">
|
||||||
<a id="menu_link_refresh" onclick="doAjaxCall('refreshSeries?ComicID=${comic['ComicID']}', $(this),'table')" href="#" data-success="${comic['ComicName']} is being refreshed">Refresh Comic</a>
|
<a id="menu_link_refresh" onclick="doAjaxCall('refreshSeries?ComicID=${comic['ComicID']}', $(this),'table')" href="#" data-success="${comic['ComicName']} is being refreshed">Refresh Comic</a>
|
||||||
<a id="menu_link_delete" href="deleteArtist?ComicID=${comic['ComicID']}">Delete Comic</a>
|
<a id="menu_link_delete" href="#">Delete Comic</a>
|
||||||
|
<div id="dialog" title="Delete Series Confirmation" style="display:none" class="configtable">
|
||||||
|
<form action="deleteSeries" method="GET" style="vertical-align: middle; text-align: center">
|
||||||
|
</br><input type="submit" value="Delete Series">
|
||||||
|
<div class="row checkbox left clearfix">
|
||||||
|
</br>
|
||||||
|
<input type="checkbox" style="vertical-align: middle; margin: 3px; margin-top: -1px;" name="delete_dir" id="deleteCheck" value="1" ${comicConfig['delete_dir']} /><label>Remove directory when deleting Series?</label>
|
||||||
|
</div>
|
||||||
|
<input type="hidden" name="ComicID" value=${comic['ComicID']}>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
%if mylar.RENAME_FILES:
|
%if mylar.RENAME_FILES:
|
||||||
<a id="menu_link_refresh" onclick="doAjaxCall('manualRename?comicid=${comic['ComicID']}', $(this),'table')" data-success="Renaming files.">Rename Files</a>
|
<a id="menu_link_refresh" onclick="doAjaxCall('manualRename?comicid=${comic['ComicID']}', $(this),'table')" data-success="Renaming files.">Rename Files</a>
|
||||||
%endif
|
%endif
|
||||||
|
@ -18,9 +29,9 @@
|
||||||
<a id="menu_link_refresh" onclick="doAjaxCall('group_metatag?dirName=${comic['ComicLocation'] |u}&ComicID=${comic['ComicID']}', $(this),'table')" data-success="(re)tagging every issue present for '${comic['ComicName']}'">Manual MetaTagging</a>
|
<a id="menu_link_refresh" onclick="doAjaxCall('group_metatag?dirName=${comic['ComicLocation'] |u}&ComicID=${comic['ComicID']}', $(this),'table')" data-success="(re)tagging every issue present for '${comic['ComicName']}'">Manual MetaTagging</a>
|
||||||
%endif
|
%endif
|
||||||
%if comic['Status'] == 'Paused':
|
%if comic['Status'] == 'Paused':
|
||||||
<a id="menu_link_resume" href="#" onclick="doAjaxCall('resumeArtist?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} resumed">Resume Comic</a>
|
<a id="menu_link_resume" href="#" onclick="doAjaxCall('resumeSeries?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} resumed">Resume Comic</a>
|
||||||
%else:
|
%else:
|
||||||
<a id="menu_link_pauze" href="#" onclick="doAjaxCall('pauseArtist?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} paused">Pause Comic</a>
|
<a id="menu_link_pauze" href="#" onclick="doAjaxCall('pauseSeries?ComicID=${comic['ComicID']}',$(this),true)" data-success="${comic['ComicName']} paused">Pause Comic</a>
|
||||||
%endif
|
%endif
|
||||||
%if annuals:
|
%if annuals:
|
||||||
<a id="menu_link_delete" href="annualDelete?comicid=${comic['ComicID']}">Delete Annuals</a>
|
<a id="menu_link_delete" href="annualDelete?comicid=${comic['ComicID']}">Delete Annuals</a>
|
||||||
|
@ -58,8 +69,9 @@
|
||||||
<li><a href="#tabs-1">Comic Details</a></li>
|
<li><a href="#tabs-1">Comic Details</a></li>
|
||||||
<li><a href="#tabs-2">Download settings</a></li>
|
<li><a href="#tabs-2">Download settings</a></li>
|
||||||
<li><a href="#tabs-3">Edit Settings</a></li>
|
<li><a href="#tabs-3">Edit Settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div id="tabs-1">
|
|
||||||
|
<div id="tabs-1">
|
||||||
|
|
||||||
|
|
||||||
<table class="comictable" summary="Comic Details">
|
<table class="comictable" summary="Comic Details">
|
||||||
|
@ -77,39 +89,39 @@
|
||||||
%elif comic['ComicPublisher'] == 'Marvel':
|
%elif comic['ComicPublisher'] == 'Marvel':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-marvel.jpg" align="right" alt="Marvel" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-marvel.jpg" align="right" alt="Marvel" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Image':
|
%elif comic['ComicPublisher'] == 'Image':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-imagecomics.png" align="right" alt="Image" height="100" width="50" />
|
<img src="interfaces/default/images/publisherlogos/logo-imagecomics.png" align="right" alt="Image" height="100" width="50" />
|
||||||
%elif comic['ComicPublisher'] == 'Dark Horse Comics' or comic['ComicPublisher'] == 'Dark Horse':
|
%elif comic['ComicPublisher'] == 'Dark Horse Comics' or comic['ComicPublisher'] == 'Dark Horse':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-darkhorse.png" align="right" alt="Darkhorse" height="100" width="75" />
|
<img src="interfaces/default/images/publisherlogos/logo-darkhorse.png" align="right" alt="Darkhorse" height="100" width="75" />
|
||||||
%elif comic['ComicPublisher'] == 'IDW Publishing':
|
%elif comic['ComicPublisher'] == 'IDW Publishing':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-idwpublish.png" align="right" alt="IDW" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-idwpublish.png" align="right" alt="IDW" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Icon':
|
%elif comic['ComicPublisher'] == 'Icon':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-iconcomics.png" align="right" alt="Icon" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-iconcomics.png" align="right" alt="Icon" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Red5':
|
%elif comic['ComicPublisher'] == 'Red5':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-red5comics.png" align="right" alt="Red5" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-red5comics.png" align="right" alt="Red5" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Vertigo':
|
%elif comic['ComicPublisher'] == 'Vertigo':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-vertigo.jpg" align="right" alt="Vertigo" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-vertigo.jpg" align="right" alt="Vertigo" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'ShadowLine':
|
%elif comic['ComicPublisher'] == 'ShadowLine':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-shadowline.png" align="right" alt="Shadowline" height="75" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-shadowline.png" align="right" alt="Shadowline" height="75" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Archie Comics':
|
%elif comic['ComicPublisher'] == 'Archie Comics':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-archiecomics.jpg" align="right" alt="Archie" height="75" width="75"/>
|
<img src="interfaces/default/images/publisherlogos/logo-archiecomics.jpg" align="right" alt="Archie" height="75" width="75"/>
|
||||||
%elif comic['ComicPublisher'] == 'Oni Press':
|
%elif comic['ComicPublisher'] == 'Oni Press':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-onipress.png" align="right" alt="Oni Press" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-onipress.png" align="right" alt="Oni Press" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Tokyopop':
|
%elif comic['ComicPublisher'] == 'Tokyopop':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-tokyopop.jpg" align="right" alt="Tokyopop" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-tokyopop.jpg" align="right" alt="Tokyopop" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Midtown Comics':
|
%elif comic['ComicPublisher'] == 'Midtown Comics':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-midtowncomics.jpg" align="right" alt="Midtown" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-midtowncomics.jpg" align="right" alt="Midtown" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Boom! Studios':
|
%elif comic['ComicPublisher'] == 'Boom! Studios':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-boom.jpg" align="right" alt="Boom!" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-boom.jpg" align="right" alt="Boom!" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Skybound':
|
%elif comic['ComicPublisher'] == 'Skybound':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-skybound.jpg" align="right" alt="Skybound" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-skybound.jpg" align="right" alt="Skybound" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Vertigo':
|
%elif comic['ComicPublisher'] == 'Vertigo':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-dynamite.jpg" align="right" alt="Dynamite" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-dynamite.jpg" align="right" alt="Dynamite" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Top Cow':
|
%elif comic['ComicPublisher'] == 'Top Cow':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-topcow.gif" align="right" alt="Top Cow" height="75" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-topcow.gif" align="right" alt="Top Cow" height="75" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Dynamite Entertainment':
|
%elif comic['ComicPublisher'] == 'Dynamite Entertainment':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-dynamite.png" align="right" alt="Dynamite" height="50" width="100"/>
|
<img src="interfaces/default/images/publisherlogos/logo-dynamite.png" align="right" alt="Dynamite" height="50" width="100"/>
|
||||||
%elif comic['ComicPublisher'] == 'Cartoon Books':
|
%elif comic['ComicPublisher'] == 'Cartoon Books':
|
||||||
<img src="interfaces/default/images/publisherlogos/logo-cartoonbooks.jpg" align="right" alt="Cartoon Books" height="75" width="90"/>
|
<img src="interfaces/default/images/publisherlogos/logo-cartoonbooks.jpg" align="right" alt="Cartoon Books" height="75" width="90"/>
|
||||||
%endif
|
%endif
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<div>
|
<div>
|
||||||
|
@ -359,7 +371,7 @@
|
||||||
<table class="display" id="issue_table">
|
<table class="display" id="issue_table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th id="select" align="left"><input type="checkbox" onClick="toggle(this)" class="checkbox" /></th>
|
<th id="select" align="left"><input type="checkbox" onClick="toggle(this)" name="issues" class="checkbox" /></th>
|
||||||
<th id="int_issuenumber">IntIssNum</th>
|
<th id="int_issuenumber">IntIssNum</th>
|
||||||
<th id="issuenumber">Number</th>
|
<th id="issuenumber">Number</th>
|
||||||
<th id="issuename">Name</th>
|
<th id="issuename">Name</th>
|
||||||
|
@ -467,6 +479,9 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="table_wrapper">
|
||||||
|
|
||||||
%if annuals:
|
%if annuals:
|
||||||
<h1>Annuals</h1>
|
<h1>Annuals</h1>
|
||||||
%for aninfo in annualinfo:
|
%for aninfo in annualinfo:
|
||||||
|
@ -501,7 +516,7 @@
|
||||||
-->
|
-->
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th id="select" align="left"><input type="checkbox" onClick="toggle(this)" class="checkbox" /></th>
|
<th id="ann_action" align="left"><input type="checkbox" onClick="toggle(this)" class="checkbox" /></th>
|
||||||
<th id="aint_issuenumber">Int_IssNumber</th>
|
<th id="aint_issuenumber">Int_IssNumber</th>
|
||||||
<th id="aissuenumber">Number</th>
|
<th id="aissuenumber">Number</th>
|
||||||
<th id="aissuename">Name</th>
|
<th id="aissuename">Name</th>
|
||||||
|
@ -533,7 +548,7 @@
|
||||||
agrade = 'A'
|
agrade = 'A'
|
||||||
%>
|
%>
|
||||||
<tr class="${annual['Status']} grade${agrade}">
|
<tr class="${annual['Status']} grade${agrade}">
|
||||||
<td id="select"><input type="checkbox" name="${annual['IssueID']}" class="checkbox" value="${annual['IssueID']}" /></td>
|
<td id="ann_action"><input type="checkbox" name="${annual['IssueID']}" class="checkbox" value="${annual['IssueID']}" /></td>
|
||||||
<%
|
<%
|
||||||
if annual['Int_IssueNumber'] is None:
|
if annual['Int_IssueNumber'] is None:
|
||||||
annual_Number = annual['Int_IssueNumber']
|
annual_Number = annual['Int_IssueNumber']
|
||||||
|
@ -624,7 +639,7 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
%endif
|
%endif
|
||||||
|
</div>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
<%def name="headIncludes()">
|
<%def name="headIncludes()">
|
||||||
|
@ -713,52 +728,12 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
$(function() {
|
function openDelete() {
|
||||||
function log( message ) {
|
$("#dialog").dialog({modal:true});
|
||||||
$( "<div>" ).text( message ).prependTo( "#log" );
|
};
|
||||||
$( "#log" ).scrollTop( 0 );
|
function deleteDirCheck() {
|
||||||
}
|
return document.getElementById("deleteCheck").checked;
|
||||||
|
}
|
||||||
$( "#annseries" ).autocomplete({
|
|
||||||
source: function( request, response ) {
|
|
||||||
$.ajax({
|
|
||||||
url: "http://api.comicvine.com/search?",
|
|
||||||
contentType: "application/json; charset=utf-8",
|
|
||||||
dataType: "json",
|
|
||||||
data: {
|
|
||||||
resources: "volume",
|
|
||||||
format: "json",
|
|
||||||
api_key: "",
|
|
||||||
query: request.term
|
|
||||||
},
|
|
||||||
success: function( data ) {
|
|
||||||
alert("success");
|
|
||||||
response( $.map( data.results, function( item ) {
|
|
||||||
return {
|
|
||||||
label: item.name + (item.id),
|
|
||||||
value: item.name
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
error: function( data ) {
|
|
||||||
alert("error");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
minLength: 2,
|
|
||||||
select: function( event, ui ) {
|
|
||||||
log( ui.item ?
|
|
||||||
"Selected: " + ui.item.label :
|
|
||||||
"Nothing selected, input was " + this.value);
|
|
||||||
},
|
|
||||||
open: function() {
|
|
||||||
$( this ).removeClass( "ui-corner-all" ).addClass( "ui-corner-top" );
|
|
||||||
},
|
|
||||||
close: function() {
|
|
||||||
$( this ).removeClass( "ui-corner-top" ).addClass( "ui-corner-all" );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -796,30 +771,12 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
hideServerDivs = function () {
|
|
||||||
$("#customoptions").slideUp();
|
|
||||||
$("#hpserveroptions").slideUp();
|
|
||||||
};
|
|
||||||
|
|
||||||
handleNewSelection = function () {
|
|
||||||
|
|
||||||
hideServerDivs();
|
|
||||||
|
|
||||||
switch ($(this).val()) {
|
|
||||||
case 'custom':
|
|
||||||
$("#customoptions").slideDown();
|
|
||||||
break;
|
|
||||||
case 'mylar':
|
|
||||||
$("#hpserveroptions").slideDown();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function initThisPage(){
|
function initThisPage(){
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
$( "#tabs" ).tabs();
|
$( "#tabs" ).tabs();
|
||||||
});
|
});
|
||||||
|
$("#menu_link_delete").click(openDelete);
|
||||||
initActions();
|
initActions();
|
||||||
$('#issue_table').dataTable(
|
$('#issue_table').dataTable(
|
||||||
{
|
{
|
||||||
|
|
|
@ -899,13 +899,16 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>API key</label><input type="text" name="prowl_keys" value="${config['prowl_keys']}" size="50">
|
<label>API key</label><input type="text" name="prowl_keys" value="${config['prowl_keys']}" size="50">
|
||||||
</div>
|
</div>
|
||||||
<div class="row checkbox">
|
<div class="row checkbox">
|
||||||
<input type="checkbox" name="prowl_onsnatch" value="1" ${config['prowl_onsnatch']} /><label>Notify on snatch?</label>
|
<input type="checkbox" name="prowl_onsnatch" value="1" ${config['prowl_onsnatch']} /><label>Notify on snatch?</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>Priority (-2,-1,0,1 or 2):</label>
|
<label>Priority (-2,-1,0,1 or 2):</label>
|
||||||
<input type="text" name="prowl_priority" value="${config['prowl_priority']}" size="2">
|
<input type="text" name="prowl_priority" value="${config['prowl_priority']}" size="2">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input type="button" value="Test PROWL" id="prowl_test" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
|
@ -923,8 +926,8 @@
|
||||||
<small>Separate multiple api keys with commas</small>
|
<small>Separate multiple api keys with commas</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>Priority</label>
|
<label>Priority</label>
|
||||||
<select name="nma_priority">
|
<select name="nma_priority">
|
||||||
%for x in [-2,-1,0,1,2]:
|
%for x in [-2,-1,0,1,2]:
|
||||||
<%
|
<%
|
||||||
if config['nma_priority'] == x:
|
if config['nma_priority'] == x:
|
||||||
|
@ -943,19 +946,22 @@
|
||||||
else:
|
else:
|
||||||
nma_priority_value = 'Emergency'
|
nma_priority_value = 'Emergency'
|
||||||
%>
|
%>
|
||||||
<option value=${x} ${nma_priority_selected}>${nma_priority_value}</option>
|
<option value=${x} ${nma_priority_selected}>${nma_priority_value}</option>
|
||||||
%endfor
|
%endfor
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input type="button" value="Test NMA" id="nma_test" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<h3><img src="interfaces/default/images/pushover_logo.png" style="vertical-align: middle; margin: 3px; margin-top: -1px;" height="30" width="30"/>Pushover</h3>
|
<h3><img src="interfaces/default/images/pushover_logo.png" style="vertical-align: middle; margin: 3px; margin-top: -1px;" height="30" width="30"/>Pushover</h3>
|
||||||
<div class="row checkbox">
|
<div class="row checkbox">
|
||||||
<input type="checkbox" name="pushover_enabled" id="pushover" value="1" ${config['pushover_enabled']} /><label>Enable Pushover Notifications</label>
|
<input type="checkbox" name="pushover_enabled" id="pushover" value="1" ${config['pushover_enabled']} /><label>Enable Pushover Notifications</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="pushoveroptions">
|
<div id="pushoveroptions">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<label>API key</label><input type="text" title="Leave blank if you don't have your own API (recommended to get your own)" name="pushover_apikey" value="${config['pushover_apikey']}" size="50">
|
<label>API key</label><input type="text" title="Leave blank if you don't have your own API (recommended to get your own)" name="pushover_apikey" value="${config['pushover_apikey']}" size="50">
|
||||||
</div>
|
</div>
|
||||||
|
@ -969,7 +975,10 @@
|
||||||
<label>Priority (-2,-1,0,1 or 2):</label>
|
<label>Priority (-2,-1,0,1 or 2):</label>
|
||||||
<input type="text" name="pushover_priority" value="${config['pushover_priority']}" size="2">
|
<input type="text" name="pushover_priority" value="${config['pushover_priority']}" size="2">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="row">
|
||||||
|
<input type="button" value="Test Pushover" id="pushover_test" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<h3><img src="interfaces/default/images/boxcar_logo.png" style="vertical-align: middle; margin: 3px; margin-top: -1px;" height="30" width="30"/>Boxcar.IO</h3>
|
<h3><img src="interfaces/default/images/boxcar_logo.png" style="vertical-align: middle; margin: 3px; margin-top: -1px;" height="30" width="30"/>Boxcar.IO</h3>
|
||||||
|
@ -983,6 +992,9 @@
|
||||||
</div>
|
</div>
|
||||||
<label>Boxcar Token</label>
|
<label>Boxcar Token</label>
|
||||||
<input type="text" name="boxcar_token" value="${config['boxcar_token']}" size="30">
|
<input type="text" name="boxcar_token" value="${config['boxcar_token']}" size="30">
|
||||||
|
<div class="row">
|
||||||
|
<input type="button" value="Test Boxcar" id="boxcar_test" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
@ -1007,6 +1019,9 @@
|
||||||
<input type="button" class="btn" value="Update device list" id="getPushbulletDevices" />
|
<input type="button" class="btn" value="Update device list" id="getPushbulletDevices" />
|
||||||
-->
|
-->
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<input type="button" value="Test Pushbullet" id="pushbullet_test" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</td>
|
</td>
|
||||||
|
@ -1286,6 +1301,35 @@
|
||||||
$('#autoadd').append('<input type="hidden" name="tsab" value=1 />');
|
$('#autoadd').append('<input type="hidden" name="tsab" value=1 />');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$('#nma_test').click(function () {
|
||||||
|
$.get("/testNMA",
|
||||||
|
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||||
|
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#prowl_test').click(function () {
|
||||||
|
$.get("/testprowl",
|
||||||
|
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||||
|
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#pushover_test').click(function () {
|
||||||
|
$.get("/testpushover",
|
||||||
|
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||||
|
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#boxcar_test').click(function () {
|
||||||
|
$.get("/testboxcar",
|
||||||
|
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||||
|
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
$('#pushbullet_test').click(function () {
|
||||||
|
$.get("/testpushbullet",
|
||||||
|
function (data) { $('#ajaxMsg').html("<div class='msg'><span class='ui-icon ui-icon-check'></span>"+data+"</div>"); });
|
||||||
|
$('#ajaxMsg').addClass('success').fadeIn().delay(3000).fadeOut();
|
||||||
|
});
|
||||||
|
|
||||||
$(function() {
|
$(function() {
|
||||||
$( "#tabs" ).tabs();
|
$( "#tabs" ).tabs();
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
.. image:: https://travis-ci.org/jtriley/pystun.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/jtriley/pystun
|
||||||
|
|
||||||
|
.. image:: https://coveralls.io/repos/jtriley/pystun/badge.png
|
||||||
|
:target: https://coveralls.io/r/jtriley/pystun
|
||||||
|
|
||||||
|
|
||||||
|
PyStun
|
||||||
|
======
|
||||||
|
A Python STUN client for getting NAT type and external IP
|
||||||
|
|
||||||
|
This is a fork of pystun originally created by gaohawk (http://code.google.com/p/pystun/)
|
||||||
|
|
||||||
|
PyStun follows RFC 3489: http://www.ietf.org/rfc/rfc3489.txt
|
||||||
|
|
||||||
|
A server following STUN-bis hasn't been found on internet so RFC3489 is the
|
||||||
|
only implementation.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
To install the latest version::
|
||||||
|
|
||||||
|
$ pip install pystun
|
||||||
|
|
||||||
|
or download/clone the source and install manually using::
|
||||||
|
|
||||||
|
$ cd /path/to/pystun/src
|
||||||
|
$ python setup.py install
|
||||||
|
|
||||||
|
If you're hacking on pystun you should use the 'develop' command instead::
|
||||||
|
|
||||||
|
$ python setup.py develop
|
||||||
|
|
||||||
|
This will make a link to the sources inside your site-packages directory so
|
||||||
|
that any changes are immediately available for testing.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
From command line::
|
||||||
|
|
||||||
|
$ pystun
|
||||||
|
NAT Type: Full Cone
|
||||||
|
External IP: <your-ip-here>
|
||||||
|
External Port: 54320
|
||||||
|
|
||||||
|
Pass --help for more options::
|
||||||
|
|
||||||
|
% pystun --help
|
||||||
|
usage: pystun [-h] [-d] [-H STUN_HOST] [-P STUN_PORT] [-i SOURCE_IP]
|
||||||
|
[-p SOURCE_PORT] [--version]
|
||||||
|
|
||||||
|
optional arguments:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
-d, --debug Enable debug logging (default: False)
|
||||||
|
-H STUN_HOST, --host STUN_HOST
|
||||||
|
STUN host to use (default: None)
|
||||||
|
-P STUN_PORT, --host-port STUN_PORT
|
||||||
|
STUN host port to use (default: 3478)
|
||||||
|
-i SOURCE_IP, --interface SOURCE_IP
|
||||||
|
network interface for client (default: 0.0.0.0)
|
||||||
|
-p SOURCE_PORT, --port SOURCE_PORT
|
||||||
|
port to listen on for client (default: 54320)
|
||||||
|
--version show program's version number and exit
|
||||||
|
|
||||||
|
From Python::
|
||||||
|
|
||||||
|
import stun
|
||||||
|
nat_type, external_ip, external_port = stun.get_ip_info()
|
||||||
|
|
||||||
|
This will rotate through an internal list of STUN servers until a response is
|
||||||
|
found. If no response is found you will get "Blocked" as the *nat_type* and
|
||||||
|
**None** for *external_ip* and *external_port*.
|
||||||
|
|
||||||
|
If you prefer to use a specific STUN server::
|
||||||
|
|
||||||
|
nat_type, external_ip, external_port = stun.get_ip_info(stun_host='stun.ekiga.net')
|
||||||
|
|
||||||
|
If you prefer to use a specific STUN server port::
|
||||||
|
|
||||||
|
nat_type, external_ip, external_port = stun.get_ip_info(stun_port=3478)
|
||||||
|
|
||||||
|
You may also specify the client interface and port that is used although this
|
||||||
|
is not needed::
|
||||||
|
|
||||||
|
sip = "0.0.0.0" # interface to listen on (all)
|
||||||
|
port = 54320 # port to listen on
|
||||||
|
nat_type, external_ip, external_port = stun.get_ip_info(sip, port)
|
||||||
|
|
||||||
|
Read the code for more details...
|
||||||
|
|
||||||
|
LICENSE
|
||||||
|
-------
|
||||||
|
MIT
|
|
@ -0,0 +1,257 @@
|
||||||
|
import binascii
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
|
||||||
|
__version__ = '0.1.0'
|
||||||
|
|
||||||
|
log = logging.getLogger("pystun")
|
||||||
|
|
||||||
|
STUN_SERVERS = (
|
||||||
|
'stun.ekiga.net',
|
||||||
|
'stun.ideasip.com',
|
||||||
|
'stun.voiparound.com',
|
||||||
|
'stun.voipbuster.com',
|
||||||
|
'stun.voipstunt.com',
|
||||||
|
'stun.voxgratia.org'
|
||||||
|
)
|
||||||
|
|
||||||
|
stun_servers_list = STUN_SERVERS
|
||||||
|
|
||||||
|
DEFAULTS = {
|
||||||
|
'stun_port': 3478,
|
||||||
|
'source_ip': '0.0.0.0',
|
||||||
|
'source_port': 54320
|
||||||
|
}
|
||||||
|
|
||||||
|
# stun attributes
|
||||||
|
MappedAddress = '0001'
|
||||||
|
ResponseAddress = '0002'
|
||||||
|
ChangeRequest = '0003'
|
||||||
|
SourceAddress = '0004'
|
||||||
|
ChangedAddress = '0005'
|
||||||
|
Username = '0006'
|
||||||
|
Password = '0007'
|
||||||
|
MessageIntegrity = '0008'
|
||||||
|
ErrorCode = '0009'
|
||||||
|
UnknownAttribute = '000A'
|
||||||
|
ReflectedFrom = '000B'
|
||||||
|
XorOnly = '0021'
|
||||||
|
XorMappedAddress = '8020'
|
||||||
|
ServerName = '8022'
|
||||||
|
SecondaryAddress = '8050' # Non standard extension
|
||||||
|
|
||||||
|
# types for a stun message
|
||||||
|
BindRequestMsg = '0001'
|
||||||
|
BindResponseMsg = '0101'
|
||||||
|
BindErrorResponseMsg = '0111'
|
||||||
|
SharedSecretRequestMsg = '0002'
|
||||||
|
SharedSecretResponseMsg = '0102'
|
||||||
|
SharedSecretErrorResponseMsg = '0112'
|
||||||
|
|
||||||
|
dictAttrToVal = {'MappedAddress': MappedAddress,
|
||||||
|
'ResponseAddress': ResponseAddress,
|
||||||
|
'ChangeRequest': ChangeRequest,
|
||||||
|
'SourceAddress': SourceAddress,
|
||||||
|
'ChangedAddress': ChangedAddress,
|
||||||
|
'Username': Username,
|
||||||
|
'Password': Password,
|
||||||
|
'MessageIntegrity': MessageIntegrity,
|
||||||
|
'ErrorCode': ErrorCode,
|
||||||
|
'UnknownAttribute': UnknownAttribute,
|
||||||
|
'ReflectedFrom': ReflectedFrom,
|
||||||
|
'XorOnly': XorOnly,
|
||||||
|
'XorMappedAddress': XorMappedAddress,
|
||||||
|
'ServerName': ServerName,
|
||||||
|
'SecondaryAddress': SecondaryAddress}
|
||||||
|
|
||||||
|
dictMsgTypeToVal = {
|
||||||
|
'BindRequestMsg': BindRequestMsg,
|
||||||
|
'BindResponseMsg': BindResponseMsg,
|
||||||
|
'BindErrorResponseMsg': BindErrorResponseMsg,
|
||||||
|
'SharedSecretRequestMsg': SharedSecretRequestMsg,
|
||||||
|
'SharedSecretResponseMsg': SharedSecretResponseMsg,
|
||||||
|
'SharedSecretErrorResponseMsg': SharedSecretErrorResponseMsg}
|
||||||
|
|
||||||
|
dictValToMsgType = {}
|
||||||
|
|
||||||
|
dictValToAttr = {}
|
||||||
|
|
||||||
|
Blocked = "Blocked"
|
||||||
|
OpenInternet = "Open Internet"
|
||||||
|
FullCone = "Full Cone"
|
||||||
|
SymmetricUDPFirewall = "Symmetric UDP Firewall"
|
||||||
|
RestricNAT = "Restric NAT"
|
||||||
|
RestricPortNAT = "Restric Port NAT"
|
||||||
|
SymmetricNAT = "Symmetric NAT"
|
||||||
|
ChangedAddressError = "Meet an error, when do Test1 on Changed IP and Port"
|
||||||
|
|
||||||
|
|
||||||
|
def _initialize():
|
||||||
|
items = dictAttrToVal.items()
|
||||||
|
for i in range(len(items)):
|
||||||
|
dictValToAttr.update({items[i][1]: items[i][0]})
|
||||||
|
items = dictMsgTypeToVal.items()
|
||||||
|
for i in range(len(items)):
|
||||||
|
dictValToMsgType.update({items[i][1]: items[i][0]})
|
||||||
|
|
||||||
|
|
||||||
|
def gen_tran_id():
|
||||||
|
a = ''.join(random.choice('0123456789ABCDEF') for i in range(32))
|
||||||
|
# return binascii.a2b_hex(a)
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
def stun_test(sock, host, port, source_ip, source_port, send_data=""):
|
||||||
|
retVal = {'Resp': False, 'ExternalIP': None, 'ExternalPort': None,
|
||||||
|
'SourceIP': None, 'SourcePort': None, 'ChangedIP': None,
|
||||||
|
'ChangedPort': None}
|
||||||
|
str_len = "%#04d" % (len(send_data) / 2)
|
||||||
|
tranid = gen_tran_id()
|
||||||
|
str_data = ''.join([BindRequestMsg, str_len, tranid, send_data])
|
||||||
|
data = binascii.a2b_hex(str_data)
|
||||||
|
recvCorr = False
|
||||||
|
while not recvCorr:
|
||||||
|
recieved = False
|
||||||
|
count = 3
|
||||||
|
while not recieved:
|
||||||
|
log.debug("sendto: %s", (host, port))
|
||||||
|
try:
|
||||||
|
sock.sendto(data, (host, port))
|
||||||
|
except socket.gaierror:
|
||||||
|
retVal['Resp'] = False
|
||||||
|
return retVal
|
||||||
|
try:
|
||||||
|
buf, addr = sock.recvfrom(2048)
|
||||||
|
log.debug("recvfrom: %s", addr)
|
||||||
|
recieved = True
|
||||||
|
except Exception:
|
||||||
|
recieved = False
|
||||||
|
if count > 0:
|
||||||
|
count -= 1
|
||||||
|
else:
|
||||||
|
retVal['Resp'] = False
|
||||||
|
return retVal
|
||||||
|
msgtype = binascii.b2a_hex(buf[0:2])
|
||||||
|
bind_resp_msg = dictValToMsgType[msgtype] == "BindResponseMsg"
|
||||||
|
tranid_match = tranid.upper() == binascii.b2a_hex(buf[4:20]).upper()
|
||||||
|
if bind_resp_msg and tranid_match:
|
||||||
|
recvCorr = True
|
||||||
|
retVal['Resp'] = True
|
||||||
|
len_message = int(binascii.b2a_hex(buf[2:4]), 16)
|
||||||
|
len_remain = len_message
|
||||||
|
base = 20
|
||||||
|
while len_remain:
|
||||||
|
attr_type = binascii.b2a_hex(buf[base:(base + 2)])
|
||||||
|
attr_len = int(binascii.b2a_hex(buf[(base + 2):(base + 4)]), 16)
|
||||||
|
if attr_type == MappedAddress:
|
||||||
|
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
|
||||||
|
ip = ".".join([
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))
|
||||||
|
])
|
||||||
|
retVal['ExternalIP'] = ip
|
||||||
|
retVal['ExternalPort'] = port
|
||||||
|
if attr_type == SourceAddress:
|
||||||
|
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
|
||||||
|
ip = ".".join([
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))
|
||||||
|
])
|
||||||
|
retVal['SourceIP'] = ip
|
||||||
|
retVal['SourcePort'] = port
|
||||||
|
if attr_type == ChangedAddress:
|
||||||
|
port = int(binascii.b2a_hex(buf[base + 6:base + 8]), 16)
|
||||||
|
ip = ".".join([
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 8:base + 9]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 9:base + 10]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 10:base + 11]), 16)),
|
||||||
|
str(int(binascii.b2a_hex(buf[base + 11:base + 12]), 16))
|
||||||
|
])
|
||||||
|
retVal['ChangedIP'] = ip
|
||||||
|
retVal['ChangedPort'] = port
|
||||||
|
# if attr_type == ServerName:
|
||||||
|
# serverName = buf[(base+4):(base+4+attr_len)]
|
||||||
|
base = base + 4 + attr_len
|
||||||
|
len_remain = len_remain - (4 + attr_len)
|
||||||
|
# s.close()
|
||||||
|
return retVal
|
||||||
|
|
||||||
|
|
||||||
|
def get_nat_type(s, source_ip, source_port, stun_host=None, stun_port=3478):
|
||||||
|
_initialize()
|
||||||
|
port = stun_port
|
||||||
|
log.debug("Do Test1")
|
||||||
|
resp = False
|
||||||
|
if stun_host:
|
||||||
|
ret = stun_test(s, stun_host, port, source_ip, source_port)
|
||||||
|
resp = ret['Resp']
|
||||||
|
else:
|
||||||
|
for stun_host in stun_servers_list:
|
||||||
|
log.debug('Trying STUN host: %s', stun_host)
|
||||||
|
ret = stun_test(s, stun_host, port, source_ip, source_port)
|
||||||
|
resp = ret['Resp']
|
||||||
|
if resp:
|
||||||
|
break
|
||||||
|
if not resp:
|
||||||
|
return Blocked, ret
|
||||||
|
log.debug("Result: %s", ret)
|
||||||
|
exIP = ret['ExternalIP']
|
||||||
|
exPort = ret['ExternalPort']
|
||||||
|
changedIP = ret['ChangedIP']
|
||||||
|
changedPort = ret['ChangedPort']
|
||||||
|
if ret['ExternalIP'] == source_ip:
|
||||||
|
changeRequest = ''.join([ChangeRequest, '0004', "00000006"])
|
||||||
|
ret = stun_test(s, stun_host, port, source_ip, source_port,
|
||||||
|
changeRequest)
|
||||||
|
if ret['Resp']:
|
||||||
|
typ = OpenInternet
|
||||||
|
else:
|
||||||
|
typ = SymmetricUDPFirewall
|
||||||
|
else:
|
||||||
|
changeRequest = ''.join([ChangeRequest, '0004', "00000006"])
|
||||||
|
log.debug("Do Test2")
|
||||||
|
ret = stun_test(s, stun_host, port, source_ip, source_port,
|
||||||
|
changeRequest)
|
||||||
|
log.debug("Result: %s", ret)
|
||||||
|
if ret['Resp']:
|
||||||
|
typ = FullCone
|
||||||
|
else:
|
||||||
|
log.debug("Do Test1")
|
||||||
|
ret = stun_test(s, changedIP, changedPort, source_ip, source_port)
|
||||||
|
log.debug("Result: %s", ret)
|
||||||
|
if not ret['Resp']:
|
||||||
|
typ = ChangedAddressError
|
||||||
|
else:
|
||||||
|
if exIP == ret['ExternalIP'] and exPort == ret['ExternalPort']:
|
||||||
|
changePortRequest = ''.join([ChangeRequest, '0004',
|
||||||
|
"00000002"])
|
||||||
|
log.debug("Do Test3")
|
||||||
|
ret = stun_test(s, changedIP, port, source_ip, source_port,
|
||||||
|
changePortRequest)
|
||||||
|
log.debug("Result: %s", ret)
|
||||||
|
if ret['Resp']:
|
||||||
|
typ = RestricNAT
|
||||||
|
else:
|
||||||
|
typ = RestricPortNAT
|
||||||
|
else:
|
||||||
|
typ = SymmetricNAT
|
||||||
|
return typ, ret
|
||||||
|
|
||||||
|
|
||||||
|
def get_ip_info(source_ip="0.0.0.0", source_port=54320, stun_host=None,
|
||||||
|
stun_port=3478):
|
||||||
|
socket.setdefaulttimeout(2)
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
s.bind((source_ip, source_port))
|
||||||
|
nat_type, nat = get_nat_type(s, source_ip, source_port,
|
||||||
|
stun_host=stun_host, stun_port=stun_port)
|
||||||
|
external_ip = nat['ExternalIP']
|
||||||
|
external_port = nat['ExternalPort']
|
||||||
|
s.close()
|
||||||
|
return (nat_type, external_ip, external_port)
|
|
@ -0,0 +1,64 @@
|
||||||
|
from __future__ import print_function
|
||||||
|
import argparse
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import stun
|
||||||
|
|
||||||
|
|
||||||
|
def make_argument_parser():
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'-d', '--debug', action='store_true',
|
||||||
|
help='Enable debug logging'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-H', '--stun-host',
|
||||||
|
help='STUN host to use'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-P', '--stun-port', type=int,
|
||||||
|
default=stun.DEFAULTS['stun_port'],
|
||||||
|
help='STUN host port to use'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-i', '--source-ip',
|
||||||
|
default=stun.DEFAULTS['source_ip'],
|
||||||
|
help='network interface for client'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-p', '--source-port', type=int,
|
||||||
|
default=stun.DEFAULTS['source_port'],
|
||||||
|
help='port to listen on for client'
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('--version', action='version', version=stun.__version__)
|
||||||
|
|
||||||
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
options = make_argument_parser().parse_args()
|
||||||
|
|
||||||
|
if options.debug:
|
||||||
|
logging.basicConfig()
|
||||||
|
stun.log.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
|
nat_type, external_ip, external_port = stun.get_ip_info(
|
||||||
|
source_ip=options.source_ip,
|
||||||
|
source_port=options.source_port,
|
||||||
|
stun_host=options.stun_host,
|
||||||
|
stun_port=options.stun_port
|
||||||
|
)
|
||||||
|
print('NAT Type:', nat_type)
|
||||||
|
print('External IP:', external_ip)
|
||||||
|
print('External Port:', external_port)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -0,0 +1,61 @@
|
||||||
|
import argparse
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
import stun
|
||||||
|
from stun import cli
|
||||||
|
|
||||||
|
|
||||||
|
class TestCLI(unittest.TestCase):
|
||||||
|
"""Test the CLI API."""
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
cls.source_ip = '123.45.67.89'
|
||||||
|
cls.source_port = 24816
|
||||||
|
cls.stun_port = 13579
|
||||||
|
cls.stun_host = 'stun.stub.org'
|
||||||
|
|
||||||
|
def test_cli_parser_default(self):
|
||||||
|
parser = cli.make_argument_parser()
|
||||||
|
options = parser.parse_args([])
|
||||||
|
|
||||||
|
self.assertEqual(options.source_ip, stun.DEFAULTS['source_ip'])
|
||||||
|
self.assertEqual(options.source_port, stun.DEFAULTS['source_port'])
|
||||||
|
self.assertEqual(options.stun_port, stun.DEFAULTS['stun_port'])
|
||||||
|
self.assertIsNone(options.stun_host)
|
||||||
|
|
||||||
|
def test_cli_parser_user_long_form(self):
|
||||||
|
parser = cli.make_argument_parser()
|
||||||
|
options = parser.parse_args([
|
||||||
|
'--source-port', str(self.source_port),
|
||||||
|
'--source-ip', self.source_ip,
|
||||||
|
'--stun-port', str(self.stun_port),
|
||||||
|
'--stun-host', self.stun_host,
|
||||||
|
'--debug'
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
self.assertTrue(options.debug)
|
||||||
|
self.assertEqual(options.source_ip, self.source_ip)
|
||||||
|
self.assertEqual(options.source_port, self.source_port)
|
||||||
|
self.assertEqual(options.stun_host, self.stun_host)
|
||||||
|
self.assertEqual(options.stun_port, self.stun_port)
|
||||||
|
|
||||||
|
def test_cli_parser_user_short_form(self):
|
||||||
|
parser = cli.make_argument_parser()
|
||||||
|
options = parser.parse_args([
|
||||||
|
'-p', str(self.source_port),
|
||||||
|
'-i', self.source_ip,
|
||||||
|
'-P', str(self.stun_port),
|
||||||
|
'-H', self.stun_host,
|
||||||
|
'-d'
|
||||||
|
])
|
||||||
|
|
||||||
|
self.assertTrue(options.debug)
|
||||||
|
self.assertEqual(options.source_ip, self.source_ip)
|
||||||
|
self.assertEqual(options.source_port, self.source_port)
|
||||||
|
self.assertEqual(options.stun_host, self.stun_host)
|
||||||
|
self.assertEqual(options.stun_port, self.stun_port)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
|
@ -174,10 +174,10 @@ class PostProcessor(object):
|
||||||
|
|
||||||
def Process(self):
|
def Process(self):
|
||||||
module = self.module
|
module = self.module
|
||||||
self._log("nzb name: " + str(self.nzb_name))
|
self._log("nzb name: " + self.nzb_name)
|
||||||
self._log("nzb folder: " + str(self.nzb_folder))
|
self._log("nzb folder: " + self.nzb_folder)
|
||||||
logger.fdebug(module + ' nzb name: ' + str(self.nzb_name))
|
logger.fdebug(module + ' nzb name: ' + self.nzb_name)
|
||||||
logger.fdebug(module + ' nzb folder: ' + str(self.nzb_folder))
|
logger.fdebug(module + ' nzb folder: ' + self.nzb_folder)
|
||||||
if mylar.USE_SABNZBD==0:
|
if mylar.USE_SABNZBD==0:
|
||||||
logger.fdebug(module + ' Not using SABnzbd')
|
logger.fdebug(module + ' Not using SABnzbd')
|
||||||
elif mylar.USE_SABNZBD != 0 and self.nzb_name == 'Manual Run':
|
elif mylar.USE_SABNZBD != 0 and self.nzb_name == 'Manual Run':
|
||||||
|
@ -606,7 +606,7 @@ class PostProcessor(object):
|
||||||
logger.warn(module + ' Failed to remove temporary directory - check directory and manually re-run.')
|
logger.warn(module + ' Failed to remove temporary directory - check directory and manually re-run.')
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.fdebug(module + ' Removed temporary directory : ' + str(self.nzb_folder))
|
logger.fdebug(module + ' Removed temporary directory : ' + self.nzb_folder)
|
||||||
|
|
||||||
#delete entry from nzblog table
|
#delete entry from nzblog table
|
||||||
IssArcID = 'S' + str(ml['IssueArcID'])
|
IssArcID = 'S' + str(ml['IssueArcID'])
|
||||||
|
@ -635,7 +635,7 @@ class PostProcessor(object):
|
||||||
logger.fdebug('[NZBNAME]: ' + nzbname)
|
logger.fdebug('[NZBNAME]: ' + nzbname)
|
||||||
#gotta replace & or escape it
|
#gotta replace & or escape it
|
||||||
nzbname = re.sub("\&", 'and', nzbname)
|
nzbname = re.sub("\&", 'and', nzbname)
|
||||||
nzbname = re.sub('[\,\:\?\']', '', nzbname)
|
nzbname = re.sub('[\,\:\?\'\+]', '', nzbname)
|
||||||
nzbname = re.sub('[\(\)]', ' ', nzbname)
|
nzbname = re.sub('[\(\)]', ' ', nzbname)
|
||||||
logger.fdebug('[NZBNAME] nzbname (remove chars): ' + nzbname)
|
logger.fdebug('[NZBNAME] nzbname (remove chars): ' + nzbname)
|
||||||
nzbname = re.sub('.cbr', '', nzbname).strip()
|
nzbname = re.sub('.cbr', '', nzbname).strip()
|
||||||
|
@ -830,7 +830,7 @@ class PostProcessor(object):
|
||||||
logger.debug(module + ' Failed to remove temporary directory - check directory and manually re-run.')
|
logger.debug(module + ' Failed to remove temporary directory - check directory and manually re-run.')
|
||||||
return
|
return
|
||||||
|
|
||||||
logger.debug(module + ' Removed temporary directory : ' + str(self.nzb_folder))
|
logger.debug(module + ' Removed temporary directory : ' + self.nzb_folder)
|
||||||
self._log("Removed temporary directory : " + self.nzb_folder)
|
self._log("Removed temporary directory : " + self.nzb_folder)
|
||||||
#delete entry from nzblog table
|
#delete entry from nzblog table
|
||||||
myDB.action('DELETE from nzblog WHERE issueid=?', [issueid])
|
myDB.action('DELETE from nzblog WHERE issueid=?', [issueid])
|
||||||
|
@ -1223,7 +1223,7 @@ class PostProcessor(object):
|
||||||
logger.fdebug(module + ' ext:' + ext)
|
logger.fdebug(module + ' ext:' + ext)
|
||||||
|
|
||||||
if ofilename is None:
|
if ofilename is None:
|
||||||
logger.error(module + ' Aborting PostProcessing - the filename does not exist in the location given. Make sure that ' + str(self.nzb_folder) + ' exists and is the correct location.')
|
logger.error(module + ' Aborting PostProcessing - the filename does not exist in the location given. Make sure that ' + self.nzb_folder + ' exists and is the correct location.')
|
||||||
self.valreturn.append({"self.log": self.log,
|
self.valreturn.append({"self.log": self.log,
|
||||||
"mode": 'stop'})
|
"mode": 'stop'})
|
||||||
return self.queue.put(self.valreturn)
|
return self.queue.put(self.valreturn)
|
||||||
|
@ -1302,7 +1302,7 @@ class PostProcessor(object):
|
||||||
self.valreturn.append({"self.log": self.log,
|
self.valreturn.append({"self.log": self.log,
|
||||||
"mode": 'stop'})
|
"mode": 'stop'})
|
||||||
return self.queue.put(self.valreturn)
|
return self.queue.put(self.valreturn)
|
||||||
self._log("Removed temporary directory : " + str(self.nzb_folder))
|
self._log("Removed temporary directory : " + self.nzb_folder)
|
||||||
logger.fdebug(module + ' Removed temporary directory : ' + self.nzb_folder)
|
logger.fdebug(module + ' Removed temporary directory : ' + self.nzb_folder)
|
||||||
else:
|
else:
|
||||||
#downtype = for use with updater on history table to set status to 'Post-Processed'
|
#downtype = for use with updater on history table to set status to 'Post-Processed'
|
||||||
|
|
|
@ -104,6 +104,8 @@ DONATEBUTTON = True
|
||||||
PULLNEW = None
|
PULLNEW = None
|
||||||
ALT_PULL = False
|
ALT_PULL = False
|
||||||
|
|
||||||
|
LOCAL_IP = None
|
||||||
|
EXT_IP = None
|
||||||
HTTP_PORT = None
|
HTTP_PORT = None
|
||||||
HTTP_HOST = None
|
HTTP_HOST = None
|
||||||
HTTP_USERNAME = None
|
HTTP_USERNAME = None
|
||||||
|
@ -147,6 +149,7 @@ CHOWNER = None
|
||||||
CHGROUP = None
|
CHGROUP = None
|
||||||
USENET_RETENTION = None
|
USENET_RETENTION = None
|
||||||
CREATE_FOLDERS = True
|
CREATE_FOLDERS = True
|
||||||
|
DELETE_REMOVE_DIR = False
|
||||||
|
|
||||||
ADD_COMICS = False
|
ADD_COMICS = False
|
||||||
COMIC_DIR = None
|
COMIC_DIR = None
|
||||||
|
@ -402,8 +405,8 @@ def initialize():
|
||||||
|
|
||||||
with INIT_LOCK:
|
with INIT_LOCK:
|
||||||
global __INITIALIZED__, DBCHOICE, DBUSER, DBPASS, DBNAME, COMICVINE_API, DEFAULT_CVAPI, CVAPI_COUNT, CVAPI_TIME, CVAPI_MAX, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, UPCOMING_SNATCHED, COMICSORT, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, MAX_LOGSIZE, LOGVERBOSE, OLDCONFIG_VERSION, OS_DETECT, \
|
global __INITIALIZED__, DBCHOICE, DBUSER, DBPASS, DBNAME, COMICVINE_API, DEFAULT_CVAPI, CVAPI_COUNT, CVAPI_TIME, CVAPI_MAX, FULL_PATH, PROG_DIR, VERBOSE, DAEMON, UPCOMING_SNATCHED, COMICSORT, DATA_DIR, CONFIG_FILE, CFG, CONFIG_VERSION, LOG_DIR, CACHE_DIR, MAX_LOGSIZE, LOGVERBOSE, OLDCONFIG_VERSION, OS_DETECT, \
|
||||||
queue, HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, HTTPS_FORCE_ON, HOST_RETURN, API_ENABLED, API_KEY, DOWNLOAD_APIKEY, LAUNCH_BROWSER, GIT_PATH, SAFESTART, AUTO_UPDATE, \
|
queue, LOCAL_IP, EXT_IP, HTTP_PORT, HTTP_HOST, HTTP_USERNAME, HTTP_PASSWORD, HTTP_ROOT, ENABLE_HTTPS, HTTPS_CERT, HTTPS_KEY, HTTPS_FORCE_ON, HOST_RETURN, API_ENABLED, API_KEY, DOWNLOAD_APIKEY, LAUNCH_BROWSER, GIT_PATH, SAFESTART, AUTO_UPDATE, \
|
||||||
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, GIT_USER, GIT_BRANCH, USER_AGENT, DESTINATION_DIR, MULTIPLE_DEST_DIRS, CREATE_FOLDERS, \
|
CURRENT_VERSION, LATEST_VERSION, CHECK_GITHUB, CHECK_GITHUB_ON_STARTUP, CHECK_GITHUB_INTERVAL, GIT_USER, GIT_BRANCH, USER_AGENT, DESTINATION_DIR, MULTIPLE_DEST_DIRS, CREATE_FOLDERS, DELETE_REMOVE_DIR, \
|
||||||
DOWNLOAD_DIR, USENET_RETENTION, SEARCH_INTERVAL, NZB_STARTUP_SEARCH, INTERFACE, DUPECONSTRAINT, AUTOWANT_ALL, AUTOWANT_UPCOMING, ZERO_LEVEL, ZERO_LEVEL_N, COMIC_COVER_LOCAL, HIGHCOUNT, \
|
DOWNLOAD_DIR, USENET_RETENTION, SEARCH_INTERVAL, NZB_STARTUP_SEARCH, INTERFACE, DUPECONSTRAINT, AUTOWANT_ALL, AUTOWANT_UPCOMING, ZERO_LEVEL, ZERO_LEVEL_N, COMIC_COVER_LOCAL, HIGHCOUNT, \
|
||||||
LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, NZB_DOWNLOADER, USE_SABNZBD, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_PRIORITY, SAB_TO_MYLAR, SAB_DIRECTORY, USE_BLACKHOLE, BLACKHOLE_DIR, ADD_COMICS, COMIC_DIR, IMP_MOVE, IMP_RENAME, IMP_METADATA, \
|
LIBRARYSCAN, LIBRARYSCAN_INTERVAL, DOWNLOAD_SCAN_INTERVAL, NZB_DOWNLOADER, USE_SABNZBD, SAB_HOST, SAB_USERNAME, SAB_PASSWORD, SAB_APIKEY, SAB_CATEGORY, SAB_PRIORITY, SAB_TO_MYLAR, SAB_DIRECTORY, USE_BLACKHOLE, BLACKHOLE_DIR, ADD_COMICS, COMIC_DIR, IMP_MOVE, IMP_RENAME, IMP_METADATA, \
|
||||||
USE_NZBGET, NZBGET_HOST, NZBGET_PORT, NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, NZBGET_DIRECTORY, NZBSU, NZBSU_UID, NZBSU_APIKEY, DOGNZB, DOGNZB_APIKEY, \
|
USE_NZBGET, NZBGET_HOST, NZBGET_PORT, NZBGET_USERNAME, NZBGET_PASSWORD, NZBGET_CATEGORY, NZBGET_PRIORITY, NZBGET_DIRECTORY, NZBSU, NZBSU_UID, NZBSU_APIKEY, DOGNZB, DOGNZB_APIKEY, \
|
||||||
|
@ -491,6 +494,7 @@ def initialize():
|
||||||
DESTINATION_DIR = check_setting_str(CFG, 'General', 'destination_dir', '')
|
DESTINATION_DIR = check_setting_str(CFG, 'General', 'destination_dir', '')
|
||||||
MULTIPLE_DEST_DIRS = check_setting_str(CFG, 'General', 'multiple_dest_dirs', '')
|
MULTIPLE_DEST_DIRS = check_setting_str(CFG, 'General', 'multiple_dest_dirs', '')
|
||||||
CREATE_FOLDERS = bool(check_setting_int(CFG, 'General', 'create_folders', 1))
|
CREATE_FOLDERS = bool(check_setting_int(CFG, 'General', 'create_folders', 1))
|
||||||
|
DELETE_REMOVE_DIR = bool(check_setting_int(CFG, 'General', 'delete_remove_dir', 0))
|
||||||
CHMOD_DIR = check_setting_str(CFG, 'General', 'chmod_dir', '0777')
|
CHMOD_DIR = check_setting_str(CFG, 'General', 'chmod_dir', '0777')
|
||||||
CHMOD_FILE = check_setting_str(CFG, 'General', 'chmod_file', '0660')
|
CHMOD_FILE = check_setting_str(CFG, 'General', 'chmod_file', '0660')
|
||||||
CHOWNER = check_setting_str(CFG, 'General', 'chowner', '')
|
CHOWNER = check_setting_str(CFG, 'General', 'chowner', '')
|
||||||
|
@ -923,6 +927,19 @@ def initialize():
|
||||||
# Start the logger, silence console logging if we need to
|
# Start the logger, silence console logging if we need to
|
||||||
logger.initLogger(verbose=VERBOSE) #logger.mylar_log.initLogger(verbose=VERBOSE)
|
logger.initLogger(verbose=VERBOSE) #logger.mylar_log.initLogger(verbose=VERBOSE)
|
||||||
|
|
||||||
|
#try to get the local IP using socket. Get this on every startup so it's at least current for existing session.
|
||||||
|
import socket
|
||||||
|
try:
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
s.connect(('8.8.8.8', 80))
|
||||||
|
LOCAL_IP = s.getsockname()[0]
|
||||||
|
s.close()
|
||||||
|
logger.info('Successfully discovered local IP and locking it in as : ' + str(LOCAL_IP))
|
||||||
|
except:
|
||||||
|
logger.warn('Unable to determine local IP - this might cause problems when downloading (maybe use host_return in the config.ini)')
|
||||||
|
LOCAL_IP = HTTP_HOST
|
||||||
|
|
||||||
|
|
||||||
# verbatim back the logger being used since it's now started.
|
# verbatim back the logger being used since it's now started.
|
||||||
if LOGTYPE == 'clog':
|
if LOGTYPE == 'clog':
|
||||||
logprog = 'Concurrent Rotational Log Handler'
|
logprog = 'Concurrent Rotational Log Handler'
|
||||||
|
@ -1024,11 +1041,6 @@ def initialize():
|
||||||
logger.info('Remapping the sorting to allow for new additions.')
|
logger.info('Remapping the sorting to allow for new additions.')
|
||||||
COMICSORT = helpers.ComicSort(sequence='startup')
|
COMICSORT = helpers.ComicSort(sequence='startup')
|
||||||
|
|
||||||
#start the db write only thread here.
|
|
||||||
#this is a thread that continually runs in the background as the ONLY thread that can write to the db.
|
|
||||||
#logger.info('Starting Write-Only thread.')
|
|
||||||
#db.WriteOnly()
|
|
||||||
|
|
||||||
#initialize the scheduler threads here.
|
#initialize the scheduler threads here.
|
||||||
dbUpdateScheduler = scheduler.Scheduler(action=dbupdater.dbUpdate(),
|
dbUpdateScheduler = scheduler.Scheduler(action=dbupdater.dbUpdate(),
|
||||||
cycleTime=datetime.timedelta(hours=48),
|
cycleTime=datetime.timedelta(hours=48),
|
||||||
|
@ -1191,6 +1203,7 @@ def config_write():
|
||||||
new_config['General']['destination_dir'] = DESTINATION_DIR
|
new_config['General']['destination_dir'] = DESTINATION_DIR
|
||||||
new_config['General']['multiple_dest_dirs'] = MULTIPLE_DEST_DIRS
|
new_config['General']['multiple_dest_dirs'] = MULTIPLE_DEST_DIRS
|
||||||
new_config['General']['create_folders'] = int(CREATE_FOLDERS)
|
new_config['General']['create_folders'] = int(CREATE_FOLDERS)
|
||||||
|
new_config['General']['delete_remove_dir'] = int(DELETE_REMOVE_DIR)
|
||||||
new_config['General']['chmod_dir'] = CHMOD_DIR
|
new_config['General']['chmod_dir'] = CHMOD_DIR
|
||||||
new_config['General']['chmod_file'] = CHMOD_FILE
|
new_config['General']['chmod_file'] = CHMOD_FILE
|
||||||
new_config['General']['chowner'] = CHOWNER
|
new_config['General']['chowner'] = CHOWNER
|
||||||
|
|
|
@ -1239,7 +1239,7 @@ def setperms(path, dir=False):
|
||||||
os.chown(os.path.join(root, momo), chowner, chgroup)
|
os.chown(os.path.join(root, momo), chowner, chgroup)
|
||||||
os.chmod(os.path.join(root, momo), permission)
|
os.chmod(os.path.join(root, momo), permission)
|
||||||
|
|
||||||
logger.info('Successfully changed ownership and permissions [' + str(mylar.CHOWNER) + ':' + str(mylar.CHGROUP) + '] / [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
|
logger.fdebug('Successfully changed ownership and permissions [' + str(mylar.CHOWNER) + ':' + str(mylar.CHGROUP) + '] / [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
|
@ -1250,7 +1250,7 @@ def setperms(path, dir=False):
|
||||||
permission = int(mylar.CHMOD_FILE, 8)
|
permission = int(mylar.CHMOD_FILE, 8)
|
||||||
os.chmod(os.path.join(root, momo), permission)
|
os.chmod(os.path.join(root, momo), permission)
|
||||||
|
|
||||||
logger.info('Successfully changed permissions [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
|
logger.fdebug('Successfully changed permissions [' + str(mylar.CHMOD_DIR) + ' / ' + str(mylar.CHMOD_FILE) + ']')
|
||||||
|
|
||||||
except OSError:
|
except OSError:
|
||||||
logger.error('Could not change permissions : ' + path + '. Exiting...')
|
logger.error('Could not change permissions : ' + path + '. Exiting...')
|
||||||
|
|
|
@ -26,6 +26,7 @@ import subprocess
|
||||||
import time
|
import time
|
||||||
import lib.simplejson as simplejson
|
import lib.simplejson as simplejson
|
||||||
import json
|
import json
|
||||||
|
import lib.requests as requests
|
||||||
|
|
||||||
# This was obviously all taken from headphones with great appreciation :)
|
# This was obviously all taken from headphones with great appreciation :)
|
||||||
|
|
||||||
|
@ -80,12 +81,7 @@ class PROWL:
|
||||||
#For uniformity reasons not removed
|
#For uniformity reasons not removed
|
||||||
return
|
return
|
||||||
|
|
||||||
def test(self, keys, priority):
|
def test_notify(self):
|
||||||
|
|
||||||
self.enabled = True
|
|
||||||
self.keys = keys
|
|
||||||
self.priority = priority
|
|
||||||
|
|
||||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||||
|
|
||||||
class NMA:
|
class NMA:
|
||||||
|
@ -137,6 +133,9 @@ class NMA:
|
||||||
if not request:
|
if not request:
|
||||||
logger.warn(module + ' Error sending notification request to NotifyMyAndroid')
|
logger.warn(module + ' Error sending notification request to NotifyMyAndroid')
|
||||||
|
|
||||||
|
def test_notify(self):
|
||||||
|
self.notify(prline='Test Message',prline2='ZOMG Lazors Pewpewpew!')
|
||||||
|
|
||||||
# 2013-04-01 Added Pushover.net notifications, based on copy of Prowl class above.
|
# 2013-04-01 Added Pushover.net notifications, based on copy of Prowl class above.
|
||||||
# No extra care has been put into API friendliness at the moment (read: https://pushover.net/api#friendly)
|
# No extra care has been put into API friendliness at the moment (read: https://pushover.net/api#friendly)
|
||||||
class PUSHOVER:
|
class PUSHOVER:
|
||||||
|
@ -198,13 +197,7 @@ class PUSHOVER:
|
||||||
logger.info(module + ' Pushover notification failed.')
|
logger.info(module + ' Pushover notification failed.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def test(self, apikey, userkey, priority):
|
def test_notify(self):
|
||||||
|
|
||||||
self.enabled = True
|
|
||||||
self.apikey = apikey
|
|
||||||
self.userkey = userkey
|
|
||||||
self.priority = priority
|
|
||||||
|
|
||||||
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
self.notify('ZOMG Lazors Pewpewpew!', 'Test Message')
|
||||||
|
|
||||||
|
|
||||||
|
@ -284,11 +277,20 @@ class BOXCAR:
|
||||||
self._sendBoxcar(message, title, module)
|
self._sendBoxcar(message, title, module)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def test_notify(self):
|
||||||
|
self.notify(prline='Test Message',prline2='ZOMG Lazors Pewpewpew!')
|
||||||
|
|
||||||
class PUSHBULLET:
|
class PUSHBULLET:
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.PUSH_URL = "https://api.pushbullet.com/v2/pushes"
|
||||||
self.apikey = mylar.PUSHBULLET_APIKEY
|
self.apikey = mylar.PUSHBULLET_APIKEY
|
||||||
self.deviceid = mylar.PUSHBULLET_DEVICEID
|
self.deviceid = mylar.PUSHBULLET_DEVICEID
|
||||||
|
self._json_header = {'Content-Type': 'application/json'}
|
||||||
|
|
||||||
|
self._session = requests.Session()
|
||||||
|
self._session.auth = (self.apikey, "")
|
||||||
|
self._session.headers.update(self._json_header)
|
||||||
|
|
||||||
def get_devices(self, api):
|
def get_devices(self, api):
|
||||||
return self.notify(method="GET")
|
return self.notify(method="GET")
|
||||||
|
@ -299,19 +301,20 @@ class PUSHBULLET:
|
||||||
if module is None:
|
if module is None:
|
||||||
module = ''
|
module = ''
|
||||||
module += '[NOTIFIER]'
|
module += '[NOTIFIER]'
|
||||||
|
|
||||||
|
# http_handler = HTTPSConnection("api.pushbullet.com")
|
||||||
|
|
||||||
http_handler = HTTPSConnection("api.pushbullet.com")
|
# if method == 'GET':
|
||||||
|
# uri = '/v2/devices'
|
||||||
|
# else:
|
||||||
|
# method = 'POST'
|
||||||
|
# uri = '/v2/pushes'
|
||||||
|
|
||||||
|
# authString = base64.b64encode(self.apikey + ":")
|
||||||
|
|
||||||
if method == 'GET':
|
if method == 'GET':
|
||||||
uri = '/v2/devices'
|
pass
|
||||||
else:
|
# http_handler.request(method, uri, None, headers={'Authorization': 'Basic %s:' % authString})
|
||||||
method = 'POST'
|
|
||||||
uri = '/v2/pushes'
|
|
||||||
|
|
||||||
authString = base64.b64encode(self.apikey + ":")
|
|
||||||
|
|
||||||
if method == 'GET':
|
|
||||||
http_handler.request(method, uri, None, headers={'Authorization': 'Basic %s:' % authString})
|
|
||||||
else:
|
else:
|
||||||
if snatched:
|
if snatched:
|
||||||
if snatched[-1] == '.': snatched = snatched[:-1]
|
if snatched[-1] == '.': snatched = snatched[:-1]
|
||||||
|
@ -325,37 +328,35 @@ class PUSHBULLET:
|
||||||
'title': event.encode('utf-8'), #"mylar",
|
'title': event.encode('utf-8'), #"mylar",
|
||||||
'body': message.encode('utf-8')}
|
'body': message.encode('utf-8')}
|
||||||
|
|
||||||
http_handler.request("POST",
|
r = self._session.post(self.PUSH_URL, data=json.dumps(data))
|
||||||
"/v2/pushes",
|
|
||||||
headers = {'Content-type': "application/json",
|
|
||||||
'Authorization': 'Basic %s' % base64.b64encode(mylar.PUSHBULLET_APIKEY + ":")},
|
|
||||||
body = json.dumps(data))
|
|
||||||
|
|
||||||
response = http_handler.getresponse()
|
|
||||||
request_body = response.read()
|
|
||||||
request_status = response.status
|
|
||||||
#logger.fdebug(u"PushBullet response status: %r" % request_status)
|
|
||||||
#logger.fdebug(u"PushBullet response headers: %r" % response.getheaders())
|
|
||||||
#logger.fdebug(u"PushBullet response body: %r" % response.read())
|
|
||||||
|
|
||||||
if request_status == 200:
|
# http_handler.request("POST",
|
||||||
|
# "/v2/pushes",
|
||||||
|
# headers = {'Content-type': "application/json",
|
||||||
|
# 'Authorization': 'Basic %s' % base64.b64encode(mylar.PUSHBULLET_APIKEY + ":")},
|
||||||
|
# body = json.dumps(data))
|
||||||
|
#
|
||||||
|
# response = http_handler.getresponse()
|
||||||
|
# request_body = response.read()
|
||||||
|
# request_status = response.status
|
||||||
|
# #logger.fdebug(u"PushBullet response status: %r" % request_status)
|
||||||
|
# #logger.fdebug(u"PushBullet response headers: %r" % response.getheaders())
|
||||||
|
# #logger.fdebug(u"PushBullet response body: %r" % response.read())
|
||||||
|
|
||||||
|
if r.status_code == 200:
|
||||||
if method == 'GET':
|
if method == 'GET':
|
||||||
return request_body
|
return r.json()
|
||||||
else:
|
else:
|
||||||
logger.info(module + ' PushBullet notifications sent.')
|
logger.info(module + ' PushBullet notifications sent.')
|
||||||
return True
|
return True
|
||||||
elif request_status >= 400 and request_status < 500:
|
elif r.status_code >= 400 and r.status_code < 500:
|
||||||
logger.error(module + ' PushBullet request failed: %s' % response.reason)
|
logger.error(module + ' PushBullet request failed: %s' % r.content)
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
logger.error(module + ' PushBullet notification failed serverside.')
|
logger.error(module + ' PushBullet notification failed serverside.')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def test(self, apikey, deviceid):
|
def test_notify(self):
|
||||||
|
self.notify(prline='Test Message', prline2='Release the Ninjas!')
|
||||||
self.enabled = True
|
|
||||||
self.apikey = apikey
|
|
||||||
self.deviceid = deviceid
|
|
||||||
|
|
||||||
self.notify('Main Screen Activate', 'Test Message')
|
|
||||||
|
|
||||||
|
|
376
mylar/search.py
376
mylar/search.py
|
@ -16,7 +16,7 @@
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
|
|
||||||
import mylar
|
import mylar
|
||||||
from mylar import logger, db, updater, helpers, parseit, findcomicfeed, notifiers, rsscheck, Failed
|
from mylar import logger, db, updater, helpers, parseit, findcomicfeed, notifiers, rsscheck, Failed, filechecker
|
||||||
|
|
||||||
import lib.feedparser as feedparser
|
import lib.feedparser as feedparser
|
||||||
import urllib
|
import urllib
|
||||||
|
@ -474,6 +474,10 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
|
||||||
if nzbprov == '':
|
if nzbprov == '':
|
||||||
bb = "no results"
|
bb = "no results"
|
||||||
elif nzbprov == '32P':
|
elif nzbprov == '32P':
|
||||||
|
#cmname = re.sub("%20", " ", str(comsrc))
|
||||||
|
#bb = rsscheck.torrents(pickfeed='4', seriesname=cmname, issue=mod_isssearch)
|
||||||
|
#rss = "no"
|
||||||
|
#logger.info('bb returned: ' + str(bb))
|
||||||
bb = "no results"
|
bb = "no results"
|
||||||
elif nzbprov == 'KAT':
|
elif nzbprov == 'KAT':
|
||||||
cmname = re.sub("%20", " ", str(comsrc))
|
cmname = re.sub("%20", " ", str(comsrc))
|
||||||
|
@ -752,44 +756,64 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
|
||||||
ComVersChk = 0
|
ComVersChk = 0
|
||||||
|
|
||||||
ctchk = cleantitle.split()
|
ctchk = cleantitle.split()
|
||||||
|
volfound = False
|
||||||
|
vol_label = None
|
||||||
|
fndcomicversion = None
|
||||||
for ct in ctchk:
|
for ct in ctchk:
|
||||||
if ct.lower().startswith('v') and ct[1:].isdigit():
|
if any([ct.lower().startswith('v') and ct[1:].isdigit(), ct.lower()[:3] == 'vol', volfound == True]):
|
||||||
logger.fdebug("possible versioning..checking")
|
if volfound == True:
|
||||||
#we hit a versioning # - account for it
|
logger.fdebug('Split Volume label detected - ie. Vol 4. Attempting to adust.')
|
||||||
if ct[1:].isdigit():
|
if ct.isdigit():
|
||||||
if len(ct[1:]) == 4: #v2013
|
vol_label = vol_label + ' ' + str(ct)
|
||||||
logger.fdebug("Version detected as " + str(ct))
|
ct = 'v' + str(ct)
|
||||||
vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
|
volfound == False
|
||||||
#cleantitle = re.sub(ct, "(" + str(vers4year) + ")", cleantitle)
|
cleantitle = re.sub(vol_label, ct, cleantitle).strip()
|
||||||
#logger.fdebug("volumized cleantitle : " + cleantitle)
|
tmpsplit = ct
|
||||||
versionfound = "yes"
|
if tmpsplit.lower().startswith('vol'):
|
||||||
break
|
logger.fdebug('volume detected - stripping and re-analzying for volume label.')
|
||||||
|
if '.' in tmpsplit:
|
||||||
|
tmpsplit = re.sub('.', '', tmpsplit).strip()
|
||||||
|
tmpsplit = re.sub('vol', '', tmpsplit.lower()).strip()
|
||||||
|
#if vol label set as 'Vol 4' it will obliterate the Vol, but pass over the '4' - set
|
||||||
|
#volfound to True so that it can loop back around.
|
||||||
|
if not tmpsplit.isdigit():
|
||||||
|
vol_label = ct #store the wording of how the Vol is defined so we can skip it later on.
|
||||||
|
volfound = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(tmpsplit[1:]) == 4 and tmpsplit[1:].isdigit(): #v2013
|
||||||
|
logger.fdebug("[Vxxxx] Version detected as " + str(tmpsplit))
|
||||||
|
vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
|
||||||
|
fndcomicversion = str(tmpsplit)
|
||||||
|
elif len(tmpsplit[1:]) == 1 and tmpsplit[1:].isdigit(): #v2
|
||||||
|
logger.fdebug("[Vx] Version detected as " + str(tmpsplit))
|
||||||
|
vers4vol = str(tmpsplit)
|
||||||
|
fndcomicversion = str(tmpsplit)
|
||||||
|
elif tmpsplit[1:].isdigit() and len(tmpsplit) < 4:
|
||||||
|
logger.fdebug('[Vxxx] Version detected as ' +str(tmpsplit))
|
||||||
|
vers4vol = str(tmpsplit)
|
||||||
|
fndcomicversion = str(tmpsplit)
|
||||||
|
elif tmpsplit.isdigit() and len(tmpsplit) <=4:
|
||||||
|
# this stuff is necessary for 32P volume manipulation
|
||||||
|
if len(tmpsplit) == 4:
|
||||||
|
vers4year = "yes"
|
||||||
|
fndcomicversion = str(tmpsplit)
|
||||||
|
elif len(tmpsplit) == 1:
|
||||||
|
vers4vol = str(tmpsplit)
|
||||||
|
fndcomicversion = str(tmpsplit)
|
||||||
|
elif len(tmpsplit) < 4:
|
||||||
|
vers4vol = str(tmpsplit)
|
||||||
|
fndcomicversion = str(tmpsplit)
|
||||||
else:
|
else:
|
||||||
if len(ct) < 4:
|
logger.fdebug("error - unknown length for : " + str(tmpsplit))
|
||||||
logger.fdebug("Version detected as " + str(ct))
|
continue
|
||||||
vers4vol = str(ct)
|
|
||||||
versionfound = "yes"
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.fdebug("false version detection..ignoring.")
|
|
||||||
|
|
||||||
elif ct.lower()[:3] == 'vol':
|
|
||||||
#if in format vol.2013/vol2013/vol01/vol.1, etc
|
|
||||||
ct = re.sub('vol', '', ct.lower())
|
|
||||||
if '.' in ct: re.sub('.', '', ct).strip()
|
|
||||||
if ct.lower()[4:].isdigit():
|
|
||||||
logger.fdebug('volume indicator detected as version #:' + str(ct))
|
|
||||||
vers4year = "yes"
|
|
||||||
versionfound = "yes"
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
vers4vol = ct
|
logger.fdebug("error - unknown length for : " + str(tmpsplit))
|
||||||
versionfound = "yes"
|
continue
|
||||||
logger.fdebug('volume indicator detected as version #:' + str(vers4vol))
|
|
||||||
break
|
|
||||||
|
|
||||||
logger.fdebug("false version detection..ignoring.")
|
|
||||||
|
|
||||||
|
if fndcomicversion:
|
||||||
|
versionfound = "yes"
|
||||||
|
break
|
||||||
|
|
||||||
if len(re.findall('[^()]+', cleantitle)) == 1 or 'cover only' in cleantitle.lower():
|
if len(re.findall('[^()]+', cleantitle)) == 1 or 'cover only' in cleantitle.lower():
|
||||||
#some sites don't have (2013) or whatever..just v2 / v2013. Let's adjust:
|
#some sites don't have (2013) or whatever..just v2 / v2013. Let's adjust:
|
||||||
|
@ -1064,112 +1088,113 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
|
||||||
#splitst = splitst - 1
|
#splitst = splitst - 1
|
||||||
|
|
||||||
if versionfound == "yes":
|
if versionfound == "yes":
|
||||||
volfound = False
|
# volfound = False
|
||||||
for tstsplit in splitit:
|
# vol_label = None
|
||||||
logger.fdebug('comparing ' + str(tstsplit))
|
# for tstsplit in splitit:
|
||||||
if volfound == True:
|
# logger.fdebug('comparing ' + str(tstsplit))
|
||||||
logger.fdebug('Split Volume label detected - ie. Vol 4. Attempting to adust.')
|
# if volfound == True:
|
||||||
if tstsplit.isdigit():
|
# logger.fdebug('Split Volume label detected - ie. Vol 4. Attempting to adust.')
|
||||||
tstsplit = 'v' + str(tstsplit)
|
# if tstsplit.isdigit():
|
||||||
volfound == False
|
# vol_label = vol_label + ' ' + str(tstsplit)
|
||||||
if tstsplit.lower().startswith('v'): #tstsplit[1:].isdigit():
|
# tstsplit = 'v' + str(tstsplit)
|
||||||
logger.fdebug("this has a version #...let's adjust")
|
# volfound == False
|
||||||
tmpsplit = tstsplit
|
# if tstsplit.lower().startswith('v'): #tstsplit[1:].isdigit():
|
||||||
if tmpsplit.lower().startswith('vol'):
|
# logger.fdebug("this has a version #...let's adjust")
|
||||||
logger.fdebug('volume detected - stripping and re-analzying for volume label.')
|
# tmpsplit = tstsplit
|
||||||
if '.' in tmpsplit:
|
# if tmpsplit.lower().startswith('vol'):
|
||||||
tmpsplit = re.sub('.', '', tmpsplit).strip()
|
# logger.fdebug('volume detected - stripping and re-analzying for volume label.')
|
||||||
tmpsplit = re.sub('vol', '', tmpsplit.lower()).strip()
|
# if '.' in tmpsplit:
|
||||||
#if vol label set as 'Vol 4' it will obliterate the Vol, but pass over the '4' - set
|
# tmpsplit = re.sub('.', '', tmpsplit).strip()
|
||||||
#volfound to True so that it can loop back around.
|
# tmpsplit = re.sub('vol', '', tmpsplit.lower()).strip()
|
||||||
if not tmpsplit.isdigit():
|
# #if vol label set as 'Vol 4' it will obliterate the Vol, but pass over the '4' - set
|
||||||
volfound = True
|
# #volfound to True so that it can loop back around.
|
||||||
continue
|
# if not tmpsplit.isdigit():
|
||||||
if len(tmpsplit[1:]) == 4 and tmpsplit[1:].isdigit(): #v2013
|
# vol_label = tstsplit #store the wording of how the Vol is defined so we can skip it later on.
|
||||||
logger.fdebug("[Vxxxx] Version detected as " + str(tmpsplit))
|
# volfound = True
|
||||||
vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
|
# continue
|
||||||
elif len(tmpsplit[1:]) == 1 and tmpsplit[1:].isdigit(): #v2
|
# if len(tmpsplit[1:]) == 4 and tmpsplit[1:].isdigit(): #v2013
|
||||||
logger.fdebug("[Vx] Version detected as " + str(tmpsplit))
|
# logger.fdebug("[Vxxxx] Version detected as " + str(tmpsplit))
|
||||||
vers4vol = str(tmpsplit)
|
# vers4year = "yes" #re.sub("[^0-9]", " ", str(ct)) #remove the v
|
||||||
elif tmpsplit[1:].isdigit() and len(tmpsplit) < 4:
|
# elif len(tmpsplit[1:]) == 1 and tmpsplit[1:].isdigit(): #v2
|
||||||
logger.fdebug('[Vxxx] Version detected as ' +str(tmpsplit))
|
# logger.fdebug("[Vx] Version detected as " + str(tmpsplit))
|
||||||
vers4vol = str(tmpsplit)
|
# vers4vol = str(tmpsplit)
|
||||||
elif tmpsplit.isdigit() and len(tmpsplit) <=4:
|
# elif tmpsplit[1:].isdigit() and len(tmpsplit) < 4:
|
||||||
# this stuff is necessary for 32P volume manipulation
|
# logger.fdebug('[Vxxx] Version detected as ' +str(tmpsplit))
|
||||||
if len(tmpsplit) == 4:
|
# vers4vol = str(tmpsplit)
|
||||||
vers4year = "yes"
|
# elif tmpsplit.isdigit() and len(tmpsplit) <=4:
|
||||||
elif len(tmpsplit) == 1:
|
# # this stuff is necessary for 32P volume manipulation
|
||||||
vers4vol = str(tmpsplit)
|
# if len(tmpsplit) == 4:
|
||||||
elif len(tmpsplit) < 4:
|
# vers4year = "yes"
|
||||||
vers4vol = str(tmpsplit)
|
# elif len(tmpsplit) == 1:
|
||||||
else:
|
# vers4vol = str(tmpsplit)
|
||||||
logger.fdebug("error - unknown length for : " + str(tmpsplit))
|
# elif len(tmpsplit) < 4:
|
||||||
continue
|
# vers4vol = str(tmpsplit)
|
||||||
else:
|
# else:
|
||||||
logger.fdebug("error - unknown length for : " + str(tmpsplit))
|
# logger.fdebug("error - unknown length for : " + str(tmpsplit))
|
||||||
continue
|
# continue
|
||||||
|
# else:
|
||||||
|
# logger.fdebug("error - unknown length for : " + str(tmpsplit))
|
||||||
|
# continue
|
||||||
|
|
||||||
logger.fdebug("volume detection commencing - adjusting length.")
|
logger.fdebug("volume detection commencing - adjusting length.")
|
||||||
|
|
||||||
logger.fdebug("watch comicversion is " + str(ComicVersion))
|
logger.fdebug("watch comicversion is " + str(ComicVersion))
|
||||||
fndcomicversion = str(tstsplit)
|
logger.fdebug("version found: " + str(fndcomicversion))
|
||||||
logger.fdebug("version found: " + str(fndcomicversion))
|
logger.fdebug("vers4year: " + str(vers4year))
|
||||||
logger.fdebug("vers4year: " + str(vers4year))
|
logger.fdebug("vers4vol: " + str(vers4vol))
|
||||||
logger.fdebug("vers4vol: " + str(vers4vol))
|
|
||||||
|
|
||||||
if vers4year is not "no" or vers4vol is not "no":
|
if vers4year is not "no" or vers4vol is not "no":
|
||||||
|
|
||||||
#if the volume is None, assume it's a V1 to increase % hits
|
#if the volume is None, assume it's a V1 to increase % hits
|
||||||
if ComVersChk == 0:
|
if ComVersChk == 0:
|
||||||
D_ComicVersion = 1
|
D_ComicVersion = 1
|
||||||
else:
|
else:
|
||||||
D_ComicVersion = ComVersChk
|
D_ComicVersion = ComVersChk
|
||||||
|
|
||||||
#if this is a one-off, SeriesYear will be None and cause errors.
|
#if this is a one-off, SeriesYear will be None and cause errors.
|
||||||
if SeriesYear is None:
|
if SeriesYear is None:
|
||||||
S_ComicVersion = 0
|
S_ComicVersion = 0
|
||||||
else:
|
else:
|
||||||
S_ComicVersion = str(SeriesYear)
|
S_ComicVersion = str(SeriesYear)
|
||||||
|
|
||||||
F_ComicVersion = re.sub("[^0-9]", "", fndcomicversion)
|
F_ComicVersion = re.sub("[^0-9]", "", fndcomicversion)
|
||||||
#if the found volume is a vol.0, up it to vol.1 (since there is no V0)
|
#if the found volume is a vol.0, up it to vol.1 (since there is no V0)
|
||||||
if F_ComicVersion == '0':
|
if F_ComicVersion == '0':
|
||||||
#need to convert dates to just be yyyy-mm-dd and do comparison, time operator in the below calc as well which probably throws off some accuracy.
|
#need to convert dates to just be yyyy-mm-dd and do comparison, time operator in the below calc as well which probably throws off some accuracy.
|
||||||
if postdate_int >= issuedate_int and nzbprov == '32P':
|
if postdate_int >= issuedate_int and nzbprov == '32P':
|
||||||
logger.fdebug('32P torrent discovery. Store date (' + str(stdate) + ') is before posting date (' + str(pubdate) + '), forcing volume label to be the same as series label (0-Day Enforcement): v' + str(F_ComicVersion) + ' --> v' + str(S_ComicVersion))
|
logger.fdebug('32P torrent discovery. Store date (' + str(stdate) + ') is before posting date (' + str(pubdate) + '), forcing volume label to be the same as series label (0-Day Enforcement): v' + str(F_ComicVersion) + ' --> v' + str(S_ComicVersion))
|
||||||
F_ComicVersion = D_ComicVersion
|
F_ComicVersion = D_ComicVersion
|
||||||
else:
|
else:
|
||||||
F_ComicVersion = '1'
|
F_ComicVersion = '1'
|
||||||
|
|
||||||
logger.fdebug("FCVersion: " + str(F_ComicVersion))
|
logger.fdebug("FCVersion: " + str(F_ComicVersion))
|
||||||
logger.fdebug("DCVersion: " + str(D_ComicVersion))
|
logger.fdebug("DCVersion: " + str(D_ComicVersion))
|
||||||
logger.fdebug("SCVersion: " + str(S_ComicVersion))
|
logger.fdebug("SCVersion: " + str(S_ComicVersion))
|
||||||
|
|
||||||
#here's the catch, sometimes annuals get posted as the Pub Year
|
#here's the catch, sometimes annuals get posted as the Pub Year
|
||||||
# instead of the Series they belong to (V2012 vs V2013)
|
# instead of the Series they belong to (V2012 vs V2013)
|
||||||
if annualize == "true" and int(ComicYear) == int(F_ComicVersion):
|
if annualize == "true" and int(ComicYear) == int(F_ComicVersion):
|
||||||
logger.fdebug("We matched on versions for annuals " + str(fndcomicversion))
|
logger.fdebug("We matched on versions for annuals " + str(fndcomicversion))
|
||||||
scount+=1
|
scount+=1
|
||||||
cvers = "true"
|
cvers = "true"
|
||||||
|
|
||||||
elif int(F_ComicVersion) == int(D_ComicVersion) or int(F_ComicVersion) == int(S_ComicVersion):
|
elif int(F_ComicVersion) == int(D_ComicVersion) or int(F_ComicVersion) == int(S_ComicVersion):
|
||||||
logger.fdebug("We matched on versions..." + str(fndcomicversion))
|
logger.fdebug("We matched on versions..." + str(fndcomicversion))
|
||||||
scount+=1
|
scount+=1
|
||||||
cvers = "true"
|
cvers = "true"
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.fdebug("Versions wrong. Ignoring possible match.")
|
logger.fdebug("Versions wrong. Ignoring possible match.")
|
||||||
scount = 0
|
scount = 0
|
||||||
cvers = "false"
|
cvers = "false"
|
||||||
|
|
||||||
if cvers == "true":
|
if cvers == "true":
|
||||||
#since we matched on versions, let's remove it entirely to improve matching.
|
#since we matched on versions, let's remove it entirely to improve matching.
|
||||||
logger.fdebug('Removing versioning from nzb filename to improve matching algorithims.')
|
logger.fdebug('Removing versioning [' + fndcomicversion + '] from nzb filename to improve matching algorithims.')
|
||||||
cissb4vers = re.sub(tstsplit, "", comic_iss_b4).strip()
|
cissb4vers = re.sub(fndcomicversion, "", comic_iss_b4).strip()
|
||||||
logger.fdebug('New b4split : ' + str(cissb4vers))
|
logger.fdebug('New b4split : ' + str(cissb4vers))
|
||||||
splitit = cissb4vers.split(None)
|
splitit = cissb4vers.split(None)
|
||||||
splitst -=1
|
splitst -=1
|
||||||
break
|
|
||||||
|
|
||||||
#do an initial check
|
#do an initial check
|
||||||
initialchk = 'ok'
|
initialchk = 'ok'
|
||||||
|
@ -1623,7 +1648,7 @@ def nzbname_create(provider, title=None, info=None):
|
||||||
logger.fdebug('[SEARCHER] entry[title]: ' + title)
|
logger.fdebug('[SEARCHER] entry[title]: ' + title)
|
||||||
#gotta replace & or escape it
|
#gotta replace & or escape it
|
||||||
nzbname = re.sub("\&", 'and', title)
|
nzbname = re.sub("\&", 'and', title)
|
||||||
nzbname = re.sub('[\,\:\?\']', '', nzbname)
|
nzbname = re.sub('[\,\:\?\'\+]', '', nzbname)
|
||||||
nzbname = re.sub('[\(\)]', ' ', nzbname)
|
nzbname = re.sub('[\(\)]', ' ', nzbname)
|
||||||
logger.fdebug('[SEARCHER] nzbname (remove chars): ' + nzbname)
|
logger.fdebug('[SEARCHER] nzbname (remove chars): ' + nzbname)
|
||||||
nzbname = re.sub('.cbr', '', nzbname).strip()
|
nzbname = re.sub('.cbr', '', nzbname).strip()
|
||||||
|
@ -1797,17 +1822,17 @@ def searcher(nzbprov, nzbname, comicinfo, link, IssueID, ComicID, tmpprov, direc
|
||||||
|
|
||||||
#make sure the cache directory exists - if not, create it (used for storing nzbs).
|
#make sure the cache directory exists - if not, create it (used for storing nzbs).
|
||||||
if os.path.exists(mylar.CACHE_DIR):
|
if os.path.exists(mylar.CACHE_DIR):
|
||||||
logger.fdebug("Cache Directory successfully found at : " + mylar.CACHE_DIR)
|
logger.fdebug("Cache Directory successfully found at : " + mylar.CACHE_DIR + ". Ensuring proper permissions.")
|
||||||
pass
|
#enforce the permissions here to ensure the lower portion writes successfully
|
||||||
|
filechecker.setperms(mylar.CACHE_DIR, True)
|
||||||
else:
|
else:
|
||||||
#let's make the dir.
|
#let's make the dir.
|
||||||
logger.fdebug("Could not locate Cache Directory, attempting to create at : " + mylar.CACHE_DIR)
|
logger.fdebug("Could not locate Cache Directory, attempting to create at : " + mylar.CACHE_DIR)
|
||||||
try:
|
try:
|
||||||
os.makedirs(mylar.CACHE_DIR)
|
filechecker.validateAndCreateDirectory(mylar.CACHE_DIR, True)
|
||||||
logger.info("Temporary NZB Download Directory successfully created at: " + mylar.CACHE_DIR)
|
logger.info("Temporary NZB Download Directory successfully created at: " + mylar.CACHE_DIR)
|
||||||
except OSError.e:
|
except OSError:
|
||||||
if e.errno != errno.EEXIST:
|
raise
|
||||||
raise
|
|
||||||
|
|
||||||
#save the nzb grabbed, so we can bypass all the 'send-url' crap.
|
#save the nzb grabbed, so we can bypass all the 'send-url' crap.
|
||||||
if not nzbname.endswith('.nzb'):
|
if not nzbname.endswith('.nzb'):
|
||||||
|
@ -1860,7 +1885,6 @@ def searcher(nzbprov, nzbname, comicinfo, link, IssueID, ComicID, tmpprov, direc
|
||||||
except (OSError, IOError):
|
except (OSError, IOError):
|
||||||
logger.warn('Failed to move nzb into blackhole directory - check blackhole directory and/or permissions.')
|
logger.warn('Failed to move nzb into blackhole directory - check blackhole directory and/or permissions.')
|
||||||
return "blackhole-fail"
|
return "blackhole-fail"
|
||||||
|
|
||||||
logger.fdebug("filename saved to your blackhole as : " + nzbname)
|
logger.fdebug("filename saved to your blackhole as : " + nzbname)
|
||||||
logger.info(u"Successfully sent .nzb to your Blackhole directory : " + os.path.join(mylar.BLACKHOLE_DIR, nzbname))
|
logger.info(u"Successfully sent .nzb to your Blackhole directory : " + os.path.join(mylar.BLACKHOLE_DIR, nzbname))
|
||||||
sent_to = "your Blackhole Directory"
|
sent_to = "your Blackhole Directory"
|
||||||
|
@ -1939,37 +1963,69 @@ def searcher(nzbprov, nzbname, comicinfo, link, IssueID, ComicID, tmpprov, direc
|
||||||
mylar.DOWNLOAD_APIKEY = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
|
mylar.DOWNLOAD_APIKEY = hashlib.sha224(str(random.getrandbits(256))).hexdigest()[0:32]
|
||||||
|
|
||||||
#generate the mylar host address if applicable.
|
#generate the mylar host address if applicable.
|
||||||
|
if mylar.ENABLE_HTTPS:
|
||||||
|
proto = 'https://'
|
||||||
|
else:
|
||||||
|
proto = 'http://'
|
||||||
|
|
||||||
|
if mylar.HTTP_ROOT is None:
|
||||||
|
hroot = '/'
|
||||||
|
elif mylar.HTTP_ROOT.endswith('/'):
|
||||||
|
hroot = mylar.HTTP_ROOT
|
||||||
|
else:
|
||||||
|
if mylar.HTTP_ROOT != '/':
|
||||||
|
hroot = mylar.HTTP_ROOT + '/'
|
||||||
|
else:
|
||||||
|
hroot = mylar.HTTP_ROOT
|
||||||
|
|
||||||
|
if mylar.LOCAL_IP is None:
|
||||||
|
#if mylar's local, get the local IP using socket.
|
||||||
|
try:
|
||||||
|
import socket
|
||||||
|
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||||
|
s.connect(('8.8.8.8', 80))
|
||||||
|
mylar.LOCAL_IP = s.getsockname()[0]
|
||||||
|
s.close()
|
||||||
|
except:
|
||||||
|
logger.warn('Unable to determine local IP. Defaulting to host address for Mylar provided as : ' + str(mylar.HTTP_HOST))
|
||||||
|
|
||||||
if mylar.HOST_RETURN:
|
if mylar.HOST_RETURN:
|
||||||
#from lib.pystun import as stun
|
#mylar has the return value already provided (easier and will work if it's right)
|
||||||
#sip = '0.0.0.0'
|
|
||||||
#port = int(mylar.HTTP_PORT)
|
|
||||||
#try:
|
|
||||||
# nat_type, ext_ip, ext_port = stun.get_ip_info(sip,port)
|
|
||||||
#except:
|
|
||||||
# logger.warn('Unable to retrieve External IP.')
|
|
||||||
|
|
||||||
if mylar.HOST_RETURN.endswith('/'):
|
if mylar.HOST_RETURN.endswith('/'):
|
||||||
mylar_host = mylar.HOST_RETURN
|
mylar_host = mylar.HOST_RETURN
|
||||||
else:
|
else:
|
||||||
mylar_host = mylar.HOST_RETURN + '/'
|
mylar_host = mylar.HOST_RETURN + '/'
|
||||||
|
|
||||||
else:
|
|
||||||
if mylar.ENABLE_HTTPS:
|
|
||||||
proto = 'https://'
|
|
||||||
else:
|
|
||||||
proto = 'http://'
|
|
||||||
|
|
||||||
if mylar.HTTP_ROOT is None:
|
elif mylar.SAB_TO_MYLAR:
|
||||||
hroot = '/'
|
#if sab & mylar are on different machines, check to see if they are local or external IP's provided for host.
|
||||||
elif mylar.HTTP_ROOT.endswith('/'):
|
if mylar.HTTP_HOST == 'localhost' or mylar.HTTP_HOST == '0.0.0.0':
|
||||||
hroot = mylar.HTTP_ROOT
|
#if mylar's local, use the local IP already assigned to LOCAL_IP.
|
||||||
|
mylar_host = proto + str(mylar.LOCAL_IP) + ':' + str(mylar.HTTP_PORT) + hroot
|
||||||
else:
|
else:
|
||||||
if mylar.HTTP_ROOT != '/':
|
if mylar.EXT_IP is None:
|
||||||
hroot = mylar.HTTP_ROOT + '/'
|
#if mylar isn't local, get the external IP using pystun.
|
||||||
|
import lib.pystun as stun
|
||||||
|
sip = mylar.HTTP_HOST
|
||||||
|
port = int(mylar.HTTP_PORT)
|
||||||
|
try:
|
||||||
|
nat_type, ext_ip, ext_port = stun.get_ip_info(sip,port)
|
||||||
|
mylar_host = proto + str(ext_ip) + ':' + str(mylar.HTTP_PORT) + hroot
|
||||||
|
mylar.EXT_IP = ext_ip
|
||||||
|
except:
|
||||||
|
logger.warn('Unable to retrieve External IP - try using the host_return option in the config.ini.')
|
||||||
|
mylar_host = proto + str(mylar.HTTP_HOST) + ':' + str(mylar.HTTP_PORT) + hroot
|
||||||
else:
|
else:
|
||||||
hroot = mylar.HTTP_ROOT
|
mylar_host = proto + str(mylar.EXT_IP) + ':' + str(mylar.HTTP_PORT) + hroot
|
||||||
mylar_host = proto + str(mylar.HTTP_HOST) + ':' + str(mylar.HTTP_PORT) + hroot
|
|
||||||
|
|
||||||
|
else:
|
||||||
|
#if all else fails, drop it back to the basic host:port and try that.
|
||||||
|
if mylar.LOCAL_IP is None:
|
||||||
|
tmp_host = mylar.HTTP_HOST
|
||||||
|
else:
|
||||||
|
tmp_host = mylar.LOCAL_IP
|
||||||
|
mylar_host = proto + str(tmp_host) + ':' + str(mylar.HTTP_PORT) + hroot
|
||||||
|
|
||||||
|
|
||||||
fileURL = mylar_host + 'api?apikey=' + mylar.DOWNLOAD_APIKEY + '&cmd=downloadNZB&nzbname=' + nzbname
|
fileURL = mylar_host + 'api?apikey=' + mylar.DOWNLOAD_APIKEY + '&cmd=downloadNZB&nzbname=' + nzbname
|
||||||
|
|
||||||
tmpapi = tmpapi + SABtype
|
tmpapi = tmpapi + SABtype
|
||||||
|
|
|
@ -34,7 +34,7 @@ import shutil
|
||||||
|
|
||||||
import mylar
|
import mylar
|
||||||
|
|
||||||
from mylar import logger, db, importer, mb, search, filechecker, helpers, updater, parseit, weeklypull, PostProcessor, librarysync, moveit, Failed, readinglist #,rsscheck
|
from mylar import logger, db, importer, mb, search, filechecker, helpers, updater, parseit, weeklypull, PostProcessor, librarysync, moveit, Failed, readinglist, notifiers #,rsscheck
|
||||||
|
|
||||||
import lib.simplejson as simplejson
|
import lib.simplejson as simplejson
|
||||||
|
|
||||||
|
@ -145,16 +145,21 @@ class WebInterface(object):
|
||||||
}
|
}
|
||||||
usethefuzzy = comic['UseFuzzy']
|
usethefuzzy = comic['UseFuzzy']
|
||||||
skipped2wanted = "0"
|
skipped2wanted = "0"
|
||||||
if usethefuzzy is None: usethefuzzy = "0"
|
if usethefuzzy is None:
|
||||||
|
usethefuzzy = "0"
|
||||||
force_continuing = comic['ForceContinuing']
|
force_continuing = comic['ForceContinuing']
|
||||||
if force_continuing is None: force_continuing = 0
|
if force_continuing is None:
|
||||||
|
force_continuing = 0
|
||||||
|
if mylar.DELETE_REMOVE_DIR is None:
|
||||||
|
mylar.DELETE_REMOVE_DIR = 0
|
||||||
comicConfig = {
|
comicConfig = {
|
||||||
"comiclocation": mylar.COMIC_LOCATION,
|
"comiclocation": mylar.COMIC_LOCATION,
|
||||||
"fuzzy_year0": helpers.radio(int(usethefuzzy), 0),
|
"fuzzy_year0": helpers.radio(int(usethefuzzy), 0),
|
||||||
"fuzzy_year1": helpers.radio(int(usethefuzzy), 1),
|
"fuzzy_year1": helpers.radio(int(usethefuzzy), 1),
|
||||||
"fuzzy_year2": helpers.radio(int(usethefuzzy), 2),
|
"fuzzy_year2": helpers.radio(int(usethefuzzy), 2),
|
||||||
"skipped2wanted": helpers.checked(skipped2wanted),
|
"skipped2wanted": helpers.checked(skipped2wanted),
|
||||||
"force_continuing": helpers.checked(force_continuing)
|
"force_continuing": helpers.checked(force_continuing),
|
||||||
|
"delete_dir": helpers.checked(mylar.DELETE_REMOVE_DIR)
|
||||||
}
|
}
|
||||||
if mylar.ANNUALS_ON:
|
if mylar.ANNUALS_ON:
|
||||||
annuals = myDB.select("SELECT * FROM annuals WHERE ComicID=?", [ComicID])
|
annuals = myDB.select("SELECT * FROM annuals WHERE ComicID=?", [ComicID])
|
||||||
|
@ -762,38 +767,51 @@ class WebInterface(object):
|
||||||
logger.warn('Failed Download Handling is not enabled. Leaving Failed Download as-is.')
|
logger.warn('Failed Download Handling is not enabled. Leaving Failed Download as-is.')
|
||||||
post_process.exposed = True
|
post_process.exposed = True
|
||||||
|
|
||||||
def pauseArtist(self, ComicID):
|
def pauseSeries(self, ComicID):
|
||||||
logger.info(u"Pausing comic: " + ComicID)
|
logger.info(u"Pausing comic: " + ComicID)
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
controlValueDict = {'ComicID': ComicID}
|
controlValueDict = {'ComicID': ComicID}
|
||||||
newValueDict = {'Status': 'Paused'}
|
newValueDict = {'Status': 'Paused'}
|
||||||
myDB.upsert("comics", newValueDict, controlValueDict)
|
myDB.upsert("comics", newValueDict, controlValueDict)
|
||||||
raise cherrypy.HTTPRedirect("comicDetails?ComicID=%s" % ComicID)
|
raise cherrypy.HTTPRedirect("comicDetails?ComicID=%s" % ComicID)
|
||||||
pauseArtist.exposed = True
|
pauseSeries.exposed = True
|
||||||
|
|
||||||
def resumeArtist(self, ComicID):
|
def resumeSeries(self, ComicID):
|
||||||
logger.info(u"Resuming comic: " + ComicID)
|
logger.info(u"Resuming comic: " + ComicID)
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
controlValueDict = {'ComicID': ComicID}
|
controlValueDict = {'ComicID': ComicID}
|
||||||
newValueDict = {'Status': 'Active'}
|
newValueDict = {'Status': 'Active'}
|
||||||
myDB.upsert("comics", newValueDict, controlValueDict)
|
myDB.upsert("comics", newValueDict, controlValueDict)
|
||||||
raise cherrypy.HTTPRedirect("comicDetails?ComicID=%s" % ComicID)
|
raise cherrypy.HTTPRedirect("comicDetails?ComicID=%s" % ComicID)
|
||||||
resumeArtist.exposed = True
|
resumeSeries.exposed = True
|
||||||
|
|
||||||
def deleteArtist(self, ComicID):
|
def deleteSeries(self, ComicID, delete_dir=None):
|
||||||
|
print delete_dir
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
comic = myDB.selectone('SELECT * from comics WHERE ComicID=?', [ComicID]).fetchone()
|
comic = myDB.selectone('SELECT * from comics WHERE ComicID=?', [ComicID]).fetchone()
|
||||||
if comic['ComicName'] is None: ComicName = "None"
|
if comic['ComicName'] is None: ComicName = "None"
|
||||||
else: ComicName = comic['ComicName']
|
else: ComicName = comic['ComicName']
|
||||||
|
seriesdir = comic['ComicLocation']
|
||||||
logger.info(u"Deleting all traces of Comic: " + ComicName)
|
logger.info(u"Deleting all traces of Comic: " + ComicName)
|
||||||
myDB.action('DELETE from comics WHERE ComicID=?', [ComicID])
|
myDB.action('DELETE from comics WHERE ComicID=?', [ComicID])
|
||||||
myDB.action('DELETE from issues WHERE ComicID=?', [ComicID])
|
myDB.action('DELETE from issues WHERE ComicID=?', [ComicID])
|
||||||
if mylar.ANNUALS_ON:
|
if mylar.ANNUALS_ON:
|
||||||
myDB.action('DELETE from annuals WHERE ComicID=?', [ComicID])
|
myDB.action('DELETE from annuals WHERE ComicID=?', [ComicID])
|
||||||
myDB.action('DELETE from upcoming WHERE ComicID=?', [ComicID])
|
myDB.action('DELETE from upcoming WHERE ComicID=?', [ComicID])
|
||||||
|
if delete_dir: #mylar.DELETE_REMOVE_DIR:
|
||||||
|
logger.fdebug('Remove directory on series removal enabled.')
|
||||||
|
if os.path.exists(seriesdir):
|
||||||
|
logger.fdebug('Attempting to remove the directory and contents of : ' + seriesdir)
|
||||||
|
try:
|
||||||
|
shutil.rmtree(seriesdir)
|
||||||
|
except:
|
||||||
|
logger.warn('Unable to remove directory after removing series from Mylar.')
|
||||||
|
else:
|
||||||
|
logger.warn('Unable to remove directory as it does not exist in : ' + seriesdir)
|
||||||
|
|
||||||
helpers.ComicSort(sequence='update')
|
helpers.ComicSort(sequence='update')
|
||||||
raise cherrypy.HTTPRedirect("home")
|
raise cherrypy.HTTPRedirect("home")
|
||||||
deleteArtist.exposed = True
|
deleteSeries.exposed = True
|
||||||
|
|
||||||
def wipenzblog(self, ComicID=None, IssueID=None):
|
def wipenzblog(self, ComicID=None, IssueID=None):
|
||||||
myDB = db.DBConnection()
|
myDB = db.DBConnection()
|
||||||
|
@ -4079,7 +4097,6 @@ class WebInterface(object):
|
||||||
group_metatag.exposed = True
|
group_metatag.exposed = True
|
||||||
|
|
||||||
def CreateFolders(self, createfolders=None):
|
def CreateFolders(self, createfolders=None):
|
||||||
print 'createfolders is ' + str(createfolders)
|
|
||||||
if createfolders:
|
if createfolders:
|
||||||
mylar.CREATE_FOLDERS = int(createfolders)
|
mylar.CREATE_FOLDERS = int(createfolders)
|
||||||
mylar.config_write()
|
mylar.config_write()
|
||||||
|
@ -4105,5 +4122,50 @@ class WebInterface(object):
|
||||||
syncfiles.exposed = True
|
syncfiles.exposed = True
|
||||||
|
|
||||||
def search_32p(self, search=None):
|
def search_32p(self, search=None):
|
||||||
mylar.rsscheck.torrents(pickfeed='4', seriesname=search)
|
return mylar.rsscheck.torrents(pickfeed='4', seriesname=search)
|
||||||
search_32p.exposed = True
|
search_32p.exposed = True
|
||||||
|
|
||||||
|
def testNMA(self):
|
||||||
|
nma = notifiers.NMA()
|
||||||
|
result = nma.test_notify()
|
||||||
|
if result:
|
||||||
|
return "Successfully sent NMA test - check to make sure it worked"
|
||||||
|
else:
|
||||||
|
return "Error sending test message to NMA"
|
||||||
|
testNMA.exposed = True
|
||||||
|
|
||||||
|
def testprowl(self):
|
||||||
|
prowl = notifiers.prowl()
|
||||||
|
result = prowl.test_notify()
|
||||||
|
if result:
|
||||||
|
return "Successfully sent Prowl test - check to make sure it worked"
|
||||||
|
else:
|
||||||
|
return "Error sending test message to Prowl"
|
||||||
|
testprowl.exposed = True
|
||||||
|
|
||||||
|
def testboxcar(self):
|
||||||
|
boxcar = notifiers.boxcar()
|
||||||
|
result = boxcar.test_notify()
|
||||||
|
if result:
|
||||||
|
return "Successfully sent Boxcar test - check to make sure it worked"
|
||||||
|
else:
|
||||||
|
return "Error sending test message to Boxcar"
|
||||||
|
testboxcar.exposed = True
|
||||||
|
|
||||||
|
def testpushover(self):
|
||||||
|
pushover = notifiers.pushover()
|
||||||
|
result = pushover.test_notify()
|
||||||
|
if result:
|
||||||
|
return "Successfully sent Pushover test - check to make sure it worked"
|
||||||
|
else:
|
||||||
|
return "Error sending test message to Pushover"
|
||||||
|
testpushover.exposed = True
|
||||||
|
|
||||||
|
def testpushbullet(self):
|
||||||
|
pushbullet = notifiers.pushbullet()
|
||||||
|
result = pushbullet.test_notify()
|
||||||
|
if result:
|
||||||
|
return "Successfully sent Pushbullet test - check to make sure it worked"
|
||||||
|
else:
|
||||||
|
return "Error sending test message to Pushbullet"
|
||||||
|
testpushbullet.exposed = True
|
||||||
|
|
Loading…
Reference in New Issue