diff --git a/README.md b/README.md new file mode 100644 index 0000000..677b31e --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# nostr-broadcast + +Fetch, backup and broadcast your Nostr events. + +Live at https://nostr-broadcast.pages.dev diff --git a/img/nostr-broadcast_og.png b/img/nostr-broadcast_og.png new file mode 100644 index 0000000..39b5455 Binary files /dev/null and b/img/nostr-broadcast_og.png differ diff --git a/img/nostr-broadcast_twitter.png b/img/nostr-broadcast_twitter.png new file mode 100644 index 0000000..a23826b Binary files /dev/null and b/img/nostr-broadcast_twitter.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..3272f5f --- /dev/null +++ b/index.html @@ -0,0 +1,144 @@ + + + + + + + NBS + + + + + + + + + + + + + + + + +
+
+

Nostr Backup Service

+
+

Share all of your events, such as notes & + lists of followers/following, with all the major + relays and generate a backup filethat can be used + to restore your data to any relay of your choice. + +

+
+
+
+ +
+

+ +

+
+ +
+ + + +

+ + +

+
+ + +
+
+

+

+

+
+
+

+

+
+
+

+

+

+
+
+ +
+ + + + + + + + + diff --git a/js/nostr-broadcast.js b/js/nostr-broadcast.js new file mode 100644 index 0000000..3510761 --- /dev/null +++ b/js/nostr-broadcast.js @@ -0,0 +1,74 @@ +// button click handler +const fetchAndBroadcast = async () => { + // reset UI + $('#fetching-status').html('') + $('#fetching-progress').css('visibility', 'hidden') + $('#fetching-progress').val(0) + $('#file-download').html('') + $('#events-found').text('') + $('#broadcasting-status').html('') + $('#broadcasting-progress').css('visibility', 'hidden') + $('#broadcasting-progress').val(0) + // messages to show to user + const checkMark = '✓' + const txt = { + broadcasting: 'Broadcasting to relays... ', + fetching: 'Fetching from relays... ', + download: `Downloading Backup file... ${checkMark}`, + } + // parse pubkey ('npub' or hexa) + const pubkey = parsePubkey($('#pubkey').val()) + if (!pubkey) return + // disable button (will be re-enable at the end of the process) + $('#fetch-and-broadcast').prop('disabled', true) + // inform user that app is fetching from relays + $('#fetching-status').text(txt.fetching) + // show and update fetching progress bar + $('#fetching-progress').css('visibility', 'visible') + const fetchInterval = setInterval(() => { + // update fetching progress bar + const currValue = parseInt($('#fetching-progress').val()) + $('#fetching-progress').val(currValue + 1) + }, 1000) + // get all events from relays + const filter = { authors: [pubkey] } + const data = await getEvents(filter) + // inform user fetching is done + $('#fetching-status').html(txt.fetching + checkMark) + clearInterval(fetchInterval) + $('#fetching-progress').val(20) + // inform user that backup file (js format) is being downloaded + $('#file-download').html(txt.download) + downloadFile(data, 'nostr-broadcast.js') + // inform user that app is broadcasting events to relays + $('#broadcasting-status').html(txt.broadcasting) + // show and update broadcasting progress bar + $('#broadcasting-progress').css('visibility', 'visible') + const broadcastInterval = setInterval(() => { + // update fetching progress bar + const currValue = parseInt($('#broadcasting-progress').val()) + $('#broadcasting-progress').val(currValue + 1) + }, 1000) + await broadcastEvents(data) + // inform user that broadcasting is done + $('#broadcasting-status').html(txt.broadcasting + checkMark) + clearInterval(broadcastInterval) + $('#broadcasting-progress').val(20) + // re-enable broadcast button + $('#fetch-and-broadcast').prop('disabled', false) +} + +const getFromExtension = async () => { + const pubkey = await window.nostr.getPublicKey() + if (pubkey) $('#pubkey').val(pubkey).change() +} + +const pubkeyOnChange = () => { + $('#fetch-and-broadcast').css('display', '') + $('#get-from-extension').css('display', 'none') +} + +if (window.nostr) { + $('#fetch-and-broadcast').css('display', 'none') + $('#get-from-extension').css('display', '') +} diff --git a/js/relays.js b/js/relays.js new file mode 100644 index 0000000..195e008 --- /dev/null +++ b/js/relays.js @@ -0,0 +1,46 @@ +// list of paid relays from: +// https://thebitcoinmanual.com/articles/paid-nostr-relay/ + +const relays = [ + 'wss://atlas.nostr.land', // paid relay 15000 npub12262qa4uhw7u8gdwlgmntqtv7aye8vdcmvszkqwgs0zchel6mz7s6cgrkj + 'wss://bitcoiner.social', // paid relay 1000 npub1dxs2pygtfxsah77yuncsmu3ttqr274qr5g5zva3c7t5s3jtgy2xszsn4st + 'wss://brb.io', + 'wss://eden.nostr.land', // paid relay 5000 npub16k7j4mwsqm8hakjl8x5ycrqmhx89lxkfwz2xxxcw75eav7sd8ztqy2rwdn + 'wss://expensive-relay.fiatjaf.com', + 'wss://freedom-relay.herokuapp.com', + 'wss://nos.lol', + 'wss://nostr-2.zebedee.cloud', + 'wss://nostr-pub.wellorder.net', + 'wss://nostr-relay.alekberg.net', + 'wss://nostr-relay.freeberty.net', + 'wss://nostr-relay.wlvs.space', + 'wss://nostr.bitcoiner.social', + 'wss://nostr.blocs.fr', + 'wss://nostr.coollamer.com', + 'wss://nostr.decentony.com', // paid relay 7000 npub1pp9csm9564ewzer3f63284mrd9u2zssmreq42x4rtt390zmkrj2st4fzpm + 'wss://nostr.fmt.wiz.biz', + 'wss://nostr.gives.africa', // paid relay 10000 npub1g8dcep2exsadx9smhdrgwds06pgfc9yyyww6ftdcgnyukcuzk2csqs5jed + 'wss://nostr.inosta.cc', // paid relay 5000 npub1r34nhc6nqswancymk452f2frgn3ypvu77h4njr67n6ppyuls4ehs44gv0h + 'wss://nostr.milou.lol', // paid relay 1000 npub1rvg76s0gz535txd9ypg2dfqv0x7a80ar6e096j3v343xdxyrt4ksmkxrck + 'wss://nostr.onsats.org', + 'wss://nostr.orangepill.dev', + 'wss://nostr.plebchain.org', // paid relay 6969 npub1u2tehhr3ye4lv4dc8aen2gkxf6zljdpf356sgfjqfun0wxehvquqgvhuec + 'wss://nostr.rocks', + 'wss://nostr.sandwich.farm', + 'wss://nostr.wine', // paid relay 8888 npub18kzz4lkdtc5n729kvfunxuz287uvu9f64ywhjz43ra482t2y5sks0mx5sz + 'wss://nostr.zebedee.cloud', + 'wss://private.red.gb.net', // paid relay 8888 npub1nctdevxxuvth3sx6r0gutv4tmvhwy9syvpkr3gfd5atz67fl97kqyjkuxk + 'wss://puravida.nostr.land', // paid relay 10000 npub16k7j4mwsqm8hakjl8x5ycrqmhx89lxkfwz2xxxcw75eav7sd8ztqy2rwdn + 'wss://relay.current.fyi', + 'wss://relay.damus.io', + 'wss://relay.nostr.bg', + 'wss://relay.nostr.com.au', // paid relay 6969 npub1qqqqqrre3jxkuyj3s4m59usdyvm0umgm0lpy6cqjtwpt649sdews5q3hw7 + 'wss://relay.nostr.info', + 'wss://relay.nostrati.com', // paid relay 2000 npub1qqqqqqqut3z3jeuxu70c85slaqq4f87unr3vymukmnhsdzjahntsfmctgs + 'wss://relay.nostrich.land', // paid relay 2100 npub1vj0wlergmkcs0sz7hfks2ywj555c2s87f40squ4sqcmqpr7897fqn6mfew + 'wss://relay.nostriches.org', // paid relay 421 npub1vnmhd287pvxxk5w9mcycf23av24nscwk0da7rrfaa5wq4l8hsehs90ftlv + 'wss://relay.orangepill.dev', // paid relay 4500 npub16jzr7npgp2a684pasnkhjf9j2e7hc9n0teefskulqmf42cqmt4uqwszk52 + 'wss://relay.snort.social', + 'wss://relayer.fiatjaf.com', + 'wss://rsslay.fiatjaf.com', +] diff --git a/style.css b/style.css new file mode 100644 index 0000000..6dd78da --- /dev/null +++ b/style.css @@ -0,0 +1,293 @@ +@import url('https://fonts.googleapis.com/css2?family=Red+Hat+Text:wght@400;700&display=swap'); + +:root{ +--color: #333; +} + +body { + font-family: 'Red Hat Text', sans-serif; +} + +.container { + text-align: center; + color: var(--color); + width: 100%; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + background-image: url(https://images.unsplash.com/photo-1519120944692-1a8d8cfc107f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=872&q=80); + background-size: cover; + background-position: center; + +/* this is where the magic happens: */ + background-attachment: fixed; +} + +h1 {font-family: "Open Sans", sans-serif; + line-height: 1.5; + font-weight: 700; + font-size: 2rem; + +} +.header-space { + display: flex; + justify-content: center; + margin-top: 9px; + font-family: "Courier", monospace; +} + +.box { + background-color: #f2f2f2; + padding: 20px; + border-radius: 10px; + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1); + } + + +progress { + -webkit-appearance: none; + appearance: none; + height: 10px; + background-color: #eee; + border-radius: 10px; + overflow: hidden; +} +progress::-webkit-progress-bar { + background-color: #eee; + border-radius: 10px; +} +progress::-webkit-progress-value { + background-color: #007bff; + border-radius: 10px; + animation: loading 2s ease-out infinite; +} +@keyframes loading { + 0% { + width: 0%; + } + 100% { + width: 100%; + } +} + + +progress { + -webkit-appearance: none; + appearance: none; + height: 10px; + background-color: #eee; + border-radius: 10px; + overflow: hidden; +} +progress::-webkit-progress-bar { + background-color: #eee; + border-radius: 10px; +} +progress::-webkit-progress-value { + background-color: #007bff; + border-radius: 10px; +} +progress[value="25"]::-webkit-progress-value { + background-color: #ffc107; +} +progress[value="50"]::-webkit-progress-value { + background-color: #28a745; +} +progress[value="75"]::-webkit-progress-value { + background-color: #dc3545; +} + + +p { + max-width: 52ch; + line-height: 1.5; + padding: .3em; + margin-top: 5px; + margin: 0; +} + + + + +a { + display: inline-block; + padding: .5em 1.5em; + background-color: var(--color); + color: white; + text-decoration: none; + text-transform: uppercase; + border-radius: 0.3em; + font-weight: 700; + letter-spacing: .5px; + font-size: .875rem; +} + +.blank, +.other { + width: 100%; + min-height: 60vh; + background-color: var(--color); +} + +.second { + background-image:url(https://images.unsplash.com/photo-1514496959998-c01c40915c5f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80); + background-attachment: fixed; + height: 1200px; +} + +.second { + background-image:url(https://images.unsplash.com/photo-1514496959998-c01c40915c5f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1740&q=80); + background-attachment: fixed; + height: 1200px; +} + +.space-between { + display: flex; + justify-content: center; + margin-bottom: 10px; +} + +h2 { + font-family: "Open Sans", sans-serif; + font-size: 1em; + line-height: 1.5; + font-weight: 500; /* Set font-weight to lighter */ + margin-bottom: 5px; + width: 50%; /* set the width of h2 element to 50% of its parent's width */ + margin: 10 auto; /* set the top and bottom margin to 0, and left and right margin to auto */ + word-wrap: break-word; + padding: 25px; +} + +@media screen and (max-width: 1080px) { /* apply this style only when the screen width is less than or equal to 600px */ + h2 { + width: 70%; /* set the width of h2 element to 100% of its parent's width */ + margin: 0; /* set all margins to 0 */ + } +} + +@media screen and (max-width: 800px) { /* apply this style only when the screen width is less than or equal to 600px */ + h2 { + width: 90%; /* set the width of h2 element to 100% of its parent's width */ + margin: 0; /* set all margins to 0 */ + } +} + +@media screen and (max-width: 600px) { /* apply this style only when the screen width is less than or equal to 600px */ + h2 { + width: 90%; /* set the width of h2 element to 100% of its parent's width */ + margin: 0; /* set all margins to 0 */ + } +} + +#pubkey { + width: 700px !important; +} + +@media screen and (max-width: 1080px) { + #pubkey { + width: 550px !important; + } +} +@media screen and (max-width: 800px) { + #pubkey { + width: 450px !important; + } +} +@media screen and (max-width: 600px) { + #pubkey { + width: 300px !important; + } +} + + + + + +main div p img { + position: relative; + top: 6px; + height: 1rem; + margin-left: 0.5rem; +} + +details { + background-color: rgba(242, 241, 238, 0.3); + border: 1px solid #ccc; + padding: 0.5rem; + margin: 1rem 0; +} + +p, +li { + line-height: 1.33; + margin-bottom: 1rem; +} + +ul { + margin-left: -1rem; +} + +.box { + border: 1px solid #ccc; + padding: 0 0.5rem; + margin: 1rem 0; +} + +.box-content { + display: flex; + justify-content: center; + margin-top: 10px; + font-family: "Courier", monospace; +} + +button { + appearance: none; + background-color: #087bee; + border: 1px solid rgba(27, 31, 35, 0.15); + border-radius: 6px; + box-shadow: rgba(27, 31, 35, 0.04) 0 1px 0, rgba(255, 255, 255, 0.25) 0 1px 0 inset; + box-sizing: border-box; + color: #f9fafb; + cursor: pointer; + display: inline-block; + font-family: -apple-system, system-ui, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; + font-size: 14px; + font-weight: 500; + line-height: 20px; + list-style: none; + padding: 6px 16px; + position: relative; + transition: background-color 0.2s cubic-bezier(0.3, 0, 0.5, 1); + user-select: none; + -webkit-user-select: none; + touch-action: manipulation; + vertical-align: middle; + white-space: nowrap; + word-wrap: break-word; +} + + + +.gray { + background-color: rgba(183, 177, 168, 0.3); +} + +.red { + background-color: rgba(229, 60, 8, 0.3); +} + +.box button { + background-color: rgba(0, 0, 0, 0.813); + color: rgb(255, 255, 255); +} + +footer p { + font-size: 0.8rem; + margin: 0; + text-align: center; +} + +