const process = require('process'); const needle = require('needle'); const cheerio = require('cheerio'); const minimist = require('minimist'); const config = minimist(process.argv.slice(2), {default: { root: 'https://content.minetest.net/', user: false, pass: false, pkg: false, version: false, min: 'none', max: 'none', branch: 'master', timeout: 60, poll: 2 }}); const missing = []; Object.keys(config).sort().forEach(k => { if(k === '_') return; const v = config[k]; if(!v || v === false) missing.push(k); }); if(missing.length) throw 'missing/invalid options: ' + missing.join(', '); config.root = config.root.replace(/\/+$/, ''); const cookiejar = {}; async function fetch(uri, meth, data, opts, ref, ...rest) { meth = meth || 'get'; opts = opts || {}; opts.headers = opts.headers || {}; opts.headers.Referer = 'https://content.minetest.net'; opts.cookies = cookiejar; const r = await needle(meth, uri, data, opts, ...rest); if(r.cookies) Object.keys(r.cookies).forEach(k => cookiejar[k] = r.cookies[k]); const $ = cheerio.load(r.body.toString()); let err; $('h1').each(function() { const t = $(this).text(); if(/^\s*Oops!/.test(t)) err = t; }); if(err) throw err; return $; } function getfields($, sel) { const fields = {}; $('input').each(function() { const e = $(this); const n = e.attr('name'); if(!n) return; const v = e.attr('value'); if(v) fields[n] = v; }); return fields; } async function login() { const uri = config.root + '/user/sign-in'; console.log('fetching sign-in page...'); let $ = await fetch(uri); const fields = getfields($); fields.username = config.user; fields.password = config.pass; console.log('submitting sign-in form...'); $ = await fetch(uri, 'post', fields); let success; $('h1').each(function() { success = success || /^\s*Redirecting\b/i.test($(this).text()); }); if(!success) throw $('body').text(); } async function checkalready() { const uri = config.root + '/packages/' + config.user + '/' + config.pkg + '/'; console.log('fetching release list...'); let $ = await fetch(uri); let already; $('a').each(function() { if(already) return; const a = $(this); const h = a.attr('href'); if(!h || !/\/releases\/\d+\/download\//.test(h)) return; if(a.text().trim() === config.version.trim()) already = true; }); return already; } function findversion($, sel, name) { let v; $(sel).each(function() { const o = $(this); const t = o.text(); if(t.toLowerCase() === name.toLowerCase()) v = o.attr('value'); }); return v; } async function makerelease() { const uri = config.root + '/packages/' + config.user + '/' + config.pkg + '/releases/new/'; console.log('fetching new release page...'); let $ = await fetch(uri); const fields = getfields($); fields.min_rel = findversion($, 'select#min_rel option', config.min); fields.max_rel = findversion($, 'select#max_rel option', config.max); fields.vcsLabel = config.branch; fields.uploadOpt = 'vcs'; fields.title = config.version; console.log('submitting new release...'); $ = await fetch(uri, 'post', fields, {follow_max: 10}); } (async () => { await login(); console.log('checking if new version is already released...'); if(await checkalready()) { console.log('version ' + config.version + ' already released'); return; } await makerelease(); console.log('waiting for new submission to be listed...'); for(let i = 0; i < config.timeout; i += config.poll) { if(await checkalready()) { console.log('version ' + config.version + ' release successful'); return; } await new Promise(r => setTimeout(r, config.poll * 1000)); } })();