Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin 2022-06-29 10:37:28 -07:00
parent c8be082f53
commit 3721564786
10 changed files with 865 additions and 0 deletions

56
android/index.html Normal file
View file

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>damus</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:title" content="Damus">
<meta property="og:description" content="A new social network that you control">
<meta property="og:image" content="https://damus.io/img/damus.png">
<meta property="og:url" content="https://damus.io">
<meta name="twitter:card" content="summary_large_image">
<link rel="stylesheet" href="/css/normalize.css">
<link rel="stylesheet" href="/css/skeleton.css?v=3">
<link rel="stylesheet" href="/css/custom.css?v=4">
</head>
<body>
<section class="header">
<span class="logo">
<img src="/img/damus-nobg.svg"/>
</span>
<span class="damus">damus</span>
</section>
<div class="container">
<section class="hero">
<h2>Damus Android crowdfund</h2>
<h5 class="subtitle">If ya'll help crowdfund me an android phone I can start working on an android version</h5>
<!-- <div class="row"> -->
<!-- <h4 class="subtitle">Developers First</h4> -->
<!-- <img class="code-example" src="img/code-placeholder.svg"> -->
<!-- </div> -->
</section>
<h4>Donations</h4>
<p>This is a bolt12 offer, you can pay this with a CLN node. Otherwise press the button to get a bolt11 invoice.</p>
<a id="bolt12" href="lightning:lno1pgt5gctdw4ejqstwv3ex76tyyp3hymmhv3n82mnyzsyxgctdw4eju6t0rcs08sggen2ndwzjdpqlpfw9sgfth8n9sjs7kjfssrnurnp5lqk66ug">
<div id="qrcode" class="tipjar-copy" style="float: left; width: 256px; cursor: pointer" ></div>
</a>
<button style="margin-left: 20px; height: 50px" onclick="click_make_invoice(this)">Request bolt11 invoice</button>
<div id="tipjar-summary" style="clear: left; width: 100%; margin-bottom: 30px;">
Loading donations...
</div>
</body>
<script src="/js/qrcode.min.js" > </script>
<script src="/js/lnsocket.js?v=10" > </script>
<script src="/js/tipjar.js?v=21" ></script>
</html>

BIN
img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

391
js/lnsocket.js Normal file

File diff suppressed because one or more lines are too long

BIN
js/lnsocket.wasm Executable file

Binary file not shown.

1
js/qrcode.min.js vendored Normal file

File diff suppressed because one or more lines are too long

172
js/tipjar.js Normal file
View file

@ -0,0 +1,172 @@
async function make_request(method, rune, params) {
const LNSocket = await lnsocket_init()
const ln = LNSocket()
ln.genkey()
await ln.connect_and_init("03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71", "wss://cln.jb55.com:443")
const {result} = await ln.rpc({ rune, method, params })
ln.disconnect()
return result
}
function fetch_tipjar_summary() {
const rune = "b3Xsg2AS2cknHYa6H94so7FAVQVdnRSP6Pv-1WOQEBc9NCZtZXRob2Q9b2ZmZXItc3VtbWFyeQ=="
return make_request("offer-summary", rune, {
offerid: "2043536dfec68d559102f73510927622812a230cfdda079e96fccbfe35a96d11",
description: "@damus-android",
limit: 5
})
}
function make_invoice(description) {
const rune = "LZwGZJO7wZgmoScFQb5reZ0Ii8qPKCeUfTb-UcbDxWw9MTImbWV0aG9kPWludm9pY2U="
description = (description && `${description} @damus-android`) || "@damus-android donation"
return make_request("invoice", rune, {
msatoshi: "any",
label: `damus-android-${new Date().getTime()}`,
description: description
})
}
function make_qrcode(dat) {
const link = dat.toUpperCase()
document.querySelector("#qrcode").innerHTML = ""
const qr = new QRCode("qrcode", {
text: link,
width: 256,
height: 256,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.L
})
}
async function click_make_invoice(el) {
const offerdata = document.querySelector("#offerdata")
const tipjar_img = document.querySelector("#tipjar-offer-qr")
const note = prompt("Leave a note!", "")
const invoice = await make_invoice(note)
make_qrcode("LIGHTNING:" + invoice.bolt11)
document.querySelector("#bolt12").href = "lightning:" + invoice.bolt11
el.style.display = "none";
}
async function copy_tip() {
const offer = document.querySelector("#offerdata").value.trim();
try {
await navigator.clipboard.writeText(offer)
alert("Invoice copied to clipboard!")
} catch(err) {
console.log("clipboard copy error", err)
document.querySelector("#offerdata").style.display = "block"
}
}
async function go() {
const summary = await fetch_tipjar_summary()
const el = document.querySelector("#tipjar-summary")
const bolt12 = document.querySelector("#bolt12")
make_qrcode(bolt12.href)
el.innerHTML = render_tips(summary)
}
function render_tips(res) {
const total_sats = res.total_msatoshi / 1000
const goal = 3000000
const perc = `${((total_sats / goal) * 100).toPrecision(2)}%`
const total_fmt = `${format_amount(total_sats)} / ${format_amount(goal)} sats goal (${perc})`
return `
<p>Total: <b>${total_fmt}</b></p>
<div class="progres" style="height:20px; background-color: #f1f1f1">
<div class="progress-bar" style="background-color: #f44336; height: 100%;width: ${perc}"></div>
</div>
<h5>Recent Donors</h5>
${render_table(res.paid_invoices)}
<h5>Top Donors</h5>
${render_table(res.top_donors)}
`
}
function render_table(invoices)
{
return `
<table style="width: 100%">
<thead>
<tr>
<th>Note</th>
<th>Amount</th>
<th></th>
</tr>
</thead>
<tbody>
${invoices.map(render_tip).join("\n")}
</tbody>
</table>
`
}
function format_amount(amt)
{
return amt.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
}
function render_tip(tip)
{
let note = tip.payer_note ? tip.payer_note : (tip.description || "Anonymous")
note = note.replace("@damus-android", "")
const amount = format_amount(tip.msatoshi / 1000)
const now = Math.floor(new Date().getTime() / 1000)
const date = time_delta(now * 1000, tip.paid_at * 1000)
return `
<tr>
<td>${note}</td>
<td>${amount} sats</td>
<td class="reldate">${date}</td>
</tr>
`
}
function time_delta(current, previous) {
var msPerMinute = 60 * 1000;
var msPerHour = msPerMinute * 60;
var msPerDay = msPerHour * 24;
var msPerMonth = msPerDay * 30;
var msPerYear = msPerDay * 365;
var elapsed = current - previous;
if (elapsed < msPerMinute) {
return Math.round(elapsed/1000) + ' seconds ago';
}
else if (elapsed < msPerHour) {
return Math.round(elapsed/msPerMinute) + ' minutes ago';
}
else if (elapsed < msPerDay ) {
return Math.round(elapsed/msPerHour ) + ' hours ago';
}
else if (elapsed < msPerMonth) {
return Math.round(elapsed/msPerDay) + ' days ago';
}
else if (elapsed < msPerYear) {
return Math.round(elapsed/msPerMonth) + ' months ago';
}
else {
return Math.round(elapsed/msPerYear ) + ' years ago';
}
}
go()

168
key/bech32.js Normal file
View file

@ -0,0 +1,168 @@
var ALPHABET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
var ALPHABET_MAP = {};
for (var z = 0; z < ALPHABET.length; z++) {
var x = ALPHABET.charAt(z);
ALPHABET_MAP[x] = z;
}
function polymodStep(pre) {
var b = pre >> 25;
return (((pre & 0x1ffffff) << 5) ^
(-((b >> 0) & 1) & 0x3b6a57b2) ^
(-((b >> 1) & 1) & 0x26508e6d) ^
(-((b >> 2) & 1) & 0x1ea119fa) ^
(-((b >> 3) & 1) & 0x3d4233dd) ^
(-((b >> 4) & 1) & 0x2a1462b3));
}
function prefixChk(prefix) {
var chk = 1;
for (var i = 0; i < prefix.length; ++i) {
var c = prefix.charCodeAt(i);
if (c < 33 || c > 126)
return 'Invalid prefix (' + prefix + ')';
chk = polymodStep(chk) ^ (c >> 5);
}
chk = polymodStep(chk);
for (var i = 0; i < prefix.length; ++i) {
var v = prefix.charCodeAt(i);
chk = polymodStep(chk) ^ (v & 0x1f);
}
return chk;
}
function convertbits(data, inBits, outBits, pad) {
var value = 0;
var bits = 0;
var maxV = (1 << outBits) - 1;
var result = [];
for (var i = 0; i < data.length; ++i) {
value = (value << inBits) | data[i];
bits += inBits;
while (bits >= outBits) {
bits -= outBits;
result.push((value >> bits) & maxV);
}
}
if (pad) {
if (bits > 0) {
result.push((value << (outBits - bits)) & maxV);
}
}
else {
if (bits >= inBits)
return 'Excess padding';
if ((value << (outBits - bits)) & maxV)
return 'Non-zero padding';
}
return result;
}
function toWords(bytes) {
return convertbits(bytes, 8, 5, true);
}
function fromWordsUnsafe(words) {
var res = convertbits(words, 5, 8, false);
if (Array.isArray(res))
return res;
}
function fromWords(words) {
var res = convertbits(words, 5, 8, false);
if (Array.isArray(res))
return res;
throw new Error(res);
}
function getLibraryFromEncoding(encoding) {
var ENCODING_CONST;
if (encoding === 'bech32') {
ENCODING_CONST = 1;
}
else {
ENCODING_CONST = 0x2bc830a3;
}
function encode(prefix, words, LIMIT) {
LIMIT = LIMIT || 90;
if (prefix.length + 7 + words.length > LIMIT)
throw new TypeError('Exceeds length limit');
prefix = prefix.toLowerCase();
// determine chk mod
var chk = prefixChk(prefix);
if (typeof chk === 'string')
throw new Error(chk);
var result = prefix + '1';
for (var i = 0; i < words.length; ++i) {
var x = words[i];
if (x >> 5 !== 0)
throw new Error('Non 5-bit word');
chk = polymodStep(chk) ^ x;
result += ALPHABET.charAt(x);
}
for (var i = 0; i < 6; ++i) {
chk = polymodStep(chk);
}
chk ^= ENCODING_CONST;
for (var i = 0; i < 6; ++i) {
var v = (chk >> ((5 - i) * 5)) & 0x1f;
result += ALPHABET.charAt(v);
}
return result;
}
function __decode(str, LIMIT) {
LIMIT = LIMIT || 90;
if (str.length < 8)
return str + ' too short';
if (str.length > LIMIT)
return 'Exceeds length limit';
// don't allow mixed case
var lowered = str.toLowerCase();
var uppered = str.toUpperCase();
if (str !== lowered && str !== uppered)
return 'Mixed-case string ' + str;
str = lowered;
var split = str.lastIndexOf('1');
if (split === -1)
return 'No separator character for ' + str;
if (split === 0)
return 'Missing prefix for ' + str;
var prefix = str.slice(0, split);
var wordChars = str.slice(split + 1);
if (wordChars.length < 6)
return 'Data too short';
var chk = prefixChk(prefix);
if (typeof chk === 'string')
return chk;
var words = [];
for (var i = 0; i < wordChars.length; ++i) {
var c = wordChars.charAt(i);
var v = ALPHABET_MAP[c];
if (v === undefined)
return 'Unknown character ' + c;
chk = polymodStep(chk) ^ v;
// not in the checksum?
if (i + 6 >= wordChars.length)
continue;
words.push(v);
}
if (chk !== ENCODING_CONST)
return 'Invalid checksum for ' + str;
return { prefix: prefix, words: words };
}
function decodeUnsafe(str, LIMIT) {
var res = __decode(str, LIMIT);
if (typeof res === 'object')
return res;
}
function decode(str, LIMIT) {
var res = __decode(str, LIMIT);
if (typeof res === 'object')
return res;
throw new Error(res);
}
return {
decodeUnsafe: decodeUnsafe,
decode: decode,
encode: encode,
toWords: toWords,
fromWordsUnsafe: fromWordsUnsafe,
fromWords: fromWords
};
}
const bech32 = getLibraryFromEncoding('bech32');
const bech32m = getLibraryFromEncoding('bech32m');

44
key/index.html Normal file
View file

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>damus key converter</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:title" content="Damus">
<meta property="og:description" content="A new social network that you control">
<meta property="og:image" content="https://damus.io/img/logo.png">
<meta property="og:url" content="https://damus.io">
<meta name="twitter:card" content="summary_large_image">
<link rel="stylesheet" href="/css/normalize.css">
<link rel="stylesheet" href="/css/skeleton.css?v=2">
<link rel="stylesheet" href="/css/custom.css?v=4">
</head>
<body>
<section class="header">
<span class="logo">
<img src="/img/damus-nobg.svg"/>
</span>
<span class="damus">damus</span>
</section>
<div class="container">
<h1>Key Converter</h2>
<p>Convert a damus key to an old-style hex key</p>
<label for="bech32">damus key</label>
<input type="text" class="u-full-width" placeholder="npub... OR nsec..." id="damus-key">
<label for="bech32">hex key</label>
<input type="text" class="u-full-width" placeholder="" id="hex-key">
</div>
<script src="bech32.js" ></script>
<script src="key.js" ></script>
</body>
</html>

32
key/key.js Normal file
View file

@ -0,0 +1,32 @@
function hex_char(val)
{
if (val < 10)
return String.fromCharCode(48 + val)
if (val < 16)
return String.fromCharCode(97 + val - 10)
}
function hex_encode(buf)
{
str = ""
for (let i = 0; i < buf.length; i++) {
const c = buf[i]
str += hex_char(c >> 4)
str += hex_char(c & 0xF)
}
return str
}
function go() {
const el = document.querySelector("#damus-key")
const hex_el = document.querySelector("#hex-key")
el.addEventListener("input", () => {
const decoded = bech32.decode(el.value)
const bytes = fromWords(decoded.words)
hex_el.value = hex_encode(bytes)
});
}
go()

1
privacy-policy.txt Normal file
View file

@ -0,0 +1 @@
Damus doesn't store any information about you other than the posts which you make, which are published to the damus relay as well other relays if configured.