Files
phishing/index.html
T
2021-02-18 10:14:35 +01:00

287 lines
10 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=0.5, maximum-scale=1">
<link rel="shortcut icon" href="favicon.ico">
<title>polkadot{.js}/phishing</title>
<style>
:root {
--font-sans: "-apple-system", BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
a { color: #ff8c00 !important; text-decoration: none }
body { color: #4e4e4e; font-family: var(--font-sans); height: 100vh; overflow-x: hidden; padding: 0 }
h3 { font-family: var(--font-sans); font-size: 1em; font-weight: 400; margin: 1rem 0; opacity: 0.95; text-transform: lowercase }
p { line-height: 1.5rem; margin: 0.75rem 0 }
.box { background: rgba(255, 255, 255, 0.85); border-radius: 0.25rem; flex: 1 1; margin: 0.5rem; min-width: 15rem; max-width: 40rem; padding: 0 1.5rem; text-align: center; white-space: nowrap; width: 40rem; z-index: 2 }
.buttons { display: none; margin: 2rem 0 1.5rem }
.buttons a { border: 1px solid #eee; border-radius: 0.25rem; margin: 0 0.25rem; padding: 0.5rem 0.75rem }
.buttons .invert { color: rgba(255, 255, 255, 0.85) !important }
.container { align-items: center; display: flex; flex-direction: column; justify-content: center; margin-top: 4rem; padding-bottom: 2rem }
.desc { font-size: 0.95rem; opacity: 0.85; white-space: normal }
.dot { border-radius: 50%; position: absolute; z-index: 0 }
.header { align-items: center; background: rgba(255, 255, 255, 0.85); display: flex; left: 0; justify-content: space-between; padding: 0.5rem 1rem; position: fixed; right: 0; top: 0; z-index: 1 }
.header div.logo { display: flex; align-items: center }
.header img { height: 1.5rem; margin-right: 0.5rem; width: 1.5rem }
.row { align-items: center; display: flex; justify-content: center }
table { margin: 0 0 1rem }
td:not(.centered) { font-family: monospace; padding: 0.25rem 0.5rem; text-align: right }
td.centered { opacity: 0.65; padding: 1rem; text-align: center }
td:nth-child(2) { width: 100% }
td > h3 { margin: 0; opacity: 0.25 }
tr + tr > td > h3 { margin-top: 0.5rem }
td > span { border-radius: 0.25em; color: white; font-size: 0.75rem; padding: 0.125em 0.375em }
td > span.active { background: darkred }
td > span.inactive { background: darkgrey; display: none }
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="box">
<h3>phishing</h3>
<p class="desc">A community driven curated list of known less-than-honest operators.</p>
<p class="desc">Any additions can be made by editing <a href="https://github.com/polkadot-js/phishing/edit/master/all.json">phishing/all.json</a> and adding any new sites in alphabetical order. In the same vein addresses can be added in <a href="https://github.com/polkadot-js/phishing/edit/master/address.json">phishing/address.json</a>. For any discrepancies or requests <a href="https://github.com/polkadot-js/phishing/issues">log an issue</a>.</p>
<p id="buttons" class="buttons"><a href="#" id="btn-sites" onclick="fillTable('sites')">Sites</a><a href="#" id="btn-addresses" onclick="fillTable('addresses')">Addresses</a></p>
<table>
<tbody id="table">
<tr>
<td></td>
<td class="centered">.. loading url & address metadata ...</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="header">
<div class="logo">
<img src="https://polkadot.js.org/logo.svg" />
<div>polkadot{.js}/phishing</div>
</div>
<a href="https://github.com/polkadot-js/phishing">view on github</a>
</div>
<script>
if (window.self !== window.top) {
window.top.location.href = window.location.href;
}
const style = document.createElement('style');
const table = document.getElementById('table');
document.head.appendChild(style);
let hue = Math.floor(Math.random() * 359);
let counter = 0;
let navTo = '';
let metaJson;
let addrJson;
let urlStatus;
let balances = [];
function draw () {
hue = (hue + 1) % 360;
document.body.style.background = `hsl(${hue}, 45%, 85%)`;
style.innerHTML = `a { color: hsl(${hue}, 45%, 45%) !important } .buttons a { border-color: hsl(${hue}, 45%, 45%) !important } .buttons .invert { background: hsl(${hue}, 45%, 45%) !important }`;
setTimeout(() => window.requestAnimationFrame(draw), 100);
}
function appendRow (date, url = '', status) {
const row = document.createElement('tr');
const a = document.createElement('td');
const b = document.createElement('td');
const c = document.createElement('td');
a.appendChild(document.createTextNode(url));
if (typeof date === 'string') {
b.appendChild(document.createTextNode(date));
} else {
b.appendChild(date);
}
if (typeof status === 'string') {
const s = document.createElement('span');
s.classList.add(status);
s.appendChild(document.createTextNode(status));
c.appendChild(s);
}
row.appendChild(a);
row.appendChild(b);
row.appendChild(c);
table.appendChild(row);
}
function fillAddressList (url, addresses) {
if (addresses && addresses.length) {
const c = document.createElement('h3');
c.appendChild(document.createTextNode(url));
appendRow(c);
addresses.forEach((address) => {
const balance = balances.find(([a]) => a === address) || ['', '']
appendRow(balance[1], address);
});
}
}
function fillAddresses () {
metaJson.forEach(({ url }) => fillAddressList(url, addrJson[url]));
Object.entries(addrJson).forEach(([url, addresses]) => {
if (!metaJson.find((m) => m.url === url)) {
fillAddressList(url, addresses);
}
});
}
function fillSites () {
let prevMonth = '';
metaJson.forEach(({ date, url }, index) => {
const thisMonth = date.split('-').slice(0, 2).join('-');
if (thisMonth !== prevMonth) {
const c = document.createElement('h3');
c.appendChild(document.createTextNode(thisMonth));
appendRow(c);
prevMonth = thisMonth;
}
appendRow(date, url, urlStatus[index] ? 'active' : 'inactive');
});
}
function fillTable (newNav) {
if (newNav !== navTo) {
if (navTo) {
document.getElementById(`btn-${navTo}`).classList = '';
}
document.getElementById(`btn-${newNav}`).classList = 'invert';
navTo = newNav;
table.innerHTML = '';
if (navTo === 'sites') {
fillSites();
} else {
fillAddresses();
}
}
}
async function fetchTimeout (url, options = {}) {
const controller = new AbortController();
const id = setTimeout(() => controller.abort(), 2500);
const response = await fetch(url, { ...options, signal: controller.signal });
clearTimeout(id);
return response;
}
function checkUpCors (url, { contents, status: { content_length, error } }) {
// has an error from the CORS proxy
if (
error &&
(error.code !== 'UNABLE_TO_VERIFY_LEAF_SIGNATURE')
) {
console.log(`${url} has an retrieval error`)
return false;
}
// has actual content or it has a refresh
if (
(content_length < 100) &&
!contents.includes('<meta http-equiv="refresh"')
) {
console.log(`${url} has no content`);
return false;
}
// include in inactive list when the provider has suspended the account
if (
contents.includes('This Account has been suspended') ||
contents.includes('Хостинг сайта временно приостановлен')
) {
console.log(`${url} has been suspended`)
return false;
}
return true;
}
async function isUp (url) {
return fetchTimeout(url, { method: 'HEAD', mode: 'no-cors' })
.then(() =>
fetchTimeout(`https://api.allorigins.win/get?url=${encodeURIComponent(url)}`)
.then((response) => response.json())
.then((json) => checkUpCors(url, json))
.catch(() => true)
)
.catch(() => false);
}
async function getBalance (key) {
try {
const response = await fetchTimeout('https://polkadot.subscan.io/api/scan/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key, page: 0, row: 1 })
});
const { data } = await response.json();
const [pre, post] = ((data && data.account.balance) || '0').split('.');
return [key, `${pre}.${`${post || ''}0000000000`.substr(0, 10)}`];
} catch (error) {
console.error(error);
return [key, ''];
}
}
async function main () {
draw();
const from = window.location.protocol === 'file:' ? 'https://polkadot.js.org/phishing/' : '';
const [addrBody, metaBody] = await Promise.all(['address', 'urlmeta'].map((j) => fetchTimeout(`${from}${j}.json`)));
[addrJson, metaJson] = await Promise.all([addrBody.json(), metaBody.json()]);
const addresses = Object
.values(addrJson)
.reduce((all, addrs) => {
addrs.forEach((a) => !all.includes(a) && all.push(a));
return all;
}, []);
[balances, urlStatus] = await Promise.all([
Promise.all(addresses.map(getBalance)),
Promise.all(metaJson.map(({ url }) => isUp(`https://${url}`)))
]);
document.getElementById('buttons').style.display = 'block';
fillTable('sites');
}
main();
</script>
</body>
</html>