@codebynithin/call-history
Version:
Convert teams call history to entries
303 lines (240 loc) • 7.7 kB
JavaScript
const { format, subDays, isWithinInterval, parse } = require('date-fns');
const { MultiSelect } = require('enquirer');
const fs = require('fs');
const os = require('os');
const path = require('path');
const DAYS_OF_WEEK = new Set([
'Today',
'Yesterday',
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
]);
function roundTimeTo5(timeStr) {
if (!timeStr?.includes('m') && !timeStr?.includes('h')) {
timeStr = `0m ${timeStr}`;
}
if (!timeStr?.includes('h')) {
timeStr = `0h ${timeStr}`;
}
const sanitizedTime = timeStr.replace(/[^0-9\s]/g, '');
const [hours, minutes, seconds] = sanitizedTime.split(' ').map(Number);
const totalSeconds = (hours || 0) * 60 * 60 + (minutes || 0) * 60 + (seconds || 0);
const roundedSeconds = 300 * Math.max(totalSeconds / 300);
const roundedMinutes = Math.floor(roundedSeconds / 60);
return Math.round((roundedMinutes + 1) / 5) * 5;
}
function getLastDayOfWeek(inputString) {
const currentDate = new Date();
let targetDate = currentDate;
switch (inputString.toLowerCase()) {
case 'today':
targetDate = currentDate;
break;
case 'yesterday':
targetDate = subDays(currentDate, 1);
break;
default:
targetDate = currentDate;
while (format(targetDate, 'EEEE').toLowerCase() !== inputString.toLowerCase()) {
targetDate = subDays(targetDate, 1);
}
}
return targetDate;
}
function formatCalls(acc, curr, filter) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let [person, direction, day, time] = curr.split(/\n/g);
if (!day || !time) return;
const formattedTime = roundTimeTo5(time);
if (!formattedTime) return;
person = person.split(', ').sort().join(',');
day = normalizeDay(day);
const hasDateFilter = filter.from && filter.to;
const passesFilter =
!hasDateFilter ||
isWithinInterval(new Date(day), {
start: new Date(filter.from),
end: new Date(filter.to),
});
if (passesFilter) {
acc.add({ day, person, time: formattedTime });
}
}
function normalizeDay(day) {
if ((day.length === 5 && day !== 'Today') || day.includes(' PM') || day.includes(' AM')) {
return format(new Date(), 'yyyy-MM-dd');
}
if (day.includes('/')) {
const year = day.split('/')[2];
const dateFormat = year?.length === 2 ? 'dd/MM/yy' : 'dd/MM/yyyy';
const parsedDate = parse(day, dateFormat, new Date());
if (parsedDate instanceof Date && !isNaN(parsedDate.getTime())) {
return parsedDate.toISOString().split('T')[0];
}
}
if (DAYS_OF_WEEK.has(day)) {
return format(getLastDayOfWeek(day), 'yyyy-MM-dd');
}
return day;
}
function getItemsFromContenWithoutLineBreaks(content) {
const lines = content.split('\n');
const resp = [];
for (let i = 0; i < lines.length; i += 3) {
const hasExtraLine =
lines[i + 3] && !isNaN(lines[i + 3].charAt(0)) && lines[i + 3].charAt(0) !== ' ';
if (hasExtraLine) {
const item = [lines[i], lines[i + 1], lines[i + 2], lines[i + 3]].join('\n');
resp.push(item);
i++;
} else if (i + 2 < lines.length) {
const item = [lines[i], lines[i + 1], lines[i + 2]].join('\n');
resp.push(item);
}
}
return resp;
}
async function filterCalls(values) {
const filter = {};
const keyMap = { from: 'from', to: 'to' };
if (values?.length) {
for (const item of values) {
let [key, ...itemValues] = item.split(' ');
const itemValue = itemValues.join(' ');
if (key.charAt(0) === '-') {
key = key.substring(1);
}
if (keyMap[key]) {
filter[keyMap[key]] = new Date(itemValue).toISOString().split('T')[0];
} else {
filter.task = itemValue;
}
}
}
const filename = 'teams-call.txt';
const filePath = path.join(os.homedir(), 'Desktop/');
let content;
let start = false;
let allTextIdentified = false;
let output;
try {
content = fs.readFileSync(`${filePath}${filename}`, 'utf8');
} catch (err) {
console.error(`${filename} file missing or it's empty in your desktop, please try again.`);
fs.appendFileSync(filename, filePath, (err) => {
if (err) {
throw err;
}
console.log('String appended to file!');
});
process.exit(1);
}
content = content.replace(/\n{3,}/g, '\n\n');
const calls = content?.split(/\n\n/g).reduce((acc, curr) => {
if (curr.includes('Contact groups') || curr.includes('Speed dial')) {
if (curr.length > 20) {
getItemsFromContenWithoutLineBreaks(curr).forEach((item) => {
formatCalls(acc, item, filter);
});
}
start = false;
return acc;
}
if (curr === 'All') {
allTextIdentified = true;
return acc;
}
if (allTextIdentified && curr !== 'Missed') {
start = true;
allTextIdentified = false;
}
if (curr.includes('Voicemail')) {
start = true;
}
if (start) {
formatCalls(acc, curr, filter);
}
return acc;
}, new Set());
const choices = Array.from(calls).map((item) => ({
name: `${item.day} - ${item.person} - ${item.time}`,
value: item,
}));
const prompt = new MultiSelect({
name: 'calls',
message: 'Select required calls',
choices,
initial: choices.map((_, index) => index),
hint: '(Use <space> to select, <a> to toggle all, <i> to invert selection)',
});
try {
const answer = await prompt.run();
output = choices.reduce((acc, { value, name }) => {
if (answer.includes(name)) {
if (!acc[value.day]) {
acc[value.day] = { [value.person]: { duration: 0, remarks: '', remarksArray: [] } };
}
if (!acc[value.day][value.person]) {
acc[value.day][value.person] = { duration: 0, remarks: '', remarksArray: [] };
}
if (acc[value.day][value.person].remarksArray?.length) {
acc[value.day][value.person].remarksArray.push(`${value.time}m`);
} else {
acc[value.day][value.person].remarksArray.push(`${value.person} ${value.time}m`);
}
acc[value.day][value.person].duration += +value.time;
acc[value.day][value.person].remarks = acc[value.day][value.person].remarksArray.join(', ');
}
return acc;
}, {});
joinCalls(output, filter.task);
} catch (error) {
console.error(error);
}
}
function joinCalls(entriesByDate, task) {
let output = '';
for (const [date, persons] of Object.entries(entriesByDate)) {
const allRemarks = [];
let totalDuration = 0;
for (const [personName, { duration, remarksArray }] of Object.entries(persons)) {
totalDuration += duration;
let firstName = personName;
if (!personName.includes(',')) {
firstName = personName.split(' ')[0];
}
allRemarks.push(
`${firstName} ${duration}m${remarksArray?.length ? ` (${remarksArray.join(', ')})` : ''}`,
);
}
output += `timectl add -dt ${date} -t ${task || '<task id>'} -w Internal calls -du ${totalDuration} -r "${allRemarks.join(', ')}"\n`;
}
console.log(output);
}
function removeEmpty(obj) {
for (let [key, val] of Object.entries(obj)) {
if (val && typeof val === 'object') {
removeEmpty(val);
if (!(Object.keys(val).length || val instanceof Date)) {
delete obj[key];
}
} else {
if (typeof val === 'string') {
val = val.trim();
}
if (val === null || val === undefined || val === '') {
delete obj[key];
} else {
obj[key] = val;
}
}
}
return obj;
}
filterCalls();
module.exports = { filterCalls, removeEmpty };