changin room email notification system

This commit is contained in:
Mentor 2022-06-09 09:53:01 +02:00
parent 0b47dfb4cb
commit 2db757e2f4
21 changed files with 748 additions and 42 deletions

View File

@ -20,3 +20,9 @@ exports.mainnetMetadata = functions.runWith( runtime ).https.onRequest( mainnetA
const { handleQueuedRocketeerOutfit } = require( './nft-media/changing-room' )
exports.mainnetGenerateOutfitsOnQueue = functions.runWith( runtime ).firestore.document( `mainnetQueueOutfitGeneration/{rocketeerId}` ).onWrite( handleQueuedRocketeerOutfit )
exports.rinkebyGenerateOutfitsOnQueue = functions.runWith( runtime ).firestore.document( `rinkebyQueueOutfitGeneration/{rocketeerId}` ).onWrite( handleQueuedRocketeerOutfit )
/* ///////////////////////////////
// Daemons
// /////////////////////////////*/
const { notify_holders_of_changing_room_updates } = require( './integrations/changingroom' )
exports.notify_holders_of_changing_room_updates = functions.pubsub.schedule( '0 0 * * *' ).onRun( notify_holders_of_changing_room_updates )

View File

@ -1,5 +1,8 @@
const { generateNewOutfitFromId, queueRocketeersOfAddressForOutfitChange } = require( '../nft-media/changing-room' )
const { db, dataFromSnap } = require( '../modules/firebase' )
const { db, dataFromSnap, FieldPath } = require( '../modules/firebase' )
const { dev, log } = require( '../modules/helpers' )
const { ask_signer_is_for_available_emails } = require( './signer_is' )
const { send_outfit_available_email } = require( './ses' )
// Web3 APIs
const { getOwingAddressOfTokenId } = require( '../modules/contract' )
@ -197,4 +200,71 @@ exports.setPrimaryOutfit = async function( req, res ) {
}
}
/* ///////////////////////////////
// Notify of changing room updates
// /////////////////////////////*/
exports.notify_holders_of_changing_room_updates = async context => {
try {
// Get all Rocketeers with outfits available
const network = dev ? `rinkeby` : `mainnet`
const limit = dev ? 50 : 2000
log( `Getting ${ limit } rocketeers on ${ network }` )
const all_rocketeers = await db.collection( `${ network }Rocketeers` )
.limit( limit ).get().then( dataFromSnap )
log( `Got ${ all_rocketeers.length } Rocketeers` )
const has_outfit_available = all_rocketeers.filter( rocketeer => {
const { attributes } = rocketeer
const outfit_available = attributes.find( ( { trait_type } ) => trait_type == 'last outfit change' )
// If outfit available is in the past, keep it
if( outfit_available?.value < Date.now() ) return true
// If outfit available in the future, discard
return false
} )
const owners = await Promise.all( has_outfit_available.map( async ( { uid } ) => {
log( `Getting owner of `, uid )
const owning_address = await getOwingAddressOfTokenId( uid )
return { uid, owning_address }
} ) )
const owners_with_signer_email = await ask_signer_is_for_available_emails( owners.map( ( { owning_address } ) => owning_address ) )
const rocketeers_by_address = has_outfit_available.reduce( ( wallets, rocketeer ) => {
const new_wallet_list = { ...wallets }
const { owning_address } = owners.find( ( { uid } ) => uid == rocketeer.uid )
// If this owner has no email, ignore it
if( !owners_with_signer_email.includes( owning_address ) ) return new_wallet_list
// If the wallet object does now have this one yet, add an empty array
if( !new_wallet_list[ owning_address ] ) new_wallet_list[owning_address] = []
new_wallet_list[owning_address] = [ ...new_wallet_list[owning_address], rocketeer ]
return new_wallet_list
}, {} )
const owners_to_email = Object.keys( rocketeers_by_address )
// Send emails to the relevant owners
await Promise.all( owners_to_email.map( async owning_address => {
const rocketeers = rocketeers_by_address[ owning_address ]
await send_outfit_available_email( rocketeers, `${ owning_address }@signer.is` )
} ) )
} catch( e ) {
console.error( `notify_holders_of_changing_room_updates error: `, e )
}
}

View File

@ -0,0 +1,70 @@
const AWS = require('aws-sdk')
const functions = require( 'firebase-functions' )
const { aws } = functions.config()
const { log } = require( '../modules/helpers' )
const SES_CONFIG = {
accessKeyId: aws?.ses?.keyid,
secretAccessKey: aws?.ses?.secretkey,
region: aws?.ses?.region,
}
const AWS_SES = new AWS.SES( SES_CONFIG )
// Email templates
const pug = require('pug')
const { promises: fs } = require( 'fs' )
const csso = require('csso')
const juice = require('juice')
async function compile_pug_to_email( pugFile, data ) {
const [ emailPug, inlineNormalise, styleExtra, styleOutlook, rocketeerStyles ] = await Promise.all( [
fs.readFile( pugFile ),
fs.readFile( `${ __dirname }/../templates/css-resets/normalize.css`, 'utf8' ),
fs.readFile( `${ __dirname }/../templates/css-resets/extra.css`, 'utf8' ),
fs.readFile( `${ __dirname }/../templates/css-resets/outlook.css`, 'utf8' ),
fs.readFile( `${ __dirname }/../templates/rocketeers.css`, 'utf8' )
] )
const { css } = csso.minify( [ styleExtra, styleOutlook, inlineNormalise, rocketeerStyles ].join( '\n' ) )
const html = pug.render( emailPug, { data, headStyles: css } )
const emailifiedHtml = juice.inlineContent( html, [ inlineNormalise, rocketeerStyles ].join( '\n' ), { removeStyleTags: false } )
return emailifiedHtml
}
async function send_email( recipient, subject, html, text ) {
const options = {
Source: aws.ses.fromemail,
Destination: {
ToAddresses: [ recipient ]
},
ReplyToAddresses: [],
Message: {
Body: {
Html: { Charset: 'UTF-8', Data: html },
Text: { Charset: 'UTF-8', Data: text }
},
Subject: { Charset: 'UTF-8', Data: subject }
}
}
log( `Sending email "${ options.Message.Subject.Data }" from ${ options.Source } to ${ options.Destination.ToAddresses[0] }` )
return AWS_SES.sendEmail( options ).promise()
}
exports.send_outfit_available_email = async ( rocketeers, email ) => {
const email_html = await compile_pug_to_email( `${ __dirname }/../templates/outfit_available.email.pug`, rocketeers )
const email_text = ( await fs.readFile( `${ __dirname }/../templates/outfit_available.email.txt`, 'utf8' ) )
// .replace( '%%address%%', email_data.address )
return send_email( email, `Your Rocketeer${rocketeers.length > 1 ? 's' : '' } ${rocketeers.length > 1 ? 'have outfits' : 'has an outfit' } available!`, email_html, email_text )
}

View File

@ -0,0 +1,23 @@
const fetch = require( 'isomorphic-fetch' )
exports.ask_signer_is_for_available_emails = async function( addresses ) {
/* ///////////////////////////////
// Check available email addresses */
const endpoint = `https://signer.is/check_availability/`
const options = {
method: 'POST',
headers:{
'Content-Type': 'application/json'
},
body: JSON.stringify( {
addresses
} )
}
const res = await fetch( endpoint, options )
const available_addresses = await res.json()
return available_addresses?.emails_available || []
}

View File

@ -1,7 +1,7 @@
// ///////////////////////////////
// Helper functions
// ///////////////////////////////
exports.dev = !!process.env.development
exports.log = ( ...messages ) => {
if( process.env.development ) console.log( ...messages )
}

View File

@ -0,0 +1,21 @@
# The MIT License (MIT)
Copyright © Arthur Koch
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,67 @@
# normalize.email.css
CSS resets for HTML emails
It's just a little css library for best default email compatibility. You can use it with your favourite email framework and self-coded templates.
## What does it do?
- Preserves useful defaults for most email clients
- Makes native platform font styling
- Corrects some popular bugs
- Explains what code does using comments
Please let me know if comments not informative and must be detailed
## Contents
- normalize.css - must be inlined to your newsletter in production
- extra.css - must be placed between `<style>` tags in `<head>` of your newsletter in production
- outlook.css - must be placed between `<style>` tags with conditional comment in `<head>` of your newsletter in production. Check out example.html to learn correct code
## Example
``` html
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- normalize.css contents must be inlined to newsletter -->
<link href="normalize.css" rel="stylesheet">
<link href="extra.css" rel="stylesheet">
<style>
/* Put extra.css contents here */
</style>
<!--[if (gte mso 9)|(IE)]>
<link href="outlook.css" rel="stylesheet">
/* Put outlook.css contents here */
<![endif]-->
<!-- Left title element empty to prevent viewing this text in subject line on Android 4 email clients -->
<title></title>
</head>
<body class="body">
<div class="webkit">
<!-- An example of bulletproof container with limited row length -->
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<!-- Add here this element -->
<!-- <th></th> -->
<!-- to align container to center -->
<th width="500" align="left">
<!-- Content here -->
<!-- You can use any HTML code which you prefer -->
</th>
<th></th>
</tr>
</table>
</div>
</body>
</html>
```

View File

@ -0,0 +1,59 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- normalize.css contents must be inlined to newsletter -->
<link href="normalize.css" rel="stylesheet">
<link href="extra.css" rel="stylesheet">
<style>
/* Put extra.css contents here */
</style>
<!--[if (gte mso 9)|(IE)]>
<link href="outlook.css" rel="stylesheet">
/* Put outlook.css contents here */
<![endif]-->
<!-- Left title element empty to prevent viewing this text in subject line on Android 4 email clients -->
<title></title>
</head>
<body class="body">
<div class="webkit">
<!-- An example of bulletproof container with limited row length -->
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<!-- Add here this element -->
<!-- <th></th> -->
<!-- to align container to center -->
<th width="500" align="left">
<p>You are receiving this because a known security researcher submitted proof of finding credentials for your npm user account on the internet.</p>
<p>In order to prevent unauthorized access, we've changed the password to your account and invalidated all of your active npm tokens.</p>
<p>Please click on the following link, or paste this into your browser to reset your password:</p>
<ul>
<li><a href="https://www.npmjs.com/forgot">https://www.npmjs.com/forgot</a></li>
</ul>
<p>When you reset your password please do not set it back to the old value.</p>
<p>We have no reason to believe that your account was compromised, but cannot be certain of this. This reset is preemptive, to prevent future compromise.</p>
<p>If you have questions:</p>
<ol>
<li>You can reply to this message or email <a href="support@npmjs.com">support@npmjs.com</a>.</li>
<li>You can also read more about this undertaking in&nbsp;our&nbsp;<a href="http://blog.npmjs.org/post/161515829950/credentials-resets">blog&nbsp;post</a>.</li>
</ol>
<p>Npm loves you.</p>
</th>
<th></th>
</tr>
</table>
</div>
</body>
</html>

View File

@ -0,0 +1,29 @@
/* Extra.css */
/* Contents of this file must be placed between <style> tags in <head> of your newsletter in production */
@media screen and (max-width: 600px) {
u + .body {
/* iOS Gmail viewport fix */
/* Make sure that your body element has .body class */
width: 100vw !important;
}
}
a[x-apple-data-detectors=true] {
/* Set default text color inheritance for auto-detected iOS links like date, time, address, etc */
color: inherit !important;
text-decoration: inherit !important;
border-bottom: none !important;
}
body {
/* Set native platform font styling */
font-family: -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
font-size: 16px;
color: black;
}
.webkit {
/* Webkit and Microsoft font-size fix */
width: 100%;
table-layout: fixed;
-webkit-text-size-adjust: 100%;
-ms-text-size-adjust: 100%;
}

View File

@ -0,0 +1,64 @@
/* Normalize.css */
/* Contents of this file must be inlined to your newsletter in production */
h1 a,
h2 a,
h3 a,
h4 a,
h5 a,
h6 a,
li a,
p a {
/* Set sexy underline styling for links except images */
text-decoration: none;
color: #2837b8 !important;
border-bottom: #d3d6f0 1px solid;
}
h1 {
/* Mail.ru <h1> styling fix */
font-size: 2em;
line-height: initial;
margin: 0.67em 0;
padding: 0;
}
table {
/* Null tables spaces */
border-spacing: 0;
border-collapse: collapse;
}
table td {
padding: 0;
}
table th {
padding: 0;
font-weight: normal;
}
img {
/* Flexible images fix + prevent any borders for images */
max-width: 100%;
border: 0;
outline: 0;
/* Set image's ALT text styling */
color: #2837b8;
font-size: 14px;
}
ol,
ul {
/* We don't touch horizontal margins to prevent hiding bullets in Oultook */
margin-top: 1em;
margin-bottom: 2em;
}
ol li,
ul li {
line-height: 1.6em;
margin: 0.5em 0;
}
p {
line-height: 1.6em;
margin: 1em 0;
}
span.code {
/* Monospace emphasis for code examples */
font-family: consolas, courier, monospace;
color: grey;
}

View File

@ -0,0 +1,24 @@
/* Outlook.css */
/* Contents of this file must be placed between <style> tags with conditional comment in <head> of your newsletter in production */
body {
/* Reset font styling. Useful when we links custom fonts to our newsletter */
font-family: Helvetica, Arial, sans-serif;
}
a {
/* Reset default links styling */
color: #2837b8;
text-decoration: underline;
}
h1, h2, h3, h4, h5, h6 {
/* Reset default headings margin */
margin: .5em 0;
}
img {
/* Scaled images fix */
-ms-interpolation-mode: bicubic;
}
table {
/* Null tables spaces */
border-collapse: collapse;
}

View File

@ -0,0 +1,83 @@
/*:root {
--color-primary: #8076fa;
--color-text: rgb(77, 86, 128);
--color-accent: rgb( 248, 117, 136 );
--color-backdrop: rgba( 0, 0, 0, .05 );
}*/
/* Note that web fonts DO NOT WORK in most email clients */
@import url('https://fonts.googleapis.com/css2?family=Archivo&family=Comfortaa:wght@500&display=swap');
body {
margin: 0;
font-family: 'Archivo', 'Helvetica Neue', sans-serif;
font-weight: 400;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ///////////////////////////////
// Flow text
// /////////////////////////////*/
html {
font-size: calc( 18px + .1vw );
}
/* ///////////////////////////////
// Brand styles
// /////////////////////////////*/
h1 {
font-size: 2.5rem;
font-weight: 500;
line-height: 1.2;
font-family: 'Comfortaa', sans-serif;
text-align: left;
/*color: var( --color-primary );*/
color: #8076fa;
}
h2 {
font-size: 1.5rem;
margin: 0 0 1rem;
line-height: 1.2;
font-weight: 400;
text-align: left;
/*color: var( --color-accent );*/
color: rgb( 248, 117, 136 );
}
p {
font-size: 1rem;
margin: 1rem 0;
line-height: 1.5rem;
/*color: var( --color-text );*/
color: rgb(77, 86, 128);
text-align: left
}
a.button {
display: inline-block;
padding: 1rem 2rem;
margin: .5rem;
margin-left: 0;
text-decoration: none;
/*border: 2px solid var( --color-primary );*/
border: 2px solid #8076fa;
/*color: var( --color-primary );*/
color: #8076fa;
font-size: 1rem;
background: none;
border-radius: 5px;
}
input {
/*background: var( --color-backdrop );*/
background: rgba( 0, 0, 0, .05 );
border: none;
/*border-left: 2px solid var( --color-primary );*/
border-left: 2px solid #8076fa;
padding: 1rem;
margin: 1rem 10% 1rem 0;
width: 90%;
}

View File

@ -0,0 +1,13 @@
doctype html
html( lang='en' )
head
style= headStyles
body
p Hello,
p Someone requested for emails to #{ data.address }@signer.is (and #{ data.ENS } ENS) to be delivered to this email address.
p If this was not you, you can safely ignore this email.
p To receive emails at this address, please verify your ownership of this email by clicking the link below.
a( href=data.verification_link ) Click here to verify your email address.
p Is the above link not working? Visit this page manually: #{ data.verification_link }
p Have a great day,
p ~ Signer.is

View File

@ -0,0 +1,13 @@
Hello,
Someone requested for emails to %%address%%@signer.is (and %%ENS%% ENS) to be delivered to this email address.
If this was not you, you can safely ignore this email.
To receive emails at this address, please verify your ownership of this email by clicking the link below.
Open this link to verify your email address: %%verification_link%%.
Have a great day,
~ Signer.is

View File

@ -6,12 +6,13 @@
"": {
"name": "functions",
"dependencies": {
"aws-sdk": "^2.1149.0",
"body-parser": "^1.19.1",
"color": "^4.0.2",
"color-namer": "^1.4.0",
"convert-svg-to-jpeg": "^0.5.0",
"cors": "^2.8.5",
"csso": "^5.0.2",
"csso": "^5.0.3",
"express": "^4.17.1",
"firebase-admin": "^10.0.0",
"firebase-functions": "^3.11.0",
@ -1508,6 +1509,62 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/aws-sdk": {
"version": "2.1149.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1149.0.tgz",
"integrity": "sha512-wNb3YMLhXoK4UkjXhGAWMjRdrXT/Zhv3KdgPmd7VWlr3nXMViLwVJEEYdVmALUdkzCefdzY1JUTRLMgCxtn9EA==",
"dependencies": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"uuid": "8.0.0",
"xml2js": "0.4.19"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/aws-sdk/node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"node_modules/aws-sdk/node_modules/ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"node_modules/aws-sdk/node_modules/punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="
},
"node_modules/aws-sdk/node_modules/url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
"dependencies": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"node_modules/aws-sdk/node_modules/uuid": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@ -2611,9 +2668,9 @@
}
},
"node_modules/csso": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.2.tgz",
"integrity": "sha512-llFAe1UfFHy38ziX+YrPMGkn5MxdjzYtz0drvgnjRY/tLPmBRxotYTGO51BsKe9voQA074pEb0udV+piXH4scQ==",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.3.tgz",
"integrity": "sha512-93gBHTJ6EQlLNhIX5Ho8VAJD2t2T2wg1xHDjbIUm/oQ7iFiSUTo9jSojiQK0pEZ3lMhYDrQO7Rcd70M68+VrtA==",
"dependencies": {
"css-tree": "~2.0.4"
},
@ -3702,6 +3759,14 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ=="
},
"node_modules/events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw==",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
@ -5171,6 +5236,14 @@
"node": ">= 4"
}
},
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/jose": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz",
@ -7198,6 +7271,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"node_modules/saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@ -9148,6 +9226,23 @@
"node": ">=12"
}
},
"node_modules/xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"node_modules/xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
"engines": {
"node": ">=4.0"
}
},
"node_modules/xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
@ -10352,6 +10447,58 @@
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
"integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw=="
},
"aws-sdk": {
"version": "2.1149.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1149.0.tgz",
"integrity": "sha512-wNb3YMLhXoK4UkjXhGAWMjRdrXT/Zhv3KdgPmd7VWlr3nXMViLwVJEEYdVmALUdkzCefdzY1JUTRLMgCxtn9EA==",
"requires": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"uuid": "8.0.0",
"xml2js": "0.4.19"
},
"dependencies": {
"buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw=="
},
"url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
"requires": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"uuid": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
"integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw=="
}
}
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
@ -11257,9 +11404,9 @@
"integrity": "sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw=="
},
"csso": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.2.tgz",
"integrity": "sha512-llFAe1UfFHy38ziX+YrPMGkn5MxdjzYtz0drvgnjRY/tLPmBRxotYTGO51BsKe9voQA074pEb0udV+piXH4scQ==",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.3.tgz",
"integrity": "sha512-93gBHTJ6EQlLNhIX5Ho8VAJD2t2T2wg1xHDjbIUm/oQ7iFiSUTo9jSojiQK0pEZ3lMhYDrQO7Rcd70M68+VrtA==",
"requires": {
"css-tree": "~2.0.4"
}
@ -12126,6 +12273,11 @@
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.4.tgz",
"integrity": "sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ=="
},
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha512-kEcvvCBByWXGnZy6JUlgAp2gBIUjfCAV6P6TgT1/aaQKcmuAEC4OZTV1I4EWQLz2gxZw76atuVyvHhTxvi0Flw=="
},
"evp_bytestokey": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz",
@ -13204,6 +13356,11 @@
"is-object": "^1.0.1"
}
},
"jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="
},
"jose": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-2.0.5.tgz",
@ -14828,6 +14985,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"saxes": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
@ -16354,6 +16516,20 @@
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
"integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw=="
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
},
"xmlchars": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",

View File

@ -15,12 +15,13 @@
},
"main": "index.js",
"dependencies": {
"aws-sdk": "^2.1149.0",
"body-parser": "^1.19.1",
"color": "^4.0.2",
"color-namer": "^1.4.0",
"convert-svg-to-jpeg": "^0.5.0",
"cors": "^2.8.5",
"csso": "^5.0.2",
"csso": "^5.0.3",
"express": "^4.17.1",
"firebase-admin": "^10.0.0",
"firebase-functions": "^3.11.0",

View File

@ -1,17 +0,0 @@
doctype html
html( lang='en' )
head
style= headStyles
body
h1 Outfit available for #{ rocketeer.name }
h2 Monthly outfit available for generation now.
p Your Rocketeer is eligible for a new outfit. You can generate it for free at the changing room.
img.avatar( src=rocketeer.image )
a.button( href='https://tools.rocketeer.fans/#/outfits/' + rocketeer.uid ) View #{ rocketeer.first_name }'s changing room
a.button( href='https://tools.rocketeer.fans/#/outfits/' ) View all available outfits
p If you have any questions or suggestions, please have a look at the FAQ here: https://rocketeer.fans/changingroom.
p For other questions or suggestions, please do not hesitate to reach out to us on discord: https://discord.gg/rocketeers
p ~ rocketeers.eth

View File

@ -1,5 +0,0 @@
Rocketeer %%name%% has a new outfit available!
You can generate it at https://tools.rocketeer.fans
Have fun!

View File

@ -0,0 +1,10 @@
doctype html
html( lang='en' )
head
style= headStyles
body
p Hello!
p Your Rocketeers have new outfits available.
p To generate new outfits, visit the changing room here: https://mint.rocketeer.fans/#/outfits
p Have a great day,
p ~ Rocketeer HQ

View File

@ -0,0 +1,9 @@
Hello!
Your Rocketeers have new outfits available.
To generate new outfits, visit the changing room here: https://mint.rocketeer.fans/#/outfits
Have a great day,
~ Rocketeer HQ

View File

@ -1,10 +0,0 @@
<svg width="71" height="55" viewBox="0 0 71 55" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<path d="M60.1045 4.8978C55.5792 2.8214 50.7265 1.2916 45.6527 0.41542C45.5603 0.39851 45.468 0.440769 45.4204 0.525289C44.7963 1.6353 44.105 3.0834 43.6209 4.2216C38.1637 3.4046 32.7345 3.4046 27.3892 4.2216C26.905 3.0581 26.1886 1.6353 25.5617 0.525289C25.5141 0.443589 25.4218 0.40133 25.3294 0.41542C20.2584 1.2888 15.4057 2.8186 10.8776 4.8978C10.8384 4.9147 10.8048 4.9429 10.7825 4.9795C1.57795 18.7309 -0.943561 32.1443 0.293408 45.3914C0.299005 45.4562 0.335386 45.5182 0.385761 45.5576C6.45866 50.0174 12.3413 52.7249 18.1147 54.5195C18.2071 54.5477 18.305 54.5139 18.3638 54.4378C19.7295 52.5728 20.9469 50.6063 21.9907 48.5383C22.0523 48.4172 21.9935 48.2735 21.8676 48.2256C19.9366 47.4931 18.0979 46.6 16.3292 45.5858C16.1893 45.5041 16.1781 45.304 16.3068 45.2082C16.679 44.9293 17.0513 44.6391 17.4067 44.3461C17.471 44.2926 17.5606 44.2813 17.6362 44.3151C29.2558 49.6202 41.8354 49.6202 53.3179 44.3151C53.3935 44.2785 53.4831 44.2898 53.5502 44.3433C53.9057 44.6363 54.2779 44.9293 54.6529 45.2082C54.7816 45.304 54.7732 45.5041 54.6333 45.5858C52.8646 46.6197 51.0259 47.4931 49.0921 48.2228C48.9662 48.2707 48.9102 48.4172 48.9718 48.5383C50.038 50.6034 51.2554 52.5699 52.5959 54.435C52.6519 54.5139 52.7526 54.5477 52.845 54.5195C58.6464 52.7249 64.529 50.0174 70.6019 45.5576C70.6551 45.5182 70.6887 45.459 70.6943 45.3942C72.1747 30.0791 68.2147 16.7757 60.1968 4.9823C60.1772 4.9429 60.1437 4.9147 60.1045 4.8978ZM23.7259 37.3253C20.2276 37.3253 17.3451 34.1136 17.3451 30.1693C17.3451 26.225 20.1717 23.0133 23.7259 23.0133C27.308 23.0133 30.1626 26.2532 30.1066 30.1693C30.1066 34.1136 27.28 37.3253 23.7259 37.3253ZM47.3178 37.3253C43.8196 37.3253 40.9371 34.1136 40.9371 30.1693C40.9371 26.225 43.7636 23.0133 47.3178 23.0133C50.9 23.0133 53.7545 26.2532 53.6986 30.1693C53.6986 34.1136 50.9 37.3253 47.3178 37.3253Z" fill="#23272A"/>
</g>
<defs>
<clipPath id="clip0">
<rect width="71" height="55" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2.0 KiB