henry_flower: A melancholy wolf (Default)
henry_flower ([personal profile] henry_flower) wrote2019-03-23 11:16 am

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.*` ув коді то було для тестів.)


Post a comment in response:

If you don't have an account you can create one now.
HTML doesn't work in the subject.
More info about formatting