mirror of
https://github.com/karl0ss/JDRssDownloader.git
synced 2025-04-27 03:49:23 +01:00
Compare commits
49 Commits
Author | SHA1 | Date | |
---|---|---|---|
79bdaf0a97 | |||
c9da595ee7 | |||
874e4eeba1 | |||
130a9f3378 | |||
b83870503e | |||
2d3ae87492 | |||
dfa7194f33 | |||
fddd3c1de7 | |||
94627f934c | |||
84180b066e | |||
f0a949aacb | |||
1324466bdd | |||
18cf07527e | |||
1f5d18615d | |||
73b2f98cb5 | |||
bb2e58e624 | |||
df44638771 | |||
0cc32b18c9 | |||
e5987247fe | |||
085e64cb34 | |||
12d8c65c39 | |||
cefd13c8da | |||
644e0712c0 | |||
588200a139 | |||
97b2fc702d | |||
e6a22d78c0 | |||
ec8501d279 | |||
949b1c668c | |||
69460fe659 | |||
c3ff742bd5 | |||
513986aad5 | |||
533bd23f6c | |||
393029ed37 | |||
![]() |
25ed5af433 | ||
![]() |
77f2029615 | ||
![]() |
736ec93030 | ||
![]() |
10a2873aa2 | ||
![]() |
495ebfbab1 | ||
![]() |
fb0ad8af5e | ||
173be22b54 | |||
![]() |
684644cdab | ||
![]() |
8b0040d8dd | ||
![]() |
a2f3a9ee69 | ||
![]() |
2df901b47e | ||
![]() |
4a0460aea3 | ||
![]() |
41392b0da7 | ||
![]() |
0bbd83f375 | ||
![]() |
26658fdf8a | ||
![]() |
3bd2754b3a |
5
.gitignore
vendored
5
.gitignore
vendored
@ -8,3 +8,8 @@ feedCache.json
|
||||
dist/jdrssdownloader-linux
|
||||
dist/jdrssdownloader-win.exe
|
||||
dist/jdrssdownloader-macos
|
||||
downloadHistory.json
|
||||
shows.json
|
||||
shows.json
|
||||
cache/retryCache.json
|
||||
shows.json
|
||||
|
@ -1,52 +1,84 @@
|
||||
const fs = require('fs')
|
||||
const { linkAdder } = require('./JDLinkAdder');
|
||||
const { getLinksFromURL } = require('./LinkGrabber')
|
||||
const { checkFileName } = require('./checkFileName')
|
||||
const { checkDownloadHistory } = require('./checkDownloadHistory')
|
||||
const { retryCache } = require('./retryCache')
|
||||
|
||||
async function filterFeed() {
|
||||
let myshowlist = JSON.parse(fs.readFileSync('config.json')).Shows
|
||||
let feed = JSON.parse(fs.readFileSync('./feedCache.json'));
|
||||
let hevcSwitch = JSON.parse(fs.readFileSync('config.json')).OnlyHEVC
|
||||
let myshowlist = JSON.parse(fs.readFileSync('shows.json'))
|
||||
let rssFeed = JSON.parse(fs.readFileSync('./cache/feedCache.json'));
|
||||
let retryCacheData = retryCache()
|
||||
let fullFeedToCheck = retryCacheData.concat(rssFeed)
|
||||
let urlsToCheck = []
|
||||
|
||||
|
||||
for (let show of myshowlist) {
|
||||
|
||||
myshowlist.forEach(async show => {
|
||||
try {
|
||||
// Find show on feed
|
||||
let list_filtered_for_show = feed.filter(item => item.title.includes(show.Name))
|
||||
if (list_filtered_for_show.length > 0) {
|
||||
// If show is found get url then return all links on that page
|
||||
let full_link_list_from_page = await getLinksFromURL(list_filtered_for_show[0].link)
|
||||
// Only get urls with HEVC in name
|
||||
let urls_with_HEVC_in_url = full_link_list_from_page.filter(item => item.includes('HEVC'))
|
||||
if (urls_with_HEVC_in_url.length == 0) {
|
||||
// If no urls with HEVC check for H265
|
||||
urls_with_HEVC_in_url = full_link_list_from_page.filter(item => item.includes('H265'))
|
||||
let listFilteredForShow = fullFeedToCheck.filter(item => item.title.includes(show.Name))
|
||||
if (listFilteredForShow.length > 0) {
|
||||
for (let match of listFilteredForShow) {
|
||||
// If show is found get url then return all links on that page
|
||||
let fullLinkListFromPage = await getLinksFromURL(match.link)
|
||||
if (hevcSwitch) {
|
||||
// Only get urls with HEVC in name
|
||||
urlsToCheck = fullLinkListFromPage.filter(item => item.includes('HEVC'))
|
||||
if (urlsToCheck.length == 0) {
|
||||
// If no urls with HEVC check for H265
|
||||
urlsToCheck = fullLinkListFromPage.filter(item => item.includes('H265'))
|
||||
}
|
||||
} else {
|
||||
urlsToCheck = fullLinkListFromPage
|
||||
}
|
||||
// Only keep urls that match show quality
|
||||
let urlsWithQualityInUrl = urlsToCheck.filter(item => item.includes(show.Quality))
|
||||
// Remove any url trying to direct to a torrent site search
|
||||
let urlsWithoutTorrentInUrl = urlsWithQualityInUrl.filter(item => !item.includes('torrent'))
|
||||
// Remove any url that doesn't include MeGusta
|
||||
if (hevcSwitch) {
|
||||
preNitroflare = urlsWithoutTorrentInUrl.filter(item => item.includes('MeGusta'))
|
||||
} else {
|
||||
preNitroflare = urlsWithoutTorrentInUrl
|
||||
}
|
||||
// NitroFlare doesn't group with the rest of the links in JD, remove them.
|
||||
let removeNitroflare = preNitroflare.filter(item => !item.includes('nitro'))
|
||||
// Do some stuff
|
||||
urlObj = checkFileName(removeNitroflare)
|
||||
let downloadList = urlObj.urlList
|
||||
// Send Links to JDdownloader
|
||||
if (downloadList.length !== 0) {
|
||||
if (checkDownloadHistory(urlObj)) {
|
||||
log.info(urlObj.fileName + ' already downloaded, skipped.')
|
||||
break
|
||||
} else {
|
||||
log.tele(downloadList.length + ' links for ' + urlObj.fileName + ' have been sent to JDdownloader.')
|
||||
linkAdder(downloadList)
|
||||
global.linkCheckTime = new Date();
|
||||
}
|
||||
} else {
|
||||
// No HEVC links found
|
||||
log.info(downloadList.length + ' links for ' + show.Name + ' have been found, will recheck next time.')
|
||||
for (let feedItem of listFilteredForShow) {
|
||||
retryCache(feedItem)
|
||||
}
|
||||
global.linkCheckTime = new Date();
|
||||
}
|
||||
}
|
||||
// Only keep urls that match show quality
|
||||
let urls_with_quality_in_url = urls_with_HEVC_in_url.filter(item => item.includes(show.Quality))
|
||||
// Remove any url trying to direct to a torrent site search
|
||||
let urls_without_torrent_in_url = urls_with_quality_in_url.filter(item => !item.includes('torrent'))
|
||||
// Remove any url that doesn't include MeGusta
|
||||
let only_MeGusta_links = urls_without_torrent_in_url.filter(item => item.includes('MeGusta'))
|
||||
// NitroFlare doesn't group with the rest of the links in JD, remove them.
|
||||
let remove_nitroflare = only_MeGusta_links.filter(item => !item.includes('nitro'))
|
||||
// Send Links to JDdownloader
|
||||
if (remove_nitroflare.length !== 0) {
|
||||
log.info(remove_nitroflare.length + ' links for ' + show.Name + ' have been sent to JDdownloader')
|
||||
linkAdder(remove_nitroflare)
|
||||
} else {
|
||||
// No HEVC links found
|
||||
log.info(remove_nitroflare.length + ' HEVC links for ' + show.Name + ' have been found')
|
||||
}
|
||||
|
||||
} else {
|
||||
// Show not found on the current feed cache
|
||||
log.info(show.Name + ' not on feed')
|
||||
}
|
||||
} catch (error) {
|
||||
log.error('Something went wrong ' + error)
|
||||
global.linkCheckTime = new Date();
|
||||
}
|
||||
|
||||
})
|
||||
// log.info('Wiping feed cache')
|
||||
// fs.writeFileSync(global.fileName, JSON.stringify('[]'));
|
||||
}
|
||||
log.info('Wiping feed cache')
|
||||
fs.writeFileSync('./cache/feedCache.json', JSON.stringify([]));
|
||||
global.linkCheckTime = new Date();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -11,17 +11,16 @@ async function feedUpdater() {
|
||||
|
||||
let items = [];
|
||||
|
||||
if (fs.existsSync('./feedCache.json')) {
|
||||
items = JSON.parse(fs.readFileSync('./feedCache.json'))
|
||||
if (fs.existsSync('./cache/feedCache.json')) {
|
||||
items = JSON.parse(fs.readFileSync('./cache/feedCache.json'))
|
||||
}
|
||||
// Compare existing cache and new items and merge differences
|
||||
let updatedArray = lodash.unionBy(feed.items, items, 'title');
|
||||
|
||||
// Save the file
|
||||
log.info(updatedArray.length + ' items in file cache')
|
||||
fs.writeFileSync('./feedCache.json', JSON.stringify(updatedArray));
|
||||
|
||||
|
||||
fs.writeFileSync('./cache/feedCache.json', JSON.stringify(updatedArray));
|
||||
global.rssRefreshTime = new Date();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -1,17 +1,60 @@
|
||||
const fs = require("fs");
|
||||
config = JSON.parse(fs.readFileSync('config.json'))
|
||||
const { feedUpdater } = require('./FeedUpdater')
|
||||
const { filterFeed } = require('./FeedFilter')
|
||||
const { telegrambot } = require('./telegramCommunication')
|
||||
const { create_empty_cache_files } = require('./utils')
|
||||
const express = require('express');
|
||||
const bodyParser = require('body-parser');
|
||||
const cors = require('cors');
|
||||
const path = require("path");
|
||||
const basicAuth = require('express-basic-auth')
|
||||
const app = express();
|
||||
app.set("views", path.join(__dirname, "views"));
|
||||
app.set("view engine", "pug");
|
||||
app.use(express.static(path.join(__dirname, "public")));
|
||||
app.use(cors());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(bodyParser.json());
|
||||
require('./routes')(app);
|
||||
app.use(basicAuth({
|
||||
users: { 'admin': config.AdminPassword },
|
||||
challenge: true,
|
||||
}))
|
||||
|
||||
global.rssRefreshTime = new Date();
|
||||
global.linkCheckTime = new Date();
|
||||
global.version = require('./package.json').version;
|
||||
|
||||
global.log = require('simple-node-logger').createSimpleLogger({
|
||||
logFilePath: 'jdrssdownloader.log',
|
||||
timestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS'
|
||||
});
|
||||
|
||||
let RSSFeedRefreshMins = JSON.parse(fs.readFileSync('config.json')).RSSFeedRefreshMins
|
||||
let JDPostLinksMins = JSON.parse(fs.readFileSync('config.json')).JDPostLinksMins
|
||||
log.tele = function () {
|
||||
var args = Array.prototype.slice.call(arguments),
|
||||
entry = log.log('info', args);
|
||||
process.nextTick(function () {
|
||||
if (config.TelegramBot) {
|
||||
telegrambot(entry.msg[0])
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
log.info('Refreshing RSS Items every ' + RSSFeedRefreshMins + ' Minutes')
|
||||
log.info('Checking for links and sending to JDdownloader every ' + JDPostLinksMins + ' Minutes')
|
||||
async function main() {
|
||||
log.tele('Running JDRssDownloader version ' + global.version)
|
||||
try {
|
||||
RSSFeedRefreshMins = config.RSSFeedRefreshMins
|
||||
JDPostLinksMins = config.JDPostLinksMins
|
||||
} catch (error) {
|
||||
log.error('config.json file is missing.')
|
||||
}
|
||||
log.tele('Refreshing RSS Items every ' + RSSFeedRefreshMins + ' Minutes')
|
||||
log.tele('Checking for links and sending to JDdownloader every ' + JDPostLinksMins + ' Minutes')
|
||||
app.listen(config.WebUIPort, () => log.info(`WebUi is listening on ${config.WebUIPort}!`))
|
||||
create_empty_cache_files()
|
||||
setInterval(await feedUpdater, RSSFeedRefreshMins * 60000);
|
||||
setInterval(await filterFeed, JDPostLinksMins * 60000);
|
||||
}
|
||||
|
||||
|
||||
setInterval(feedUpdater, RSSFeedRefreshMins * 60000);
|
||||
setInterval(filterFeed, JDPostLinksMins * 60000);
|
||||
main()
|
@ -1,13 +1,32 @@
|
||||
const axios = require('axios');
|
||||
var cheerio = require('cheerio');
|
||||
|
||||
async function flareSolverr(url) {
|
||||
var data = JSON.stringify({
|
||||
"cmd": "request.get",
|
||||
"url": url,
|
||||
"maxTimeout": 120000
|
||||
});
|
||||
var config = {
|
||||
method: 'post',
|
||||
url: 'http://127.0.01:8191/v1',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
data: data
|
||||
};
|
||||
res = await axios(config)
|
||||
return res.data.solution.response
|
||||
}
|
||||
|
||||
async function getLinksFromURL(url) {
|
||||
|
||||
try {
|
||||
let links = [];
|
||||
let httpResponse = await axios.get(url);
|
||||
let scrape = await flareSolverr(url)
|
||||
// let httpResponse = await axios.get(url);
|
||||
|
||||
let $ = cheerio.load(httpResponse.data);
|
||||
let $ = cheerio.load(scrape);
|
||||
let linkObjects = $('a'); // get all hyperlinks
|
||||
|
||||
linkObjects.each((index, element) => {
|
||||
|
134
README.md
134
README.md
@ -1,74 +1,152 @@
|
||||
|
||||
# JDRssDownloader
|
||||
|
||||
|
||||
|
||||
JDownloader 2 is a great tool, but since V1 has been missing a way to automatically download from RSS feeds, and filter downloads to only download what you want, in my case 720p HEVC files, MeGusta rips by preference.
|
||||
|
||||
|
||||
|
||||
I have put together this simple project to allow me to do that, people may find useful.
|
||||
|
||||
|
||||
|
||||
- Automatically check an RSS feed and send to JDownloader
|
||||
|
||||
- Uses MyJDownloader API to allow running on separate system
|
||||
|
||||
- Local file cache of RSS feed
|
||||
|
||||
- Specify time to check RSS feed
|
||||
|
||||
- Specify time to check file cache to send links to JDownloader
|
||||
|
||||
- Ability to add multiple shows to check for
|
||||
|
||||
- Ability to check for different qualities per show you are looking for
|
||||
|
||||
|
||||
- Ability to turn OFF only HEVC search
|
||||
|
||||
|
||||
|
||||
# Configuration
|
||||
There is a ``config-sample.json`` file that needs to be renamed to ``config.json``, after this you can update it with your required settings.
|
||||
|
||||
|
||||
|
||||
There is a `config-sample.json` file that needs to be renamed to `config.json`, after this you can update it with your required settings.
|
||||
|
||||
|
||||
|
||||
- JDUserName - Your MyJDownloader Username
|
||||
|
||||
- JDPassword - Your MyJDownloader Password
|
||||
- RSSFeed - The url to the rss feed you want to watch (Only tested with - https://rlsbb.cc/feed/)
|
||||
|
||||
- AdminPassword - Password to be set for the WebUI
|
||||
|
||||
- WebUIPort - Port for the WebUI to run on
|
||||
|
||||
- RSSFeed - The url to the rss feed you want to watch (Only tested with - rlsbb)
|
||||
|
||||
- RSSFeedRefreshMins - How often to poll your rss feed down to local file cache
|
||||
- JDPostLinksMins": How often to check your file cache for your shows and send found links to JDownloader
|
||||
|
||||
- JDPostLinksMins - How often to check your file cache for your shows and send found links to JDownloader
|
||||
|
||||
- Autostart - Tells JDownloader to add and start the downloads straight away (true/false)
|
||||
- Shows - This needs to be a comma separated list of json objects of the show and quality you want to check for.
|
||||
|
||||
- OnlyHEVC - If false, this will download any files that it finds on the post that matches the quality (true/false)
|
||||
|
||||
- TelegramBot - Set to true if you wish to have updates sent via telegramBot
|
||||
|
||||
- TelegramBotID - Set this to the id you recieve from TheBotFather
|
||||
|
||||
- TelegramChatID - Chat or Group ID for the bot to send messages to
|
||||
|
||||
|
||||
|
||||
An example shown below
|
||||
|
||||
|
||||
```
|
||||
{
|
||||
"JDUserName": "User",
|
||||
"JDPassword": "Pass",
|
||||
"RSSFeed": "https://rlsbb.cc/feed/",
|
||||
"RSSFeedRefreshMins": 10,
|
||||
"JDPostLinksMins": 180,
|
||||
"Autostart": false,
|
||||
"Shows": [
|
||||
{
|
||||
"Name": "Obi-Wan Kenobi",
|
||||
"Quality": "1080"
|
||||
},
|
||||
{
|
||||
"Name": "Taskmaster",
|
||||
"Quality": "720"
|
||||
}
|
||||
]
|
||||
"JDUserName": "User",
|
||||
"JDPassword": "Pass",
|
||||
"AdminPassword":"",
|
||||
"WebUIPort": 3100,
|
||||
"RSSFeed": "https://mypage.com/feed/",
|
||||
"RSSFeedRefreshMins": 10,
|
||||
"JDPostLinksMins": 180,
|
||||
"Autostart": false,
|
||||
"OnlyHEVC": true,
|
||||
"TelegramBot": true,
|
||||
"TelegramBotID":"",
|
||||
"TelegramChatID":123456789,
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Running
|
||||
|
||||
|
||||
|
||||
## FlareSolverr
|
||||
|
||||
Due to issues with a number of sites I use, I have had to rework the client to now use [FlareSolverr](https://github.com/FlareSolverr/FlareSolverr) this allows the tool to bypass/workout Cloudflare site issues, the implication of this is that we need to run a docker image in the background that will actually open the page up in a Chrome window, then return the HTML and get the links as usual, this does slow down the process, as it takes upto 2 mins for it to return HTML from the page, but it works..
|
||||
|
||||
So you need to have docker installed and to run this command
|
||||
|
||||
docker-compose up -d
|
||||
|
||||
This will run the container in the background, then you can run as usual, and it will proxy all requests via FlareSolverr, please open an issue if you find problems.
|
||||
|
||||
## Release Version
|
||||
|
||||
|
||||
|
||||
Either download the version on the releases, as well as the `config-sample.json` and run execute, this is the simplest way, but may not be the latest code, and will not run in the background
|
||||
|
||||
|
||||
|
||||
## Source Version
|
||||
|
||||
|
||||
|
||||
You will need NodeJS installed, then you can checkout this repo.
|
||||
|
||||
For basic usage you can just navigate into the folder and run -
|
||||
- ``npm i`` to install the requirements.
|
||||
- ``node JDRssDownloader.js`` This will execute the process and add the links if they are found.
|
||||
|
||||
|
||||
My suggestion would be to use pm2 so it can run "in the background"
|
||||
For basic usage you can just navigate into the folder and run -
|
||||
|
||||
|
||||
|
||||
- `npm i` to install the requirements.
|
||||
|
||||
- `node JDRssDownloader.js` This will execute the process and add the links if they are found.
|
||||
|
||||
|
||||
|
||||
My suggestion would be to use [pm2](https://pm2.keymetrics.io/docs/usage/quick-start/) so it can run "in the background"
|
||||
|
||||
|
||||
|
||||
# Issues
|
||||
|
||||
|
||||
|
||||
Not alot of testing has gone into this, and I threw it together in a few hours, and only for my use case, so there are bound to be issues, please open them and let me know if you find any.
|
||||
|
||||
|
||||
|
||||
# Future
|
||||
I have some ideas to make this a bit smarter, at the moment it doesn't clean the cache at all, and will keep sending the same links to JDownloader once they are in the cache, I am working on cleaning them out, but for now the best thing to do it to set JDownloader to automatically mark already downloaded links as finished, then it doesn't bother redownloading all the time.
|
||||
|
||||
Also want to add the ability to look at multiple RSS feeds, this seems quite easy, and I will do in the next couple of weeks.
|
||||
|
||||
|
||||
I have some ideas to make this a bit smarter and I want to add the ability to look at multiple RSS feeds, this seems quite easy, and I will do in the next couple of weeks.
|
||||
|
||||
|
||||
|
||||
# Thanks
|
||||
Thank for all the people who made any of the modules that I used to create this.
|
||||
|
||||
|
||||
|
||||
Thank for all the people who made any of the modules that I used to create this.
|
||||
|
84
apiFunctions.js
Normal file
84
apiFunctions.js
Normal file
@ -0,0 +1,84 @@
|
||||
const fs = require('fs');
|
||||
|
||||
|
||||
async function addNewShow(showData) {
|
||||
let shows = JSON.parse(fs.readFileSync('shows.json'))
|
||||
let exist = false
|
||||
for (let show of shows) {
|
||||
if (show.Name == showData.showName) {
|
||||
exist = true
|
||||
}
|
||||
}
|
||||
if (exist) {
|
||||
log.error(showData.showName + ' Already exists in list and not added')
|
||||
} else {
|
||||
shows.push({
|
||||
"Name": showData.showName,
|
||||
"Quality": showData.quality
|
||||
})
|
||||
try {
|
||||
fs.writeFileSync('shows.json', JSON.stringify(shows));
|
||||
log.info(showData.showName + ' Added to the list, checking for ' + showData.quality + 'p')
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function removeShow(showData) {
|
||||
let shows = JSON.parse(fs.readFileSync('shows.json'))
|
||||
|
||||
myArray = shows.filter(function (obj) {
|
||||
return obj.Name !== showData.showName;
|
||||
});
|
||||
|
||||
shows = myArray
|
||||
try {
|
||||
fs.writeFileSync('shows.json', JSON.stringify(shows));
|
||||
log.info(showData.showName + ' Removed from tracking list.')
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function editShow(showData) {
|
||||
let shows = JSON.parse(fs.readFileSync('shows.json'))
|
||||
for (let index = 0; index < shows.length; index++) {
|
||||
const element = shows[index];
|
||||
if (element.Name == showData.showName) {
|
||||
shows[index] = {
|
||||
"Name": showData.showName,
|
||||
"Quality": showData.quality
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
fs.writeFileSync('shows.json', JSON.stringify(shows));
|
||||
log.info(showData.showName + ' Quality modified to ' + showData.quality + 'p')
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
async function removeShowFromCache(showData) {
|
||||
let shows = JSON.parse(fs.readFileSync('./cache/retryCache.json'))
|
||||
|
||||
myArray = shows.filter(function (obj) {
|
||||
return obj.title !== showData;
|
||||
});
|
||||
|
||||
shows = myArray
|
||||
try {
|
||||
fs.writeFileSync('./cache/retryCache.json', JSON.stringify(shows));
|
||||
log.info(showData + ' Removed from retry cache.')
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
addNewShow, removeShow, editShow, removeShowFromCache
|
||||
}
|
||||
|
14
checkDownloadHistory.js
Normal file
14
checkDownloadHistory.js
Normal file
@ -0,0 +1,14 @@
|
||||
const fs = require('fs')
|
||||
|
||||
function checkDownloadHistory(urlObj) {
|
||||
history = JSON.parse(fs.readFileSync('./cache/downloadHistory.json'));
|
||||
if (history.includes(urlObj.fileName)) {
|
||||
return true
|
||||
} else {
|
||||
history.push(urlObj.fileName)
|
||||
fs.writeFileSync('./cache/downloadHistory.json', JSON.stringify(history));
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { checkDownloadHistory }
|
16
checkFileName.js
Normal file
16
checkFileName.js
Normal file
@ -0,0 +1,16 @@
|
||||
function checkFileName(urls) {
|
||||
let urlObj = {
|
||||
"fileName": "",
|
||||
"urlList": []
|
||||
}
|
||||
urls.forEach(url => {
|
||||
let cut = url.match(/([^\/]*$)/mg);
|
||||
if (cut[0] !== '') {
|
||||
urlObj.fileName = cut[0].replace('.html', '')
|
||||
urlObj.urlList.push(url)
|
||||
}
|
||||
});
|
||||
return urlObj
|
||||
}
|
||||
|
||||
module.exports = { checkFileName }
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"JDUserName": "",
|
||||
"JDPassword": "",
|
||||
"AdminPassword":"",
|
||||
"WebUIPort": 3100,
|
||||
"RSSFeed": "",
|
||||
"RSSFeedRefreshMins": 10,
|
||||
"JDPostLinksMins": 180,
|
||||
"Autostart": false,
|
||||
"Shows": [
|
||||
{
|
||||
"Name": "",
|
||||
"Quality": "720"
|
||||
}
|
||||
]
|
||||
"OnlyHEVC": true,
|
||||
"TelegramBot": true,
|
||||
"TelegramBotID":"",
|
||||
"TelegramChatID":123456789
|
||||
}
|
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
version: "2.1"
|
||||
services:
|
||||
flaresolverr:
|
||||
# DockerHub mirror flaresolverr/flaresolverr:latest
|
||||
image: ghcr.io/flaresolverr/flaresolverr:latest
|
||||
container_name: flaresolverr
|
||||
environment:
|
||||
- LOG_LEVEL=${LOG_LEVEL:-info}
|
||||
- LOG_HTML=${LOG_HTML:-false}
|
||||
- CAPTCHA_SOLVER=${CAPTCHA_SOLVER:-none}
|
||||
- TZ=Europe/London
|
||||
ports:
|
||||
- "127.0.0.1:${PORT:-8191}:8191"
|
||||
restart: unless-stopped
|
12
package.json
12
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jdrssdownloader",
|
||||
"version": "1.0.0",
|
||||
"version": "1.2.0",
|
||||
"description": "",
|
||||
"main": "JDRssDownloader.js",
|
||||
"bin": "JDRssDownloader.js",
|
||||
@ -11,9 +11,19 @@
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.2",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.20.2",
|
||||
"cheerio": "^1.0.0-rc.11",
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"express-validator": "^6.14.2",
|
||||
"jdownloader-client": "^1.0.0",
|
||||
"line-reader": "^0.4.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.4",
|
||||
"node-telegram-bot-api": "^0.59.0",
|
||||
"pug": "^3.0.2",
|
||||
"rss-parser": "^3.12.0",
|
||||
"simple-node-logger": "^21.8.12"
|
||||
},
|
||||
|
158
public/style.css
Normal file
158
public/style.css
Normal file
@ -0,0 +1,158 @@
|
||||
@import url("https://fonts.googleapis.com/css?family=Raleway:800|Merriweather+Sans|Share+Tech+Mono");
|
||||
|
||||
:root {
|
||||
--ui-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.06),
|
||||
0 4px 5px 0 rgba(0, 0, 0, 0.06), 0 1px 10px 0 rgba(0, 0, 0, 0.08);
|
||||
fill: rgba(0, 0, 0, 0.54);
|
||||
--ui-shadow-border: 1px solid rgba(0, 0, 0, 0.14);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
|
||||
background-color: #0e0e0e;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
font-family: "Raleway", sans-serif;
|
||||
text-transform: uppercase;
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
color: #2a3747;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.View {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
padding: 20px;
|
||||
|
||||
background-size: cover;
|
||||
|
||||
font-family: "Merriweather Sans", sans-serif;
|
||||
}
|
||||
|
||||
.Banner {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
width: 100%;
|
||||
|
||||
border-radius: 5px;
|
||||
|
||||
overflow: hidden;
|
||||
background: white;
|
||||
padding: 15px;
|
||||
|
||||
font-family: "Share Tech Mono", monospace;
|
||||
|
||||
border-bottom: var(--ui-shadow-border);
|
||||
box-shadow: var(--ui-shadow);
|
||||
}
|
||||
|
||||
.Message {
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
border-bottom: var(--ui-shadow-border);
|
||||
box-shadow: var(--ui-shadow);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
/* text-align: center; */
|
||||
margin-top:10px;
|
||||
margin-bottom:10px;
|
||||
}
|
||||
|
||||
.Message > .Title {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.Message > .Details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
|
||||
.NavButtons {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: space-around;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.NavButton {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
height: 55px;
|
||||
width: 150px;
|
||||
|
||||
background: #fa4141;
|
||||
|
||||
border-radius: 30px;
|
||||
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
|
||||
text-transform: capitalize;
|
||||
|
||||
border-bottom: var(--ui-shadow-border);
|
||||
box-shadow: var(--ui-shadow);
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
|
||||
}
|
||||
|
||||
td { border-left: 1px solid #000;
|
||||
border-right: 1px solid #000;
|
||||
}
|
21
retryCache.js
Normal file
21
retryCache.js
Normal file
@ -0,0 +1,21 @@
|
||||
const fs = require('fs')
|
||||
|
||||
function retryCache(cache) {
|
||||
if (cache == null) {
|
||||
retryCacheData = JSON.parse(fs.readFileSync('./cache/retryCache.json'));
|
||||
return retryCacheData
|
||||
}
|
||||
retryCacheData = JSON.parse(fs.readFileSync('./cache/retryCache.json'));
|
||||
if (retryCacheData.some(e => e.title === cache.title)) {
|
||||
log.info(cache.title + ' is already in the retry cache.')
|
||||
}
|
||||
else {
|
||||
cache.retryCount = 5
|
||||
retryCacheData.push(cache)
|
||||
fs.writeFileSync('./cache/retryCache.json', JSON.stringify(retryCacheData));
|
||||
log.info(cache.title + ' written to retry cache.')
|
||||
return retryCacheData
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { retryCache }
|
28
routes/api.js
Normal file
28
routes/api.js
Normal file
@ -0,0 +1,28 @@
|
||||
const fs = require("fs");
|
||||
const { nextLinkCheck, nextRssRefresh, get_last_downloaded } = require('.././utils')
|
||||
|
||||
|
||||
module.exports = function (app) {
|
||||
app.get("/api/stats", (req, res) => {
|
||||
retryCache = JSON.parse(fs.readFileSync('./cache/retryCache.json'))
|
||||
for (let index = 0; index < retryCache.length; index++) {
|
||||
const item = retryCache[index];
|
||||
retryCache[index].newtitle = item.title.replace(/ /g, "‡");
|
||||
}
|
||||
showList = JSON.parse(fs.readFileSync('shows.json'));
|
||||
feedCache = JSON.parse(fs.readFileSync('./cache/feedCache.json'));
|
||||
rssTime = nextRssRefresh()
|
||||
linkCheck = nextLinkCheck()
|
||||
lastDownloaded = get_last_downloaded()
|
||||
|
||||
res.json({
|
||||
"ShowList": showList.length,
|
||||
"FeedCache": feedCache.length,
|
||||
"RetryCache": retryCache.length,
|
||||
"RSSCheck": rssTime,
|
||||
"LinkChecker": linkCheck,
|
||||
"LastDownloaded": lastDownloaded
|
||||
});
|
||||
});
|
||||
|
||||
}
|
25
routes/cache.js
Normal file
25
routes/cache.js
Normal file
@ -0,0 +1,25 @@
|
||||
const fs = require("fs");
|
||||
const {removeShowFromCache} = require('../apiFunctions')
|
||||
|
||||
module.exports = function (app) {
|
||||
app.get("/feedCache", (req, res) => {
|
||||
feedCache = JSON.parse(fs.readFileSync('./cache/feedCache.json'))
|
||||
res.render("feedCache", { title: "Feed Cache", feedCache: feedCache });
|
||||
});
|
||||
|
||||
app.get("/retryCache", (req, res) => {
|
||||
retryCache = JSON.parse(fs.readFileSync('./cache/retryCache.json'))
|
||||
for (let index = 0; index < retryCache.length; index++) {
|
||||
const item = retryCache[index];
|
||||
retryCache[index].newtitle = item.title.replace(/ /g, "‡");
|
||||
}
|
||||
res.render("retryCache", { title: "Retry Cache", retryCache: retryCache });
|
||||
});
|
||||
|
||||
app.get('/retryCache/remove', (req, res) => {
|
||||
const showName = req.query.name.replaceAll("‡", " ");
|
||||
console.log(req)
|
||||
removeShowFromCache(showName)
|
||||
res.redirect("/retryCache");
|
||||
});
|
||||
}
|
10
routes/index.js
Normal file
10
routes/index.js
Normal file
@ -0,0 +1,10 @@
|
||||
var fs = require('fs');
|
||||
|
||||
module.exports = function(app) {
|
||||
fs.readdirSync(__dirname).forEach(function(file) {
|
||||
if (file === "index.js" || file.substr(file.lastIndexOf('.') + 1) !== 'js')
|
||||
return;
|
||||
var name = file.substr(0, file.indexOf('.'));
|
||||
require('./' + name)(app);
|
||||
});
|
||||
}
|
15
routes/logFIle.js
Normal file
15
routes/logFIle.js
Normal file
@ -0,0 +1,15 @@
|
||||
const lineReader = require("line-reader");
|
||||
const Promise = require("bluebird");
|
||||
|
||||
module.exports = function (app) {
|
||||
app.get("/logFile", (req, res) => {
|
||||
logFile = []
|
||||
const eachLine = Promise.promisify(lineReader.eachLine);
|
||||
eachLine('jdrssdownloader.log', function (line) {
|
||||
logFile.push(line)
|
||||
}).then(() => {
|
||||
logFile = logFile.slice((logFile.length - 30), logFile.length)
|
||||
res.render("logFile", { title: "App Logs", logFile: logFile.reverse() });
|
||||
});
|
||||
});
|
||||
}
|
14
routes/root.js
Normal file
14
routes/root.js
Normal file
@ -0,0 +1,14 @@
|
||||
const fs = require("fs");
|
||||
const { nextLinkCheck, nextRssRefresh, get_last_downloaded } = require('.././utils')
|
||||
|
||||
module.exports = function (app) {
|
||||
|
||||
app.get("/", (req, res) => {
|
||||
showListLength = JSON.parse(fs.readFileSync('shows.json')).length
|
||||
feedCacheLength = JSON.parse(fs.readFileSync('./cache/feedCache.json')).length
|
||||
retryCacheLength = JSON.parse(fs.readFileSync('./cache/retryCache.json')).length
|
||||
rssTime = nextRssRefresh()
|
||||
linkCheck = nextLinkCheck()
|
||||
res.render("index", { title: "Home", showListLength: showListLength, version: global.version, rssTime: rssTime, linkCheck: linkCheck, lastDownloaded: get_last_downloaded() });
|
||||
});
|
||||
}
|
49
routes/shows.js
Normal file
49
routes/shows.js
Normal file
@ -0,0 +1,49 @@
|
||||
const fs = require("fs");
|
||||
const { addNewShow, removeShow, editShow } = require('.././apiFunctions')
|
||||
const { check, validationResult } = require('express-validator');
|
||||
|
||||
|
||||
module.exports = function (app) {
|
||||
app.get("/shows", (req, res) => {
|
||||
showList = JSON.parse(fs.readFileSync('shows.json'))
|
||||
res.render("shows", { title: "Show List", showList: showList });
|
||||
});
|
||||
|
||||
app.get("/shows/add", (req, res) => {
|
||||
res.render("addshow", { title: "Add Show" });
|
||||
});
|
||||
|
||||
app.get("/shows/remove", (req, res) => {
|
||||
showList = JSON.parse(fs.readFileSync('shows.json'))
|
||||
res.render("removeshow", { title: "Remove Show", showList: showList });
|
||||
});
|
||||
|
||||
app.get("/shows/edit", (req, res) => {
|
||||
showList = JSON.parse(fs.readFileSync('shows.json'))
|
||||
res.render("editShow", { title: "Edit Show", showList: showList });
|
||||
});
|
||||
|
||||
app.post('/addNewShow', [
|
||||
check('showName')
|
||||
.isLength({ min: 1 })
|
||||
], (req, res) => {
|
||||
if (validationResult(req).isEmpty()) {
|
||||
addNewShow(req.body)
|
||||
res.redirect("/shows");
|
||||
} else {
|
||||
log.error('You cannot add a show without a name.')
|
||||
res.redirect("/shows");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
app.post('/removeShow', (req, res) => {
|
||||
removeShow(req.body)
|
||||
res.redirect("/shows");
|
||||
});
|
||||
|
||||
app.post('/editShow', (req, res) => {
|
||||
editShow(req.body)
|
||||
res.redirect("/shows");
|
||||
});
|
||||
}
|
20
telegramCommunication.js
Normal file
20
telegramCommunication.js
Normal file
@ -0,0 +1,20 @@
|
||||
const fs = require("fs");
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
const token = JSON.parse(fs.readFileSync('config.json')).TelegramBotID;
|
||||
const chatId = JSON.parse(fs.readFileSync('config.json')).TelegramChatID;
|
||||
|
||||
const bot = new TelegramBot(token, { polling: false });
|
||||
|
||||
const telegrambot = (message) => {
|
||||
try {
|
||||
bot.sendMessage(chatId, message, {
|
||||
parse_mode: 'html'
|
||||
});
|
||||
} catch (err) {
|
||||
console.log('Something went wrong when trying to send a Telegram notification', err);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
telegrambot
|
||||
}
|
61
utils.js
Normal file
61
utils.js
Normal file
@ -0,0 +1,61 @@
|
||||
const fs = require('fs');
|
||||
config = JSON.parse(fs.readFileSync('config.json'))
|
||||
var moment = require('moment');
|
||||
|
||||
function returnUpdatedDate(date, offset) {
|
||||
var newDate = moment(date);
|
||||
newDate.add(offset, 'm');
|
||||
return new moment(newDate).format("ddd, LTS")
|
||||
}
|
||||
|
||||
function nextRssRefresh() {
|
||||
return returnUpdatedDate(global.rssRefreshTime, config.RSSFeedRefreshMins)
|
||||
}
|
||||
|
||||
function nextLinkCheck() {
|
||||
return returnUpdatedDate(global.linkCheckTime, config.JDPostLinksMins)
|
||||
}
|
||||
|
||||
function get_last_downloaded() {
|
||||
history = JSON.parse(fs.readFileSync('./cache/downloadHistory.json'))
|
||||
last = history.slice(-1)[0]
|
||||
return last
|
||||
}
|
||||
|
||||
function create_empty_downloadHistory() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync('./cache/downloadHistory.json'));
|
||||
} catch (error) {
|
||||
fs.writeFileSync('./cache/downloadHistory.json', JSON.stringify([]));
|
||||
return JSON.parse(fs.readFileSync('./cache/downloadHistory.json'));
|
||||
}
|
||||
}
|
||||
|
||||
function create_empty_retry_cache() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync('./cache/retryCache.json'));
|
||||
} catch (error) {
|
||||
fs.writeFileSync('./cache/retryCache.json', JSON.stringify([]));
|
||||
return JSON.parse(fs.readFileSync('./cache/retryCache.json'));
|
||||
}
|
||||
}
|
||||
|
||||
function create_empty_shows_file() {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync('./shows.json'));
|
||||
} catch (error) {
|
||||
fs.writeFileSync('./shows.json', JSON.stringify([]));
|
||||
return JSON.parse(fs.readFileSync('./shows.json'));
|
||||
}
|
||||
}
|
||||
|
||||
function create_empty_cache_files() {
|
||||
create_empty_downloadHistory()
|
||||
create_empty_retry_cache()
|
||||
create_empty_shows_file()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
nextRssRefresh, nextLinkCheck, get_last_downloaded, create_empty_cache_files
|
||||
}
|
||||
|
18
views/addshow.pug
Normal file
18
views/addshow.pug
Normal file
@ -0,0 +1,18 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner Add Show
|
||||
div.Message
|
||||
form(action="/addNewShow" method="POST")
|
||||
p Show Name:
|
||||
input(type="text" name="showName" placeholder="Enter the show to track ")
|
||||
p Quality:
|
||||
select(name="quality")
|
||||
option(value='720') #{'720p'}
|
||||
option(value='1080') #{'1080p'}
|
||||
option(value='2160') #{'2160p'}
|
||||
input(type="submit", value="Add Show")
|
||||
div.NavButtons
|
||||
a(href="/")
|
||||
div.NavButton Home
|
20
views/editShow.pug
Normal file
20
views/editShow.pug
Normal file
@ -0,0 +1,20 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner Edit Show
|
||||
div.Message
|
||||
form(action="/editShow" method="POST")
|
||||
p Show Name:
|
||||
select(name="showName")
|
||||
each show in showList
|
||||
option(value=show.Name) #{show.Name}
|
||||
p Quality:
|
||||
select(name="quality")
|
||||
option(value='720') #{'720p'}
|
||||
option(value='1080') #{'1080p'}
|
||||
option(value='2160') #{'2160p'}
|
||||
input(type="submit", value="Edit Show")
|
||||
div.NavButtons
|
||||
a(href="/")
|
||||
div.NavButton Home
|
26
views/feedCache.pug
Normal file
26
views/feedCache.pug
Normal file
@ -0,0 +1,26 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner Feed Cache
|
||||
if (feedCache.length==0)
|
||||
div.Message
|
||||
h2 No shows in Feed Cache
|
||||
else
|
||||
div.Message
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th Show Name
|
||||
//- th Remove
|
||||
tbody
|
||||
each val, key in feedCache
|
||||
tr
|
||||
td
|
||||
a(href=val.link
|
||||
target="_blank") #{val.title}
|
||||
//- td
|
||||
//- a(href='/retryCache/remove?name=' + val.newtitle) Remove
|
||||
div.NavButtons
|
||||
a(href="/")
|
||||
div.NavButton Home
|
33
views/index.pug
Normal file
33
views/index.pug
Normal file
@ -0,0 +1,33 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner JDRssDownloader #{version}
|
||||
body
|
||||
div.Message
|
||||
h2 Number of Tracked Shows
|
||||
h3 #{showListLength}
|
||||
h2 Last Downloaded
|
||||
h3 #{lastDownloaded}
|
||||
div.Message
|
||||
h2
|
||||
a(href='/feedCache') RSS Feed Cache Size
|
||||
h3 #{feedCacheLength}
|
||||
h2
|
||||
a(href='/retryCache') Retry Cache Size
|
||||
h3 #{retryCacheLength}
|
||||
h2 Next RSS Refresh
|
||||
h3 #{rssTime}
|
||||
h2 Next Link Check
|
||||
h3 #{linkCheck}
|
||||
div.NavButtons
|
||||
a(href="/shows")
|
||||
div.NavButton Show List
|
||||
a(href="/shows/add")
|
||||
div.NavButton Add New Show
|
||||
a(href="/shows/edit")
|
||||
div.NavButton Edit Show
|
||||
a(href="/shows/remove")
|
||||
div.NavButton Remove Show
|
||||
a(href="/logFile")
|
||||
div.NavButton Logs
|
13
views/layout.pug
Normal file
13
views/layout.pug
Normal file
@ -0,0 +1,13 @@
|
||||
block variables
|
||||
doctype html
|
||||
html
|
||||
head
|
||||
meta(charset="utf-8")
|
||||
link(rel="shortcut icon", href="/favicon.ico")
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1, shrink-to-fit=yes")
|
||||
meta(name="theme-color", content="#000000")
|
||||
title #{title} | JDRssDownloader
|
||||
link(rel="stylesheet" href="/style.css")
|
||||
body
|
||||
div#root
|
||||
block layout-content
|
11
views/logFile.pug
Normal file
11
views/logFile.pug
Normal file
@ -0,0 +1,11 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner Log File
|
||||
div.Message
|
||||
each val in logFile
|
||||
li= val
|
||||
div.NavButtons
|
||||
a(href="/")
|
||||
div.NavButton Home
|
15
views/removeshow.pug
Normal file
15
views/removeshow.pug
Normal file
@ -0,0 +1,15 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner Remove Show
|
||||
div.Message
|
||||
form(action="/removeShow" method="POST")
|
||||
p Show Name:
|
||||
select(name="showName")
|
||||
each show in showList
|
||||
option(value=show.Name) #{show.Name}
|
||||
input(type="submit", value="Remove Show")
|
||||
div.NavButtons
|
||||
a(href="/")
|
||||
div.NavButton Home
|
26
views/retryCache.pug
Normal file
26
views/retryCache.pug
Normal file
@ -0,0 +1,26 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner Retry Cache
|
||||
if (retryCache.length==0)
|
||||
div.Message
|
||||
h2 No shows in Retry Cache
|
||||
else
|
||||
div.Message
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th Show Name
|
||||
th Remove
|
||||
tbody
|
||||
each val, key in retryCache
|
||||
tr
|
||||
td
|
||||
a(href=val.link
|
||||
target="_blank") #{val.title}
|
||||
td
|
||||
a(href='/retryCache/remove?name=' + val.newtitle) Remove
|
||||
div.NavButtons
|
||||
a(href="/")
|
||||
div.NavButton Home
|
19
views/shows.pug
Normal file
19
views/shows.pug
Normal file
@ -0,0 +1,19 @@
|
||||
extends layout
|
||||
|
||||
block layout-content
|
||||
div.View
|
||||
h1.Banner Show List
|
||||
div.Message
|
||||
table
|
||||
thead
|
||||
tr
|
||||
th Show Name
|
||||
th Quality
|
||||
tbody
|
||||
each val, key in showList
|
||||
tr
|
||||
td= val.Name
|
||||
td= val.Quality
|
||||
div.NavButtons
|
||||
a(href="/")
|
||||
div.NavButton Home
|
Loading…
x
Reference in New Issue
Block a user