Files
dotfiles/i3/i3status/index.js
T

553 lines
12 KiB
JavaScript

import { URL } from "url";
import { request } from "https";
import { readFile } from "fs/promises";
import { cpus as cpuInfo, homedir } from "os";
import { exec } from "child_process";
import { createInterface } from "readline/promises";
import { stdin as input, stdout as output } from "process";
import { createRenderer } from "@aminnairi/i3status";
import { join } from "path";
// SETTINGS
const settings = await readFile(join(homedir(), ".config/i3/i3status/settings.json")).then(fileContent => {
return JSON.parse(fileContent);
}).catch(() => {
return {
weather: {
appid: ""
}
};
});
// HELPERS
const toHexadecimal = (number) => {
const hexadecimal = number.toString(16);
return hexadecimal.length === 1
? `0${hexadecimal}`
: hexadecimal;
};
const getHexadecimalColorBetween = (color1, color2, ratio) => {
const red = Math.round(color1.red + (color2.red - color1.red) * ratio);
const green = Math.round(color1.green + (color2.green - color1.green) * ratio);
const blue = Math.round(color1.blue + (color2.blue - color1.blue) * ratio);
return `#${toHexadecimal(red)}${toHexadecimal(green)}${toHexadecimal(blue)}`;
};
const red = {
red: 255,
green: 0,
blue: 0
};
const green = {
red: 0,
green: 255,
blue: 0
};
const json = {
get: (url) => {
return new Promise((resolve, reject) => {
const { hostname, pathname, search } = new URL(url);
const path = `${pathname}${search}`;
let data = "";
const req = request({
hostname,
path,
port: 443,
method: "GET"
}, response => {
response.on("data", chunk => {
data += chunk.toString();
});
response.on("end", () => {
try {
resolve(JSON.parse(data));
} catch (error) {
reject(error);
}
});
});
req.on("error", error => {
reject(error);
});
req.end();
});
}
};
const execute = (command) => {
return new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
return reject(error);
}
if (stderr) {
return reject(new Error(stderr));
}
resolve(stdout);
});
});
};
const ignore = () => { };
// BUSINESS
const getCov19 = async () => {
return json.get("https://coronavirusapifr.herokuapp.com/data/live/france").then(response => {
return ` ${Math.round(response[0].TO * 10000) / 100}%`;
});
};
const getClock = () => {
const date = new Date();
return ` ${date.toLocaleDateString("fr-FR")} ${date.toLocaleTimeString("fr-FR")}`;
};
const getBitcoinRate = async () => {
return json.get("https://api.coindesk.com/v1/bpi/currentprice.json").then(json => {
return ` ${json.bpi.USD.rate}`;
});
};
const getWeather = async () => {
return json.get(`https://api.openweathermap.org/data/2.5/weather?q=paris&appid=${settings.weather.appid}&units=metric`).then(json => {
return ` ${Math.round((Number(json.main.temp) || 0) * 10) / 10}°C ${json.weather[0].main}`;
});
};
const getCpus = () => {
const cpus = cpuInfo();
const count = cpus.length;
const speed = Math.round(cpus.reduce((clock, cpu) => clock + cpu.speed, 0) / count) / 1000;
return ` ${count} CPUs ${speed}GHz`;
};
const getBattery = async () => {
return Promise.all([
execute("/usr/bin/cat /sys/class/power_supply/BAT*/capacity"),
execute("/usr/bin/cat /sys/class/power_supply/BAT*/status")
]).then(([rawBattery, rawStatus]) => {
const battery = Number(rawBattery.trim()) || 0;
const status = rawStatus.trim();
const icon = status === "Discharging" ? "" : "";
const color = getHexadecimalColorBetween(red, green, battery / 100);
return {
color,
full_text: `${icon} ${battery}%`
}
});
};
const getBrightness = async () => {
return execute("/usr/bin/cat /sys/class/backlight/*/brightness").then(brightness => {
const brightnessLevel = Number(brightness.trim()) || 0;
return ` ${brightnessLevel}%`;
});
};
const getVolume = async () => {
return Promise.all([
execute("pactl get-sink-volume 0 | grep 'Volume' | cut -d '/' -f 2"),
execute("pactl get-sink-mute 0")
]).then(([volume, muted]) => {
return `${muted.includes("no") ? "" : ""} ${volume.trim()}`;
});
};
const getMemory = async () => {
return Promise.all([
execute("/usr/bin/cat /proc/meminfo | grep MemTotal | cut -d ' ' -f 8"),
execute("/usr/bin/cat /proc/meminfo | grep MemFree | cut -d ' ' -f 10")
]).then(([rawTotalMemory, rawFreeMemory]) => {
const totalMemory = Math.round((Number(rawTotalMemory.trim()) || 0) / 100000) / 10;
const freeMemory = Math.round((Number(rawFreeMemory.trim()) || 0) / 100000) / 10;
return ` ${freeMemory}Go / ${totalMemory}Go`;
});
};
const getDiskSpace = async () => {
return Promise.all([
execute("df -h | grep '/\$' | cut -d ' ' -f 7"),
execute("df -h | grep '/\$' | cut -d ' ' -f 3")
]).then(([rawDiskSpaceRemaining, rawDiskSpaceTotal]) => {
const diskSpaceRemaining = rawDiskSpaceRemaining.trim();
const diskSpaceTotal = rawDiskSpaceTotal.trim();
return ` ${diskSpaceRemaining} / ${diskSpaceTotal}`;
});
};
// SETUP
const render = createRenderer({
createInterface,
input,
output
});
const dispatch = render({
initialBlocks: [
{
name: "disk",
full_text: "",
separator: false
},
{
name: "memory",
full_text: "",
separator: false
},
{
name: "cpus",
full_text: getCpus(),
separator: false
},
{
name: "bitcoin",
full_text: "",
separator: false
},
{
name: "cov19",
full_text: "",
separator: false
},
{
name: "weather",
full_text: "",
separator: false
},
{
name: "battery",
full_text: "",
separator: false
},
{
name: "brightness",
full_text: "",
separator: false
},
{
name: "volume",
full_text: "",
separator: false
},
{
name: "clock",
full_text: getClock(),
separator: false
},
],
onEvent: ({ event: { name, button, modifiers }, dispatch }) => {
const leftClick = 1;
if (name === "clock" && button === leftClick && modifiers.length === 1 && modifiers.includes("Control")) {
return execute("librewolf https://calendar.google.com");
}
if (name === "weather" && button === 1 && modifiers.length === 0) {
return getWeather().then(weather => {
dispatch({
name: "WEATHER_SET_FULL_TEXT",
payload: weather
});
}).catch(ignore);
}
if (name === "bitcoin" && button === 1) {
if (modifiers.length === 0) {
return getBitcoinRate().then(rate => {
dispatch({
name: "BITCOIN_SET_FULL_TEXT",
payload: rate
});
}).catch(ignore);
}
if (modifiers.length === 1 && modifiers.includes("Control")) {
return execute("librewolf https://coindesk.com/price/bitcoin");
}
}
},
onDispatch: ({ blocks, action: { name, payload } }) => {
switch (name) {
case "CLOCK_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "clock") {
return block;
}
return {
...block,
full_text: payload
};
})
case "BITCOIN_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "bitcoin") {
return block;
}
return {
...block,
full_text: payload
};
});
case "WEATHER_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "weather") {
return block;
}
return {
...block,
full_text: payload
};
});
case "CPUS_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "cpus") {
return block;
}
return {
...block,
full_text: payload
};
});
case "BATTERY_SET_FULL_TEXT_COLOR":
return blocks.map(block => {
if (block.name !== "battery") {
return block;
}
return {
...block,
full_text: payload.full_text,
color: payload.color
};
});
case "BRIGHTNESS_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "brightness") {
return block;
}
return {
...block,
full_text: payload
};
});
case "VOLUME_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "volume") {
return block;
}
return {
...block,
full_text: payload
};
});
case "MEMORY_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "memory") {
return block;
}
return {
...block,
full_text: payload
};
});
case "DISK_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "disk") {
return block;
}
return {
...block,
full_text: payload
};
});
case "COV19_SET_FULL_TEXT":
return blocks.map(block => {
if (block.name !== "cov19") {
return block;
}
return {
...block,
full_text: payload
};
});
default:
return blocks;
}
}
});
// INTERVALS
setInterval(() => {
dispatch({
name: "CLOCK_SET_FULL_TEXT",
payload: getClock()
});
dispatch({
name: "CPUS_SET_FULL_TEXT",
payload: getCpus()
});
getBattery().then(battery => {
dispatch({
name: "BATTERY_SET_FULL_TEXT_COLOR",
payload: battery
});
}).catch(ignore);
getBrightness().then(brightness => {
dispatch({
name: "BRIGHTNESS_SET_FULL_TEXT",
payload: brightness
});
}).catch(ignore);
getVolume().then(volume => {
dispatch({
name: "VOLUME_SET_FULL_TEXT",
payload: volume
});
}).catch(ignore);
getMemory().then(memory => {
dispatch({
name: "MEMORY_SET_FULL_TEXT",
payload: memory
});
}).catch(ignore);
getDiskSpace().then(diskSpace => {
dispatch({
name: "DISK_SET_FULL_TEXT",
payload: diskSpace
});
}).catch(ignore);
}, 1000);
setInterval(() => {
getBitcoinRate().then(rate => {
dispatch({
name: "BITCOIN_SET_FULL_TEXT",
payload: rate
});
}).catch(ignore);
}, 1000 * 60 * 10);
setInterval(() => {
getWeather().then(weather => {
dispatch({
name: "WEATHER_SET_FULL_TEXT",
payload: weather
});
}).catch(ignore);
}, 1000 * 60 * 30);
setInterval(() => {
getCov19().then(payload => {
dispatch({
name: "COV19_SET_FULL_TEXT",
payload
});
}).catch(ignore);
}, 1000 * 60 * 60 * 12);
// INITIAL SETUP
getBitcoinRate().then(rate => {
dispatch({
name: "BITCOIN_SET_FULL_TEXT",
payload: rate
});
}).catch(ignore);
getWeather().then(weather => {
dispatch({
name: "WEATHER_SET_FULL_TEXT",
payload: weather
});
}).catch(ignore);
getBattery().then(battery => {
dispatch({
name: "BATTERY_SET_FULL_TEXT_COLOR",
payload: battery
});
}).catch(ignore);
getBrightness().then(brightness => {
dispatch({
name: "BRIGHTNESS_SET_FULL_TEXT",
payload: brightness
});
}).catch(ignore);
getVolume().then(volume => {
dispatch({
name: "VOLUME_SET_FULL_TEXT",
payload: volume
});
}).catch(ignore);
getMemory().then(memory => {
dispatch({
name: "MEMORY_SET_FULL_TEXT",
payload: memory
});
}).catch(ignore);
getDiskSpace().then(diskSpace => {
dispatch({
name: "DISK_SET_FULL_TEXT",
payload: diskSpace
});
}).catch(ignore);
getCov19().then(payload => {
dispatch({
name: "COV19_SET_FULL_TEXT",
payload
});
}).catch(ignore);