Merge branch 'development'

This commit is contained in:
evilhero 2020-02-23 14:14:33 -05:00
commit a6bc5d1ec6
No known key found for this signature in database
GPG Key ID: 3E12C51E39D91142
25 changed files with 2132 additions and 68 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ ehtumbs.db
Thumbs.db
lib/comictaggerlib/ct_settings/
settings.json
.DS_Store

View File

@ -308,6 +308,7 @@ def main():
'opds_authentication': mylar.CONFIG.OPDS_AUTHENTICATION,
'opds_username': mylar.CONFIG.OPDS_USERNAME,
'opds_password': mylar.CONFIG.OPDS_PASSWORD,
'opds_pagesize': mylar.CONFIG.OPDS_PAGESIZE,
}
# Try to start the server.

View File

@ -1,5 +1,8 @@
## ![Mylar Logo](https://github.com/evilhero/mylar/blob/master/data/images/mylarlogo.png) Mylar
## Note that feature development has stopped as we have moved to [Mylar3](https://github.com/mylar3/mylar3).
## This means only critical bug errors will get addressed until such time as we decide not to continue supporting this version. EOL is still to be decided.
Mylar is an automated Comic Book (cbr/cbz) downloader program for use with NZB and torrents written in python. It supports SABnzbd, NZBGET, and many torrent clients in addition to DDL.
It will allow you to monitor weekly pull-lists for items belonging to user-specific series to download, as well as being able to monitor story-arcs. Support for TPB's and GN's is also now available.

330
data/css/webviewerstyle.css Normal file
View File

@ -0,0 +1,330 @@
html {
}
header,
main,
footer {
background: #757575;
}
body {
color: #FFFFFF;
background: #757575;
}
#breadcrumbs {
top: 0;
background: #BDBDBD;
}
#comic-info {
display: inline;
}
#dash_dashboard {
padding-left: 8px;
display: inline-block;
}
#dash_library {
padding-left: 8px;
display: inline-block;
}
#dash_settings {
padding-left: 8px;
display: inline-block;
}
#directory-card-content {
display:flex;
color: #FFFFFF;
background-color: #BDBDBD;
text-position: center;
}
#footer {
background: #757575;
}
#theme-settings .dropdown-content li>a, .dropdown-content li>span, .select-dropdown li.disabled, .select-dropdown li.disabled>span, .select-dropdown li.optgroup {
background: #757575;
color: #FFFFFF;
}
#log-modal {
background: #BDBDBD;
color: #FFFFFF;
width: 100%;
height: 100%;
}
#logo-wrapper {
background: #757575;
}
i {
color: #FFFFFF;
}
#nav-dropdown {
background-color: #BDBDBD;
}
#nav-dropdown a {
color: #FFFFFF;
}
#page-settings-text {
color: #FFFFFF;
}
#page-settings-right-text i {
color: #FFFFFF;
}
#page-settings-left-text i {
color: #FFFFFF;
}
#pagination-num a {
color: #FFFFFF;
}
#pagination-num.active {
background-color: #BDBDBD;
}
#read-link {
color: #FFFFFF;
}
#search-modal {
background: #BDBDBD;
color: #FFFFFF;
}
#search-modal input {
margin: 0 auto;
}
#settings-arrow {
color: #BDBDBD;
}
#settings-button {
background: #BDBDBD;
color: #FFFFFF;
}
#settings-help {
position: absolute;
z-index:5000;
color: #FFFFFF;
background: #BDBDBD;
}
#size-height-button {
background: #BDBDBD;
color: #FFFFFF;
}
#size-width-button {
background: #BDBDBD;
color: #FFFFFF;
}
#size-normal-button {
background: #BDBDBD;
color: #FFFFFF;
}
#summary-pane {
background: #BDBDBD;
color: #FFFFFF;
}
#tab-row {
background: #757575;
color: #FFFFFF;
}
.btn-flat {
background: #BDBDBD;
color: #FFFFFF;
}
.card {
position: relative;
background-color: #BDBDBD;
box-shadow: 1px 1px 10px 1px rgba(0, 0, 0, 0.7);
}
.card-image {
position: relative;
height: 350px;
overflow: hidden;
}
.card-image img {
position: absolute;
height: 100%;
width: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.card-content {
height: auto;
background: #BDBDBD;
color: #FFFFFF;
text-align: center;
}
.card-content a {
color: #FFFFFF;
}
.center-cols > .col {
float:none; /* disable the float */
display: inline-block; /* make blocks inline-block */
text-align: initial; /* restore text-align to default */
vertical-align: top;
}
.dimmed {
background-color: rgba(0, 0, 0, 0.7);
}
.dropdown-button {
background-color: #BDBDBD;
color: #FFFFFF;
}
.input-field {
color: #FFFFFF;
}
.input-field label {
color: #FFFFFF;
}
.input-field input[type=text]:focus + label {
color: #FFFFFF;
}
.input-field input[type=text]:focus {
border-bottom: 1px solid #FFFFFF;
box-shadow: 0 1px 0 0 #FFFFFF;
}
.input-field input[type=password]:focus + label {
color: #FFFFFF;
}
.input-field input[type=password]:focus {
border-bottom: 1px solid #FFFFFF;
box-shadow: 0 1px 0 0 #FFFFFF;
}
.nav-wrapper .input-field input[type="search"] {
height: auto;
color: #FFFFFF;
background: #BDBDBD;
}
.nav-wrapper .input-field input[type="search"]:focus {
background: #BDBDBD;
color: #FFFFFF;
box-shadow: 0 1px 0 0 #BDBDBD;
}
.nav-wrapper .input-field input[type="search"]:focus ~ .material-icons.icon-close {
right: 2rem;
}
.nav-wrapper .dropdown-button {
position:absolute;
left: 0;
top: 0;
}
.overlay-settings {
position: fixed;
width: 100%;
background-color: rgba(0, 0, 0, 0);
color: #000;
z-index:4000;
min-height: 1px;
}
.overlay-settings-text {
color: #FFFFFF;
height: 900px;
line-height: 900px;
text-align: center;
}
.page-left {
position: fixed;
left: 0px;
top: 0px;
height: 100%;
width: 33.33%;
}
.page-settings {
position: fixed;
left: 33.33%;
top: 0px;
height: 100%;
width: 33.33%;
}
.page-right {
position: fixed;
right: 0px;
top: 0px;
height: 100%;
width: 33.33%;
}
.reader-overlay {
position: absolute;
background-color:#757575;
width:100%;
height:100%;
top:0px;
left:0px;
z-index:1000;
max-width:100%;
}
.reader-page-wide {
width:100%;
}
.reader-page-high {
position: absolute;
margin: auto;
top: 0px;
bottom: 0px;
left: 0;
right: 0;
height: 100%;
max-width:100%;
}
.reader-page-norm {
position: absolute;
margin-left: auto;
margin-right: auto;
top: 0px;
bottom: 0px;
left: 0;
right: 0;
max-width:100%;
}
.settings-span-left {
display: inline-block;
left: 0px;
vertical-align: middle;
line-height: normal;
}
.settings-span-center {
display: inline-block;
text-align: center;
vertical-align: middle;
line-height: normal;
}
.settings-span-right {
display: inline-block;
right: 0px;
vertical-align: middle;
line-height: normal;
}
.tabs .tab a {
background: #757575;
color: #FFFFFF;
}
.tabs .tab a:hover {
color: #BDBDBD;
}
.tabs .indicator {
background-color: #FFFFFF;
}
@media only screen and (min-width : 601px) and (max-width : 1260px) {
.toast {
border-radius: 0;
}
}
@media only screen and (min-width : 1261px) {
.toast {
border-radius: 0;
}
}
@media only screen and (min-width : 601px) and (max-width : 1260px) {
#toast-container {
bottom: 0;
top: 90%;
right: 50%;
transform: translate(50%, 0);
white-space: nowrap;
}
}
@media only screen and (min-width : 1261px) {
#toast-container {
bottom: 0;
top: 90%;
right: 50%;
transform: translate(50%, 0);
white-space: nowrap;
}
}

View File

@ -26,6 +26,7 @@
${next.headIncludes()}
<script src="js/libs/modernizr-2.8.3.min.js"></script>
<script src="js/libs/jquery-1.7.2.min.js"></script>
</head>
<body>
<%
@ -109,7 +110,6 @@
<a href="#main" id="toTop"><span>Back to top</span></a>
</div>
<script src="js/libs/jquery-1.7.2.min.js"></script>
<script src="js/libs/jquery-ui.min.js"></script>
<script src="js/common.js"></script>

View File

@ -431,6 +431,7 @@
%>
%if linky:
<a href="downloadthis?pathfile=${linky |u}"><img src="interfaces/default/images/download_icon.png" height="25" width="25" title="Download the Issue" class="highqual" /></a>
<a href="read_comic?ish_id=${issue['IssueID']}&page_num=0&size=high"><img src="interfaces/default/images/readabook.png" height="25" width="25" title="Read the Issue in your Browser" class="highqual" /></a>
%if linky.endswith('.cbz'):
<a href="#issue-box" onclick="return runMetaIssue('${linky |u}', '${comic['ComicName']| u}', '${issue['Issue_Number']}', '${issue['IssueDate']}', '${issue['IssueName'] |u}');" class="issue-window"><img src="interfaces/default/images/issueinfo.png" height="25" width="25" title="View Issue Details" class="highqual" /></a>
<div id="issue-box" class="issue-popup">

View File

@ -244,6 +244,10 @@
<div id="opdsoptions">
<div class="row_checkbox">
<small>Access the OPDS server at http://mylarhost/opds/ - keep in mind your scheme (http or https), your hostname, port, and any http_root you may have set. </small></br>
<div class="row">
<label>OPDS Page Size</label>
<input type="text" name="opds_pagesize" value="${config['opds_pagesize']}" size="10">
</div>
<input id="opds_authentication" type="checkbox" name="opds_authentication" value="1" ${config['opds_authentication']} /><label>OPDS Requires Credentials</label>
<%
opds_notes = "Require authentication for OPDS. If checked\nyou will need to provide a username/password.\nThe service user name will work (if set). Additionally,\nyou can provide a user with only OPDS access below.\nNOTE: If this is not checked, OPDS will be available\nwithout a password."
@ -412,7 +416,7 @@
</div>
</div>
<div align="center" class="row">
<img name="sabnzbd_statusicon" id="sabnzbd_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="sabnzbd_statusicon" id="sabnzbd_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test SABnzbd" id="test_sab" style="float:center" /></br>
<input type="text" name="sabstatus" style="text-align:center; font-size:11px;" id="sabstatus" size="50" DISABLED />
<div name="sabversion" id="sabversion" style="font-size:11px;" align="center">
@ -480,7 +484,7 @@
</div>
<div align="center" class="row">
<img name="nzbget_statusicon" id="nzbget_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="nzbget_statusicon" id="nzbget_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test NZBGet" id="test_nzbget" style="float:center" /></br>
<input type="text" name="nzbgetstatus" style="text-align:center; font-size:11px;" id="nzbgetstatus" size="50" DISABLED />
</div>
@ -631,7 +635,7 @@
<small>Automatically start torrent on successful loading within rtorrent client</small>
</div>
<div class="row">
<img name="rtorrent_statusicon" id="rtorrent_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="rtorrent_statusicon" id="rtorrent_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Connection" id="rtorrent_test" />
</div>
</fieldset>
@ -690,7 +694,7 @@
</div>
</div>
<div class="row">
<img name="deluge_status_icon" id="deluge_status_icon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="deluge_status_icon" id="deluge_status_icon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Connection" id="deluge_test" /><br/>
</div>
</fieldset>
@ -732,7 +736,7 @@
</select>
</div>
<div class="row">
<img name="qbittorrent_statusicon" id="qbittorrent_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="qbittorrent_statusicon" id="qbittorrent_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Connection" id="qbittorrent_test" />
</div>
</fieldset>
@ -893,6 +897,10 @@
else:
torznab_enabled = ""
if torznab[2] == '1' or torznab[2] == 1:
torznab_verify = "checked"
else:
torznab_verify = ""
%>
<div class="config" id="torznab${torznab_number}">
@ -904,13 +912,17 @@
<label>Torznab Host</label>
<input type="text" name="torznab_host${torznab_number}" id="torznab_host${torznab_number}" value="${torznab[1]}" size="30">
</div>
<div class="row">
<label>Verify SSL</label>
<input type="checkbox" name="torznab_verify${torznab_number}" id="torznab_verify${torznab_number}" value="${torznab[2]}">
</div>
<div class="row">
<label>Torznab API</label>
<input type="text" name="torznab_apikey${torznab_number}" id="torznab_apikey${torznab_number}" value="${torznab[2]}" size="36">
<input type="text" name="torznab_apikey${torznab_number}" id="torznab_apikey${torznab_number}" value="${torznab[3]}" size="36">
</div>
<div class="row">
<label>Torznab Category</label>
<input type="text" name="torznab_category${torznab_number}" id="torznab_category${torznab_number}" value="${torznab[3]}" size="12">
<input type="text" name="torznab_category${torznab_number}" id="torznab_category${torznab_number}" value="${torznab[4]}" size="12">
</div>
<div class="row checkbox">
<input id="torznab_enabled${torznab_number}" type="checkbox" name="torznab_enabled${torznab_number}" value="1" ${torznab_enabled} /><label>Enabled</label>
@ -1339,7 +1351,7 @@
<input type="text" name="prowl_priority" value="${config['prowl_priority']}" size="2">
</div>
<div class="row">
<img name="prowl_statusicon" id="prowl_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="prowl_statusicon" id="prowl_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test PROWL" id="prowl_test" />
</div>
</div>
@ -1367,7 +1379,7 @@
<input type="text" name="pushover_priority" value="${config['pushover_priority']}" size="2">
</div>
<div align="center" class="row">
<img name="pushover_statusicon" id="pushover_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="pushover_statusicon" id="pushover_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Pushover" id="pushover_test" style="float:center" /></br>
<input type="text" name="pushoverstatus" style="text-align:center; font-size:11px;" id="pushoverstatus" size="55" DISABLED />
</div>
@ -1387,7 +1399,7 @@
<label>Boxcar Token</label>
<input type="text" name="boxcar_token" value="${config['boxcar_token']}" size="30">
<div class="row">
<img name="boxcar_statusicon" id="boxcar_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="boxcar_statusicon" id="boxcar_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Boxcar" id="boxcar_test" />
</div>
</div>
@ -1419,7 +1431,7 @@
<small>Send to all subscribers of the channel with this tag (Optional)</small>
</div>
<div align="center" class="row">
<img name="pushbullet_statusicon" id="pushbullet_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="pushbullet_statusicon" id="pushbullet_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Pushbullet" id="pushbullet_test" style="float:center" /></br>
<input type="text" name="pbstatus" style="text-align:center; font-size:11px;" id="pbstatus" size="55" DISABLED />
</div>
@ -1442,7 +1454,7 @@
<input type="checkbox" name="telegram_onsnatch" value="1" ${config['telegram_onsnatch']} /><label>Notify on snatch?</label>
</div>
<div align="center" class="row">
<img name="telegram_statusicon" id="telegram_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="telegram_statusicon" id="telegram_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Telegram" id="telegram_test" style="float:center" /></br>
<input type="text" name="telegramstatus" style="text-align:center; font-size:11px;" id="telegramstatus" size="55" DISABLED />
</div>
@ -1462,7 +1474,7 @@
<input type="checkbox" name="slack_onsnatch" value="1" ${config['slack_onsnatch']} /><label>Notify on snatch?</label>
</div>
<div align="center" class="row">
<img name="slack_statusicon" id="slack_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="slack_statusicon" id="slack_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Slack" id="slack_test" style="float:center" /></br>
<input type="text" name="slackstatus" style="text-align:center; font-size:11px;" id="slackstatus" size="55" DISABLED />
</div>
@ -1514,7 +1526,7 @@
<small>Notify when comics are post processed?</small>
</div>
<div align="center" class="row">
<img name="email_statusicon" id="email_statusicon" src="interfaces/default/images/successs.png" style="float:right;visibility:hidden;" height="20" width="20" />
<img name="email_statusicon" id="email_statusicon" src="interfaces/default/images/success.png" style="float:right;visibility:hidden;" height="20" width="20" />
<input type="button" value="Test Email" id="email_test" style="float:center" /></br>
<input type="text" name="emailstatus" style="text-align:center; font-size:11px;" id="emailstatus" size="55" DISABLED />
</div>
@ -2179,7 +2191,7 @@
$("#add_torznab").click(function() {
var intId = $("#torznab_providers > div").size() + deletedTorznabs + 1;
var torformfields = $("<div class=\"config\" id=\"torznab" + intId + "\"><div class=\"row\"><label>Torznab Name</label><input type=\"text\" id=\"torznab_name" + intId + "\" name=\"torznab_name" + intId + "\" size=\"36\"></div><div class=\"row\"><label>Torznab Host</label><input type=\"text\" id=\"torznab_host" + intId + "\" name=\"torznab_host" + intId + "\" + value=\"http://\" + size=\"30\"></div><div class=\"row\"><label>Torznab API</label><input type=\"text\" id=\"torznab_apikey" + intId + "\" name=\"torznab_apikey" + intId + "\" size=\"36\"></div><div class=\"row\"><label>Torznab Category</label><input type=\"text\" id=\"torznab_category" + intId + "\" name=\"torznab_category" + intId + "\" size=\"36\"></div><div class=\"row checkbox\"><input type=\"checkbox\" name=\"torznab_enabled" + intId + "\" value=\"1\" checked /><label>Enabled</label></div>");
var torformfields = $("<div class=\"config\" id=\"torznab" + intId + "\"><div class=\"row\"><label>Torznab Name</label><input type=\"text\" id=\"torznab_name" + intId + "\" name=\"torznab_name" + intId + "\" size=\"36\"></div><div class=\"row\"><label>Torznab Host</label><input type=\"text\" id=\"torznab_host" + intId + "\" name=\"torznab_host" + intId + "\" + value=\"http://\" + size=\"30\"></div><div class=\"row checkbox\"><input id=\"torznab_verify" + intId + "\" type=\"checkbox\" name=\"torznab_verify" + intId + "\" value=\"1\"/><label>Verify SSL</label></div><div class=\"row\"><label>Torznab API</label><input type=\"text\" id=\"torznab_apikey" + intId + "\" name=\"torznab_apikey" + intId + "\" size=\"36\"></div><div class=\"row\"><label>Torznab Category</label><input type=\"text\" id=\"torznab_category" + intId + "\" name=\"torznab_category" + intId + "\" size=\"36\"></div><div class=\"row checkbox\"><input type=\"checkbox\" name=\"torznab_enabled" + intId + "\" value=\"1\" checked /><label>Enabled</label></div>");
var tortestButton = $("<div class=\"row\"><img name=\"torznabstatus" + intId + "\" id=\"torznabstatus" + intId + "\" src=\"interfaces/default/images/success.png\" style=\"float:right;visibility:hidden;\" height=\"20\" width=\"20\" /><input type=\"button\" class=\"torznabtest\" value=\"Test\" id=\"torznab_test" + intId + "\" name=\"torznab_test" + intId + "\" style=\"float:right;margin-right:10px;\" /></div>");
var torremoveButton = $("<div class=\"row\"><input type=\"button\" class=\"remove\" value=\"Remove\" /></div>");
torremoveButton.click(function() {
@ -2322,10 +2334,11 @@
$(".torznabtest").click(function () {
var torznab = this.attributes["name"].value.replace('torznab_test', '');
var imagechk = document.getElementById("tornabstatus"+torznab);
var imagechk = document.getElementById("torznabstatus"+torznab);
var name = document.getElementById("torznab_name"+torznab).value;
var host = document.getElementById("torznab_host"+torznab).value;
var apikey = document.getElementById("torznab_api"+torznab).value;
var ssl = document.getElementById("torznab_verify"+torznab).checked;
var apikey = document.getElementById("torznab_apikey"+torznab).value;
$.get("testtorznab",
{ name: name, host: host, ssl: ssl, apikey: apikey },
function(data){

View File

@ -0,0 +1,16 @@
<%page args="jscolor=False"/>
<head>
<title>Mylar WebViewer</title>
<meta name="description" content="Mylar Web Viewer">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/css/materialize.min.css">
<link rel="stylesheet" type="text/css" href="css/webviewerstyle.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.6/js/materialize.min.js"></script>
% if jscolor is True:
<script src="js/jscolor.min.js"></script>
% endif
</head>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@ -32,17 +32,23 @@
%if mylar.IMPORT_STATUS == 'Import completed.':
<input type="text" name="importstatus" id="importstatus" style="text-align:center; font-size:11px;" size="60" DISABLED /></br>
<script>
turnitoff();
$(function() {
turnitoff();
}):
</script>
%else:
<input type="text" name="importstatus" id="importstatus" style="text-align:center; font-size:11px;" size="60" DISABLED /></br>
<script>
turniton();
$(function() {
turniton();
});
</script>
%endif
%else:
<script>
turnitoff();
$(function() {
turnitoff();
});
</script>
<input type="text" name="importstatus" id="importstatus" style="text-align:center; font-size:11px;" size="60" value="Import is currently not running" DISABLED /></br>
%endif
@ -277,7 +283,9 @@
};
function turnitoff() {
CheckEnabled = false;
clearInterval(ImportTimer);
if (typeof ImportTimer !== 'undefined') {
clearInterval(ImportTimer);
};
};
function turniton() {
if (CheckEnabled == false) {

View File

@ -0,0 +1,114 @@
<%!
import mylar
%>
<%
now_page = pages[current_page]
%>
<!doctype html>
<html class="whole-page">
<%include file="header.html" />
<body class="inner-page">
<div class="reader-whole">
% if (current_page + 1) == 1:
<a class="btn btn-floating btn-large pulse tooltipped" id="settings-help" data-position="bottom" data-delay="50" data-tooltip="Click the center to bring up settings. Click the left and right sides of the page to change pages."><i class="material-icons">help_outline</i></a>
% endif
<div class="col s12 overlay-settings">
<div class="overlay-settings-text">
<div class="page-left">
<span class="settings-span-left">
<h4 style="display: none;" id="page-settings-left-text">
<i class="large material-icons" id="settings-arrow">arrow_back</i>
</h4>
</span>
</div>
<div class="page-settings">
<span class="settings-span-center">
<p style="display: none;" id="page-settings-text">
<a class="waves-effect waves-light btn-large" id="settings-button" href="index">Home</a>
</br>
</br>
<b>On Page ${current_page + 1} of ${nop} Pages</b>
</br>
</br>
<a class="waves-effect waves-light btn-large" id="settings-button" action="action" value="Back" onclick="window.history.go(-1); return false">Close Book</a>
</br>
</br>
<b>Fit Comic to Height/Width/No Fit</b>
</br>
</br>
<a class="waves-effect waves-light btn-large" id="size-width-button" href="#!">Width</a>
<a class="waves-effect waves-light btn-large" id="size-height-button" href="#!">Height</a>
<a class="waves-effect waves-light btn-large" id="size-normal-button" href="#!">No Fit</a>
</p>
</span>
</div>
<div class="page-right">
<span class="settings-span-right">
<h4 style="display: none;" id="page-settings-right-text"><i class="large material-icons" id="settings-arrow">arrow_forward</i></h4>
</span>
</div>
</div>
</div>
<div class="row reader-overlay">
% if size == "wide":
<img class="reader-page-wide" src="${now_page}"/>
% elif size == "high":
<img class="reader-page-high" src="${now_page}"/>
% elif size == "norm":
<img class="reader-page-norm" src="${now_page}"/>
% else:
<img class="reader-page-wide" src="${now_page}"/>
% endif:
</div>
</div>
</body>
<script type="text/javascript">
$(document).ready(function() {
$('.modal-trigger').leanModal();
$(".page-settings").click(function() {
$(".page-settings").toggleClass( "dimmed" );
$(".page-left").toggleClass( "dimmed" );
$(".page-right").toggleClass( "dimmed" );
$("#page-settings-text").toggle();
$("#page-settings-left-text").toggle();
$("#page-settings-right-text").toggle();
});
$(".page-right").click(function() {
$(".whole-page").load("${mylar.CONFIG.HTTP_ROOT}read_comic?ish_id=${ish_id}&page_num=${np}&size=${size}");
});
$(".page-left").click(function() {
$(".whole-page").load("${mylar.CONFIG.HTTP_ROOT}read_comic?ish_id=${ish_id}&page_num=${pp}&size=${size}");
});
$(document).one("keyup", function(e) {
if (e.which == 39) {
$(".whole-page").load("${mylar.CONFIG.HTTP_ROOT}read_comic?ish_id=${ish_id}&page_num=${np}&size=${size}");
}
});
$(document).one("keyup", function(e) {
if (e.which == 37) {
$(".whole-page").load("${mylar.CONFIG.HTTP_ROOT}read_comic?ish_id=${ish_id}&page_num=${pp}&size=${size}");
}
});
$("#size-width-button").click(function() {
$(".reader-overlay").html("<img class='reader-page-wide' src='${now_page}'/>");
// $.ajax({
// url: "up_size_pref?pref=wide"
// });
});
$("#size-height-button").click(function() {
$(".reader-overlay").html("<img class='reader-page-high' src='${now_page}'/>");
// $.ajax({
// url: "up_size_pref?pref=high"
// });
});
$("#size-normal-button").click(function() {
$(".reader-overlay").html("<img class='reader-page-norm' src='${now_page}'/>");
// $.ajax({
// url: "up_size_pref?pref=norm"
// });
});
});
</script>
</html>

View File

@ -369,6 +369,8 @@
"sInfoFiltered":"(filtered from _MAX_ total issues)",
"sSearch": ""},
"bStateSave": true,
"StateSave": true,
"StateDuration": 0,
"iDisplayLength": 25,
"sPaginationType": "full_numbers",
"aaSorting": [[0, 'asc']]

10
data/js/jscolor.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1279
lib/pathlib.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1942,8 +1942,12 @@ class PostProcessor(object):
logger.info('%s Post-Processing completed for: [ %s #%s ] %s' % (module, comicname, issuenumber, grab_dst))
self._log(u"Post Processing SUCCESSFUL! ")
imageUrl = myDB.select('SELECT ImageURL from issues WHERE IssueID=?', [issueid])
if imageUrl:
imageUrl = imageUrl[0][0]
try:
self.sendnotify(comicname, issueyear=None, issuenumOG=issuenumber, annchk=annchk, module=module)
self.sendnotify(comicname, issueyear=None, issuenumOG=issuenumber, annchk=annchk, module=module, imageUrl=imageUrl)
except:
pass
@ -2521,7 +2525,7 @@ class PostProcessor(object):
rem_issueid = nfilename[xyb+3:yyb]
logger.fdebug('issueid: %s' % rem_issueid)
nfilename = '%s %s'.strip() % (nfilename[:xyb], nfilename[yyb+3:])
logger.fdebug('issueid information [%s] removed successsfully: %s' % (rem_issueid, nfilename))
logger.fdebug('issueid information [%s] removed successfully: %s' % (rem_issueid, nfilename))
self._log("New Filename: %s" % nfilename)
logger.fdebug('%s New Filename: %s' % (module, nfilename))
@ -2771,7 +2775,11 @@ class PostProcessor(object):
# self.sendnotify(series, issueyear, dispiss, annchk, module)
# return self.queue.put(self.valreturn)
self.sendnotify(series, issueyear, dispiss, annchk, module)
imageUrl = myDB.select('SELECT ImageURL from issues WHERE IssueID=?', [issueid])
if imageUrl:
imageUrl = imageUrl[0][0]
self.sendnotify(series, issueyear, dispiss, annchk, module, imageUrl)
logger.info('%s Post-Processing completed for: %s %s' % (module, series, dispiss))
self._log(u"Post Processing SUCCESSFUL! ")
@ -2784,7 +2792,7 @@ class PostProcessor(object):
return self.queue.put(self.valreturn)
def sendnotify(self, series, issueyear, issuenumOG, annchk, module):
def sendnotify(self, series, issueyear, issuenumOG, annchk, module, imageUrl):
if issueyear is None:
prline = '%s %s' % (series, issuenumOG)
@ -2812,7 +2820,7 @@ class PostProcessor(object):
if mylar.CONFIG.TELEGRAM_ENABLED:
telegram = notifiers.TELEGRAM()
telegram.notify(prline2)
telegram.notify(prline2, imageUrl)
if mylar.CONFIG.SLACK_ENABLED:
slack = notifiers.SLACK()

View File

@ -11,6 +11,7 @@ import re
import ConfigParser
import mylar
from mylar import logger, helpers, encrypted
import errno
config = ConfigParser.SafeConfigParser()
@ -368,6 +369,7 @@ _CONFIG_DEFINITIONS = OrderedDict({
'OPDS_USERNAME': (str, 'OPDS', None),
'OPDS_PASSWORD': (str, 'OPDS', None),
'OPDS_METAINFO': (bool, 'OPDS', False),
'OPDS_PAGESIZE': (int, 'OPDS', 30),
})
@ -555,7 +557,7 @@ class Config(object):
if self.CONFIG_VERSION < 8:
print('Checking for existing torznab configuration...')
if not any([self.TORZNAB_NAME is None, self.TORZNAB_HOST is None, self.TORZNAB_APIKEY is None, self.TORZNAB_CATEGORY is None]):
torznabs =[(self.TORZNAB_NAME, self.TORZNAB_HOST, self.TORZNAB_APIKEY, self.TORZNAB_CATEGORY, str(int(self.ENABLE_TORZNAB)))]
torznabs =[(self.TORZNAB_NAME, self.TORZNAB_HOST, self.TORZNAB_VERIFY, self.TORZNAB_APIKEY, self.TORZNAB_CATEGORY, str(int(self.ENABLE_TORZNAB)))]
setattr(self, 'EXTRA_TORZNABS', torznabs)
config.set('Torznab', 'EXTRA_TORZNABS', str(torznabs))
print('Successfully converted existing torznab for multiple configuration allowance. Removing old references.')
@ -563,9 +565,9 @@ class Config(object):
print('No existing torznab configuration found. Just removing config references at this point..')
config.remove_option('Torznab', 'torznab_name')
config.remove_option('Torznab', 'torznab_host')
config.remove_option('Torznab', 'torznab_verify')
config.remove_option('Torznab', 'torznab_apikey')
config.remove_option('Torznab', 'torznab_category')
config.remove_option('Torznab', 'torznab_verify')
print('Successfully removed outdated config entries.')
if self.newconfig < 9:
#rejig rtorrent settings due to change.
@ -1111,7 +1113,7 @@ class Config(object):
return extra_newznabs
def get_extra_torznabs(self):
extra_torznabs = zip(*[iter(self.EXTRA_TORZNABS.split(', '))]*5)
extra_torznabs = zip(*[iter(self.EXTRA_TORZNABS.split(', '))]*6)
return extra_torznabs
def provider_sequence(self):
@ -1154,7 +1156,7 @@ class Config(object):
if self.ENABLE_TORZNAB:
for ets in self.EXTRA_TORZNABS:
if str(ets[4]) == '1': # if torznabs are enabled
if str(ets[5]) == '1': # if torznabs are enabled
if ets[0] == "":
et_name = ets[1]
else:

View File

@ -272,6 +272,11 @@ class FileChecker(object):
logger.fdebug('[SARC] Removed Reading Order sequence from subname. Now set to : %s' % modfilename)
#make sure all the brackets are properly spaced apart
if modfilename.find('\s') == -1:
#if no spaces exist, assume decimals being used as spacers (ie. nzb name)
modspacer = '.'
else:
modspacer = ' '
m = re.findall('[^()]+', modfilename)
cnt = 1
#2019-12-24----fixed to accomodate naming convention like Amazing Mary Jane (2019) 002.cbr, and to account for brackets properly
@ -279,10 +284,10 @@ class FileChecker(object):
while cnt < len(m):
#logger.fdebug('[m=%s] modfilename.find: %s' % (m[cnt], modfilename[modfilename.find('('+m[cnt]+')')+len(m[cnt])+2]))
#logger.fdebug('mod_1: %s' % modfilename.find('('+m[cnt]+')'))
if modfilename[modfilename.find('('+m[cnt]+')')-1] != ' ' and modfilename.find('('+m[cnt]+')') != -1:
if modfilename[modfilename.find('('+m[cnt]+')')-1] != modspacer and modfilename.find('('+m[cnt]+')') != -1:
#logger.fdebug('before_space: %s' % modfilename[modfilename.find('('+m[cnt]+')')-1])
#logger.fdebug('after_space: %s' % modfilename[modfilename.find('('+m[cnt]+')')+len(m[cnt])+2])
modfilename = '%s%s%s' % (modfilename[:modfilename.find('('+m[cnt]+')')], ' ', modfilename[modfilename.find('('+m[cnt]+')'):])
modfilename = '%s%s%s' % (modfilename[:modfilename.find('('+m[cnt]+')')], modspacer, modfilename[modfilename.find('('+m[cnt]+')'):])
cnt+=1
except Exception as e:
#logger.warn('[ERROR] %s' % e)
@ -335,7 +340,7 @@ class FileChecker(object):
issueid = modfilename[x+3:y]
logger.fdebug('issueid: %s' % issueid)
modfilename = '%s %s'.strip() % (modfilename[:x], modfilename[y+3:])
logger.fdebug('issueid %s removed successsfully: %s' % (issueid, modfilename))
logger.fdebug('issueid %s removed successfully: %s' % (issueid, modfilename))
#here we take a snapshot of the current modfilename, the intent is that we will remove characters that match
#as we discover them - namely volume, issue #, years, etc
@ -373,13 +378,14 @@ class FileChecker(object):
ret_sf1 = ' '.join(sf)
#here we should account for some characters that get stripped out due to the regex's
#namely, unique characters - known so far: +, &
#namely, unique characters - known so far: +, &, @
#c11 = '\+'
#f11 = '\&'
#g11 = '\''
ret_sf1 = re.sub('\+', 'c11', ret_sf1).strip()
ret_sf1 = re.sub('\&', 'f11', ret_sf1).strip()
ret_sf1 = re.sub('\'', 'g11', ret_sf1).strip()
ret_sf1 = re.sub('\@', 'h11', ret_sf1).strip()
#split_file = re.findall('(?imu)\([\w\s-]+\)|[-+]?\d*\.\d+|\d+[\s]COVERS+|\d{4}-\d{2}-\d{2}|\d+[(th|nd|rd|st)]+|\d+|[\w-]+|#?\d\.\d+|#[\.-]\w+|#[\d*\.\d+|\w+\d+]+|#(?<![\w\d])XCV(?![\w\d])+|#[\w+]|\)', ret_sf1, re.UNICODE)
split_file = re.findall('(?imu)\([\w\s-]+\)|[-+]?\d*\.\d+|\d+[\s]COVERS+|\d{4}-\d{2}-\d{2}|\d+[(th|nd|rd|st)]+|[\(^\)+]|\d+|[\w-]+|#?\d\.\d+|#[\.-]\w+|#[\d*\.\d+|\w+\d+]+|#(?<![\w\d])XCV(?![\w\d])+|#[\w+]|\)', ret_sf1, re.UNICODE)
@ -1131,16 +1137,19 @@ class FileChecker(object):
if alt_series is not None:
if 'XCV' in alt_series:
alt_series = re.sub('XCV', x, alt_series,1)
elif 'XCV' in alt_issue:
if alt_issue is not None:
if 'XCV' in alt_issue:
alt_issue = re.sub('XCV', x, alt_issue,1)
series_name = re.sub('c11', '+', series_name)
series_name = re.sub('f11', '&', series_name)
series_name = re.sub('g11', '\'', series_name)
series_name = re.sub('h11', '@', series_name)
if alt_series is not None:
alt_series = re.sub('c11', '+', alt_series)
alt_series = re.sub('f11', '&', alt_series)
alt_series = re.sub('g11', '\'', alt_series)
alt_series = re.sub('h11', '@', alt_series)
if series_name.endswith('-'):
series_name = series_name[:-1].strip()

View File

@ -124,7 +124,7 @@ class GC(object):
option_find = f.find("p", {"style": "text-align: center;"})
i = 0
while i <= 2:
while (i <= 2 and option_find is not None):
option_find = option_find.findNext(text=True)
if 'Year' in option_find:
year = option_find.findNext(text=True)

View File

@ -3657,6 +3657,42 @@ def newznab_test(name, host, ssl, apikey):
logger.info('[ERROR:%s] - %s' % (code, description))
return False
def torznab_test(name, host, ssl, apikey):
from xml.dom.minidom import parseString, Element
params = {'t': 'search',
'apikey': apikey,
'o': 'xml'}
if host[-1:] == '/':
host = host[:-1]
headers = {'User-Agent': str(mylar.USER_AGENT)}
logger.info('host: %s' % host)
try:
r = requests.get(host, params=params, headers=headers, verify=bool(ssl))
except Exception as e:
logger.warn('Unable to connect: %s' % e)
return
else:
try:
data = parseString(r.content)
except Exception as e:
logger.warn('[WARNING] Error attempting to test: %s' % e)
try:
error_code = data.getElementsByTagName('error')[0].attributes['code'].value
except Exception as e:
logger.info('Connected - Status code returned: %s' % r.status_code)
if r.status_code == 200:
return True
else:
logger.warn('Received response - Status code returned: %s' % r.status_code)
return False
code = error_code
description = data.getElementsByTagName('error')[0].attributes['description'].value
logger.info('[ERROR:%s] - %s' % (code, description))
return False
def get_free_space(folder):
min_threshold = 100000000 #threshold for minimum amount of freespace available (#100mb)
if platform.system() == "Windows":

View File

@ -176,7 +176,7 @@ def addComictoDB(comicid, mismatch=None, pullupd=None, imported=None, ogcname=No
logger.info('Corrected year of ' + str(SeriesYear) + ' to corrected year for series that was manually entered previously of ' + str(csyear))
SeriesYear = csyear
logger.info('Sucessfully retrieved details for ' + comic['ComicName'])
logger.info('Successfully retrieved details for ' + comic['ComicName'])
#since the weekly issue check could return either annuals or issues, let's initialize it here so it carries through properly.
weeklyissue_check = []

View File

@ -340,24 +340,33 @@ class TELEGRAM:
else:
self.token = test_token
def notify(self, message):
def notify(self, message, imageUrl=None):
# Construct message
payload = {'chat_id': self.userid, 'text': message}
sendMethod = "sendMessage"
if imageUrl:
# Construct message
payload = {'chat_id': self.userid, 'caption': message, 'photo': imageUrl}
sendMethod = "sendPhoto"
# Send message to user using Telegram's Bot API
try:
response = requests.post(self.TELEGRAM_API % (self.token, "sendMessage"), json=payload, verify=True)
except Exception, e:
logger.info(u'Telegram notify failed: ' + str(e))
response = requests.post(self.TELEGRAM_API % (self.token, sendMethod), json=payload, verify=True)
except Exception as e:
logger.info('Telegram notify failed: ' + str(e))
# Error logging
sent_successfuly = True
sent_successfully = True
if not response.status_code == 200:
logger.info(u'Could not send notification to TelegramBot (token=%s). Response: [%s]' % (self.token, response.text))
sent_successfuly = False
sent_successfully = False
if not sent_successfully and sendMethod != "sendMessage":
return self.notify(message)
logger.info(u"Telegram notifications sent.")
return sent_successfuly
return sent_successfully
def test_notify(self):
return self.notify('Test Message: Release the Ninjas!')

View File

@ -39,7 +39,7 @@ class OPDS(object):
def __init__(self):
self.cmd = None
self.PAGE_SIZE=30
self.PAGE_SIZE=mylar.CONFIG.OPDS_PAGESIZE
self.img = None
self.issue_id = None
self.file = None

View File

@ -762,7 +762,7 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
break
except requests.exceptions.RequestException as e:
logger.warn('General Error fetching data from %s: %s' % (nzbprov, e))
if e.r.status_code == 503:
if str(r.status_code) == '503':
#HTTP Error 503
logger.warn('Aborting search due to Provider unavailability')
foundc['status'] = False
@ -1109,7 +1109,7 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
logger.fdebug('Cleaned up title to : %s' % cleantitle)
#send it to the parser here.
p_comic = filechecker.FileChecker(file=ComicTitle)
p_comic = filechecker.FileChecker(file=ComicTitle, watchcomic=ComicName)
parsed_comic = p_comic.listFiles()
logger.fdebug('parsed_info: %s' % parsed_comic)
@ -1376,18 +1376,18 @@ def NZB_SEARCH(ComicName, IssueNumber, ComicYear, SeriesYear, Publisher, IssueDa
intIss = 1000
else:
intIss = 9999999999
if parsed_comic['issue_number'] is not None:
logger.fdebug("issue we found for is : %s" % parsed_comic['issue_number'])
comintIss = helpers.issuedigits(parsed_comic['issue_number'])
if filecomic['justthedigits'] is not None:
logger.fdebug("issue we found for is : %s" % filecomic['justthedigits'])
comintIss = helpers.issuedigits(filecomic['justthedigits'])
logger.fdebug("integer value of issue we have found : %s" % comintIss)
else:
comintIss = 11111111111
#do this so that we don't touch the actual value but just use it for comparisons
if parsed_comic['issue_number'] is None:
if filecomic['justthedigits'] is None:
pc_in = None
else:
pc_in = helpers.issuedigits(parsed_comic['issue_number'])
pc_in = helpers.issuedigits(filecomic['justthedigits'])
#issue comparison now as well
if int(intIss) == int(comintIss) or all([cmloopit == 4, findcomiciss is None, pc_in is None]) or all([cmloopit == 4, findcomiciss is None, pc_in == 1]):
nowrite = False
@ -2751,7 +2751,7 @@ def notify_snatch(sent_to, comicname, comyear, IssueNumber, nzbprov, pack):
if mylar.CONFIG.TELEGRAM_ENABLED and mylar.CONFIG.TELEGRAM_ONSNATCH:
logger.info(u"Sending Telegram notification")
telegram = notifiers.TELEGRAM()
telegram.notify(snline + " - " + snatched_name)
telegram.notify("%s - %s - Mylar %s" % (snline, snatched_name, sent_to))
if mylar.CONFIG.SLACK_ENABLED and mylar.CONFIG.SLACK_ONSNATCH:
logger.info(u"Sending Slack notification")
slack = notifiers.SLACK()

View File

@ -4692,13 +4692,17 @@ class WebInterface(object):
if len(search_matches) > 1:
# if we matched on more than one series above, just save those results instead of the entire search result set.
for sres in search_matches:
if type(sres['haveit']) == dict:
imp_cid = sres['haveit']['comicid']
else:
imp_cid = sres['haveit']
cVal = {"SRID": SRID,
"comicid": sres['comicid']}
#should store ogcname in here somewhere to account for naming conversions above.
nVal = {"Series": ComicName,
"results": len(search_matches),
"publisher": sres['publisher'],
"haveit": sres['haveit'],
"haveit": imp_cid,
"name": sres['name'],
"deck": sres['deck'],
"url": sres['url'],
@ -4707,6 +4711,7 @@ class WebInterface(object):
"issues": sres['issues'],
"ogcname": ogcname,
"comicyear": sres['comicyear']}
#logger.fdebug('search_values: [%s]/%s' % (cVal, nVal))
myDB.upsert("searchresults", nVal, cVal)
logger.info('[IMPORT] There is more than one result that might be valid - normally this is due to the filename(s) not having enough information for me to use (ie. no volume label/year). Manual intervention is required.')
#force the status here just in case
@ -4718,13 +4723,17 @@ class WebInterface(object):
# store the search results for series that returned more than one result for user to select later / when they want.
# should probably assign some random numeric for an id to reference back at some point.
for sres in sresults:
if type(sres['haveit']) == dict:
imp_cid = sres['haveit']['comicid']
else:
imp_cid = sres['haveit']
cVal = {"SRID": SRID,
"comicid": sres['comicid']}
#should store ogcname in here somewhere to account for naming conversions above.
nVal = {"Series": ComicName,
"results": len(sresults),
"publisher": sres['publisher'],
"haveit": sres['haveit'],
"haveit": imp_cid,
"name": sres['name'],
"deck": sres['deck'],
"url": sres['url'],
@ -5008,7 +5017,7 @@ class WebInterface(object):
"dognzb_verify": helpers.checked(mylar.CONFIG.DOGNZB_VERIFY),
"experimental": helpers.checked(mylar.CONFIG.EXPERIMENTAL),
"enable_torznab": helpers.checked(mylar.CONFIG.ENABLE_TORZNAB),
"extra_torznabs": sorted(mylar.CONFIG.EXTRA_TORZNABS, key=itemgetter(4), reverse=True),
"extra_torznabs": sorted(mylar.CONFIG.EXTRA_TORZNABS, key=itemgetter(5), reverse=True),
"newznab": helpers.checked(mylar.CONFIG.NEWZNAB),
"extra_newznabs": sorted(mylar.CONFIG.EXTRA_NEWZNABS, key=itemgetter(5), reverse=True),
"enable_ddl": helpers.checked(mylar.CONFIG.ENABLE_DDL),
@ -5140,6 +5149,7 @@ class WebInterface(object):
"opds_username": mylar.CONFIG.OPDS_USERNAME,
"opds_password": mylar.CONFIG.OPDS_PASSWORD,
"opds_metainfo": helpers.checked(mylar.CONFIG.OPDS_METAINFO),
"opds_pagesize": mylar.CONFIG.OPDS_PAGESIZE,
"dlstats": dlprovstats,
"dltotals": freq_tot,
"alphaindex": mylar.CONFIG.ALPHAINDEX
@ -5286,28 +5296,41 @@ class WebInterface(object):
newValues['AlternateFileName'] = str(alt_filename)
#force the check/creation of directory com_location here
updatedir = True
if any([mylar.CONFIG.CREATE_FOLDERS is True, os.path.isdir(orig_location)]):
if os.path.isdir(str(com_location)):
logger.info(u"Validating Directory (" + str(com_location) + "). Already exists! Continuing...")
else:
if orig_location != com_location:
if orig_location != com_location and os.path.isdir(orig_location) is True:
logger.fdebug('Renaming existing location [%s] to new location: %s' % (orig_location, com_location))
try:
os.rename(orig_location, com_location)
except Exception as e:
logger.warn('Unable to rename existing directory: %s' % e)
return
if 'No such file or directory' in e:
checkdirectory = filechecker.validateAndCreateDirectory(com_location, True)
if not checkdirectory:
logger.warn('Error trying to validate/create directory. Aborting this process at this time.')
updatedir = False
else:
logger.warn('Unable to rename existing directory: %s' % e)
updatedir = False
else:
logger.fdebug("Updated Directory doesn't exist! - attempting to create now.")
if orig_location != com_location and os.path.isdir(orig_location) is False:
logger.fdebug("Original Directory (%s) doesn't exist! - attempting to create new directory (%s)" % (orig_location, com_location))
else:
logger.fdebug("Updated Directory doesn't exist! - attempting to create now.")
checkdirectory = filechecker.validateAndCreateDirectory(com_location, True)
if not checkdirectory:
logger.warn('Error trying to validate/create directory. Aborting this process at this time.')
return
updatedir = False
newValues['ComicLocation'] = com_location
else:
logger.info('[Create directories False] Not creating physical directory, but updating series location in dB to: %s' % com_location)
if updatedir is True:
newValues['ComicLocation'] = com_location
myDB.upsert("comics", newValues, controlValueDict)
logger.fdebug('Updated Series options!')
myDB.upsert("comics", newValues, controlValueDict)
logger.fdebug('Updated Series options!')
raise cherrypy.HTTPRedirect("comicDetails?ComicID=%s" % ComicID)
comic_config.exposed = True
@ -5377,7 +5400,7 @@ class WebInterface(object):
'lowercase_filenames', 'autowant_upcoming', 'autowant_all', 'comic_cover_local', 'alternate_latest_series_covers', 'cvinfo', 'snatchedtorrent_notify',
'prowl_enabled', 'prowl_onsnatch', 'pushover_enabled', 'pushover_onsnatch', 'boxcar_enabled',
'boxcar_onsnatch', 'pushbullet_enabled', 'pushbullet_onsnatch', 'telegram_enabled', 'telegram_onsnatch', 'slack_enabled', 'slack_onsnatch',
'email_enabled', 'email_enc', 'email_ongrab', 'email_onpost', 'opds_enable', 'opds_authentication', 'opds_metainfo', 'enable_ddl', 'deluge_pause'] #enable_public
'email_enabled', 'email_enc', 'email_ongrab', 'email_onpost', 'opds_enable', 'opds_authentication', 'opds_metainfo', 'opds_pagesize', 'enable_ddl', 'deluge_pause'] #enable_public
for checked_config in checked_configs:
if checked_config not in kwargs:
@ -5426,6 +5449,10 @@ class WebInterface(object):
if torznab_name == "":
continue
torznab_host = helpers.clean_url(kwargs['torznab_host' + torznab_number])
try:
torznab_verify = kwargs['torznab_verify' + torznab_number]
except:
torznab_verify = 0
torznab_api = kwargs['torznab_apikey' + torznab_number]
torznab_category = kwargs['torznab_category' + torznab_number]
try:
@ -5435,7 +5462,7 @@ class WebInterface(object):
del kwargs[kwarg]
mylar.CONFIG.EXTRA_TORZNABS.append((torznab_name, torznab_host, torznab_api, torznab_category, torznab_enabled))
mylar.CONFIG.EXTRA_TORZNABS.append((torznab_name, torznab_host, torznab_verify, torznab_api, torznab_category, torznab_enabled))
mylar.CONFIG.process_kwargs(kwargs)
@ -6006,6 +6033,24 @@ class WebInterface(object):
return 'Error - failed running test for %s' % name
testnewznab.exposed = True
def testtorznab(self, name, host, ssl, apikey):
logger.fdebug('ssl/verify: %s' % ssl)
if 'ssl' == '0' or ssl == '1':
ssl = bool(int(ssl))
else:
if ssl == 'false':
ssl = False
else:
ssl = True
result = helpers.torznab_test(name, host, ssl, apikey)
if result is True:
logger.info('Successfully tested %s [%s] - valid api response received' % (name, host))
return 'Successfully tested %s!' % name
else:
print result
logger.warn('Testing failed to %s [HOST:%s][SSL:%s]' % (name, host, bool(ssl)))
return 'Error - failed running test for %s' % name
testtorznab.exposed = True
def orderThis(self, **kwargs):
return
@ -6425,3 +6470,12 @@ class WebInterface(object):
download_specific_release.exposed = True
def read_comic(self, ish_id, page_num, size):
from mylar.webviewer import WebViewer
wv = WebViewer()
page_num = int(page_num)
#cherrypy.session['ishid'] = ish_id
data = wv.read_comic(ish_id, page_num, size)
#data = wv.read_comic(ish_id)
return data
read_comic.exposed = True

168
mylar/webviewer.py Normal file
View File

@ -0,0 +1,168 @@
import os
import re
import cherrypy
import stat
import zipfile
from lib.rarfile import rarfile
import mylar
try:
from PIL import Image
except ImportError:
logger.debug("WebReader Requested, but PIL or pillow libraries must be installed. Please execute 'pip install pillow', then restart Mylar.")
return serve_template(templatename="index.html", title="Home", comics=comics, alphaindex=mylar.CONFIG.ALPHAINDEX)
from mylar import logger, db, importer, mb, search, filechecker, helpers, updater, parseit, weeklypull, librarysync, moveit, Failed, readinglist, config
from mylar.webserve import serve_template
class WebViewer(object):
def __init__(self):
self.ish_id = None
self.page_num = None
self.kwargs = None
self.data = None
if not os.path.exists(os.path.join(mylar.DATA_DIR, 'sessions')):
os.makedirs(os.path.abspath(os.path.join(mylar.DATA_DIR, 'sessions')))
updatecherrypyconf = {
'tools.gzip.on': True,
'tools.gzip.mime_types': ['text/*', 'application/*', 'image/*'],
'tools.sessions.timeout': 1440,
'tools.sessions.storage_class': cherrypy.lib.sessions.FileSession,
'tools.sessions.storage_path': os.path.join(mylar.DATA_DIR, "sessions"),
'request.show_tracebacks': False,
'engine.timeout_monitor.on': False,
}
if mylar.CONFIG.HTTP_PASSWORD is None:
updatecherrypyconf.update({
'tools.sessions.on': True,
})
cherrypy.config.update(updatecherrypyconf)
cherrypy.engine.signals.subscribe()
def read_comic(self, ish_id = None, page_num = None, size = None):
logger.debug("WebReader Requested, looking for ish_id %s and page_num %s" % (ish_id, page_num))
if size == None:
user_size_pref = 'wide'
else:
user_size_pref = size
try:
ish_id
except:
logger.warn("WebReader: ish_id not set!")
myDB = db.DBConnection()
comic = myDB.selectone('select comics.ComicLocation, issues.Location from comics, issues where comics.comicid = issues.comicid and issues.issueid = ?' , [ish_id]).fetchone()
if comic is None:
logger.warn("WebReader: ish_id %s requested but not in the database!" % ish_id)
raise cherrypy.HTTPRedirect("home")
# cherrypy.config.update()
comic_path = os.path.join(comic['ComicLocation'], comic['Location'])
logger.debug("WebReader found ish_id %s at %s" % (ish_id, comic_path))
# cherrypy.session['ish_id'].load()
# if 'sizepref' not in cherrypy.session:
# cherrypy.session['sizepref'] = user_size_pref
# user_size_pref = cherrypy.session['sizepref']
# logger.debug("WebReader setting user_size_pref to %s" % user_size_pref)
scanner = ComicScanner()
image_list = scanner.reading_images(ish_id)
logger.debug("Image list contains %s pages" % (len(image_list)))
if len(image_list) == 0:
logger.debug("Unpacking ish_id %s from comic_path %s" % (ish_id, comic_path))
scanner.user_unpack_comic(ish_id, comic_path)
else:
logger.debug("ish_id %s already unpacked." % ish_id)
num_pages = len(image_list)
logger.debug("Found %s pages for ish_id %s from comic_path %s" % (num_pages, ish_id, comic_path))
if num_pages == 0:
image_list = ['images/skipped_icon.png']
cookie_comic = re.sub(r'\W+', '', comic_path)
cookie_comic = "wv_" + cookie_comic.decode('unicode_escape')
logger.debug("about to drop a cookie for " + cookie_comic + " which represents " + comic_path)
cookie_check = cherrypy.request.cookie
if cookie_comic not in cookie_check:
logger.debug("Cookie Creation")
cookie_path = '/'
cookie_maxage = '2419200'
cookie_set = cherrypy.response.cookie
cookie_set['cookie_comic'] = 0
cookie_set['cookie_comic']['path'] = cookie_path.decode('unicode_escape')
cookie_set['cookie_comic']['max-age'] = cookie_maxage.decode('unicode_escape')
next_page = page_num + 1
prev_page = page_num - 1
else:
logger.debug("Cookie Read")
page_num = int(cherrypy.request.cookie['cookie_comic'].value)
logger.debug("Cookie Set To %d" % page_num)
next_page = page_num + 1
prev_page = page_num - 1
logger.info("Reader Served")
logger.debug("Serving comic " + comic['Location'] + " page number " + str(page_num))
return serve_template(templatename="read.html", pages=image_list, current_page=page_num, np=next_page, pp=prev_page, nop=num_pages, size=user_size_pref, cc=cookie_comic, comicpath=comic_path, ish_id=ish_id)
def up_size_pref(self, pref):
cherrypy.session.load()
cherrypy.session['sizepref'] = pref
cherrypy.session.save()
return
class ComicScanner(object):
# This method will handle scanning the directories and returning a list of them all.
def dir_scan(self):
logger.debug("Dir Scan Requested")
full_paths = []
full_paths.append(mylar.CONFIG.DESTINATION_DIR)
for root, dirs, files in os.walk(mylar.CONFIG.DESTINATION_DIR):
full_paths.extend(os.path.join(root, d) for d in dirs)
logger.info("Dir Scan Completed")
logger.info("%i Dirs Found" % (len(full_paths)))
return full_paths
def user_unpack_comic(self, ish_id, comic_path):
logger.info("%s unpack requested" % comic_path)
for root, dirs, files in os.walk(os.path.join(mylar.CONFIG.CACHE_DIR, "webviewer", ish_id), topdown=False):
for f in files:
os.chmod(os.path.join(root, f), stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # 0777
os.remove(os.path.join(root, f))
for root, dirs, files in os.walk(os.path.join(mylar.CONFIG.CACHE_DIR, "webviewer", ish_id), topdown=False):
for d in dirs:
os.chmod(os.path.join(root, d), stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) # 0777
os.rmdir(os.path.join(root, d))
if comic_path.endswith(".cbr"):
opened_rar = rarfile.RarFile(comic_path)
opened_rar.extractall(os.path.join(mylar.CONFIG.CACHE_DIR, "webviewer", ish_id))
elif comic_path.endswith(".cbz"):
opened_zip = zipfile.ZipFile(comic_path)
opened_zip.extractall(os.path.join(mylar.CONFIG.CACHE_DIR, "webviewer", ish_id))
return
# This method will return a list of .jpg files in their numberical order to be fed into the reading view.
def reading_images(self, ish_id):
logger.debug("Image List Requested")
image_list = []
image_src = os.path.join(mylar.CONFIG.CACHE_DIR, "webviewer", ish_id)
image_loc = os.path.join(mylar.CONFIG.HTTP_ROOT, 'cache', "webviewer", ish_id)
for root, dirs, files in os.walk(image_src):
for f in files:
if f.endswith((".png", ".gif", ".bmp", ".dib", ".jpg", ".jpeg", ".jpe", ".jif", ".jfif", ".jfi", ".tiff", ".tif")):
image_list.append( os.path.join(image_loc, f) )
image_list.sort()
logger.debug("Image List Created")
return image_list