mirror of
https://github.com/stronk-dev/RandomChad.git
synced 2025-07-05 02:35:08 +02:00
✨ multi-outfit generation alpha version
This commit is contained in:
parent
6a4e080e63
commit
2cc6948946
@ -13,3 +13,10 @@ exports.testnetMetadata = functions.runWith( runtime ).https.onRequest( testnetA
|
||||
|
||||
// Mainnet endpoint
|
||||
exports.mainnetMetadata = functions.runWith( runtime ).https.onRequest( mainnetAPI )
|
||||
|
||||
/* ///////////////////////////////
|
||||
// Firestore listeners
|
||||
// /////////////////////////////*/
|
||||
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 )
|
||||
|
@ -1,4 +1,4 @@
|
||||
const { generateNewOutfitFromId, generateNewOutfitsByAddress } = require( '../nft-media/changing-room' )
|
||||
const { generateNewOutfitFromId, queueRocketeersOfAddressForOutfitChange } = require( '../nft-media/changing-room' )
|
||||
const { db, dataFromSnap } = require( '../modules/firebase' )
|
||||
|
||||
// Web3 APIs
|
||||
@ -69,17 +69,17 @@ exports.generateNewOutfit = async function( req, res ) {
|
||||
exports.generateMultipleNewOutfits = async function( req, res ) {
|
||||
|
||||
// Parse the request
|
||||
let { address } = req.params
|
||||
if( !address ) return res.json( { error: `No address specified in URL` } )
|
||||
let { address } = req.params
|
||||
if( !address ) return res.json( { error: `No address specified in URL` } )
|
||||
|
||||
// Protect against malformed input
|
||||
if( !address.match( /0x.{40}/ ) ) return res.json( { error: `Malformed request` } )
|
||||
// Protect against malformed input
|
||||
if( !address.match( /0x.{40}/ ) ) return res.json( { error: `Malformed request` } )
|
||||
|
||||
// Lowercase the address
|
||||
address = address.toLowerCase()
|
||||
|
||||
// Internal beta
|
||||
if( !address.includes( '0xe3ae14' ) && !address.includes( '0x7dbf68' ) ) return res.json( { error: `Sorry this endpoint is in private beta for now <3` } )
|
||||
// Internal beta
|
||||
// if( !address.includes( '0xe3ae14' ) && !address.includes( '0x7dbf68' ) ) return res.json( { error: `Sorry this endpoint is in private beta for now <3` } )
|
||||
|
||||
try {
|
||||
|
||||
@ -97,28 +97,30 @@ exports.generateMultipleNewOutfits = async function( req, res ) {
|
||||
const network = chainId == '0x1' ? 'mainnet' : 'rinkeby'
|
||||
if( signer.toLowerCase() !== confirmedSignatory.toLowerCase() || action != 'generateMultipleNewOutfits' || !network ) throw new Error( `Invalid setPrimaryOutfit message with ${ signer }, ${confirmedSignatory}, ${action}, ${chainId}, ${network}` )
|
||||
|
||||
|
||||
// Check that the signer is the owner of the token
|
||||
const outfits = await generateNewOutfitsByAddress( address, network )
|
||||
const amountOfOutfits = await queueRocketeersOfAddressForOutfitChange( address, network )
|
||||
|
||||
await db.collection( 'meta' ).doc( address ).set( {
|
||||
last_changing_room: Date.now(),
|
||||
last_changing_room_errors: outfits.error.map( ( { id } ) => id ),
|
||||
last_changing_room_successes: outfits.success.map( ( { id } ) => id )
|
||||
outfits_last_changing_room: amountOfOutfits,
|
||||
outfits_in_queue: amountOfOutfits,
|
||||
updated: Date.now()
|
||||
}, { merge: true } )
|
||||
|
||||
return res.json( outfits )
|
||||
return res.json( { amountOfOutfits } )
|
||||
|
||||
|
||||
|
||||
} catch( e ) {
|
||||
} catch( e ) {
|
||||
|
||||
// Log error for debugging
|
||||
console.error( `POST generateMultipleNewOutfits Changing room api error for ${ address }: `, e )
|
||||
// Log error for debugging
|
||||
console.error( `POST generateMultipleNewOutfits Changing room api error for ${ address }: `, e )
|
||||
|
||||
// Return error to frontend
|
||||
return res.json( { error: e.mesage || e.toString() } )
|
||||
// Return error to frontend
|
||||
return res.json( { error: e.mesage || e.toString() } )
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,30 @@
|
||||
const { db, dataFromSnap } = require( '../modules/firebase' )
|
||||
const { db, dataFromSnap, FieldValue } = require( '../modules/firebase' )
|
||||
const { getRgbArrayFromColorName, randomNumberBetween } = require( '../modules/helpers' )
|
||||
const { getTokenIdsOfAddress } = require( '../modules/contract' )
|
||||
const svgFromAttributes = require( './svg-generator' )
|
||||
const Throttle = require( 'promise-parallel-throttle' )
|
||||
const { notifyDiscordWebhook } = require( '../integrations/discord' )
|
||||
|
||||
// ///////////////////////////////
|
||||
// Rocketeer generator
|
||||
// Rocketeer outfit generator
|
||||
// ///////////////////////////////
|
||||
async function getRocketeerIfOutfitAvailable( id, network='mainnet' ) {
|
||||
|
||||
const newOutfitAllowedInterval = 1000 * 60 * 60 * 24 * 30
|
||||
|
||||
// Retreive old Rocketeer data
|
||||
const rocketeer = await db.collection( `${ network }Rocketeers` ).doc( id ).get().then( dataFromSnap )
|
||||
|
||||
// Validate this request
|
||||
const { value: last_outfit_change } = rocketeer.attributes.find( ( { trait_type } ) => trait_type == "last outfit change" ) || { value: 0 }
|
||||
|
||||
// Check whether this Rocketeer is allowed to change
|
||||
const timeUntilAllowedToChange = newOutfitAllowedInterval - ( Date.now() - last_outfit_change )
|
||||
if( timeUntilAllowedToChange > 0 ) throw new Error( `You changed your outfit too recently, a change is avalable in ${ Math.floor( timeUntilAllowedToChange / ( 1000 * 60 * 60 ) ) } hours (${ new Date( Date.now() + timeUntilAllowedToChange ).toString() })` )
|
||||
|
||||
return rocketeer
|
||||
|
||||
|
||||
}
|
||||
async function generateNewOutfitFromId( id, network='mainnet' ) {
|
||||
|
||||
/* ///////////////////////////////
|
||||
@ -15,12 +32,11 @@ async function generateNewOutfitFromId( id, network='mainnet' ) {
|
||||
// /////////////////////////////*/
|
||||
// Set the entropy level. 255 would mean 0 can become 255 and -255
|
||||
let colorEntropy = 10
|
||||
const newOutfitAllowedInterval = 1000 * 60 * 60 * 24 * 30
|
||||
const specialEditionMultiplier = 1.1
|
||||
const entropyMultiplier = 1.1
|
||||
|
||||
// Retreive old Rocketeer data
|
||||
const rocketeer = await db.collection( `${ network }Rocketeers` ).doc( id ).get().then( dataFromSnap )
|
||||
// Retreive old Rocketeer data if outfit is available
|
||||
const rocketeer = await getRocketeerIfOutfitAvailable( id, network )
|
||||
|
||||
// Validate this request
|
||||
const { value: available_outfits } = rocketeer.attributes.find( ( { trait_type } ) => trait_type == "available outfits" ) || { value: 0 }
|
||||
@ -31,10 +47,6 @@ async function generateNewOutfitFromId( id, network='mainnet' ) {
|
||||
if( edition != 'regular' ) colorEntropy *= specialEditionMultiplier
|
||||
if( available_outfits ) colorEntropy *= ( entropyMultiplier ** available_outfits )
|
||||
|
||||
// Check whether this Rocketeer is allowed to change
|
||||
const timeUntilAllowedToChange = newOutfitAllowedInterval - ( Date.now() - last_outfit_change )
|
||||
if( timeUntilAllowedToChange > 0 ) throw new Error( `You changed your outfit too recently, a change is avalable in ${ Math.floor( timeUntilAllowedToChange / ( 1000 * 60 * 60 ) ) } hours (${ new Date( Date.now() + timeUntilAllowedToChange ).toString() })` )
|
||||
|
||||
// Grab attributes that will not change
|
||||
const staticAttributes = rocketeer.attributes.filter( ( { trait_type } ) => ![ 'last outfit change', 'available outfits' ].includes( trait_type ) )
|
||||
|
||||
@ -120,32 +132,162 @@ async function generateNewOutfitFromId( id, network='mainnet' ) {
|
||||
|
||||
}
|
||||
|
||||
async function generateNewOutfitsByAddress( address, network='mainnet' ) {
|
||||
async function queueRocketeersOfAddressForOutfitChange( address, network='mainnet' ) {
|
||||
|
||||
|
||||
const ids = await getTokenIdsOfAddress( address, network )
|
||||
const queue = ids.map( id => function() {
|
||||
return generateNewOutfitFromId( id, network ).then( outfit => ( { id: id, src: outfit } ) ).catch( e => {
|
||||
console.error( `Error in generateNewOutfitsByAddress: `, e.message || e )
|
||||
return { id: id, error: e.message }
|
||||
} )
|
||||
} )
|
||||
try {
|
||||
|
||||
const outfits = await Throttle.all( queue, {
|
||||
maxInProgress: 1,
|
||||
failFast: false,
|
||||
progressCallback: ( { amountDone } ) => process.env.NODE_ENV == 'development' ? console.log( `Completed ${amountDone}/${queue.length}` ) : false
|
||||
} )
|
||||
const ids = await getTokenIdsOfAddress( address, network )
|
||||
|
||||
const idsWithOutfitsAvailable = await Promise.all( ids.map( async id => {
|
||||
|
||||
try {
|
||||
|
||||
// If rocketeer has outfit, return id
|
||||
await getRocketeerIfOutfitAvailable( id )
|
||||
return id
|
||||
|
||||
} catch( e ) {
|
||||
|
||||
// If no outfit available, return false
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
} ) )
|
||||
|
||||
// Filter out the 'false' entries
|
||||
const onlyIds = idsWithOutfitsAvailable.filter( id => id )
|
||||
|
||||
// Mark Rocketeers for processing
|
||||
await Promise.all( onlyIds.map( id => db.collection( `${ network }QueueOutfitGeneration` ).doc( id ).set( {
|
||||
updated: Date.now(),
|
||||
running: false,
|
||||
network,
|
||||
address
|
||||
}, { merge: true } ) ) )
|
||||
|
||||
// Return amount queued for meta tracking
|
||||
return onlyIds.length
|
||||
|
||||
|
||||
} catch( e ) {
|
||||
|
||||
console.error( `Error in queueRocketeersOfAddressForOutfitChange: `, e )
|
||||
throw e
|
||||
|
||||
return {
|
||||
success: outfits.filter( ( { src } ) => src ),
|
||||
error: outfits.filter( ( { error } ) => error ),
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
async function handleQueuedRocketeerOutfit( change, context ) {
|
||||
|
||||
// If this was not a newly added queue item, exit gracefully
|
||||
if( change.before.exists ) return
|
||||
|
||||
// If this was a deletion, exit gracefully
|
||||
if( !change.after.exists ) return
|
||||
|
||||
const { rocketeerId } = context.params
|
||||
const { network, running, address } = change.after.data()
|
||||
|
||||
try {
|
||||
|
||||
/////
|
||||
// Validations
|
||||
|
||||
// If process is already running, stop
|
||||
if( running ) throw new Error( `Rocketeer ${ rocketeerId } is already generating a new outfit for ${ network }` )
|
||||
|
||||
/////
|
||||
// Start the generation process
|
||||
|
||||
// Mark this entry as running
|
||||
await db.collection( `${network}QueueOutfitGeneration` ).doc( rocketeerId ).set( { running: true, updated: Date.now() }, { merge: true } )
|
||||
|
||||
// Generate the new outfit
|
||||
await generateNewOutfitFromId( rocketeerId, network )
|
||||
|
||||
} catch( e ) {
|
||||
|
||||
// if this was just a "too recently" error, exit gracefully
|
||||
if( e.message.includes( 'You changed your outfit too recently' ) ) return
|
||||
|
||||
// Log error to console and store
|
||||
console.error( `handleQueuedRocketeerOutfit error: `, e )
|
||||
await db.collection( 'errors' ).add( {
|
||||
source: `handleQueuedRocketeerOutfit`,
|
||||
network,
|
||||
rocketeerId,
|
||||
updated: Date.now(),
|
||||
timestamp: new Date().toString(),
|
||||
error: e.message || e.toString()
|
||||
} )
|
||||
|
||||
} finally {
|
||||
|
||||
// Delete queue entry
|
||||
await db.collection( `${network}QueueOutfitGeneration` ).doc( rocketeerId ).delete( )
|
||||
|
||||
// Mark the outfits generating as decremented
|
||||
await db.collection( 'meta' ).doc( address ).set( {
|
||||
updated: Date.now(),
|
||||
outfits_in_queue: FieldValue.increment( -1 )
|
||||
}, { merge: true } )
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
// async function generateNewOutfitsByAddress( address, network='mainnet' ) {
|
||||
|
||||
|
||||
// try {
|
||||
// const ids = await getTokenIdsOfAddress( address, network )
|
||||
|
||||
// // Build outfit generation queue
|
||||
// const queue = ids.map( id => function() {
|
||||
|
||||
// // Generate new outfit and return it
|
||||
// // Since "no outfit available until X" is an error, we'll catch the errors and propagate them
|
||||
// return generateNewOutfitFromId( id, network ).then( outfit => ( { id: id, src: outfit } ) ).catch( e => {
|
||||
|
||||
// // Log out unexpected errors
|
||||
// if( !e.message.includes( 'You changed your outfit too recently' ) ) console.error( 'Unexpected error in generateNewOutfitFromId: ', e )
|
||||
|
||||
// return { id: id, error: e.message || e.toString() }
|
||||
|
||||
// } )
|
||||
|
||||
// } )
|
||||
|
||||
// const outfits = await Throttle.all( queue, {
|
||||
// maxInProgress: 10,
|
||||
// failFast: false,
|
||||
// progressCallback: ( { amountDone, rejectedIndexes } ) => {
|
||||
// process.env.NODE_ENV == 'development' ? console.log( `Completed ${amountDone}/${queue.length}, rejected: `, rejectedIndexes ) : false
|
||||
// }
|
||||
// } )
|
||||
|
||||
// return {
|
||||
// success: outfits.filter( ( { src } ) => src ),
|
||||
// error: outfits.filter( ( { error } ) => error ),
|
||||
// }
|
||||
|
||||
// } catch( e ) {
|
||||
// console.error( `Error in generateNewOutfitsByAddress: `, e )
|
||||
// throw e
|
||||
// }
|
||||
|
||||
|
||||
// }
|
||||
|
||||
module.exports = {
|
||||
generateNewOutfitFromId,
|
||||
generateNewOutfitsByAddress
|
||||
// generateNewOutfitsByAddress,
|
||||
queueRocketeersOfAddressForOutfitChange,
|
||||
handleQueuedRocketeerOutfit
|
||||
}
|
@ -136,19 +136,14 @@ export default function Verifier() {
|
||||
|
||||
setLoading( 'Generating new outfits, this can take a few minutes' )
|
||||
|
||||
const { error, success } = await callApi( `/rocketeers/${ address }`, {
|
||||
const { amountOfOutfits } = await callApi( `/rocketeers/${ address }`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify( signature )
|
||||
} )
|
||||
log( `Errored: `, error )
|
||||
log( `Succeeded: `, success )
|
||||
log( `Amount of outfits queued: `, amountOfOutfits )
|
||||
|
||||
if( typeof error == 'string' ) throw new Error( error )
|
||||
|
||||
|
||||
alert( `Success! ${ success.length } outfits generated, ${ error.length } failed.` )
|
||||
window?.location.reload()
|
||||
alert( `Success! Outfit generation started, check back in a few minutes to view your new outfits!` )
|
||||
|
||||
|
||||
} catch( e ) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user