UNPKG

@egeria/1337x-plugin

Version:

Egeria | 1337x plugin

158 lines (142 loc) 5.28 kB
/* .--. .-'. .--. .--. .--. .--. .`-. .--. :::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\::::::::.\ ' `--' `.-' `--' `--' `--' `-.' `--' ` 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