miru/src/main/torrent.js
2022-04-11 14:56:31 +02:00

204 lines
5.9 KiB
JavaScript

const { app, ipcMain } = require('electron')
const WebTorrent = require('webtorrent')
const http = require('http')
const pump = require('pump')
const rangeParser = require('range-parser')
const mime = require('mime')
const { SubtitleParser, SubtitleStream } = require('matroska-subtitles')
let window = null
app.on('browser-window-created', (event, data) => {
window = data
window.on('closed', () => {
window = null
})
})
let client = null
let settings = {}
const server = http.createServer((request, response) => {
if (!request.url) return null
let [infoHash, ...filePath] = request.url.slice(request.url.indexOf('/webtorrent/') + 12).split('/')
filePath = decodeURI(filePath.join('/'))
if (!infoHash || !filePath) return null
const file = client?.get(infoHash)?.files.find(file => file.path === filePath)
if (!file) return null
response.setHeader('Access-Control-Allow-Origin', '*')
response.setHeader('Content-Type', mime.getType(file.name) || 'application/octet-stream')
response.setHeader('Accept-Ranges', 'bytes')
let range = rangeParser(file.length, request.headers.range || '')
if (Array.isArray(range)) {
response.statusCode = 206
range = range[0]
response.setHeader(
'Content-Range',
`bytes ${range.start}-${range.end}/${file.length}`
)
response.setHeader('Content-Length', range.end - range.start + 1)
} else {
response.statusCode = 200
range = null
response.setHeader('Content-Length', file.length)
}
if (response.method === 'HEAD') {
return response.end()
}
let stream = file.createReadStream(range)
if (stream && !parsed) {
if (file.name.endsWith('.mkv')) {
parserInstance = new SubtitleStream(parserInstance)
handleSubtitleParser(parserInstance, true)
stream = pump(stream, parserInstance)
}
}
pump(stream, response)
})
server.on('error', console.log)
server.listen(41785)
let current = null
let parsed = false
let parserInstance = null
function parseSubtitles () {
if (current.name.endsWith('.mkv')) {
const parser = new SubtitleParser()
handleSubtitleParser(parser, true)
const finish = () => {
console.log('Sub parsing finished')
parsed = true
fileStream?.destroy()
stream?.destroy()
stream = undefined
}
parser.once('tracks', tracks => {
if (!tracks.length) finish()
})
parser.once('finish', finish)
console.log('Sub parsing started')
const fileStream = current.createReadStream()
let stream = fileStream.pipe(parser)
}
}
function parseFonts (file) {
const stream = new SubtitleParser()
handleSubtitleParser(stream)
stream.once('tracks', tracks => {
if (!tracks.length) {
parsed = true
stream.destroy()
fileStreamStream.destroy()
}
})
stream.once('subtitle', () => {
fileStreamStream.destroy()
stream.destroy()
window?.webContents.send('fonts')
})
const fileStreamStream = file.createReadStream()
fileStreamStream.pipe(stream)
}
function handleSubtitleParser (parser, skipFile) {
parser.once('tracks', tracks => {
if (!tracks.length) {
parsed = true
parser?.destroy()
} else {
window?.webContents.send('tracks', tracks)
}
})
parser.on('subtitle', (subtitle, trackNumber) => {
window?.webContents.send('subtitle', { subtitle, trackNumber })
})
if (!skipFile) {
parser.on('file', file => {
if (file.mimetype === 'application/x-truetype-font' || file.mimetype === 'application/font-woff' || file.mimetype === 'application/vnd.ms-opentype') {
window?.webContents.send('file', { mimetype: file.mimetype, data: Array.from(file.data) })
}
})
}
}
ipcMain.on('current', (event, data) => {
current?.removeListener('done', parseSubtitles)
parserInstance?.destroy()
parserInstance = null
current = null
parsed = false
if (data) {
current = client?.get(data.infoHash)?.files.find(file => file.path === data.path)
if (current.name.endsWith('.mkv')) {
if (current.done) parseSubtitles()
current.on('done', parseSubtitles)
parseFonts(current)
}
// findSubtitleFiles(current) TODO:
}
})
ipcMain.on('settings', (event, data) => {
if (!client) {
settings = data
client = new WebTorrent({
dht: !settings.torrentDHT,
downloadLimit: settings.torrentSpeed * 1048576 || 0,
uploadLimit: settings.torrentSpeed * 1572864 || 0 // :trolled:
})
setInterval(() => {
window?.webContents?.send('stats', {
numPeers: (client?.torrents.length && client?.torrents[0].numPeers) || 0,
uploadSpeed: (client?.torrents.length && client?.torrents[0].uploadSpeed) || 0,
downloadSpeed: (client?.torrents.length && client?.torrents[0].downloadSpeed) || 0
})
}, 200)
setInterval(() => {
if (client?.torrents[0]?.pieces) window?.webContents?.send('pieces', [...client?.torrents[0]?.pieces.map(piece => piece === null ? 77 : 33)])
}, 2000)
client.on('torrent', torrent => {
const files = torrent.files.map(file => {
return {
infoHash: torrent.infoHash,
name: file.name,
type: file._getMimeType(),
size: file.size,
path: file.path,
url: encodeURI('http://localhost:41785/webtorrent/' + torrent.infoHash + '/' + file.path)
}
})
window?.webContents.send('files', files)
window?.webContents.send('pieces', torrent.pieces.length)
window?.webContents.send('torrent', Array.from(torrent.torrentFile))
})
}
})
ipcMain.on('torrent', (event, data) => {
if (client.torrents.length) client.remove(client.torrents[0].infoHash)
if (typeof data !== 'string') data = Buffer.from(data)
client.add(data, {
private: settings.torrentPeX,
path: settings.torrentPath,
destroyStoreOnDestroy: !settings.torrentPersist,
announce: [
'wss://tracker.openwebtorrent.com',
'wss://spacetradersapi-chatbox.herokuapp.com:443/announce',
'wss://peertube.cpy.re:443/tracker/socket'
]
})
})