@egeria/1337x-plugin
Version:
Egeria | 1337x plugin
158 lines (142 loc) • 5.28 kB
JavaScript
/*
.--. .-'. .--. .--. .--. .--. .`-. .--.
:::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\
' `--' `.-' `--' `--' `--' `-.' `--' `
Egeria - She bestows Knowledge and Wisdom
Copyright (C) 2016-2019 MySidesTheyAreGone <mysidestheyaregone@protonmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
.--. .-'. .--. .--. .--. .--. .`-. .--.
:::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\
' `--' `.-' `--' `--' `--' `-.' `--' `
*/
const R = require('ramda')
const T = require('@egeria/tools')
const W = require('@egeria/httplib')
const divine = require('@egeria/divine').video
const filesize = require('file-size')
const urllib = require('url')
const magnetlib = require('magnet-uri')
const clean = R.pipe(R.defaultTo(''), R.replace(/\r?\n|\r/g, ' '), R.trim, R.replace(/\s+/g, ' '))
function mkSearchUrl (proxy, search, category = 'Other', sortBy = 'time', order = 'desc') {
return `https://${proxy}/sort-category-search/${encodeURIComponent(search)}/${category}/${sortBy}/${order}/1/`
}
async function execute (api, cfg, topTorrentOnly = false, origin = '1337xSearch') {
let proxy = R.defaultTo('1337x.to', cfg.proxy)
let url = mkSearchUrl(proxy, cfg.search, cfg.category, cfg.sortBy, cfg.order)
let titleIsRejected = R.anyPass(R.map(R.pipe(T.mkRegExp, R.test), R.defaultTo([], cfg.reject)))
let body = await api.enqueue(() => W.httpreq({ url }))
let torrents = await W.xscrape(body.body, 'tr', [{
title: 'td.name a[href*="torrent"]',
torrentPage: 'td.name a[href*="torrent"]@href',
seeds: 'td.seeds',
leeches: 'td.leeches',
date: 'td.coll-date',
size: 'td.size',
uploader: 'td.uploader'
}])
let topTorrent
for (let torrent of torrents) {
torrent.title = clean(torrent.title)
if (titleIsRejected(torrent.title)) {
continue
}
let torrentQuality = divine(torrent.title)
let rejectedByQualityFilters = false
for (let type in cfg.quality) {
if (R.isNil(torrentQuality[type]) || !R.contains(torrentQuality[type], cfg.quality[type])) {
rejectedByQualityFilters = true
break
}
}
if (rejectedByQualityFilters) {
continue
}
torrent.torrentPage = urllib.resolve(`https://${proxy}`, torrent.torrentPage)
let torrentPage = await api.enqueue(() => W.httpreq({ url: torrent.torrentPage }))
let magnet = W.scrapeHTML('a[href*="magnet:?"]', 'href', torrentPage.body)
if (T.isMissing(magnet) || T.isMissing(magnet[0])) {
continue
}
torrent = R.merge(torrent, torrentQuality)
torrent.key = magnetlib.decode(magnet[0]).xt
torrent.download = magnet[0]
torrent.hrsize = filesize(parseInt(torrent.size)).human()
let metadata = T.collapse('1337x:', torrent)
metadata.key = metadata['1337x:key']
metadata.origin = origin
if (topTorrentOnly) {
topTorrent = metadata
break
} else {
api.announce(metadata)
}
}
return topTorrent
}
async function searchAction (api, cfg) {
await execute(api, cfg)
}
async function lookupAction (api, cfg, fact) {
let search = fact.applyTemplate(cfg.search)
if (T.isMissing(search)) {
return fact
}
let topTorrent = await execute(api, R.assoc('search', search, cfg), true, '1337xLookup')
if (T.isMissing(topTorrent)) {
return fact
} else {
return fact.map(R.merge(topTorrent))
}
}
let sanity = {
type: 'object',
required: ['category', 'search', 'sortBy'],
properties: {
proxy: { type: 'string' },
category: { type: 'string', enum: ['Other', 'TV', 'Movies'] },
search: { type: 'string' },
sortBy: { type: 'string', enum: ['seeders', 'time'] },
order: { type: 'string', enum: ['asc', 'desc'] },
quality: {
type: 'object',
properties: {
audioCodec: { type: 'array' },
videoCodec: { type: 'array' },
videoResolution: { type: 'array' },
videoSource: { type: 'array' }
}
},
reject: { type: 'array' }
}
}
const limits = {
upstream: 'http',
concurrency: 4,
delay: 1,
timeout: 60
}
const plugins = {
'1337xSearch': {
type: 'input',
requires: ['schedule'],
sanity,
limits,
act: searchAction
},
'1337xLookup': {
type: 'mutator',
sanity,
limits,
act: lookupAction
}
}
module.exports = plugins