Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
BoredLama committed Apr 4, 2019
0 parents commit 77c86c9
Show file tree
Hide file tree
Showing 5 changed files with 464 additions and 0 deletions.
38 changes: 38 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"jackettKey": {
"title": "Jackett API Key",
"type": "string",
"default": "",
"required": true
},
"jackettHost": {
"title": "Jackett host",
"type": "string",
"default": "http://127.0.0.1:9117/"
},
"respTimeout": {
"title": "Response timeout",
"type": "number",
"default": 11000
},
"minimumSeeds": {
"title": "Minimum torrent seeds",
"type": "number",
"default": 3
},
"maximumResults": {
"title": "Maximum torrents for a response, 0 = no limit",
"type": "number",
"default": 15
},
"openTimeout": {
"title": "Requests open timeout",
"type": "number",
"default": 10000
},
"readTimeout": {
"title": "Requests read timeout",
"type": "number",
"default": 10000
}
}
122 changes: 122 additions & 0 deletions helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@

const modules = require('./modules')

const ticker = {}

const helper = {

isObject: (s) => {
return (s !== null && typeof s === 'object')
},

setTicker: (ticks, cb) => {

// const tick = setTicket(3, callback); tick(); tick(); tick();

const tag = Date.now()

ticker[tag] = ticks

return () => {
ticker[tag]--
if (!ticker[tag]) {
delete ticker[tag]
cb()
}
}

},

followRedirect: (url, cb) => {
if (!url.startsWith('magnet:')) {
// follow redirect to see if the jackett url is a torrent link or a magnet link
modules.get.needle.get(url, (err, resp, body) => {
if (resp && resp.headers && resp.headers.location)
cb(resp.headers.location)
else
cb(url)
})
return
}
cb(url)
},

episodeTag: (season, episode) => {
return 'S' + ('0' + season).slice(-2) + 'E' + ('0' + episode).slice(-2)
},

simpleName: (name) => {

// Warning: black magic ahead

name = name.replace(/\.|_|\-|\–|\(|\)|\[|\]|\:|\,/g, ' ') // remove all unwanted characters
name = name.replace(/\s+/g, ' ') // remove duplicate spaces
name = name.replace(/\\\\/g, '\\').replace(new RegExp('\\\\\'|\\\'|\\\\\"|\\\"', 'g'), '') // remove escaped quotes

return name
},

extraTag: (name, searchQuery) => {

// Warning: black magic ahead

const parsedName = modules.get['video-name-parser'](name + '.mp4')

let extraTag = helper.simpleName(name)

searchQuery = helper.simpleName(searchQuery)

// remove search query from torrent title
extraTag = extraTag.replace(new RegExp(searchQuery, 'gi'), '')

// remove parsed movie/show title from torrent title
extraTag = extraTag.replace(new RegExp(parsedName.name, 'gi'), '')

// remove year
if (parsedName.year)
extraTag = extraTag.replace(parsedName.year+'', '')

// remove episode tag
if (parsedName.season && parsedName.episode && parsedName.episode.length)
extraTag = extraTag.replace(new RegExp(helper.episodeTag(parsedName.season, parsedName.episode[0]), 'gi'), '')

// send to barber shop
extraTag = extraTag.trim()

let extraParts = extraTag.split(' ')

// scenarios where extraTag starts with '06', and it refers to 'S03E01-06'
// in this case we'll add the episode tag back in the title so it makes sense
if (parsedName.season && parsedName.episode && parsedName.episode.length) {
if (extraParts[0] && (extraParts[0] + '').length == 2 && !isNaN(extraParts[0])) {
const possibleEpTag = helper.episodeTag(parsedName.season, parsedName.episode[0]) + '-' + extraParts[0]
if (name.toLowerCase().includes(possibleEpTag.toLowerCase())) {
extraParts[0] = possibleEpTag
}
}
}

const foundPart = name.toLowerCase().indexOf(extraParts[0].toLowerCase())

if (foundPart > -1) {

// clean up extra tags, we'll allow more characters here
extraTag = name.substr(foundPart).replace(/_|\(|\)|\[|\]|\,/g, ' ')

// remove dots, only if there are more then 1. one still makes
// sense for cases like '1.6gb', but in cases of more it is
// probably used instead of space
if ((extraTag.match(/\./g) || []).length > 1)
extraTag = extraTag.replace(/\./g, ' ')

// remove duplicate space
extraTag = extraTag.replace(/\s+/g,' ')

}

return extraTag

}
}

module.exports = helper
175 changes: 175 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
const modules = require('./modules')
const jackettApi = require('./jackett')
const helper = require('./helpers')

const streamFromMagnet = (tor, uri, type, cb) => {
const parseTorrent = modules.get['parse-torrent']
const toStream = (parsed) => {

const infoHash = parsed.infoHash.toLowerCase()

let title = tor.extraTag || parsed.name

const subtitle = tor.seeders + ' S / ' + tor.peers + ' L'

title += '\r\n' + subtitle

cb({
name: tor.from,
type: type,
infoHash: infoHash,
sources: (parsed.announce || []).map(x => { return "tracker:"+x }).concat(["dht:"+infoHash]),
title: title
})
}
if (uri.startsWith("magnet:?")) {
toStream(parseTorrent(uri))
} else {
parseTorrent.remote(uri, (err, parsed) => {
if (err) {
cb(false)
return
}
toStream(parsed)
})
}
}

module.exports = {
manifest: () => {
return {
"id": "org.stremio.jackett",
"version": "1.0.0",

"name": "Jackett",
"description": "Stremio Add-on to get torrent results from Jackett",

"icon": "https://static1.squarespace.com/static/55c17e7ae4b08ccd27be814e/t/599b81c32994ca8ff6c1cd37/1508813048508/Jackett-logo-2.jpg",

"resources": [
"stream"
],

"types": ["movie", "series"],

"idPrefixes": [ "tt" ],

"catalogs": []

}
},
handler: (args, local) => {
modules.set(local.modules)
const config = local.config
const cinemeta = modules.get.internal.cinemeta
const async = modules.get.async
return new Promise((resolve, reject) => {

if (args.resource != 'stream'){
reject(new Error('Resource Unsupported'))
return
}

if (!args.id) {
reject(new Error('No ID Specified'))
return
}

let results = []

let sentResponse = false

const respondStreams = () => {

if (sentResponse) return
sentResponse = true

if (results && results.length) {

tempResults = results

// filter out torrents with less then 3 seeds

if (config.minimumSeeds)
tempResults = tempResults.filter(el => { return !!(el.seeders && el.seeders > config.minimumSeeds -1) })

// order by seeds desc

tempResults = tempResults.sort((a, b) => { return a.seeders < b.seeders ? 1 : -1 })

// limit to 15 results

if (config.maximumResults)
tempResults = tempResults.slice(0, config.maximumResults)

const streams = []

const q = async.queue((task, callback) => {
if (task && (task.magneturl || task.link)) {
const url = task.magneturl || task.link
// jackett links can sometimes redirect to magnet links or torrent files
// we follow the redirect if needed and bring back the direct link
helper.followRedirect(url, url => {
// convert torrents and magnet links to stream object
streamFromMagnet(task, url, args.type, stream => {
if (stream)
streams.push(stream)
callback()
})
})
return
}
callback()
}, 1)

q.drain = () => {
resolve({ streams: streams })
}

tempResults.forEach(elm => { q.push(elm) })
} else {
resolve({ streams: [] })
}
}

const idParts = args.id.split(':')

const imdb = idParts[0]

cinemeta.get({ type: args.type, imdb }).then(meta => {
if (meta) {

const searchQuery = {
name: meta.name,
year: meta.year,
type: args.type
}

if (idParts.length == 3) {
searchQuery.season = idParts[1]
searchQuery.episode = idParts[2]
}

jackettApi.search(config, searchQuery,

partialResponse = (tempResults) => {
results = results.concat(tempResults)
},

endResponse = (tempResults) => {
results = tempResults
respondStreams()
})


if (config.responseTimeout)
setTimeout(respondStreams, config.responseTimeout)

} else {
resolve({ streams: [] })
}
})

})
}
}
Loading

0 comments on commit 77c86c9

Please sign in to comment.