henry_flower (
henry_flower) wrote2019-03-23 11:16 am
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
You're coming in wall to wall & tree top tall
Якщо переписати зе мейкфайлу з попереднього посту на ноуді, з кон ла преcунцьйоне що зе скрипта буде коротшим, нас чекає страшне розчарування:
$ cloc.node w* | grep ^[LJm]
Language files blank comment code
JavaScript 1 18 3 114
make 1 19 1 28
Це, звичайно, не настільки погано, як експерименти людей що пишуть суто для віндюка:
$ curl -s https://github.com/pbatard/Fido/raw/master/Fido.ps1 \
| cloc --stdin-name=1.ps1 - | grep ^[LP]
Language files blank comment code
PowerShell 1 45 61 612
але виявляється що з 1970x нічого кращого так і не з'явилося.
(плаче)
#!/usr/bin/env node
'use strict'
let spawn = require('child_process').spawnSync
let path = require('path')
let util = require('util')
let log = util.debuglog(progname())
let cheerio = require('cheerio')
let node_fetch = require('node-fetch')
function main() { links().then(console.log).catch(err) }
function links() {
let session_id = spawn('uuidgen').stdout.toString().trim()
let rel = new exports.Releases('Select edition', session_id)
let langs = new exports.Languages('Select the product language', session_id, rel)
let platforms = new Platforms(null, session_id, rel, langs)
return rel.run().then( id => langs.run(id))
.then( lang_id => platforms.url(lang_id))
.then( url => platforms.fetch_and_parse(url))
.then(platforms.links) // finally!
}
exports.links = links
class Menu {
constructor(desc, session_id) {
this.desc = desc
this.session_id = session_id
}
async fetch_and_parse(url) { return this.$ = await exports.fetch(url).then(cheerio.load) }
answer(list) { return list[exports.dialog(this.desc, Object.keys(list))] }
find(id) { return Object.keys(this.db).find( v => this.db[v] === id) }
url_from(node) {
let hd = (node, name) => decodeURIComponent(node.data(name))
let url = new URL('https://www.microsoft.com/en-us/api/controls/contentinclude/html')
url.searchParams.set('sdVersion', 2)
url.searchParams.set('sessionId', this.session_id)
url.searchParams.set('pageId', hd(node, "defaultpageid"))
url.searchParams.set('host', hd(node, "host"))
url.searchParams.set('segments', hd(node, "host-segments"))
return url
}
run(id) {
return Promise.resolve(this.url(id))
.then(this.fetch_and_parse.bind(this))
.then(this.list.bind(this))
.then(this.answer.bind(this))
}
}
exports.Releases = class extends Menu {
url() { return 'https://www.microsoft.com/en-us/software-download/windows10ISO/' }
// { 'Windows 10 October 2018 Update': '1060',
// 'Windows 10 April 2018 Update': '651' }
list($) {
let r = $("option:not([value=])").get().reduce( (acc, cur) => {
acc[$(cur.parent).attr("label")] = $(cur).attr("value")
return acc
}, {})
log('releases list:\n', r)
return r
}
}
exports.Languages = class extends Menu {
constructor(desc, session_id, rel) {
super(desc, session_id)
this.rel = rel
}
url(release_id) {
let url = this.url_from(this.rel.$("#SoftwareDownload_LanguageSelectionByProductEdition"))
url.searchParams.set('action', 'getskuinformationbyproductedition')
url.searchParams.set('productEditionId', release_id)
return url.toString()
}
// { Arabic: '7604',
// 'Brazilian Portuguese': '7629', ... }
list($) {
let r = $("option:not([value=])").get().reduce( (acc, cur) => {
acc[$(cur).html()] = JSON.parse($(cur).attr("value")).id
return acc
}, {})
log('lang list:\n', r)
this.db = r
return r
}
}
class Platforms extends Menu {
constructor(desc, session_id, rel, langs) {
super(desc, session_id)
this.rel = rel
this.langs = langs
}
url(lang_id) {
let url = this.url_from(this.rel.$("#SoftwareDownload_DownloadLinks"))
url.searchParams.set('action', 'GetProductDownloadLinksBySku')
url.searchParams.set('skuId', lang_id)
url.searchParams.set('language', this.langs.find(lang_id))
return url.toString()
}
links($) {
return $("a.button").get().map( v => $(v).attr("href")).join`\n`
}
}
exports.fetch = function(url, opt) {
log('fetch:', url)
let fetcherr = r => { if (r.ok) return r; throw new Error(r.status) }
return node_fetch(url, opt).then(fetcherr).then( r => r.text())
}
exports.dialog = function(desc, menu_tags) {
let r = spawn('dialog', ['--keep-tite', '--no-items', '--menu', desc,
'0', '0', '0', ...menu_tags],
{stdio: [0, 1, 'pipe']})
if (r.status !== 0) throw new Error('user interrupt')
return r.stderr.toString()
}
function err(s) {
console.error(progname(), 'error:', s instanceof Error ? s.message : s)
if (s instanceof Error) log(s.stack)
process.exit(1)
}
function progname() { return path.basename(process.argv[1]); }
if (require.main === module) main()
(вимагає `npm i cheerio node-fetch`; `exports.*` ув коді то було для тестів.)