#!/usr/bin/env /usr/local/bin/node
const https = require('https');
/* EDIT HERE */
const API_TOKEN = '';
/* DON'T EDIT BELOW */
// Github Notifications
// v1.0
// Gil Barbara
// gilbarbara
// List notifications.
// node
// https://github.com/gilbarbara/bitbar-plugins
const BASE_URL = 'api.github.com';
const ICON = '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ZWQgOCAwIFIgXQplbmRvYmoKMyAwIG9iago8PCAvVHlwZSAvUGFnZXMgL01lZGlhQm94IFswIDAgMTYgMTZdIC9Db3VudCAxIC9LaWRzIFsgMiAwIFIgXSA+PgplbmRvYmoKMTAgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL1BhZ2VzIDMgMCBSID4+CmVuZG9iagoxMSAwIG9iagooTWFjIE9TIFggMTAuMTMuNiBRdWFydHogUERGQ29udGV4dCkKZW5kb2JqCjEyIDAgb2JqCihEOjIwMTgwODExMjIzMjU0WjAwJzAwJykKZW5kb2JqCjEgMCBvYmoKPDwgL1Byb2R1Y2VyIDExIDAgUiAvQ3JlYXRpb25EYXRlIDEyIDAgUiAvTW9kRGF0ZSAxMiAwIFIgPj4KZW5kb2JqCnhyZWYKMCAxMwowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDQ3NDQgMDAwMDAgbiAKMDAwMDAwMTYwMiAwMDAwMCBuIAowMDAwMDA0NTE4IDAwMDAwIG4gCjAwMDAwMDAwMjIgMDAwMDAgbiAKMDAwMDAwMTU4MiAwMDAwMCBuIAowMDAwMDAxNjgyIDAwMDAwIG4gCjAwMDAwMDQ0ODMgMDAwMDAgbiAKMDAwMDAwMTc1MCAwMDAwMCBuIAowMDAwMDA0NDYzIDAwMDAwIG4gCjAwMDAwMDQ1OTkgMDAwMDAgbiAKMDAwMDAwNDY0OSAwMDAwMCBuIAowMDAwMDA0NzAyIDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgMTMgL1Jvb3QgMTAgMCBSIC9JbmZvIDEgMCBSIC9JRCBbIDw4YmRjYjYxNzEwOThkYWFjYzhkYTBlMDc0OGM3MDA5ZD4KPDhiZGNiNjE3MTA5OGRhYWNjOGRhMGUwNzQ4YzcwMDlkPiBdID4+CnN0YXJ0eHJlZgo0ODE5CiUlRU9GCg==';
const RELOAD_ICON = 'iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAmElEQVR4AY3SJdYCYRhA4V+BRGY5rAF3ZwvkiSyANLugYgUtOJsg4s7LxV3uOU8a+fTrom9EUMEAfZQRwDEFuwxIQ9CEetCBIIk4BF/fSKMPL26LYgHZoQgEXjxKgQCgCpqfvQwaQMVtOhhv3X7wURU08apvxGB5v2giFwTmT7bVigly+H11cAnUIChAD3p+NbrIwotv7NoAffg2NR6lsPIAAAAASUVORK5CYII=';
function request(options = {}) {
const OPTIONS = {
hostname: BASE_URL,
path: options.path || '/',
port: 443,
method: options.method || 'GET',
headers: {
Authorization: `Bearer ${API_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'github-notifications-bitbar-plugin',
...options.headers,
},
};
return new Promise((resolve, reject) => {
const req = https.request(OPTIONS, (response) => {
const { headers, statusCode } = response;
const isJSON = headers['content-type'].includes('application/json');
// temporary data holder
const body = [];
// on every content chunk, push it to the data array
response.on('data', chunk => body.push(chunk));
// we are done, resolve promise with those joined chunks
response.on('end', () => {
const content = body.join('');
const data = isJSON ? JSON.parse(content) : content;
if (statusCode < 200 || statusCode > 299) {
reject(new Error(`Request failed [${response.statusCode}] - ${data}`));
return;
}
resolve(data);
});
});
// handle connection errors of the request
req.on('error', err => reject(err));
req.end();
});
}
function timeSince(dateString) {
const date = new Date(dateString);
const seconds = Math.floor((new Date() - date) / 1000);
let intervalType;
let interval = Math.floor(seconds / 31536000);
if (interval >= 1) {
intervalType = 'year';
}
else {
interval = Math.floor(seconds / 2592000);
if (interval >= 1) {
intervalType = 'month';
}
else {
interval = Math.floor(seconds / 86400);
if (interval >= 1) {
intervalType = 'day';
}
else {
interval = Math.floor(seconds / 3600);
if (interval >= 1) {
intervalType = 'hour';
}
else {
interval = Math.floor(seconds / 60);
if (interval >= 1) {
intervalType = 'minute';
}
else {
interval = seconds;
intervalType = 'second';
}
}
}
}
}
if (interval > 1 || interval === 0) {
intervalType += 's';
}
return `${interval} ${intervalType}`;
}
function formatTitle(data) {
const { subject, unread } = data;
return `${subject.title} | href=${getURL(data)} length=48 color=${unread ? '#4078C0' : 'black'}`;
}
function formatInfo({ repository, updated_at: updatedAt }) {
return `${repository.full_name} - ${timeSince(updatedAt)} ago | size=12`;
}
function formatNotification(data) {
return [
formatTitle(data),
formatInfo(data),
].join('\n');
}
function getURL(data) {
const { repository, subject } = data;
let output = '';
if (subject.url) {
output = subject.url.replace('api.github.com/repos', 'github.com');
}
else {
output = repository.html_url;
if (subject.type === 'Discussion') {
output += '/discussions';
}
}
return output;
}
function handleResponse(body) {
const content = body.map(formatNotification).join('\n---\n');
const output = [
`|image=${ICON}`,
content,
`RELOAD | image=${RELOAD_ICON} refresh=true`,
];
console.log(output.join('\n---\n'));
}
const getNotifications = () => request({
path: '/notifications?all=true&per_page=10',
});
getNotifications()
.then(handleResponse)
.catch(err => console.log(err.toString()));