Skip to content

Instantly share code, notes, and snippets.

@JuninhoFreitas
Created March 31, 2026 04:11
Show Gist options
  • Select an option

  • Save JuninhoFreitas/0fb229ad02a4022b0831f24f6f1f160e to your computer and use it in GitHub Desktop.

Select an option

Save JuninhoFreitas/0fb229ad02a4022b0831f24f6f1f160e to your computer and use it in GitHub Desktop.
Uber Trips Export
// Aperta F12 -> clique na aba console -> copie e cole esse texto lá e aperte enter
// No canto inferior direito vai surgir um botão de exportar dados das ultimas 50 corridas
(function () {
'use strict';
const PAGE_SIZE = 50;
// ─── Estado global ────────────────────────────────────────────────────────
let allRows = [];
let currentCursor = null;
let hasMore = false;
// ─── UI ───────────────────────────────────────────────────────────────────
const container = document.createElement('div');
Object.assign(container.style, {
position: 'fixed',
bottom: '24px',
right: '24px',
zIndex: '99999',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
gap: '8px',
fontFamily: 'Arial, sans-serif',
});
document.body.appendChild(container);
const statusEl = document.createElement('div');
Object.assign(statusEl.style, {
padding: '8px 14px',
background: '#222',
color: '#fff',
borderRadius: '6px',
fontSize: '13px',
display: 'none',
maxWidth: '320px',
lineHeight: '1.5',
});
container.appendChild(statusEl);
const btnRow = document.createElement('div');
Object.assign(btnRow.style, { display: 'flex', gap: '8px' });
container.appendChild(btnRow);
function makeBtn(label, bg) {
const b = document.createElement('button');
b.innerText = label;
Object.assign(b.style, {
padding: '12px 18px',
background: bg,
color: '#fff',
border: 'none',
borderRadius: '8px',
fontSize: '14px',
fontWeight: 'bold',
cursor: 'pointer',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
whiteSpace: 'nowrap',
});
return b;
}
const btnFetch = makeBtn('🚗 Buscar 50 corridas', '#000');
const btnMore = makeBtn('➕ Buscar mais 50', '#1a73e8');
const btnExport = makeBtn('📥 Exportar CSV', '#2e7d32');
btnMore.style.display = 'none';
btnExport.style.display = 'none';
btnRow.appendChild(btnFetch);
btnRow.appendChild(btnMore);
btnRow.appendChild(btnExport);
// ─── Helpers ──────────────────────────────────────────────────────────────
function setStatus(msg, color = '#fff') {
statusEl.style.display = 'block';
statusEl.style.color = color;
statusEl.innerText = msg;
}
function getHeaders() {
return {
'accept': '*/*',
'content-type': 'application/json',
'x-csrf-token': 'x',
};
}
async function fetchTrips(cursor = null) {
const body = { role: 'rider', options: { hidden: false } };
if (cursor) body.cursor = cursor;
const res = await fetch('/api/getTrips?localeCode=pt_BR', {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify(body),
credentials: 'include',
});
return res.json();
}
async function fetchTripDetail(uuid) {
const res = await fetch('/api/getTrip?localeCode=pt_BR', {
method: 'POST',
headers: getHeaders(),
body: JSON.stringify({ role: 'rider', uuid }),
credentials: 'include',
});
return res.json();
}
function formatDate(ms) {
return new Date(ms).toLocaleString('pt-BR', {
timeZone: 'America/Sao_Paulo',
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit',
});
}
function formatDuration(seconds) {
if (!seconds) return '';
return `${Math.floor(seconds / 60)}min ${seconds % 60}s`;
}
const STATUS_MAP = {
ORDER_STATUS_COMPLETED: 'Concluída',
ORDER_STATUS_REQUESTER_CANCELED: 'Cancelada',
ORDER_STATUS_NO_DRIVERS_AVAILABLE: 'Sem motoristas',
};
function escapeCSV(val) {
if (val === null || val === undefined) return '';
const str = String(val);
return (str.includes(',') || str.includes('"') || str.includes('\n'))
? '"' + str.replace(/"/g, '""') + '"'
: str;
}
function toCSV(rows) {
const headers = ['Data/Hora', 'Status', 'Produto', 'Origem', 'Destino', 'Distância (km)', 'Duração', 'Valor (BRL)'];
const lines = [headers.map(escapeCSV).join(',')];
for (const r of rows) {
lines.push([r.date, r.status, r.product, r.pickup, r.dropoff, r.distance, r.duration, r.fare]
.map(escapeCSV).join(','));
}
return lines.join('\n');
}
function downloadCSV(rows) {
const bom = '\uFEFF';
const blob = new Blob([bom + toCSV(rows)], { type: 'text/csv;charset=utf-8;' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `corridas_uber_${new Date().toISOString().slice(0, 10)}.csv`;
a.click();
URL.revokeObjectURL(url);
}
function updateUI() {
btnMore.style.display = hasMore ? 'block' : 'none';
btnExport.style.display = allRows.length > 0 ? 'block' : 'none';
if (hasMore) btnMore.innerText = `➕ Buscar mais 50 (${allRows.length} carregadas)`;
}
function disableButtons(val) {
btnFetch.disabled = val;
btnMore.disabled = val;
btnExport.disabled = val;
}
// ─── Coleta um lote: preenche batchTrips até PAGE_SIZE ───────────────────
async function collectBatch(startCursor) {
const batchTrips = [...(window.__uberOverflow || [])];
window.__uberOverflow = [];
let internalCursor = startCursor;
while (batchTrips.length < PAGE_SIZE) {
setStatus('📋 Buscando lista de corridas...');
const res = await fetchTrips(internalCursor);
if (res.status !== 'success') throw new Error('Erro na API: ' + JSON.stringify(res));
const trips = res.data.trips || [];
batchTrips.push(...trips);
internalCursor = res.data.cursor || null;
if (!internalCursor || trips.length === 0) break;
}
const toProcess = batchTrips.slice(0, PAGE_SIZE);
const remainder = batchTrips.slice(PAGE_SIZE);
hasMore = remainder.length > 0 || internalCursor !== null;
currentCursor = internalCursor;
window.__uberOverflow = remainder;
return toProcess;
}
// ─── Detalha e converte uma lista de trips em rows ───────────────────────
async function detailTrips(trips) {
const newRows = [];
for (let i = 0; i < trips.length; i++) {
const trip = trips[i];
setStatus(`🔍 Detalhando corrida ${allRows.length + i + 1} (${i + 1}/${trips.length})...`);
try {
const detail = await fetchTripDetail(trip.uuid);
const d = detail.data || {};
newRows.push({
date: formatDate(d.requestedAt || trip.requestedAt),
status: STATUS_MAP[d.status || trip.status] || (d.status || trip.status),
product: d.product || trip.product,
pickup: d.pickupAddress || '',
dropoff: d.dropoffAddress || '',
distance: d.distance != null ? d.distance.toFixed(2) : '',
duration: formatDuration(d.duration),
fare: d.fare != null ? d.fare.toFixed(2).replace('.', ',') : '',
});
} catch {
newRows.push({
date: formatDate(trip.requestedAt), status: trip.status,
product: trip.product, pickup: '', dropoff: '', distance: '', duration: '', fare: '',
});
}
await new Promise(r => setTimeout(r, 300));
}
return newRows;
}
// ─── Eventos dos botões ───────────────────────────────────────────────────
btnFetch.addEventListener('click', async () => {
// Reset completo
allRows = [];
currentCursor = null;
hasMore = false;
window.__uberOverflow = [];
disableButtons(true);
try {
const trips = await collectBatch(null);
const rows = await detailTrips(trips);
allRows.push(...rows);
setStatus(
`✅ ${allRows.length} corridas carregadas.${hasMore ? ' Há mais disponíveis.' : ' Todas carregadas!'}`,
'#4caf50'
);
updateUI();
} catch (err) {
console.error(err);
setStatus('❌ Erro: ' + err.message, '#f44336');
} finally {
disableButtons(false);
btnFetch.innerText = '🔄 Rebuscar tudo';
}
});
btnMore.addEventListener('click', async () => {
disableButtons(true);
try {
const trips = await collectBatch(currentCursor);
const rows = await detailTrips(trips);
allRows.push(...rows);
setStatus(
`✅ ${allRows.length} corridas carregadas.${hasMore ? ' Há mais disponíveis.' : ' Todas carregadas!'}`,
'#4caf50'
);
updateUI();
} catch (err) {
console.error(err);
setStatus('❌ Erro: ' + err.message, '#f44336');
} finally {
disableButtons(false);
}
});
btnExport.addEventListener('click', () => {
if (allRows.length === 0) return;
downloadCSV(allRows);
setStatus(`📥 CSV exportado com ${allRows.length} corridas!`, '#4caf50');
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment