// ==UserScript==
// @name M-Pesa CSV Extractor
// @namespace https://openai.com
// @version 1.0
// @description Extracts M-Pesa messages from messages.google.com and creates a CSV
// @match https://messages.google.com/web/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
function createFloatingButton() {
const btn = document.createElement('button');
btn.id = 'mpesa-monkey-button';
btn.textContent = '🐒';
btn.title = 'Save M-Pesa Transactions';
Object.assign(btn.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
width: '48px',
height: '48px',
borderRadius: '50%',
border: 'none',
background: '#1a73e8',
color: 'white',
fontSize: '24px',
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
zIndex: '9999',
cursor: 'pointer'
});
const style = document.createElement('style');
style.textContent = `
@keyframes mpesa-jump {
0% { transform: translateY(0); }
50% { transform: translateY(-10px); }
100% { transform: translateY(0); }
}
`;
document.head.appendChild(style);
btn.onclick = () => {
btn.style.animation = 'mpesa-jump 0.4s ease';
setTimeout(() => btn.style.animation = '', 400);
openDateModal();
};
document.body.appendChild(btn);
}
function openDateModal() {
const existing = document.getElementById('mpesa-modal');
if (existing) existing.remove();
const modal = document.createElement('div');
modal.id = 'mpesa-modal';
Object.assign(modal.style, {
position: 'fixed',
top: '0', left: '0', right: '0', bottom: '0',
backgroundColor: 'rgba(0,0,0,0.5)',
display: 'flex', alignItems: 'center', justifyContent: 'center',
zIndex: '10000'
});
const content = document.createElement('div');
Object.assign(content.style, {
background: 'white',
padding: '20px',
borderRadius: '8px',
textAlign: 'center',
display: 'flex',
flexDirection: 'column',
gap: '10px',
maxWidth: '300px'
});
const title = document.createElement('h3');
title.textContent = 'Select start date';
const reminder = document.createElement('p');
reminder.style.fontSize = '12px';
reminder.style.color = '#666';
reminder.textContent = 'Reminder: Scroll to load all messages before extracting.';
const input = document.createElement('input');
input.type = 'date';
input.id = 'mpesa-date';
const buttons = document.createElement('div');
buttons.style.display = 'flex';
buttons.style.justifyContent = 'space-between';
buttons.style.gap = '10px';
const submit = document.createElement('button');
submit.textContent = 'Extract';
submit.onclick = () => {
const dateValue = input.value;
modal.remove();
if (dateValue) processMessages(new Date(dateValue));
};
const cancel = document.createElement('button');
cancel.textContent = 'Cancel';
cancel.onclick = () => modal.remove();
buttons.appendChild(submit);
buttons.appendChild(cancel);
content.appendChild(title);
content.appendChild(reminder);
content.appendChild(input);
content.appendChild(buttons);
modal.appendChild(content);
document.body.appendChild(modal);
}
function showToast(message) {
const toast = document.createElement('div');
toast.textContent = message;
Object.assign(toast.style, {
position: 'fixed',
bottom: '80px',
right: '20px',
background: '#333',
color: '#fff',
padding: '10px 15px',
borderRadius: '5px',
zIndex: '10001',
fontSize: '14px',
opacity: '0',
transition: 'opacity 0.3s ease'
});
document.body.appendChild(toast);
setTimeout(() => toast.style.opacity = '1', 10);
setTimeout(() => {
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3000);
};
function normalizeAmount(str) {
if (!str) return "0.00";
return parseFloat(str.replace(/,/g, '').replace(/[^\d.]/g, '')).toFixed(2);
}
function processMessages(startDate) {
const rawMessages = Array.from(document.querySelectorAll('mws-text-message-part'))
.map(el => el.getAttribute('aria-label')?.trim())
.filter(Boolean);
const rows = [];
for (const message of rawMessages) {
const dateMatch = message.match(/Received on (.+?) at/);
if (!dateMatch) continue;
const msgDate = new Date(dateMatch[1]);
const startOfDay = new Date(startDate);
startOfDay.setHours(0, 0, 0, 0);
if (msgDate < startOfDay) continue;
const reversalMatch = message.match(/.*?factura\s+(\w{11}).*?revertido/i);
if (reversalMatch) {
const code = reversalMatch[1];
const index = rows.findIndex(row => row[1] === code);
if (index !== -1) rows.splice(index, 1);
continue;
}
const transfer = message.match(
/Confirmado\s+([A-Z0-9]{11,12})[\s\S]*?Transferiste\s+([\d.,]+)MT[\s\S]*?taxa\s+foi\s+de\s+([\d.,]+)MT[\s\S]*?aos\s+(\d{1,2})\/(\d{1,2})\/(\d{2})/i
);
if (transfer) {
const [_, code, value, fee, day, month, year] = transfer;
const formattedDate = `${day.padStart(2, '0')}/${month.padStart(2, '0')}/20${year}`;
rows.push([rows.length + 1, code, normalizeAmount(value), formattedDate, normalizeAmount(fee)]);
continue;
}
const compra = message.match(
/Confirmado\s+([A-Z0-9]{11,12})[\s\S]*?operacao de compra[\s\S]*?([\d.,]+)MT[\s\S]*?aos\s+(\d{1,2})\/(\d{1,2})\/(\d{2})/i
);
if (compra) {
const [_, code, value, day, month, year] = compra;
const formattedDate = `${day.padStart(2, '0')}/${month.padStart(2, '0')}/20${year}`;
console.log(value)
rows.push([rows.length + 1, code, normalizeAmount(value), formattedDate, '0.00']);
continue;
}
}
if (rows.length === 0) {
showToast("No transactions found");
return;
}
const csvContent = [
['Numero', 'Codigo M-Pesa', 'Valor', 'Data', 'Taxa'],
...rows
].map(row => row.map(field => `"${field}"`).join(';')).join('\r\n');
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'mpesa_export.csv';
link.click();
}
window.addEventListener('load', () => {
setTimeout(createFloatingButton, 1500);
document.addEventListener('keydown', e => {
if (e.altKey && e.key.toLowerCase() === 'm') {
e.preventDefault();
openDateModal();
}
});
});
})();