From b54b08442a772d1617a14676c21c12988af3fb84 Mon Sep 17 00:00:00 2001 From: Jan Andrle Date: Mon, 19 Feb 2024 20:37:09 +0100 Subject: [PATCH] :zap: add bin (scripts) --- .config/uurc | 9 + bin/bw-ftp.js | 76 +++++++ bin/chrome-autoinspect.mjs | 82 ++++++++ bin/github-releases.js | 418 +++++++++++++++++++++++++++++++++++++ bin/github-releases.json | 216 +++++++++++++++++++ bin/jsconfig.json | 8 + bin/nocodb.mjs | 19 ++ bin/onedrive | 9 + bin/piper.mjs | 71 +++++++ bin/pocket-sh-add.sh | 10 + bin/socky.mjs | 80 +++++++ bin/uu | 119 +++++++++++ bin/§ai-commit.mjs | 159 ++++++++++++++ bin/§awk | 26 +++ bin/§battery | 34 +++ bin/§calc | 6 + bin/§cordova-release.mjs | 60 ++++++ bin/§extract | 61 ++++++ bin/§mail.mjs | 94 +++++++++ bin/§software | 71 +++++++ bin/§trans.mjs | 22 ++ bin/§ubuntu-info | 58 +++++ bin/§vim_cache_clean | 3 + bin/§vim_plugins.mjs | 281 +++++++++++++++++++++++++ bin/§wallpaper_BIOTD.mjs | 44 ++++ bin/§wolframalpha.mjs | 20 ++ 26 files changed, 2056 insertions(+) create mode 100644 .config/uurc create mode 100755 bin/bw-ftp.js create mode 100755 bin/chrome-autoinspect.mjs create mode 100755 bin/github-releases.js create mode 100644 bin/github-releases.json create mode 100644 bin/jsconfig.json create mode 100755 bin/nocodb.mjs create mode 100755 bin/onedrive create mode 100755 bin/piper.mjs create mode 100755 bin/pocket-sh-add.sh create mode 100755 bin/socky.mjs create mode 100755 bin/uu create mode 100755 bin/§ai-commit.mjs create mode 100755 bin/§awk create mode 100755 bin/§battery create mode 100755 bin/§calc create mode 100755 bin/§cordova-release.mjs create mode 100755 bin/§extract create mode 100755 bin/§mail.mjs create mode 100755 bin/§software create mode 100755 bin/§trans.mjs create mode 100755 bin/§ubuntu-info create mode 100755 bin/§vim_cache_clean create mode 100755 bin/§vim_plugins.mjs create mode 100755 bin/§wallpaper_BIOTD.mjs create mode 100755 bin/§wolframalpha.mjs diff --git a/.config/uurc b/.config/uurc new file mode 100644 index 0000000..76ec134 --- /dev/null +++ b/.config/uurc @@ -0,0 +1,9 @@ +#! /usr/bin/ +uu_w_alias="wttr.in/${1:-prague}?M" +uu_w_is_raw=0 +uu_h_alias="cheat.sh/$1" +uu_h_is_raw=0 +uu_surl_alias="tinyurl.com/api-create.php?url=$1" +uu_surl_is_raw=1 +uu_ip_alias="ifconfig.co/json" +uu_ip_is_raw=1 diff --git a/bin/bw-ftp.js b/bin/bw-ftp.js new file mode 100755 index 0000000..27f2302 --- /dev/null +++ b/bin/bw-ftp.js @@ -0,0 +1,76 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ +$.is_fatal= true; +const css= echo.css` + .code, .url{ color: lightblue; } + .code::before, .code::after{ content: "\`"; } +`; +testRequirements(); +$.api() + .version("2023-03-21") + .describe([ + "Small utility to find out FTP url with credentials using Bitwarden CLI.", + echo.format("The idea is to use saved login %cusername%c, %cpassword%c and %curl%c.", + css.code, css.unset, css.code, css.unset, css.code, css.unset) + ]) + .command("get [name]", "Get url with credentials.") + .alias("item") + .option("--copy", echo.format("Uses %cxclip -selection clipboard%c.", css.code)) + .action(get) + .command("list", echo.format("List all %cftp-*%c.", css.code)) + .option("--json", "Print output in JSON format.") + .action(list) + .parse(); + +async function get(name, { copy: is_copy= false }){ + if(!name) + name= await $.read({ + "-p": "Name", + completions: list({ is_internal: true }).map(o=> o.name) + }); + const item= s.$().run`bw get item ${name}`; + if(!item.trim()) + $.error(`No record found for ${name}.`); + + const { uris, username, password }= item + .xargs(JSON.parse) + .login; + const url= urlFromUris(uris).replace('://', `://${username}:${password}@`); + if(!is_copy){ + echo(url); + $.exit(0); + } + s.echo(url).run`xclip -selection clipboard 2>1 > /dev/null`; + $.exit(0); +} +function list({ json= false, is_internal= false }){ + const list= s.$().run`bw list items --search="ftp"` + .xargs(JSON.parse) + .filter(o=> o.name.startsWith("ftp-")) + .map(({ name, note, login: { uris } })=> ({ name, url: urlFromUris(uris), note })) + .filter(o=> o.url); + if(is_internal) + return list; + if(json) + $.exit(0, echo(JSON.stringify(list))); + + list.forEach(pipe( + line=> echo.format(line), + t=> t.replaceAll("\n", " ").slice(2, -2), + echo + )); + $.exit(0); +} + +function urlFromUris(uris){ return uris.find(o=> o.uri)?.uri; } + +function testRequirements(){ + if(!s.which("bw")) + $.error([ + echo.format("The %cbw%c utility has not been found.", css.code), + echo.format("Please install it using %cnpm i @bitwarden/cli --location=global%c.", css.code), + echo.format("Respectively, follow the instructions at %chttps://github.com/bitwarden/clients/tree/master/apps/cli", css.url) + ].join("\n")); +} +// vim: set tabstop=4 shiftwidth=4 textwidth=250 noexpandtab : +// vim>60: set foldmethod=indent foldlevel=1 foldnestmax=2: diff --git a/bin/chrome-autoinspect.mjs b/bin/chrome-autoinspect.mjs new file mode 100755 index 0000000..145909a --- /dev/null +++ b/bin/chrome-autoinspect.mjs @@ -0,0 +1,82 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ +const css= echo.css` + .url{ color: lightblue; } + .code { font-style: italic; } + .code::before, .code::after { content: "\`"; } +`; +$.api() + .version("2023-03-23") + .describe([ + echo.format("This is small utility around %cchrome-remote-interface%c¹ to auto open inspect for cordova apps.", css.code), + echo.format("[1] %chttps://github.com/cyrus-and/chrome-remote-interface", css.url) + ]) + .command("open", "This open browser and after 30secs enable auto inspect openning.", true) + .option("--browser, -B", "Browser exec. Use one of supported browsers, such as chrome, chromium, opera, edge, …", "chromium") + .option("--port, -P", "Chanhe debugging port", 9222) + .action(async function({ browser, port }){ + s.runA`${browser} --remote-debugging-port=${port}`; + const { setTimeout }= await import("node:timers/promises"); + await setTimeout(30_000); + await register(); + $.exit(0); + }) + .command("enable", "Enable auto inspect openning for already running browser.") + .action(async function(){ + await register(); + $.exit(0); + }) + .parse(); + +async function register(){ + const { default: CDP } = await import($.xdg.globalPackage`chrome-remote-interface`); + let client; + try{ + await CDP.New(); + client= await CDP(); + const { Network, Page, Runtime }= client; + await Network.enable(); + await Page.enable(); + await Page.navigate({url: 'chrome://inspect/#devices'}); + await Page.loadEventFired(); + await Runtime.evaluate({ expression: `const debugCordova= (${autoRunCordovaAppInspect.toString()})();` }); + } finally { + if(!client) return; + return await client.close(); + } +} + +function autoRunCordovaAppInspect(){ + const { filter, map }= Array.prototype; + let /* filters */ + device_filter, app_filter, last_state; + const /* get elements */ + devicesElements= ()=> filter.call(document.getElementsByClassName("device"), el=> el.id!=="device:localhost"), + getApp= el=> el.getElementsByClassName("browser-name")[0].textContent, + appTest= browser_candidate_el=> getApp(browser_candidate_el).indexOf(app_filter)!==-1, + browsersElements= wrapper_el=> wrapper_el.getElementsByClassName("browser"), + actionElementFilter= wrapper_el=> filter.call(wrapper_el.getElementsByClassName("action"), el=> el.textContent==="inspect")[0]; + + function run(){ + const device= !device_filter ? (d=> d&&d[d.length-1])(devicesElements()) : filter.call(devicesElements(), el=> el.id===device_filter)[0]; + if(!device) return false; + const app= !app_filter ? (a=> a&&a[a.length-1])(browsersElements(device)) : filter.call(browsersElements(document), appTest)[0]; + if(!app) return false; + const { id }= app; + if(last_state===id) return false; + last_state= id; + const action= actionElementFilter(app); + if(!action) return false; + action.click(); + } + const observer= new MutationObserver(run); + observer.observe(document.getElementById("devices-list"), { childList: true, subtree: true }) + return { + /* Set string to filter, typically app id or ip */ + setApp: _app_filter=> app_filter= _app_filter, + /* Apply only for given device (see debugCordova.devices). */ + setDevice: _device_filter=> device_filter= _device_filter, + get apps(){ return map.call(browsersElements(document), getApp); }, + get devices(){ return map.call(devicesElements(), el=> el.id); } + }; +} diff --git a/bin/github-releases.js b/bin/github-releases.js new file mode 100755 index 0000000..ea499c0 --- /dev/null +++ b/bin/github-releases.js @@ -0,0 +1,418 @@ +#!/usr/bin/env node +/* jshint esversion: 8,-W097, -W040, node: true, expr: true, undef: true */ +const /* dependencies */ + [ fs, readline, https, { spawn } ]= [ "fs", "readline", "https", "child_process" ].map(p=> require(p)); +const /* helper for coloring console | main program params */ + colors= { e: "\x1b[38;2;252;76;76m", s: "\x1b[38;2;76;252;125m", w: "\x1b[33m", R: "\x1b[0m", y: "\x1b[38;2;200;190;90m", g: "\x1b[38;2;150;150;150m" }, + info= { + name: __filename.slice(__filename.lastIndexOf("/")+1, __filename.lastIndexOf(".")), + version: "1.2.1", + description: "Helper for working with “packages” stored in GitHub releases.", + config: `${__filename.slice(0, __filename.lastIndexOf("."))}.json`, + folder: __filename.slice(0, __filename.lastIndexOf("/")+1), + commands: [ + { + cmd: "help", args: [ "--help", "-h" ], + desc: "Shows this text" + }, + { + cmd: "config", args: [ "--config" ], + desc: "Opens config file in terminal editor (defaults to vim)" + }, + { + cmd: "check", args: [ "--check", "-c" ], + desc: "Shows/checks updates for registered packages" + }, + { + cmd: "update", args: [ "--update", "-u" ], param: "group", + desc: "Installs lates versions of registered packages" + }, + { + cmd: "uninstall", args: [ "--uninstall", "-u" ], param: "package", + desc: "Deletes downloaded file and moves package to the 'skip' group" + }, + { + cmd: "register", args: [ "--register", "--change" ], param: "package", + desc: "Add package infos to internal list to be able installing/updating" + }, + { + cmd: "remove", args: [ "--remove" ], param: "package", + desc: ([ + "Uninstall package if needed (see `-u`)", + "And remove it from internal list (see `--config`)" + ]).join(". ") + } + ], + params: { + group: ([ + "You can label each package to update only choosen one", + "There are sereved options:", + " - '' (empty): these packages are includes in all groups", + " - 'all': in case of `--update` process all packages (except skipped)", + " - 'skip': these packages are “uninstalled”", + " No updates will be downloaded", + "Group can be setted via '--register'" + ]).join(". "), + package: ([ + "Represents package identificator, it is in fact GitHub repository path", + "So, it schould be in the form `username/repository`" + ]).join(". ") + } + }; +printMain(); +const current= getCurrent(process.argv.slice(2)); +(function main_(){ + const { cmd }= current.command; + if(!cmd) return Promise.resolve("No arguments (use `--help` for showing all oprions)."); + switch(cmd){ + case "help": return Promise.resolve(printHelp()); + case "config": return vim_(info.config); + } + const config= getConfig(); + switch(cmd){ + case "register": return register_(config); + } + if(!config.packages) return Promise.resolve("No packages yet!"); + switch(cmd){ + case "check": return check_(config); + case "update": return update_(config); + case "uninstall": + case "remove": + return uninstall_(cmd, config); + } +})() +.then(function(message){ + if(message) + log(1, `Operation '${current.command.cmd}' successfull: @s_${message}`); + process.exit(); +}) +.catch(error); + +async function uninstall_(cmd, config){ + const progress= [ + [ "Deleting file", "not needed" ], + [ "Check out from updates", "yes" ], + [ "Remove from packages list", "no" ] + ]; + const pkg_name= current.param; + const pkg_index= config.packages.findIndex(({ repository })=> repository===pkg_name); + if(pkg_index===-1) return "nothing to do (maybe typo)"; + + const pkg= config.packages[pkg_index]; + const { downloads }= pkg; + if(downloads&&fs.existsSync(downloads)){ + try{ fs.unlinkSync(downloads); progress[0][1]= "done"; } + catch (_){ progress[0][1]= colors.e+"error, try manually – "+downloads; } + } + Reflect.deleteProperty(pkg, "last_update"); + Reflect.set(pkg, "group", "skip"); + progress[1][1]= "done"; + if(cmd!=="remove") return gotoEnd(); + + const y= await promt_(`Are you realy want to remove package ${pkg.repository} (yes/no)`, "no"); + if(y!=="yes") return gotoEnd(); + + config.packages.splice(pkg_index, 1); + progress[2][1]= "done"; + return gotoEnd(); + + function gotoEnd(){ + const o= progress.reduce((o, [ k, v ])=> Reflect.set(o, k, v)&&o, {}); + logSection(" ", pkg_name, o); + save(config); + } +} +function vim_(file){ return new Promise(function(resolve, reject){ + const cmd= spawn((process.env.EDITOR||"vim")+(process.platform==="win32"?".bat":""), [ file ], { stdio: 'inherit' }); + cmd.on('exit', e=> e ? reject("Editor error, try manually: "+file) : resolve("OK")); +});} +async function update_(config){ + const filter= current.param; + const is_all= filter==="all"; + let updates= []; + log(1, "Collecting packages to download:"); + for(const [ + i, { repository, last_update, group, file_name, exec, downloaded, tag_name_regex } + ] of Object.entries(config.packages)){ + if(group==="skip") continue; + if(!is_all&&group&&filter!==group) continue; + + const { tag_name, published_at, html_url, assets_url }= await githubRelease_(repository, tag_name_regex); + const status= packageStatus(last_update, published_at); + if(status!==3) continue; + + const assets= await downloadJSON_(repository, assets_url); + if(!assets.length){ + console.log(" Nothing to download: Visit "+html_url); + continue; + } + + const options= assets.map(({ name, download_count, size })=> + `${name} | size: ${Math.round(size/1048576)}MB | downloads: ${download_count}`); + logSection(" ", " "+repository, { + "Version": tag_name, + "Url": html_url + }); + logSection(" ", " Available assets:", options); + const choose= await promt_(" Choose (empty for skip)", ""); + if(choose==="") continue; + + const { browser_download_url: url, name: remote_name, size }= assets[choose]; + updates.push({ + index: i, + file_name, exec, downloaded, + repository, version: tag_name, last_update: published_at, + url, remote_name, size + }); + } + if(!updates.length){ + log(2, "No packages in "+`group ${filter} needs updates.`); + return Promise.resolve("nothing to update"); + } + log(1, "Downloading:"); + return applySequentially_(updates, async function(todo){ + const to= todo.file_name ? info.folder+todo.file_name : ( + todo.downloaded ? todo.downloaded : info.folder+todo.remote_name); + const d= await downloadFile_(to, todo); + return Object.assign(todo, d); + }) + .then(function(dones){ + log(1, "Finalizing:"); + let e= 0; + for(const nth of dones){ + if(!nth.success){ + e+= 1; + log(2, `${nth.repository}: @e_${nth.message}`); + continue; + } + Object.assign(config.packages[nth.index], registerDownloads(nth)); + } + save(config); + const { length }= dones; + const msg= `updated ${length-e} of ${length} packages.`; + return e ? Promise.reject(msg) : Promise.resolve(msg); + }); +} +function registerDownloads({ repository, last_update, message: downloads, exec, version }){ + let msg= colors.s+"OK"; + if(exec==="yes"){ + try{ fs.chmodSync(downloads, 0o755); } + catch(e){ msg= colors.e+"try manual `chmod+x` for '"+downloads+"'"; } + } + log(2, `${repository}: ${msg}`); + return { last_update, downloads, version }; +} +async function check_({ packages }){ + let updates= 0, skipped= 0; + for(const { repository, name, version, last_update, group, tag_name_regex } of packages){ + const { tag_name, published_at }= await githubRelease_(repository, tag_name_regex); + const status= packageStatus(last_update, published_at); + updates+= status===3; + const skip= group==="skip"; + skipped+= skip; + log(2, `@g_${repository} [${group}]: `+( !version ? "not installed" : packageStatusText(status, skip) )); + } + const u= updates-skipped; + const s= skipped ? ` (inc. skipped: ${updates})` : ""; + return (!u ? "" : colors.w)+u+" update(s) available"+s; +} +async function register_(config){ + const { param: repository }= current; + if(!Reflect.has(config, "packages")) Reflect.set(config, "packages", []); + const packages= Reflect.get(config, "packages"); + let local_id= packages.findIndex(p=> p.repository===repository); + if(local_id===-1) + local_id= packages.push({ repository })-1; + const local= config.packages[local_id]; + const remote= await githubRepo_(repository) || {}; + + log(1, "Registering: "+repository); + const spaces= " "; + local.name= await promt_(spaces+"Name", local.name || remote.name || ""); + if(!local.description) local.description= remote.description; + logLines(2, [ + "@g_Group info:", + "- you can update specific packages by using their group name", + "- There some reserved options:", + " - '' (empty): will be included in all groups", + " - 'skip': will be always skipped" + ]); + local.group= await promt_(spaces+"Group", local.group || ""); + local.file_name= await promt_(spaces+"File Name", local.file_name || local.name.toLowerCase().replace(/\s/g, "-") || ""); + local.exec= await promt_(spaces+"Make executable (yes/no)", local.exec || "no"); + save(config); + return `${repository}: saved`; +} +function packageStatusText(status, skip){ + const s= skip ? colors.R+"skipped – "+colors.g : ""; + switch(status){ + case 0: return s+"nothing to compare"; + case 1: return s+"@s_up-to-date"; + case 2: return s+"newer"; + case 3: return s+"@e_outdated/not instaled"; + } +} +function packageStatus(local, remote){ + if(!remote) return 0; + if(!local) return 3; + if(remote===local) return 1; + return 2+(local colors[m])+colors.R); +} +function githubRelease_(repository, tag_name_regex= ""){ + return downloadJSON_(repository, "https://api.github.com/repos/"+repository+"/releases") + .then(data=> data.find(function find({ draft, published_at, tag_name }){ + if(draft||!published_at) return false; + if(!tag_name_regex) return true; + return (new RegExp(tag_name_regex, 'g')).test(tag_name); + })||{}); +} +function githubRepo_(repository){ return downloadJSON_(repository, "https://api.github.com/repos/"+repository); } +function promt_(q, def){ + const rl= readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise(function(resolve){ + rl.question(q+": ", a=> { rl.close(); resolve(a); }); + rl.write(def); + }); +} +function getConfig(){ + let config; + try{ config= JSON.parse(fs.readFileSync(info.config)); } + catch(e){ config= {}; log(1, "@w_Missing or corrupted config file. Creates empty one."); } + return config; +} +function save(config){ + return fs.writeFileSync(info.config, JSON.stringify(config, null, " ")); +} +function getCurrent(args){ + let command, command_arg, param; + const hasArg= arg=> ({ args })=> args.includes(arg); + for(let i=0, { length }= args, arg; i data+= chunk); + response.on("end", ()=> resolve(data)); + }); }); +} +function downloadFile_(to, { url, repository, size }){ + const file= fs.createWriteStream(to); + return get_(url) + .then(r=> get_(r.headers.location)) + .then(function(response){ return new Promise(function(resolve){ + let progress= 0, pc_prev= 0, avg= 0; + const start= new Date(); + const i= setInterval(function(){ + readline.clearLine(process.stdout); + const pc= (100*progress/size).toFixed(2); + if(!pc_prev) pc_prev= pc; + else { + avg= ((100-pc)/(60*(pc-pc_prev))).toFixed(2); + pc_prev= 0; + } + const running= ((new Date()-start)/60000).toFixed(2); + log(2, repository+": "+pc+"%"+` (end in ~${avg} mins, running ${running} mins)`); + readline.moveCursor(process.stdout, 0, -1); + }, 500); + response.on('data', function(chunk){ + file.write(chunk); + progress+= chunk.length; + }); + response.on('end', function(){ + clearInterval(i); + readline.clearLine(process.stdout); + log(2, repository+": @s_OK"); + file.close(()=> resolve({ success: 1, message: to })); /* close() is async, call cb after close completes. */ + }); + }); }) + .catch(({ message })=> { + fs.unlink(to); // Delete the file async. (But we don't check the result) + return { success: 0, message }; + }); +} +function get_(url){ return new Promise(function(resolve, reject){ + https.get( + url, + { headers: { 'Cache-Control': 'no-cache', 'User-Agent': 'node' } }, + resolve + ).on("error", reject); +});} +function applySequentially_(input, pF){ + const data= []; + let p= pF(input[0]); + const tie= nth=> result_mth=> ( data.push(result_mth), pF(input[nth]) ); + for(let i= 1, { length }= input; i (data.push(o), data)); +} +function error(message){ + const help_text= `@w_See help using '${info.commands[0].args[0]}'.`; + log(1, `@e_Error: ${message} ${help_text}`); + return process.exit(1); +} +function printMain(){ + const { name, version, description }= info; + log(1, `@w_${name}@${version}`); + log(1, description); + const cmds= info.commands.map(({args})=> args[0].replace("--", "")).join(", "); + log(1, `@w_Usage: ${name} --[cmd] [param]`); + log(2, `…cmd: ${cmds}`); + log(2, "…param: Based on cmd\n"); +} +function printHelp(){ + log(1, "@s_Help:"); + log(2, "Commands:"); + info.commands.forEach(({ args, param, desc })=> { + const args_text= args.join("|"); + param= param ? " "+param : ""; + log(3, `@g_${args_text}@R_${param}`); + logLines(4, desc); + }); + log(2, "Params:"); + for(const [ param, desc ] of Object.entries(info.params)){ + log(3, `@g_${param}`); + logLines(4, desc); + } +} +function log(tab, text){ + return console.log(" ".repeat(tab)+text.replace(/@(\w)_/g, (_, m)=> colors[m])+colors.R); +} +function logLines(tab, multiline_text){ + if(!Array.isArray(multiline_text)) multiline_text= multiline_text.split(/(?<=\.) /g); + return log(tab, multiline_text.join("\n"+" ".repeat(tab))); +} diff --git a/bin/github-releases.json b/bin/github-releases.json new file mode 100644 index 0000000..c14780a --- /dev/null +++ b/bin/github-releases.json @@ -0,0 +1,216 @@ +{ + "packages": [ + { + "repository": "shiftkey/desktop", + "name": "GitHub Desktop", + "group": "dev", + "file_name": "github-desktop", + "exec": "yes", + "description": "Fork of GitHub Desktop to support various Linux distributions", + "last_update": "2023-12-20T15:25:06Z", + "downloads": "/home/jaandrle/bin/github-desktop", + "version": "release-3.3.6-linux3" + }, + { + "repository": "jaandrle/jaaCSS-cli", + "name": "jaaCSS", + "description": "EXPERIMENT – Helper for managing functional CSS classes", + "group": "dev", + "file_name": "jaaCSS.js", + "exec": "yes", + "downloads": "/home/jaandrle/bin/jaaCSS.js", + "version": "v1.3.2", + "last_update": "2022-09-02T13:33:16Z" + }, + { + "repository": "th-ch/youtube-music", + "name": "youtube-music", + "description": "YouTube Music Desktop App bundled with custom plugins (and built-in ad blocker / downloader)", + "group": "nondev", + "file_name": "youtube-music", + "exec": "yes", + "last_update": "2024-01-05T14:44:27Z", + "downloads": "/home/jaandrle/bin/youtube-music", + "version": "v3.2.2" + }, + { + "repository": "ArchGPT/insomnium", + "name": "insomnium", + "description": "Insomnium is a fast local API testing tool that is privacy-focused and 100% local. For testing GraphQL, REST, WebSockets and gRPC. This is a fork of Kong/insomnia", + "group": "dev", + "file_name": "insomnium", + "exec": "yes", + "last_update": "2023-11-13T10:03:28Z", + "downloads": "/home/jaandrle/bin/insomnium", + "tag_name_regex": "core@.*", + "version": "core@0.2.3-a" + }, + { + "repository": "Kong/insomnia", + "name": "insomnia", + "description": "The open-source, cross-platform API client for GraphQL, REST, and gRPC.", + "group": "skip", + "file_name": "insomnia", + "exec": "yes", + "last_update": "2023-10-16T10:03:28Z", + "downloads": "/home/jaandrle/bin/insomnia", + "tag_name_regex": "core@.*", + "version": "core@8.3.0" + }, + { + "repository": "rvpanoz/luna", + "name": "luna", + "description": "Manage npm dependencies through a modern UI.", + "group": "skip", + "file_name": "luna", + "exec": "yes" + }, + { + "repository": "angela-d/wifi-channel-watcher", + "name": "wifi-channel-watcher", + "group": "skip", + "file_name": "wifi-channel-watcher", + "exec": "no", + "description": "Monitor channel usage of neighboring routers & get an alert if your active channel is not optimal.\tTroubleshoot wifi without lifting a finger!" + }, + { + "repository": "vinceliuice/Tela-circle-icon-theme", + "name": "Tela-circle-icon-theme", + "description": "Tela-circle-icon-theme", + "group": "themes", + "file_name": "tela-circle-icon-theme.zip", + "last_update": "2021-07-19T14:12:05Z", + "exec": "no" + }, + { + "repository": "AppImage/AppImageKit", + "name": "AppImageKit", + "group": "skip", + "file_name": "appimagekit", + "exec": "yes", + "description": "Package desktop applications as AppImages that run on common Linux-based operating systems, such as RHEL, CentOS, openSUSE, SLED, Ubuntu, Fedora, debian and derivatives. Join #AppImage on irc.freenode.net" + }, + { + "repository": "dynobo/normcap", + "name": "NormCap", + "description": "OCR powered screen-capture tool to capture information instead of images", + "group": "nondev", + "file_name": "normcap", + "exec": "yes", + "last_update": "2023-12-12T22:23:37Z", + "downloads": "/home/jaandrle/bin/normcap", + "version": "v0.5.2" + }, + { + "repository": "upscayl/upscayl", + "name": "upscayl", + "description": "🆙 Upscayl - Free and Open Source AI Image Upscaler for Linux, MacOS and Windows built with Linux-First philosophy.", + "group": "nondev", + "file_name": "upscayl", + "exec": "yes", + "last_update": "2024-01-16T09:54:25Z", + "downloads": "/home/jaandrle/bin/upscayl", + "version": "v2.9.8" + }, + { + "repository": "RasmusLindroth/tut", + "name": "tut", + "description": "TUI for Mastodon with vim inspired keys", + "group": "nondev", + "file_name": "tut", + "exec": "yes", + "last_update": "2023-01-26T17:48:00Z", + "downloads": "/home/jaandrle/bin/tut", + "version": "2.0.1" + }, + { + "repository": "sunner/ChatALL", + "name": "ChatALL", + "description": " Concurrently chat with ChatGPT, Bing Chat, bard, Alpaca, Vincuna, Claude, ChatGLM, MOSS, iFlytek Spark, ERNIE and more, discover the best answers", + "group": "skip", + "file_name": "chatall", + "exec": "yes", + "last_update": "2023-09-30T14:08:00Z", + "downloads": "/home/jaandrle/bin/chatall", + "version": "v1.50.73" + }, + { + "repository": "jaandrle/bs", + "name": "bs", + "description": "The simplest possible build system using executables", + "group": "dev", + "file_name": "bs", + "exec": "yes", + "last_update": "2023-06-30T07:48:58Z", + "downloads": "/home/jaandrle/bin/bs", + "version": "v0.7.3" + }, + { + "repository": "h3poteto/fedistar", + "name": "Fedistar", + "description": "Multi-column Mastodon, Pleroma, and Friendica client for desktop", + "group": "nondev", + "file_name": "fedistar", + "exec": "yes", + "last_update": "2024-01-29T10:29:58Z", + "downloads": "/home/jaandrle/bin/fedistar", + "version": "v1.8.3" + }, + { + "repository": "ollama/ollama", + "name": "ollama", + "description": "Get up and running with Llama 2 and other large language models locally", + "group": "ai", + "file_name": "ollama", + "exec": "yes", + "last_update": "2024-01-26T18:19:36Z", + "downloads": "/home/jaandrle/bin/ollama", + "version": "v0.1.22" + }, + { + "repository": "neovim/neovim", + "name": "neovim", + "tag_name_regex": "v.*", + "description": "Vim-fork focused on extensibility and usability", + "group": "dev-test", + "file_name": "nvim", + "exec": "yes", + "downloads": "/home/jaandrle/bin/nvim", + "version": "v0.9.5", + "last_update": "2023-12-30T13:31:47Z" + }, + { + "repository": "viarotel-org/escrcpy", + "name": "Escrcpy", + "description": "📱 Graphical Scrcpy to display and control Android, devices powered by Electron. | 使用图形化的 Scrcpy 显示和控制您的 Android 设备,由 Electron 驱动。", + "group": "dev", + "file_name": "escrcpy", + "exec": "yes", + "last_update": "2023-12-27T01:18:21Z", + "downloads": "/home/jaandrle/bin/escrcpy", + "version": "v1.16.8" + }, + { + "repository": "drovp/drovp", + "name": "drovp", + "description": "Desktop app for encoding, converting, upscaling, and much more.", + "group": "dev-test", + "file_name": "drovp", + "exec": "yes", + "last_update": "2023-12-06T11:30:02Z", + "downloads": "/home/jaandrle/bin/drovp", + "version": "0.8.0" + }, + { + "repository": "janhq/jan", + "name": "Jan", + "description": "Jan is an open source alternative to ChatGPT that runs 100% offline on your computer", + "group": "ai", + "file_name": "jan", + "exec": "yes", + "last_update": "2024-01-29T05:19:22Z", + "downloads": "/home/jaandrle/bin/jan", + "version": "v0.4.5" + } + ] +} \ No newline at end of file diff --git a/bin/jsconfig.json b/bin/jsconfig.json new file mode 100644 index 0000000..ece3be3 --- /dev/null +++ b/bin/jsconfig.json @@ -0,0 +1,8 @@ +{ + "include": [ + "/home/jaandrle/.nvm/versions/node/v18.18.0/lib/node_modules/nodejsscript/index.d.ts", + "./*.mjs", + "./*.js", + "chrome-autoinspect.mjs" + ] +} diff --git a/bin/nocodb.mjs b/bin/nocodb.mjs new file mode 100755 index 0000000..191eee3 --- /dev/null +++ b/bin/nocodb.mjs @@ -0,0 +1,19 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ +const url_api= "https://nocodb.jaandrle.cz/api/v1/db/data/v1/Linky/Linky"; +$.api() +.command("add-md") + .action(async function addMd(){ + const { clipboard }= $; + const [ title, url ]= clipboard.split("]("); + + const res= await fetch(url_api, { + body: JSON.stringify({ Popis: title.slice(1), Url: url.slice(0, -1) }), + headers: { "xc-token": "Js7Qu0oT_wTmGJDFKlIZEBhwk87WF0L1wLmQO01F", + "accept": "application/json", "Content-Type": "application/json" + }, + method: "post" + }).then(res=>res.json()); + echo(res); + }) +.parse(); diff --git a/bin/onedrive b/bin/onedrive new file mode 100755 index 0000000..41f525e --- /dev/null +++ b/bin/onedrive @@ -0,0 +1,9 @@ +#!/bin/bash +if [[ $(mount | grep OneDrive) ]]; then + fusermount -uz ~/Vzdálené/OneDrive + notify-send -i system-file-manager "OneDrive disk ODPOJEN" +else + rclone --vfs-cache-mode full mount "onedrive": ~/Vzdálené/OneDrive & + dolphin ~/Vzdálené/OneDrive/ & + notify-send -i system-file-manager "OneDrive disk PŘIPOJEN" +fi diff --git a/bin/piper.mjs b/bin/piper.mjs new file mode 100755 index 0000000..b3a7851 --- /dev/null +++ b/bin/piper.mjs @@ -0,0 +1,71 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ +const p_home= $.xdg.home`Applications/piper/` +const p_models= p_home+"models/" + +$.api("", true) +.version("2023-05-31") +.describe([ + "This is a wrapper around the piper CLI.", + "It allows you to get the list of available models, or to simplify `piper` callings.", + "", + "Visit: https://github.com/rhasspy/piper", + "Original help:", + ...s.$().run`${p_home}piper --help`.stderr.split("\n").filter(Boolean).map(l=> "\t"+l) +]) +.option("--models", "prints available models") +.option("--model, -m", "chooses voice model by it's index or by text search (full name)") +.option("--input_file, -I", "path to input text file (defaults to stdin)") + .action(function main({ + models: is_models, + model= 0, + input_file, + _, + ...pass + }){ + if(is_models){ + models() + .forEach(l=> echo(l)); + echo(helpTextModels("…for more models")) + $.exit(0); + } + model= getModel(model); + pass= Object.entries(pass) + .map(([ name, value ])=> `${name.length > 1 ? "--" : "-"}${name} '${value}'`) + .join(" "); + const o= s.run(`echo ${text(input_file)} | ${p_home}piper --model ${model} ${pass}`); + $.exit(o.code); + }) +.parse(); + +function text(input_file){ + const candidate= input_file ? s.cat(input_file).stdout : $.stdin.text(); + if(typeof candidate!=="string") + $.error("Missing input text file or piped text. Use `--help` for more information."); + return "'"+candidate.trim().replaceAll("\n", "\t — \t").replaceAll("'", "\"")+"'"; +} +function getModel(identifier){ + const candidate= typeof identifier==="number" ? + models()[identifier] : + models().find(l=> l.includes(identifier)); + if(!candidate) + $.error([ + `Model identifier '${identifier}' seems not to matching any existing model.`, + "Try `--models` to see all available models." + ].join("\n")); + return candidate.slice(candidate.indexOf(" ")+1); +} +function models(){ + const out= s.ls(`${p_models}*.onnx`) + .map((l, n)=> `${n}: ${l}`); + if(!out.length) + $.error(helpTextModels("No available models.")); + return out; +} +function helpTextModels(...text_initial){ + return [ + ...text_initial, + "Visits https://github.com/rhasspy/piper", + "and download/extract model(s) into "+p_models + ].join("\n"); +} diff --git a/bin/pocket-sh-add.sh b/bin/pocket-sh-add.sh new file mode 100755 index 0000000..1141f76 --- /dev/null +++ b/bin/pocket-sh-add.sh @@ -0,0 +1,10 @@ +[ ! -r ~/.config/pocketshaddrc ] && echo "\`~/.config/pocketshaddrc\` not found" && exit 1 +. ~/.config/pocketshaddrc + +curl -sS -X POST \ + -F "url=$1" \ + -F "title=$2" \ + -F "consumer_key=$CONSUMER_KEY" \ + -F "access_token=$ACCESS_TOKEN" \ + https://getpocket.com/v3/add \ + > /dev/null diff --git a/bin/socky.mjs b/bin/socky.mjs new file mode 100755 index 0000000..09a217b --- /dev/null +++ b/bin/socky.mjs @@ -0,0 +1,80 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ + +$.api("") +.version("2023-08-11") +.command("textInImage [file]", "Generovat obrázek s textem pro sdílení na sociálních sítích.") + .alias("tii") + .option("--pointsize", "Text size", "62") + .option("--gravity", "`convert -list gravity`", "Center") + .option("--text", "Text") + .action(textInImage) +.command("textSplit ", "Rozdělit text dle zadaného limitu.") + .alias("ts") + .option("--limit", "Počet znaků kde text rozdělovat.", 500) + .option("--counter", "Prepend counter, set no. of chars of counter.", 5) + .option("--text", "Text") + .action(textSplit) +.parse(); + +function textSplit(file, { limit, counter, text }){ + limit= parseInt(limit) - parseInt(counter); + const words= getText(file, text).split(" "); + let buffer= [], i= -1, chars= limit; + for(const word of words){ + const { length }= word; + if((chars+length) < limit){ + chars+= ( is_empty ? 0 : 1 ) + length; + buffer[i].end= i; + continue; + } + chars= 0; + i+= 1; + buffer[i]= { start: i }; + } + const { length }= buffer; + echo(buffer.map(function({ start, end }, i){ + return (i+1)+"/"+length+" "+words.slice(start, end).join(" "); + }).join("\n============\n")); + $.exit(0); +} +function textInImage(file, { text, gravity, pointsize }){ + text= getText(file, text); + const output= !file ? "textInImage.png" : (({ pathname: p })=> p.slice(1, p.lastIndexOf(".")+1)+"png")(new URL(file, "file://")); + const round_corners= [ //https://9to5answer.com/rounding-corners-of-pictures-with-imagemagick + "\\(", + "+clone -alpha extract", + "-draw 'fill black polygon 0,0 0,15 15,0 fill white circle 15,15 15,0'", + "\\( +clone -flip \\) -compose Multiply -composite", + "\\( +clone -flop \\) -compose Multiply -composite", + "\\) -alpha off -compose CopyOpacity -composite", + ].join(" "); + s.run([ + "convert", + "\\(", + "-size ::size::", + "-pointsize ::pointsize::", + "-fill ::fill:: -background ::background::", + "-gravity ::gravity::", + "-family ::family:: -weight Bold", + "-bordercolor ::background:: -border 5%", + "caption:::text::", + "\\)", + round_corners, + "::output::" + ].join(" "), { + fill: "whitesmoke", + background: "#2b2b2b", + size: "1024x512", + family: "Ubuntu Mono", + output, text, gravity, pointsize + }); + $.exit(0); +} +function getText(file, text){ + if(file && s.test("-f", file)) + return s.cat(file).toString(); + if(!text) + return $.stdin.text().trim(); + return text; +} diff --git a/bin/uu b/bin/uu new file mode 100755 index 0000000..c0c6260 --- /dev/null +++ b/bin/uu @@ -0,0 +1,119 @@ +#!/usr/bin/env bash +set -eo pipefail +this="${0##*/}" +version="2022-07-27" +config_file="$HOME/.config/${this}rc" +[ ! -t 0 ] && exo-open --launch TerminalEmulator -- -e "$this $*" && exit 0 + +arg=${1:---help} +if [[ "$arg" = "--completion-bash" ]]; then + echo "_${this}_completion(){ + local cur=\"\${COMP_WORDS[COMP_CWORD]}\" + local com_basic=\"--alias --help --version --tips\" + local com_urls=\"cht.sh wttr.in rate.sx qrenco.de ifconfig.co\" + if [[ \$COMP_CWORD != 1 ]]; then + local com_web=\"\" + case \"\${COMP_WORDS[1]}\" in + --raw) + COMPREPLY=( \$(compgen -W \"\$com_urls \$com_basic\" -- \"\$cur\" ) ) + return 0; + ;; + --alias) + COMPREPLY=( \$(compgen -W \"? + -\" -- \"\$cur\" ) ) + return 0; + ;; + wttr.in) + local com_web+=\"moon m u M 0 1 2 A F n q Q T\" + ;; + ifconfig.co) + local com_web+=\"json\" + ;; + cht.sh) + local com_web+=\"\$(compgen -c)\" + ;; + esac + COMPREPLY=( \$(compgen -W \":help \$com_web\" -- \"\$cur\" ) ) + return 0; + fi + + COMPREPLY=( \$(compgen -W \"--raw \$com_urls \$com_basic\" -- \"\$cur\" ) ) +} +complete -o bashdefault -o default -F _${this}_completion ${this} +" + exit 0; +fi + +_echo(){ [[ $is_raw == 1 ]] && echo -e "$1" || echo "$1" | less -R -S; } +[[ "$arg" = "--raw" ]] && is_raw=1 && command shift && arg=${1:---help} || is_raw=0 + +if [[ "$arg" = "--help" ]]; then + _echo "\ + $this@v$version – URL UTILS + This is helper around \`curl\` to run web-based commands such as 'wttr.in', 'cht.sh', … + Usage: + $this [--raw] BASE_URL [PARAMS] + $this [--raw] --[help|version|tips] + $this [--raw] --alias NAME +|-|? FULL_URL + + Options: + --raw – no output using \`less\` + --alias – you can also use vars '\\\$1', … + PARAMS – parameters to be concatenated with BASE_URL with '/' or '?'/'&' when parameter starts with '?' … se below + Examples: + $this cht.sh/less $this cht.sh less + $this wttr.in/prague?M&n $this wttr.in prague ?M ?n + $this --raw ident.me/json | jq + Config file: + $config_file +" + exit 0 +fi +[[ "$arg" = "--version" ]] && echo "$version" && exit 0 +if [[ "$arg" = "--tips" ]]; then + _echo "\ + cht.sh – The only cheat sheet you need Unified access to the best community driven documentation repositories of the world. + wttr.in – Weather report + rate.sx – show exchange rates for cryptocurrencies + qrenco.de + ifconfig.co + https://github.com/chubin/awesome-console-services/tree/65e8e897c9c5a2ec013747dd9f1acc03c8573fe7 +" + exit 0 +fi +command shift + +if [[ "$arg" == "--alias" ]]; then + alias_name="${1:-[^_]*}" + [[ $alias_name == *.* ]] && echo "Alias should not contains '.'" && exit 1 + case "$2" in + "+") + out=$(grep -v -e "uu_${alias_name}_alias=" -e "uu_${alias_name}_is_raw=" $config_file) + out="$out\nuu_${alias_name}_alias=\"$3\"" + out="$out\nuu_${alias_name}_is_raw=$is_raw" + echo -e "$out" > $config_file + exit 0 + ;; + "-") echo -e "$(grep -v -e "uu_${alias_name}_alias=" -e "uu_${alias_name}_is_raw=" $config_file)" > $config_file || echo "No aliases yet"; exit 0;; + *) grep -e "uu_${alias_name}_alias=" -e "uu_${alias_name}_is_raw=" $config_file || echo "No aliases yet"; exit 0;; + esac + exit 1 +fi + +args="" +if [[ $arg != *.* ]]; then + . $config_file + is_raw_name=uu_${arg}_is_raw + is_raw=${!is_raw_name} + + [[ -z $is_raw ]] && echo "No alias '$arg' found." && exit 1 + arg_name=uu_${arg}_alias + arg=${!arg_name} +else + for p in "$@"; do + [[ $p == "?"* ]] \ + && args+=" --data-urlencode ${p:1} " \ + || arg+="/$p" + done +fi +out=$(curl -fGsS -H 'Accept-Language: cs' $args --compressed $arg || echo 'Curl error, see terminal error output.') +_echo "$out" diff --git a/bin/§ai-commit.mjs b/bin/§ai-commit.mjs new file mode 100755 index 0000000..57e6ee0 --- /dev/null +++ b/bin/§ai-commit.mjs @@ -0,0 +1,159 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ +$.is_fatal= true; +const token_file= "~/.config/openai.token"; +let token; +const gitmoji_list= { + build: "building_construction", + chore: "bricks", + ci: "construction_worker", + docs: "memo", + feat: "sparkles", + fix: "bug", + perf: "zap", + refactor: "recycle", + revert: "rewind", + style: "art", + test: "white_check_mark" +}; +const git3moji_list= { + build: "tv", + chore: "tv", + ci: "tv", + docs: "abc", + feat: "zap", + fix: "bug", + perf: "zap", + refactor: "cop", + style: "zap", + test: "cop" +}; +const conventional_desc= [ + "Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)", + "Other changes that don't modify src or test files", + "Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)", + "Documentation only changes", + "A new feature", + "A bug Fix", + "A code change that improves performance", + "A code change that neither fixes a bug nor adds a feature", + "Reverts a previous commit", + "Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)", + "Adding missing tests or correcting existing tests" +]; + +$.api("", true) + .version("2024-02-19") + .describe([ + "Utility to use ChatGPT to generate a commit message from COMMIT_EDITMSG file.", + `Don't forget to set the token in ${token_file} file.`, + "", + "Conventional/gitmoji commits should follow https://www.conventionalcommits.org/en/v1.0.0/ (https://github.com/pvdlg/conventional-commit-types).", + "For the gitmoji the uses emojis as follows:", + ...Object.entries(gitmoji_list).map(( v, i )=> echo.format("%c"+v[0]+` (${v[1]}): ${conventional_desc[i]}`, "display: list-item")), + ...Object.entries(git3moji_list).map(( v, i )=> echo.format("%c"+v[0]+` (${v[1]}): ${conventional_desc[i]}`, "display: list-item")) + ]) + .option("--format, -f", [ "Use one of the following formats to generate the commit message: [regular (default), conventional, gitmoji, git3moji]", + "For gitmoji see: https://gitmoji.dev/" + ]) + .action(async function({ format= "regular" }= {}){ + const question= questionChatGPT(format); + const response= (await pipe( + ()=> s.cat("./.git/COMMIT_EDITMSG"), + s=> s.slice(s.indexOf("diff --git")), + diffToChunks(3900-545), //the worst scenario of ★ +new lines + ch=> ch.map(pipe( question, requestCommitMessage )), + ch=> Promise.all(ch) + )()) + .map(pipe( + j=> j.choices[0].text.trim(), + t=> t.match(/\[[^\]]*\]/) ?? convertToJSONArray(t), + JSON.parse, + format==="regular" ? i=> i : i=> gitmoji(i, format==="git3moji"), + a=> a.join("\n") + )) + .join("\n\n"); + echo(response); + $.exit(0); + }) + .parse(); + +function diffToChunks(max_tokens){ return function(input){ + if(input.length < max_tokens) + return [ input ]; + + return input.split(/(?=diff --git)/g) + .flatMap(function(input){ + if(input.length < max_tokens) + return [ input ]; + + const [ file, ...diffs ]= input.split(/\n(?=@@)/g); + if(file.includes("new file")) + return [ file ]; + return diffs + .filter(chunk=> chunk.length < max_tokens) + .reduce(function(chunks, chunk){ + const i= chunks.length-1; + if(chunks[i].length + chunk.length < max_tokens-1) + chunks[i]+= "\n"+chunk; + else + chunks.push(file+"\n"+chunk); + return chunks; + }, [ file ]) + .filter(chunk=> chunk.length < max_tokens); + }); +}; } +function convertToJSONArray(text){ + const arr= text.split("\n") + .filter(line=> line.trim().match(/^[1-3]\. /)) + .map(line=> line.slice(3)) + .map(line => line.startsWith('"') ? line : "\"" + ( "'`".slice("").includes(line[0]) ? line.slice(1, -1) : line ) + "\""); + return `[${arr.join(", ")}]`; +} +function questionChatGPT(format){ return function(diff){ + const msg= [ + [ + "I would like to ask you to act like a git commit message writer.", + "I will enter a git diff, and your job is to convert it into a useful commit message and make 3 options as JSON array.", + "Do not preface the commit with anything, use a concise, precise, present-tense, complete sentence.", + "The length should be fewer than 50 characters if possible.", + ].join(" ") //340chars★ + ]; + if(format!=="regular") + msg.push( + [ + "It should follow the conventional commits.", + "The format is : .", + "A type can be one of the following: build, chore, ci, docs, feat, fix, perf, refactor, revert, style, or test.", + ].join(" ") //203chars★ + ); + msg.push("", diff); + return msg.join("\n"); +}; } +function gitmoji(candidates, is_three= false){ + return candidates.map(message=> message.trim().replace(/^[^:]*:/, toGitmoji)); + + function toGitmoji(name){ + const candidate= ( is_three ? git3moji_list : gitmoji_list )[name.slice(0, -1)]; + return !candidate ? name : `:${candidate}:`; + } +} +function requestCommitMessage(prompt){ + if(!token) token= s.cat(token_file).stdout.trim(); + const model= "text-davinci-003"; + return fetch(`https://api.openai.com/v1/engines/${model}/completions`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": "Bearer "+token + }, + body: JSON.stringify({ + max_tokens: 1000, + temperature: 0.1, + prompt + }), + signal: AbortSignal.timeout(10000) + }).then(r=> r.json()); +} +// vim: set tabstop=4 shiftwidth=4 textwidth=250 noexpandtab : +// vim>60: set foldmethod=indent foldlevel=1 foldnestmax=2: diff --git a/bin/§awk b/bin/§awk new file mode 100755 index 0000000..d116b48 --- /dev/null +++ b/bin/§awk @@ -0,0 +1,26 @@ +#!/bin/bash +this="${0##*/}" +USAGE="\ +usage: $this [] + Ex: getent passwd | grep andy | $this -F: 5 + Ex: echo \"A B\" | $this 2 +" +err(){ echo -e "$USAGE" >&2; exit 1; } + +[[ $# -eq 0 ]] && err +# bail if the *last* argument isn't a number (source: +# http://stackoverflow.com/a/808740) +last=${@:(-1)} +if ! [ $last -eq $last ] &>/dev/null; then + echo "_awk! Last argument (awk field) must be numeric." >&2 + err +fi + +if [ $# -gt 1 ]; then + # Source: + # http://www.cyberciti.biz/faq/linux-unix-bsd-apple-osx-bash-get-last-argument/ + rest=${@:1:$(( $# - 1 ))} +else + rest='' # just to be sure +fi +awk $rest "{ print \$$last }" diff --git a/bin/§battery b/bin/§battery new file mode 100755 index 0000000..411163d --- /dev/null +++ b/bin/§battery @@ -0,0 +1,34 @@ +#!/bin/bash +version="2022-01-25" +this="${0##*/}" +USAGE="\ + $this@v$version + Wrapper around 'upower' to show battery info(s). + Usage: $this --[help|all|oneline|notify] + + 'oneline' [Default] prints 'status | time | percentage' + 'notify' sends 'oneline' to 'notify-send' + 'all' prints 'upower -i' + 'help' prints this text +" +arg=${1:---oneline} +if [[ "$arg" = "--help" ]]; then + echo -e "$USAGE" + exit 0 +fi + +batt_name=`upower -e | grep 'BAT'` +batt_info=`upower -i $batt_name` +if [[ "$arg" = "--all" ]]; then + echo -e "$batt_info" + exit 0 +fi + +batt_oneline=`echo "$batt_info" | grep -E "state|percentage|to\ full|to\ empty" | §awk -F: 2 | sed 's/^ *//g' | tr -s '\n' '|' | sed 's/|$/\n/' | sed 's/|/ | /g'` +if [[ "$arg" = "--oneline" ]]; then + echo -e "$batt_oneline" + exit 0 +fi + +icon=`echo "$batt_info" | grep "icon-name" | §awk -F\' 2` +notify-send --icon=$icon "Battery" "$batt_oneline" diff --git a/bin/§calc b/bin/§calc new file mode 100755 index 0000000..8c537b8 --- /dev/null +++ b/bin/§calc @@ -0,0 +1,6 @@ +#!/bin/bash +function _echo(){ + [ -t 0 ] && echo "$1" && exit + notify-send -i kcalc "$1" && exit +} +_echo `python3 -c 'import sys; print(eval(" ".join(sys.argv[1:])))' "$*"` diff --git a/bin/§cordova-release.mjs b/bin/§cordova-release.mjs new file mode 100755 index 0000000..973480f --- /dev/null +++ b/bin/§cordova-release.mjs @@ -0,0 +1,60 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, style, fetch, cyclicLoop */ +import { join as pathJoin } from "path"; +import { platform } from "process"; +const config_path= $.xdg.data`package_global.json`; + +$.is_fatal= true; +$.api("[name]", true) +.version("2022-10-06") +.describe([ + "Release cordova app with saved data in: "+config_path+".", + "This should be JSON file with `cordova_keys_store` key/object:", + `{"cordova_keys_store": { "NAME": { "path": "", "password": "", "alias": "" } }}`, + "You can lists all saved options (NAME), when you run without arguments." ]) +.option("--noclear", "Skipping cleaning existing apk files.") +.action(function main(name, { noclear }){ + if(!name){ + echo("Available options:"); + pipe(getConfig, Object.keys, a=> "- "+a.join("\n- "), echo)(); + $.exit(0); + } + const /* runtime arguments and cwd */ + { path, password, alias }= getConfigFor(name), + cwd= process.cwd(), + platform_android= toDirPath( cwd, "platforms", "android" ), + platform_build= !s.test("-e", toDirPath(platform_android, "app")) ? toDirPath(platform_android, "build") : toDirPath(platform_android, "app", "build"), + apk_dir= toDirPath(platform_build, "outputs", "apk"), + key_path= pathJoin(cwd, "keystore.jks"), + process_clear= !noclear && !s.test("-e", toDirPath(platform_android, "app")); + + $.configAssign({ verbose: true, fatal: true }); + if(process_clear) s.rm("-Rf", apk_dir+"*"); + s.cp(path, key_path); + s.run("cordova" + ( platform==="win32" ? ".cmd" : "" ) + " ::args::", + { args: [ "build", "--release", "android", "--",'--keystore=keystore.jks', "--storePassword="+password, "--password="+password, "--alias="+alias ] }); + s.rm(key_path);// cordova si to uklada a uz potom bez nej nelze buildit vubec + s.rm(platform_android+"release-signing.properties"); + $.exit(0); +}) +.parse(process.argv); + +function toDirPath(...path){ return pathJoin(...path)+"/"; } +function getConfigFor(name){ + const config= getConfig(); + if(Object.hasOwn(config, name)) + return config[name]; + + $.error(`Name '${name}' not found, use one of: `+Object.keys(config)); +} +function getConfig(){ + if(!s.test("-f", config_path)) + $.error("No config file found! Tested file path: "+config_path); + try{ + const config= s.cat(config_path).xargs(JSON.parse).cordova_keys_store; + if(!Object.keys(config).length) throw new Error(); + return config; + } catch(e){ + $.error("Unsupported config file: "+config_path+"! Use `--help` for more information."); + } +} diff --git a/bin/§extract b/bin/§extract new file mode 100755 index 0000000..88c3b3e --- /dev/null +++ b/bin/§extract @@ -0,0 +1,61 @@ +#!/bin/bash +this="${0##*/}" +this_version="2021-03-14" +while read; do printf '%s\n' "$REPLY" +done <<-EOF + $this ($this_version) + Utility for extracting archives into folder with the same name. + +EOF +err() { + printf >&2 "Error: $*\n" + exit 1 +} + +ARC="$1" +[[ ! -z "$ARC" ]] || ARC="--help" + +if [[ "$ARC" = "--help" ]]; then + while read; do printf '%s\n' "$REPLY" + done <<-EOF + Usage: $this [file|--help|] + [--help|] - show this text + [file] - path to file for extracting + Supported formats (used utilities): +EOF + sed -n 42,52p $0 | sed -e 's/^/ /' + exit 0 +fi + +[[ -f $ARC ]] || err $"'$ARC' does not exist" + +ARC_name_ext="${ARC##*/}" +ARC="$(readlink -f "$ARC")" +ARC_name="${ARC_name_ext%.*}" + +mkdir "$ARC_name" || err $"Directory '$ARC_name' can not be created" +[[ -d $ARC_name ]] || err $"Directory '$ARC_name' does not exist" +[[ -w $ARC_name ]] || err $"Permission denied: '$ARC_name' is not writable" + +cd "$ARC_name" +case "$ARC" in + *.tar.bz2) tar xjf "$ARC" ;; + *.tar.gz) tar xzf "$ARC" ;; + *.bz2) bunzip2 "$ARC" ;; + *.rar) unrar e "$ARC" ;; + *.gz) gunzip "$ARC" ;; + *.tar) tar xf "$ARC" ;; + *.tbz2) tar xjf "$ARC" ;; + *.tgz) tar xzf "$ARC" ;; + *.zip) unzip "$ARC" ;; + *.epub) unzip "$ARC" ;; + *.docx) unzip "$ARC" ;; + *.xmlx) unzip "$ARC" ;; + *.pptx) unzip "$ARC" ;; + *.Z) uncompress "$ARC" ;; + *.7z) 7z x "$ARC" ;; + *.eml) munpack -t "$ARC" ;; + *) err $"'$ARC' cannot be extracted by $this" ;; +esac + +# sudo apt install mpack diff --git a/bin/§mail.mjs b/bin/§mail.mjs new file mode 100755 index 0000000..ce1920a --- /dev/null +++ b/bin/§mail.mjs @@ -0,0 +1,94 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, style, fetch, cyclicLoop, xdg, $ */ +import { basename } from "path"; +const app= { + name: basename(process.argv[1]), + version: "2022-09-28", + cmd: $.xdg.home`.local/bin/himalaya`, + configs: $.xdg.config`himalaya/`, + modificator: "§" +}; +const chars= { "&AOE-": "á", "&AWE-": "š", "&ARs-": "ě" }; +let argv_arr= argvArr(); + +if("help"===argv_arr.toString().replaceAll("-", "")){ //#region + echo([ `${app.name}@${app.version}`, + `This is small wrapper around 'himalaya' fixing coding errors and provide better 'read'. (Use § for calling himalaya directly)`, + "" ].join("\n")); + s.run(app.cmd+" --help"); + $.exit(0); //#endregion +} +if("version"===argv_arr.toString().replaceAll("-", "")){//#region + echo(`${app.name} ${app.version}`); + s.run(app.cmd+" --version"); + $.exit(0);//#endregion +} +if("completion,bash"===argv_arr.toString()){//#region + const completion= s.run(app.cmd+" ::argv_arr::", { argv_arr }); + echo(completion.toString().replace("himalaya)", `himalaya|${app.name})`)); + echo(`alias ${app.name}-inbox="§mail § | less -R -S"`); + echo(`complete -F _himalaya -o bashdefault -o default ${app.name}`); + $.exit(0);//#endregion +} +(async function main(){ + if(argv_arr.indexOf(app.modificator)!==-1) await runH(argv_arr.filter(l=> l!==app.modificator)); + + argv_arr= argv_arr.filter(str=> str!==app.modificator); + if(argv_arr.indexOf("list")!==-1){ + argv_arr.push("-w", process.stdout.columns); + await runH(argv_arr); + } + if(argv_arr.indexOf("read")!==-1){ + const email= $.xdg.temp`/himalaya-read.eml`; + argv_arr.push("-h", "From", "-h", "Subject"); + await s.$().runA(app.cmd+" ::argv_arr::", { argv_arr }).pipe(s=> s.to(email)); + await s.runA`vim ${email}`.pipe(process.stdout); + $.exit(0); + } + + if(argv_arr[0] && argv_arr[0]!=="--rofi") await runH(argv_arr); + + const template_path= app.configs+"template-inbox.json"; + if(!s.test("-f", template_path)) await runH([]); + + const out= await pipe( + f=> s.cat(f).xargs(JSON.parse), + argv_arr.indexOf('--rofi')===-1 ? templateRead : templateRofi, + a=> Promise.all(a) + )(template_path); + for(const l of out){ + if(typeof l=="string"&&!l.indexOf("%c===\n")) echo(l, "unset:all", "color:magenta"); + else echo(l); + } + $.exit(0); +})(); + +function templateRofi(lines){ + return lines.filter(line=> line.type!=="text") + .map(line=> + s.$().runA(app.cmd+" ::value:: -w 120", line) + .then(data=> data.toString().split("\n") + .filter(l=> l) + .map(line=> line.replaceAll("✷ ", "* ")) + .map(line_result=> line_result+" │ "+line.label) + .join("\n")) + ); +} +function templateRead(lines){ + argv_arr.push("-w", process.stdout.columns); + return lines.map(line=> line.type==="text" ? + Promise.resolve("%c===\n%c"+line.value) : + s.$().runA(app.cmd+" ::value:: ::argv_arr::", { value: line.value, argv_arr }) + ); +} + +function argvArr(){ + const _chars= Object.entries(chars).reduce((acc, [ key, val ])=> Reflect.set(acc, val, key) && acc, {}); + return process.argv.slice(2).map(str=> str.replace(new RegExp(`(${Object.keys(_chars).join("|")})`, "g"), l=> _chars[l])); +} +async function runH(args){ + const result= await s.runA(app.cmd+" ::args::", { args }).pipe(process.stdout); + $.exit(result.exitCode); +} +// vim: set tabstop=4 shiftwidth=4 textwidth=250 noexpandtab ft=javascript : +// vim>60: set foldmethod=marker foldmarker=#region,#endregion : diff --git a/bin/§software b/bin/§software new file mode 100755 index 0000000..dd1daa1 --- /dev/null +++ b/bin/§software @@ -0,0 +1,71 @@ +#!/bin/bash +this="${0##*/}" +this_version="2021-12-01" +err() { printf >&2 "Error: $*\n"; exit 1; } +log() { printf ":: $* ::\n"; } + +if [ ! -t 0 ]; then + exo-open --launch TerminalEmulator -- -e "$this $*" + exit +fi +action="${1:---help}" +if [[ "$action" = "--help" ]]; then + while read; do printf '%s\n' "$REPLY" + done <<-EOF +$this ($this_version) +Utility for updating my packages managers such as \`npm\`, \`apt\`, \`github-releases\` … + +Usage: $this --[help|check|update] [basic|dangerous] + [default] --help: show this text + --check: check updates + --update: update all + --list: list of packages managers + + modificators: + [default] basic: checks/updates regular packages managers + dangerous: ↘+also \`pip\` +EOF + exit 0 +fi + +if [[ "$action" = "--list" ]]; then + echo npm + echo github-releases.js + echo _vim_plugins – updates only + echo snap – updates only + echo flatpak – updates only + echo pip [dangerous] + exit 0 +fi +modificator="${2:-basic}" +if [[ "$action" = "--check" ]]; then + log "npm outdated --global (for update use: \`npm update --global\`)" + npm outdated --global + [[ "${?}" = "0" ]] && echo "> all up-to.date" + log "github-releases.js --check (for update use for example: \`github-releases.js --update all\`)" + github-releases.js --check + if [[ ! "$modificator" = "basic" ]]; then + log "pip list --outdated" + pip list --outdated + fi + exit 0 +fi +if [[ "$action" = "--update" ]]; then + log "npm update --global" + npm update --global + log "github-releases.js --update all" + github-releases.js --update all + log "_vim_plugins --update" + _vim_plugins --update + log "snap refresh --list" + snap refresh --list + log "flatpak update" + flatpak update + if [[ ! "$modificator" = "basic" ]]; then + log "pip list --outdated --format=freeze | grep -v '^\\-e' | cut -d = -f 1 | xargs -n1 pip install -U --user" + pip list --outdated --format=freeze | grep -v '^\-e' | cut -d = -f 1 | xargs -n1 pip install -U --user + fi + exit 0 +fi + +err "Wrong arguments (use \`--help\`)" diff --git a/bin/§trans.mjs b/bin/§trans.mjs new file mode 100755 index 0000000..8f0929f --- /dev/null +++ b/bin/§trans.mjs @@ -0,0 +1,22 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 8,-W097, -W040, node: true, expr: true, undef: true *//* global echo, $, pipe, s, style, fetch, cyclicLoop */ +$.is_fatal= true; +$.api("") +.version("2022-09-23") +.describe("This is just wrapper around 'trans' cli utility.") +.option("-b", "Turn off '-b' parametrer for 'trans' which is give by this script.") +.option("-c", "Redirect into clipboard ('| xclip -selection clipboard')") +.example("2en pes → 'trans cs:en -b pes'") +.example("en -b dog → 'trans en:cs dog'") +.action(function main(lang, { _: query, c: to_clipboard, b: to_long }){ + query= [ `"${query.join(" ").replaceAll('"', '\\"')}"` ]; + if(!to_long) query.unshift('-b'); + query.unshift(lang[0]==="2" ? lang.replace("2", "cs:") : ( lang.indexOf(":")!==-1 ? lang : lang+":cs" )); + query.push('-no-ansi'); + + const result= s.$().run("trans "+query.join(" ")); + if(!to_clipboard) return echo(result.toString()); + result.runA("xclip -selection clipboard 2> /dev/null 1> /dev/null") + .then($.exit.bind(null, 0)).catch($.exit.bind(null, 1)); +}) +.parse(process.argv); diff --git a/bin/§ubuntu-info b/bin/§ubuntu-info new file mode 100755 index 0000000..91106b2 --- /dev/null +++ b/bin/§ubuntu-info @@ -0,0 +1,58 @@ +#!/bin/bash +this="${0##*/}" +this_version="2021-01-22" +USAGE="\ + $this@v$this_version + Wrapper around '/etc/os-release' to show Ubuntu (like) os info. + Usage: $this --[help|all|raw] + + 'all' [default] prints all infos + 'pick' prints only given key (default key is 'DESCRIPTION') + 'raw' cats '/etc/os-release' + 'help' prints this text + + Examples: + $this --all + $this --pick NAME + $this --pick VERSION_NAME + $this --all | grep NAME +" +arg=${1:---all} +if [[ "$arg" = "--help" ]]; then + echo -e "$USAGE" + exit 0 +fi +if [[ "$arg" = "--raw" ]]; then + cat /etc/os-release + exit 0 +fi + +. /etc/os-release +out="ID=$ID" +out="${out}\nDESCRIPTION=$PRETTY_NAME" +case "$ID" in + neon) out="${out}\nNAME=$NAME (${VARIANT:-User Edition})" + out="${out}\nVERSION=$VERSION ($VERSION_ID)" + ;; + *) out="${out}\nNAME=$NAME" + out="${out}\nVERSION=$VERSION" + ;; +esac +out="${out}\nVERSION_NICK=$UBUNTU_CODENAME" +codename=`grep $(lsb_release -rs) /usr/share/python-apt/templates/Ubuntu.info | grep -m 1 "Description: Ubuntu " | cut -d "'" -f2` +out="${out}\nVERSION_NAME=$codename" +# http://www.releases.ubuntu.com/jammy/ +out="${out}\nLIKE=$ID_LIKE" +out="${out}\n`grep URL /etc/os-release | sed 's/^\([A-Z_]*\)_URL/URL_\1/'`" + +if [[ "$arg" = "--all" ]]; then + echo -e "$out" + exit 0 +fi +if [[ "$arg" = "--pick" ]]; then + echo -e `echo -e "$out" | grep "\b${2:-DESCRIPTION}\b" | cut -d = -f 2-` + exit 0 +fi + +echo "Wrong argument, see '--help'." +exit 1 diff --git a/bin/§vim_cache_clean b/bin/§vim_cache_clean new file mode 100755 index 0000000..30b4d36 --- /dev/null +++ b/bin/§vim_cache_clean @@ -0,0 +1,3 @@ +#!/bin/bash +find ~/.vim/undodir/ -maxdepth 1 -mindepth 1 -type f -mtime +180 -delete +find ~/.vim/view/ -maxdepth 1 -mindepth 1 -type f -mtime +180 -delete diff --git a/bin/§vim_plugins.mjs b/bin/§vim_plugins.mjs new file mode 100755 index 0000000..3495f81 --- /dev/null +++ b/bin/§vim_plugins.mjs @@ -0,0 +1,281 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 11,-W097, -W040, module: true, node: true, expr: true, undef: true *//* global echo, $, pipe, s, fetch, cyclicLoop */ +//TODO: save options!? +$.is_fatal= true; +const dirs= { vim_root: $.xdg.home`.vim` }; +Object.assign(dirs, { + pack: dirs.vim_root+"/pack/", + bundle: dirs.vim_root+"/bundle/", + one_files: dirs.vim_root+"/bundle/__one_files/plugin/" }); +const file_one_file= dirs.bundle+"one_file.json"; +const runResToArr= pipe( s.$().run, ({ stderr, stdout })=> stdout+stderr, o=> o.split("\n")); +const css= echo.css` + .code{ color: yellow; } + .code::before, .code::after{ content: "\`"; } + .url{ color: lightblue; } + .bold{ color: magenta; } +`; + +$.api() +.version("2023-04-18") +.describe([ + "Utility for managing vim plugins “native” way. It uses two types:", + `- “old” way (${f("bundle", css.code)}): inspiration from ${f("https://shapeshed.com/vim-packages/", css.url)}`, + `- vim8 native package managing (${f("pack", css.code)}): see for example ${f("https://shapeshed.com/vim-packages/", css.url)}` +]) +.command("path ", [ "Prints paths for given package type", + "Use (‘S’ is alias for this script name):", + "- "+f("cd `S path bundle`", css.code), + "- "+f("cd `S path one_files`", css.code), + "- "+f("cd `S path pack`", css.code) +]) + .action(function(type){ echo(dirs[type]); $.exit(0); }) +.command("clone ", [ "Add/install new package.", + `Use ${f("bundle", css.code )}/${f("pack", css.code)} to specify the package ${f("type", css.code)}.`, + `The ${f("url", css.url)} should be a URL to the script itself or url of the git repository or github repository in the form of ${f("username/reponame", css.url)}.` +]) + .alias("C") + .option("--target, -t [target]", `In case of ${f("pack", css.code)} type, specify the target sub-directory (typically/defaults ${f("start", css.code)}).`) + .option("--branch, -b [branch]", `In case of ${f("git", css.bold)} repository, specify the branch if it is different from default one.`) + .action(function(type, url, options){ + switch(type){ + case "bundle": return addBundle(url); + case "pack": return addPack(url, options); + } + echo("Nothing todo, check given arguments (compare to `--help`):", { type, url, options }); + $.exit(1); + }) +.command("remove ", [ "Remove/uninstall package.", + `As ${f("type", css.bold)}/${f("path", css.bold)} use output printed by ${f("list", css.code)} command.` +]) + .alias("R").alias("rm") + .action(function(type, path){ + switch(type){ + case "bundle": return removeBundle(path); + case "pack": return removePack(path); + } + echo("Nothing todo, check given arguments (compare to `--help`):", { type, path }); + $.exit(1); + }) +.command("list", "List all plugins paths/url/… (based on option).") + .alias("L").alias("ls") + .option("--type, -t [type]", `Defaults to list of paths (${f("paths", css.code)}). Use ${f("repos", css.code)} to show plugins origin.`) + .example("list") + .example("list --type paths") + .example("list --type repos") + .action(actionList) +.command("export", "List all plugins in the form that can be imported by this Utility.") + .action(actionList.bind(null, { type: "json" })) +.command("status", "Loops through all installed plugins and shows overall status.") + .alias("S") + .action(actionStatus) +.command("pull", "Loops through all installed plugins and updates them.") + .alias("P").alias("update") + .action(actionUpdate) +.parse(); + +function removePack(path){ + s.cd(dirs.pack); + s.$("-V").rm("-rf", path); + const root= dirs.pack+path.split("/")[0]; + const { code, stdout }= s.$().find(root+"/*/*"); + if(!code) echo(stdout); + else if(s.test("-d", root)) + s.$("-V").rm("-rf", root); + $.exit(0); +} +function removeBundle(path){ + const is_onefile= dirs.one_files.endsWith(path.split("/").slice(0, 2).join("/")+"/"); + const name= path.slice(path.lastIndexOf("/")+1); + s.cd(dirs.bundle); + if(is_onefile){ + s.rm("-f", path); + pipe( s.cat, JSON.parse, f=> f.filter(u=> !u.endsWith(name)), JSON.stringify, s.echo ) + (file_one_file).to(file_one_file); + } else { + s.run`git submodule deinit -f ${path}`; + s.run`git rm ${path}`; + s.rm("-rf", ".git/modules/"+path); + } + const type= is_onefile ? "file" : "repo"; + s.run`git commit --all -m "Remove ${type}: ${name}"`; + $.exit(0); +} +async function addBundle(url){ + const is_onefile= url.endsWith(".vim"); + if(!is_onefile) + url= gitUrl(url); + const name= url.split(/[\/\.]/g).at(is_onefile ? -2 : -1); + s.cd(dirs.bundle); + if(is_onefile){ + const file= await fetch(url).then(r=> r.text()); + s.echo(file).to(dirs.one_files+name+".vim"); + const log= new Set(s.cat(file_one_file).xargs(JSON.parse)); + log.add(url); + s.echo(JSON.stringify([ ...log ])).to(file_one_file); + } else { + s.run`git submodule init`; + s.run`git submodule add ${url}`; + } + s.run`git add .`; + const type= is_onefile ? "file" : "repo"; + s.run`git commit -m "Added ${type}: ${name}"`; + $.exit(0); +} +/** @param {string} url @param {{ target: "start", branch?: string }} options */ +function addPack(url, { target= "start", branch }){ + url= gitUrl(url); + const author= url.split(/[:\/]/).at(-2); + const path= dirs.pack+author+"/"+target; + s.mkdir("-p", path); + s.cd(path); + branch= !branch ? "" : `-b ${branch}`; + s.run`git clone ${branch} --single-branch ${url} --depth 1`; + $.exit(0); +} +function gitUrl(url_candidate){ + if(url_candidate.endsWith(".git")) + return url_candidate; + return "git@github.com:"+url_candidate; +} +async function actionUpdate(){ + const css= echo.css` + .success{ color: lightgreen; } + .success::before{ content: "✓ "; } + `; + updateRepo(dirs.bundle, getBundle()); + const todo= getOneFilesUrls(); + const progress= echoProgress(todo.length, "Downloaded one-file plugins"); + await Promise.all(todo.map(function(url, i){ + return fetch(url).then(r=> { + progress.update(i, url); + return r.text(); + }).then(f=> s.echo(f).to(dirs.one_files+fileName(url))); + })); + echo("One-file plugin(s) updated."); + s.cd(dirs.bundle).$().run`git commit -m "Update"`; + updateRepo(dirs.pack, getPack()); + + $.exit(0); + + function updateRepo(dir, paths){ + echo(dir); + const progress= echoProgress(paths.length, "Pulling"); + const todo= paths.map(function(p, i){ + progress.update(i, p); + return pull(p); + }).filter(isUpToDate); + if(!todo.length) + return echo("%cAll up-to-date!", css.success); + todo.forEach(([ p, result ])=> echo("%c"+p+"\n", css.success, result.join("\n"))); + } + function pull(p){ + s.cd(p); + return [ p, runResToArr("git pull") ]; + } + function isUpToDate([ _, result ]){ + return result[0]===" Already up-to-date."; + } +} +function actionList({ type= "paths" }){ + if("paths"===type){ + echo("%cbundle", css.bold, dirs.bundle); + getOneFiles().forEach(echoPath); + getBundle().forEach(echoPath); + + echo("%cpack", css.bold, dirs.pack); + getPack().forEach(echoPath); + $.exit(0); + } + const progress= echoProgress(3, "Collecting plugins urls"); + progress.update(0, dirs.bundle); + const urls_bundle= getBundle().map(getRepo); + progress.update(1, dirs.bundle); + const urls_onefiles= getOneFilesUrls(); + progress.update(2, dirs.pack); + const urls_pack= getPack().map(getRepo); + + if("repos"===type){ + const echoUrl= pipe( + u=> u.replace("git@github.com:", "https://github.com/"), + u=> f(u, css.url), + echo + ) + echo("%cbundle", css.bold, dirs.bundle); + urls_bundle.forEach(echoUrl); + echo("%cbundle", css.bold, dirs.one_files); + urls_onefiles.forEach(echoUrl); + echo("%cpack", css.bold, dirs.pack); + urls_pack.forEach(echoUrl); + } + if("json"===type){ //TODO: save options!? + const o= {}; + o.bundle= urls_bundle; + o.one_files= urls_onefiles; + o.pack= urls_pack; + echo(JSON.stringify(o)); + } + $.exit(0); + + function getRepo(p){ s.cd(p); return runResToArr("git remote -v")[0].split(/\s+/g)[1]; } +} +function actionStatus(){ + const css= echo.css` + .success { color: lightgreen; } + .success::before { content: "✓ "; } + `; + check(dirs.bundle, getBundle()); + echo("Onefiles plugins are not supported yet"); + check(dirs.pack, getPack()); + $.exit(0); + + function check(root, repos){ + echo(root); + const progress= echoProgress(repos.length); + const results= repos.flatMap(function(p, i){ + progress.update(i, p); + s.cd(p); + const result= runResToArr("git fetch --dry-run --verbose") + .filter(l=> !l ? false : l.startsWith("From") || (!l.startsWith(" = [up-to-date]") && !l.startsWith("POST") )); + if(result.length===1) return []; + return [ [ p, result.join("\n") ] ]; + }); + if(!results.length) + return echo("%cup-to-date", css.success); + results.forEach(([ p, l ])=> { + echoPath(p); + echo(l); + }); + } +} + +import { relative } from 'node:path'; +function echoPath(path){ return echo(formatPath(path)); } +function formatPath(path){ + const type= path.startsWith(dirs.bundle) ? "bundle" : "pack"; + return echo.format("%c"+relative(dirs[type], path), "color:lightblue"); +} +import { stdout } from 'node:process'; +function echoProgress(length, message_start= "Working"){ + if(typeof stdout.isTTY === "undefined") return { update(){} }; + + const css= echo.css` + .progress { color: lightblue; } + `; + echo.use("-R", `${message_start} (%c0/${length}%c)`, css.progress); + return { + update(i, status){ + const s= status ? `: ${status}` : ""; + return echo.use("-R", `${message_start} (%c${i+1}/${length}%c)${s}`, css.progress); + } + }; +} + +function getPack(){ return s.ls(dirs.pack).flatMap(f=> s.find(dirs.pack+f+"/start/*/")[0]).filter(Boolean); } +function getBundle(){ return s.cd(dirs.bundle).grep("path", ".gitmodules").split("\n").filter(Boolean).map(l=> dirs.bundle+l.split(" = ")[1]); } +function getOneFiles(){ return s.find(dirs.one_files+"*"); } +function getOneFilesUrls(){ return s.cat(file_one_file).xargs(JSON.parse); } + +function fileName(url){ return url.split("/").pop(); } +/** Quick formating of one piece of text. */ +function f(text, css){ return echo.format("%c"+text, css); } diff --git a/bin/§wallpaper_BIOTD.mjs b/bin/§wallpaper_BIOTD.mjs new file mode 100755 index 0000000..aa382fa --- /dev/null +++ b/bin/§wallpaper_BIOTD.mjs @@ -0,0 +1,44 @@ +#!/usr/bin/env node +/* jshint esversion: 6,-W097, -W040, browser: true, expr: true, undef: true */ +import { readFileSync, writeFileSync, createWriteStream } from "fs"; +import { get } from "https"; +import { homedir } from "os"; + +const url_main= "https://www.bing.com"; +const folder= homedir()+"/Obrázky/Bing Image Of The Day/"; + +get_(url_main+"/HPImageArchive.aspx?format=js&idx=0&n=2&mkt=cs-CZ") +.then(res=> { + let body= ""; + res.on("data", chunk=> body+= chunk); + res.on("end", ()=> pipe(data, update)(body)); +}) +.catch(e=> console.error(String(e))); + +function update(data){ + if(data===null) return false; + Promise.allSettled(data.map(({ url, copyright }, id)=> getImage_(url, id ? "prev" : "now", copyright))) + .then(res=> { + let template= readFileSync(folder+"index_template.html").toString(); + writeFileSync(folder+"index.html", res.reduce((out, { status, value })=> status==="rejected" ? out : out.replace(`::${value.name}::`, value.description), template)); + }) + .catch(e=> console.error(String(e))); +} +function getImage_(url, name, desc){ + const description= desc.replace("(©", "
(©"); + return get_(url_main+url) + .then(res=> { + const fs= createWriteStream(folder+name+'.jpg'); + res.pipe(fs); + return new Promise(res=> fs.on("finish", ()=> { + fs.close(); + res({ name, description }); + })); + }); +} +function data(body){ + try { return JSON.parse(body).images; } + catch (_) { return null; } +} +function get_(url){ return new Promise(function(res,rej){ get(url, res).on("error", rej); }); } +function pipe(...f){ return Array.prototype.reduce.bind(f, (acc, f)=> f(acc)); } diff --git a/bin/§wolframalpha.mjs b/bin/§wolframalpha.mjs new file mode 100755 index 0000000..abd6548 --- /dev/null +++ b/bin/§wolframalpha.mjs @@ -0,0 +1,20 @@ +#!/usr/bin/env nodejsscript +/* jshint esversion: 8,-W097, -W040, node: true, expr: true, undef: true *//* global echo, $, pipe, s, style, fetch, cyclicLoop */ +$.is_fatal= true; +$.api("<...query_array>", true) +.version("v2022-09-23") +.describe([ + "This is just redirection to [WolframAlpha](https://www.wolframalpha.com/) site.", + "Use the same expressions as on web page." +]) +.example("linear fit {1.3, 2.2},{2.1, 5.8},{3.7, 10.2},{4.2, 11.8}") +.example("polynomial fit {1,2},{2,3.1},{3,3.9}") +.example("Fit[{{1,2},{2,3.1},{3,3.9}}, {1,x}, x]") +.action(function main(first, { _: query_array= [] }){ + query_array.unshift(first); + echo("Opening:"); + echo("https://www.wolframalpha.com/input/?i="+encodeURI(query_array.join(" ")).replace(/\+/g, '%2B')) + .xargs(s.runA, "exo-open --launch WebBrowser {}") + .then($.exit.bind(null, 0)).catch($.exit.bind(null, 1)); +}) +.parse();