https://bra365.game/*
(() => {
const previousInput = document.querySelector("#lottery-panel textarea, #loteria-painel textarea");
if (previousInput?.disabled) {
console.warn("A purchase is in progress.");
return;
}
window.__lotteryWidgetCleanup?.();
window.__dxLoteriaPararColeta?.();
document.getElementById("lottery-float")?.remove();
document.getElementById("lottery-panel")?.remove();
document.getElementById("loteria-flutuante")?.remove();
document.getElementById("loteria-painel")?.remove();
document.getElementById("dx-loteria-v2")?.remove();
document.getElementById("lottery-widget-style")?.remove();
// Remove only the app-update promotion, never unrelated dialogs.
const removeUpdatePopup = () => {
for (const dialog of document.querySelectorAll(".react-dialog-box")) {
const text = dialog.textContent || "";
const promotionTitle = /descobrir uma nova vers/i.test(text);
const downloadPromotion = Boolean(
dialog.querySelector('[style*="website/download/"]')
) && /atualizar/i.test(text);
if (promotionTitle || downloadPromotion) {
dialog.remove();
}
}
};
removeUpdatePopup();
const PRICE = 3;
const records = new Map();
const submittedThisSession = new Set();
let verifiedActiveCount = null;
let confirmedThisSession = 0;
let uncertainPurchase = false;
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const money = value =>
value.toLocaleString("pt-BR", { style: "currency", currency: "BRL" });
const gameText = item =>
`${item.date}: ${item.numbers.map(n => String(n).padStart(2, "0")).join(" ")}`;
const waitFor = async (test, timeout = 15000) => {
const start = Date.now();
while (Date.now() - start < timeout) {
if (test()) return true;
await delay(200);
}
return false;
};
const page = () => {
const element = document.querySelector(
'.page-stack__layer[aria-hidden="false"] .page-stack__route-content'
);
if (!element) throw Error("Página do sorteio indisponível");
return element;
};
const grid = () => {
const element = [...page().querySelectorAll(".grid.grid-cols-7")]
.find(candidate =>
candidate.children.length === 30 &&
[...candidate.children].every(
(child, index) => Number(child.textContent.trim()) === index + 1
)
);
if (!element) throw Error("Grade não encontrada");
return element;
};
const selected = () =>
[...grid().children]
.filter(element => !element.classList.contains("bg-[var(--act-bg-2709)]"))
.map(element => Number(element.textContent.trim()))
.sort((a, b) => a - b);
const counter = () => {
const element = [...page().querySelectorAll(".cursor-pointer")]
.find(item => /^Meus Bilhetes\s*\d+$/.test(
item.textContent.replace(/\s+/g, " ").trim()
));
const match = element?.textContent.match(/Meus Bilhetes\s*(\d+)/);
return match ? Number(match[1]) : null;
};
const clear = async () => {
const button = [...grid().parentElement.querySelectorAll("*")]
.find(element => element.children.length === 0 &&
element.textContent.trim() === "Limpar");
if (!button) throw Error("Botão Limpar não encontrado");
button.click();
if (!await waitFor(() => selected().length === 0, 3000)) {
throw Error("Falha ao limpar seleção");
}
};
const choose = async numbers => {
await clear();
for (const number of numbers) {
const element = [...grid().children]
.find(child => Number(child.textContent.trim()) === number);
if (!element) throw Error(`Dezena ${number} não encontrada`);
element.click();
if (!await waitFor(() => selected().includes(number), 2000)) {
throw Error(`Falha ao selecionar ${number}`);
}
}
if (selected().join(",") !== numbers.join(",")) {
throw Error("Seleção incorreta");
}
};
const buyButton = () => {
const matches = [...page().querySelectorAll(".btn_press")]
.filter(button => /^ComprarcomR\$3(?:[,.]00)?$/.test(
button.textContent.replace(/\s+/g, "")
));
if (matches.length !== 1) throw Error("Botão Comprar não encontrado");
return matches[0];
};
const parseGames = text => {
const lines = text.split(/\r?\n/).map(x => x.trim()).filter(Boolean);
if (!lines.length) throw Error("Nenhum jogo informado");
const seen = new Set();
const games = [];
let duplicates = 0;
lines.forEach((line, index) => {
const match = line.match(/^(\d+)\s*:\s*(.+)$/);
if (!match) throw Error(`Formato inválido na linha ${index + 1}`);
const tokens = match[2].trim().split(/[\s,;]+/);
if (tokens.length !== 5 || tokens.some(x => !/^\d+$/.test(x))) {
throw Error(`Jogo ${match[1]}: são necessárias 5 dezenas`);
}
const numbers = tokens.map(Number).sort((a, b) => a - b);
if (numbers.some(x => x < 1 || x > 30) || new Set(numbers).size !== 5) {
throw Error(`Jogo ${match[1]}: dezenas inválidas`);
}
const key = numbers.join(",");
if (seen.has(key)) {
duplicates++;
return;
}
seen.add(key);
games.push({ label: Number(match[1]), numbers });
});
return { games, duplicates };
};
// Identify the blue share action inside a ticket card for accounting only.
const hasShareAction = row => [...row.querySelectorAll("*")].some(element => {
const label = [
element.getAttribute("aria-label"),
element.getAttribute("title")
].filter(Boolean).join(" ");
if (/\b(compartilhar|compartilhe|share)\b/i.test(label)) return true;
if (!element.querySelector("svg, img, i")) return false;
if (element.textContent.trim().length > 2) return false;
const color = getComputedStyle(element).backgroundColor;
const match = color.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/i);
if (!match) return false;
const [, red, green, blue] = match.map(Number);
return blue >= 150 && blue > red * 1.5 && blue > green * 1.2 && green >= 65;
});
// Read each ticket card, including hits, prize and redemption state.
const readRow = row => {
const spans = [...row.querySelectorAll("span")];
const texts = spans.map(span => span.textContent.trim());
const date = texts.find(text => /^20\d{6}$/.test(text));
const numbers = [...row.querySelectorAll("span.rounded-full")]
.map(span => span.textContent.trim())
.filter(text => /^\d{1,2}$/.test(text))
.map(Number)
.sort((a, b) => a - b);
if (!date || numbers.length !== 5 ||
new Set(numbers).size !== 5 ||
numbers.some(number => number < 1 || number > 30)) return null;
const hitsText = texts.find(text => /^[0-5]\s*\/\s*5\s*ACERTOS?$/i.test(text));
const hits = hitsText ? Number(hitsText.match(/^([0-5])/)[1]) : null;
const prizeText = texts.find(text => /^R\$\s*[\d.]+(?:,\d{2})?$/.test(text));
const prize = prizeText
? Number(prizeText.replace(/[^\d,.]/g, "").replace(/\./g, "").replace(",", "."))
: null;
const label = texts.find(text => /^(Aguardando|Resgatado|Resgatada|Premiado|Premiada|Não premiado|Não premiada|Expirado|Expirada|Pendente|Concluído|Concluída|Perdido|Perdeu)$/i.test(text));
const status = row.querySelector(".lottery-awaiting-pulse")
? "Aguardando"
: label || "Não identificado";
return { date: Number(date), numbers, hits, prize, status, paid: hasShareAction(row) };
};
const sameDetails = (a, b) =>
a.status === b.status && a.hits === b.hits && a.prize === b.prize && a.paid === b.paid;
// Keep the highest simultaneously observed multiplicity of each game.
// There is no ticket ID in the card HTML.
const scan = () => {
const visible = new Map();
for (const row of document.querySelectorAll(".rv-list > div")) {
const ticket = readRow(row);
if (!ticket) continue;
const key = `${ticket.date}|${ticket.numbers.join(",")}`;
if (!visible.has(key)) visible.set(key, []);
visible.get(key).push(ticket);
}
for (const [key, observed] of visible) {
const saved = records.get(key) || [];
const used = new Set();
for (const ticket of observed) {
let index = saved.findIndex((item, i) =>
!used.has(i) && sameDetails(item, ticket)
);
if (index < 0) index = saved.findIndex((_, i) => !used.has(i));
if (index < 0) {
index = saved.length;
saved.push(ticket);
} else {
const old = saved[index];
saved[index] = {
...ticket,
hits: ticket.hits ?? old.hits,
prize: ticket.prize ?? old.prize,
paid: ticket.paid,
status: ticket.status === "Não identificado" ? old.status : ticket.status
};
}
used.add(index);
}
records.set(key, saved);
}
updateSummary();
};
const isWinner = item =>
(item.hits !== null && item.hits >= 2) ||
(item.prize !== null && item.prize > 0) ||
/^(Resgatado|Resgatada|Premiado|Premiada)$/i.test(item.status);
const allTickets = () => [...records.values()].flat().sort((a, b) =>
b.date - a.date ||
a.numbers.join(",").localeCompare(b.numbers.join(","), "en", { numeric: true })
);
const numbersKey = numbers => numbers.join(",");
const activeNumberKeys = () => new Set(
allTickets()
.filter(item => item.status === "Aguardando")
.map(item => numbersKey(item.numbers))
);
// The tab counter represents awaiting tickets, not the complete history.
// Only awaiting tickets are relevant for checking current duplicate plays.
const collectedActiveCount = () =>
allTickets().filter(item => item.status === "Aguardando").length;
const historyProgress = () => {
let reportedCount = null;
try {
reportedCount = counter();
} catch {
// The history route may not expose the counter.
}
return {
collectedCount: collectedActiveCount(),
expectedCount: reportedCount === null
? (verifiedActiveCount === null
? null
: verifiedActiveCount + confirmedThisSession)
: reportedCount
};
};
const requireVerifiedHistory = () => {
if (uncertainPurchase) {
throw Error("Compra anterior incerta. Confira os bilhetes antes de tentar novamente");
}
const reportedCount = counter();
if (reportedCount === null) {
throw Error("Contador de apostas ativas indisponível");
}
const collectedCount = collectedActiveCount();
if (verifiedActiveCount === null) {
if (collectedCount !== reportedCount) {
throw Error(
`Apostas ativas não conferidas (${collectedCount}/${reportedCount}). ` +
"Abra Meus Bilhetes e percorra toda a lista antes de apostar"
);
}
verifiedActiveCount = reportedCount;
}
// The counter must move exactly one unit for every confirmed purchase.
const expectedCount = verifiedActiveCount + confirmedThisSession;
if (reportedCount !== expectedCount) {
throw Error(
`Quantidade de apostas ativas mudou (${reportedCount}/${expectedCount}). ` +
"Confira Meus Bilhetes antes de apostar"
);
}
// The initial active list may be supplemented by new purchases.
// It must never shrink or exceed the verified counter during the batch.
if (collectedCount < verifiedActiveCount || collectedCount > expectedCount) {
throw Error("Coleta de apostas ativas divergiu. Confira Meus Bilhetes");
}
return reportedCount;
};
const totals = () => {
const items = allTickets();
const winners = items.filter(isWinner);
const prize = winners.reduce((sum, item) => sum + (item.prize ?? 0), 0);
const redeemed = winners.filter(item => /^Resgatad[oa]$/i.test(item.status))
.reduce((sum, item) => sum + (item.prize ?? 0), 0);
const invested = items.reduce((sum, item) => sum + (item.paid ? PRICE : 0), 0);
return {
tickets: items.length,
active: items.filter(item => item.status === "Aguardando").length,
winners: winners.length,
invested: Number(invested.toFixed(2)),
prizes: Number(prize.toFixed(2)),
redeemed: Number(redeemed.toFixed(2)),
partial_net: Number((prize - invested).toFixed(2))
};
};
const report = () => {
scan();
const items = allTickets();
return JSON.stringify({
summary: totals(),
active: items.filter(item => item.status === "Aguardando").map(gameText),
winners: items.filter(isWinner).map(item => ({
game: gameText(item),
hits: item.hits,
prize: item.prize,
status: item.status
})),
other: items.filter(item => item.status !== "Aguardando" && !isWinner(item))
.map(item => ({ game: gameText(item), status: item.status }))
}, null, 2);
};
const download = content => {
const blob = new Blob([content], { type: "application/json;charset=utf-8" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
link.href = url;
link.download = `loteria-resultados-${timestamp}.json`;
link.style.display = "none";
document.body.append(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(url), 5000);
};
// Minimal floating interface; rows are captured while the user scrolls.
const style = document.createElement("style");
style.id = "lottery-widget-style";
style.textContent = `
#lottery-float{position:fixed;top:16px;right:16px;width:34px;height:34px;
padding:0;border-radius:50%;border:1px solid #ffffff60;background:#28282833;
backdrop-filter:blur(5px);-webkit-backdrop-filter:blur(5px);color:white;
font:19px/32px Arial,sans-serif;text-align:center;cursor:pointer;z-index:2147483646}
#lottery-panel{position:fixed;top:60px;right:12px;
width:min(340px,calc(100vw - 24px));max-height:calc(100dvh - 75px);
overflow-y:auto;padding:14px;border-radius:12px;box-sizing:border-box;
background:#202020;color:white;box-shadow:0 8px 30px #0008;
font:14px Arial,sans-serif;z-index:2147483646;display:none}
#lottery-panel textarea{width:100%;height:165px;padding:10px;
border:1px solid #555;border-radius:8px;box-sizing:border-box;
background:#303030;color:white;font:14px monospace;resize:vertical}
#lottery-panel button{width:100%;margin-top:9px;padding:11px;border:0;
border-radius:8px;cursor:pointer;background:#16a34a;color:white;font-weight:bold}
#lottery-panel button:disabled{opacity:.5;cursor:default}
#lottery-panel #lottery-download{background:#2563eb}
#lottery-summary,#lottery-status{margin-top:9px;font-size:12px;color:#ccc;
line-height:1.5;white-space:pre-line;overflow-wrap:anywhere}
`;
document.head.append(style);
const floatingButton = document.createElement("button");
floatingButton.id = "lottery-float";
floatingButton.textContent = "+";
floatingButton.title = "Apostas";
const panel = document.createElement("div");
panel.id = "lottery-panel";
const title = document.createElement("div");
title.textContent = "Apostas";
title.style.cssText = "font-weight:bold;margin-bottom:10px";
const input = document.createElement("textarea");
input.placeholder = "1: 01 03 06 19 23\n2: 01 03 24 25 26";
const placeButton = document.createElement("button");
placeButton.textContent = "Apostar";
const copyButton = document.createElement("button");
copyButton.id = "lottery-download";
copyButton.textContent = "Baixar JSON";
const summary = document.createElement("div");
summary.id = "lottery-summary";
const status = document.createElement("div");
status.id = "lottery-status";
panel.append(title, input, placeButton, copyButton, summary, status);
document.body.append(floatingButton, panel);
let messageTimeout;
let purchasing = false;
function notify(text, duration = 2000) {
clearTimeout(messageTimeout);
status.textContent = text;
if (duration > 0) {
messageTimeout = setTimeout(() => { status.textContent = ""; }, duration);
}
}
function updateSummary() {
const t = totals();
const { collectedCount, expectedCount } = historyProgress();
const historyLabel = verifiedActiveCount !== null
? `Ativos conferidos: ${verifiedActiveCount} + ${confirmedThisSession} compra(s)`
: expectedCount === null
? `Ativos capturados: ${collectedCount} (contador indisponível)`
: collectedCount === expectedCount
? `Ativos capturados: ${collectedCount}/${expectedCount}`
: collectedCount < expectedCount
? `Coleta de ativos: ${collectedCount}/${expectedCount}`
: `Contagem de ativos divergente: ${collectedCount}/${expectedCount}`;
summary.textContent =
`${t.tickets} bilhetes · ${t.active} ativos · ${t.winners} premiados\n` +
`Investido: ${money(t.invested)} · Prêmios: ${money(t.prizes)}\n` +
`Resgatado: ${money(t.redeemed)} · Resultado parcial: ${money(t.partial_net)}\n` +
historyLabel;
}
floatingButton.onclick = () => {
panel.style.display = panel.style.display === "block" ? "none" : "block";
scan();
};
copyButton.onclick = () => {
scan();
if (!records.size) return notify("Nenhum bilhete capturado", 3500);
try {
download(report());
notify("Download iniciado", 2000);
} catch (error) {
console.error("JSON download failed", error);
notify("Falha ao baixar JSON", 3500);
}
};
placeButton.onclick = async () => {
if (purchasing) return;
let parsed;
try {
parsed = parseGames(input.value);
buyButton();
scan();
requireVerifiedHistory();
} catch (error) {
notify(error.message, 6500);
return;
}
// All awaiting tickets participate in the same current draw,
// including those bought on previous dates.
const activeKeys = activeNumberKeys();
const pendingGames = [];
let skipped = parsed.duplicates;
for (const game of parsed.games) {
const key = numbersKey(game.numbers);
if (activeKeys.has(key) || submittedThisSession.has(key)) {
skipped++;
} else {
pendingGames.push(game);
}
}
if (!pendingGames.length) {
input.value = "";
notify(`Nenhuma aposta nova. ${skipped} duplicada(s) bloqueada(s)`, 5000);
return;
}
purchasing = true;
placeButton.disabled = true;
input.disabled = true;
// Collapse immediately after validation; purchases continue in sequence.
panel.style.display = "none";
floatingButton.textContent = "…";
floatingButton.title = "Apostas em andamento";
let completed = 0;
let failed = false;
for (const game of pendingGames) {
const key = numbersKey(game.numbers);
try {
notify(`${completed + 1}/${pendingGames.length} · ${skipped} duplicada(s) ignorada(s)`, 0);
const before = requireVerifiedHistory();
// Re-check before clicking Buy. Even an uncertain submission is
// remembered for this page session to prevent an automatic retry.
if (activeNumberKeys().has(key) || submittedThisSession.has(key)) {
skipped++;
continue;
}
await choose(game.numbers);
if (activeNumberKeys().has(key) || submittedThisSession.has(key)) {
skipped++;
continue;
}
const button = buyButton();
submittedThisSession.add(key);
uncertainPurchase = true;
button.click();
if (!await waitFor(() => counter() === before + 1, 15000)) {
throw Error("Compra sem confirmação; confira Meus Bilhetes antes de tentar novamente");
}
uncertainPurchase = false;
confirmedThisSession++;
completed++;
await delay(500);
} catch (error) {
failed = true;
notify(`Jogo ${game.label}: ${error.message}`, 0);
console.error("Purchase stopped", game, error);
break;
}
}
purchasing = false;
placeButton.disabled = false;
input.disabled = false;
floatingButton.textContent = "+";
floatingButton.title = "Apostas";
if (failed) {
// Keep unprocessed entries after a failure; the user must inspect
// purchase history before retrying an uncertain transaction.
panel.style.display = "block";
} else {
// Clear only after a completed operation; never auto-repeat the batch.
input.value = "";
notify(`${completed} comprada(s) · ${skipped} duplicada(s) bloqueada(s)`, 5000);
}
};
const observer = new MutationObserver(mutations => {
removeUpdatePopup();
const changed = mutations.some(mutation => {
const target = mutation.target.nodeType === 1
? mutation.target
: mutation.target.parentElement;
return Boolean(target?.closest?.(".rv-list") ||
(mutation.addedNodes && [...mutation.addedNodes].some(node =>
node.nodeType === 1 &&
(node.matches?.(".rv-list") || node.querySelector?.(".rv-list"))
)));
});
if (changed) scan();
});
observer.observe(document.body, {
subtree: true,
childList: true,
characterData: true,
attributes: true,
attributeFilter: ["class"]
});
document.addEventListener("scroll", scan, true);
const interval = setInterval(scan, 800);
window.__lotteryWidgetCleanup = () => {
observer.disconnect();
document.removeEventListener("scroll", scan, true);
clearInterval(interval);
clearTimeout(messageTimeout);
style.remove();
floatingButton.remove();
panel.remove();
};
scan();
})();Install requires the InjectJS Chrome extension. Scripts run only on sites matching the pattern above. Review code before installing any community script.