I have a simple Discord Bot setup, that automatically sends embeds with a button into newly created Tickets (first part) and then picks up the button press and sends a conformation (second part), which is also supposed to then update the button to be both deactivated and have a different label.
How would I go about this? All other questions/guides had the embed sent using a prior interaction and therefore could use its attributes.
client.on('messageCreate', (message) => { //Sends the Embed with Button upon Tank Ticket Creation
if (message.author.id === '149565760950239232' && message.channel.name.includes('ticket')) {
const tanksEmbed = new EmbedBuilder() //The Embed sent on Ticket Creation
.setColor(0xb054c8)
.setTitle('First steps to get your tank souls:')
.setDescription('Press the button below to submit your order.')
.setFooter({ text: 'Contact Aldoraz#0001 for issues and questions' })
const tanksButton = new ActionRowBuilder() //The button on the Embed
.addComponents(
new ButtonBuilder()
.setCustomId('tanks_button')
.setLabel('Submit Tank Order')
.setStyle(ButtonStyle.Success)
.setEmoji('🪙')
.setDisabled(false),
);
message.channel.send({
embeds: [tanksEmbed],
components: [tanksButton]
});
}
});
client.on('interactionCreate', (interaction) => { // Sends Conformation on Button press and Updates Button
if (!interaction.isButton() && !interaction.customId === 'tanks_button') return;
console.log('Button pressed!');
let channel = client.channels.cache.find(channel => channel.id === interaction.channelId);
interaction.reply('Button was pressed!')
const tanksButtonClicked = new ActionRowBuilder() //The updated button on the Embed
.addComponents(
new ButtonBuilder()
.setCustomId('tanks_button_pressed')
.setLabel('Order Submitted!')
.setStyle(ButtonStyle.Success)
.setEmoji('🪙')
.setDisabled(true),
);
interaction.message.edit({components: tanksButtonClicked})
});
You don't need to use client.on("interactionCreate"), that's for slash commands.
What you need to do in order to collect the button is create a collector on the message, and updating the button once it's pressed.
You can also create the collector on the channel, but doing it on the message is preferred.
Add async to your client event:
client.on('messageCreate', async (message) => {})
Store your message in a variable:
const MSG = await message.channel.send({embeds: [tanksEmbed], components: [tanksButton]});
Create your filter and collector
const filter = i => !i.user.bot;
const collector = MSG.createMessageComponentCollector({ filter, time: 20000 });
Then, if the button is pressed, send the form and modify the button.
collector.on(`collect`, async i => {
// here we check if someone else used the button
if(i.member.id != message.author.id) {
return i.reply({ content: `${i.member} This button is not for you`, ephemeral: true})
}
if(i.customId === 'tanks_button') {
// Your code that does its thing.
tanksButton.components[0].setLabel('Your new Label').setDisabled(true);
MSG.edit({embeds: [tankEmbed], components: [tanksButton]});
}
})
Related
I am using SolidJS and building a SPA (no server rendering). For authentication, I use the #aws-amplify/core and #aws-amplify/auth packages. At the application root I call the Hub.listen function:
Hub.listen('auth', ({ payload }) => console.log(payload));
In the SignUp component I call Auth.federatedSignIn:
const SignUp = () => {
return (
<button onClick={() => {
Auth.federatedSignIn({ provider: CognitoHostedUIIdentityProvider.Google });
}}>
Sign up
</button>
);
}
I have configured the Amplify as such:
Amplify.configure({
Auth: {
region: import.meta.env.VITE_AWS_REGION,
userPoolId: import.meta.env.VITE_AWS_POOL_ID,
userPoolWebClientId: import.meta.env.VITE_AWS_POOL_CLIENT_ID,
oauth: {
domain: import.meta.env.VITE_AUTH_URL,
responseType: 'code',
redirectSignIn: location.origin + '/account/external',
redirectSignOut: location.origin + '/my',
},
},
});
When I click on the button I am redirected to the import.meta.env.VITE_AUTH_URL (simply outside of my app), choose an account, and then return back to the /account/external page. At that time I expect a consoled payload object in Web tools, but there is nothing. I get it when I call Auth.signOut(), so I assume that I configured Amplify correctly and Hub is subscribed to the auth channel.
My thoughts were that Hub cannot catch any events because after returning the application basically renders again in a new context and Hub simply isn't able to catch anything (events aren't sent from AWS?). I tried to declare the urlOpener function under the oauth property in the config and Google's sign page opened in a new tab, but even then I couldn't get any events in the preserved old page (from which I called Auth.federatedSignIn).
Questions:
How should I organize the code to get the signIn and signUp events?
Can I pass some data into the Auth.federatedSignIn to get it back in the Hub.listen, so I will be able to join the CognitoUser with the data that existed at the time of starting Sign in/Sign up (I want to add a new login type to existed user)?
Here is an example regarding the first question. Just check that your listener is set before you call the Auth.federatedSignIn() method.
export default class SignInService {
constructor(private landingFacade: LandingFacade) {
this.setupAuthListeners(); // Should be called at the top level.
}
private setupAuthListeners() {
Hub.listen('auth', ({ payload: { event, data } }) => {
switch (event) {
case 'signIn':
this.landingFacade.signInSuccess();
break;
case 'signIn_failure':
console.log('Sign in failure', data);
break;
case 'configured':
console.log('the Auth module is configured', data);
}
});
}
public async signIn(): Promise<void> {
await Auth.federatedSignIn();
}
}
For the second one: I'll use a local state and set/query the object you need.
I have an app with different 'procedures' (think posts or pages), which one can like. Currently the process works: Tap like => run method "likeProcedure" => run dispatch action "likeProcedure" => update UI. It usually happens almost immediately, but sometimes there's a lag that gives this a "non-native" feel. Is there some sort of way that I could return feedback immediately, while stile holding single origin of truth on the firebase database?
Thank you!
Page Code:
<v-icon
v-if="!userProfile.likedProcedures || !userProfile.likedProcedures[procedure.id]"
color="grey lighten-1"
#click="likeProcedure({ id: procedure.id })"
>
mdi-star-outline
</v-icon>
and
computed: {
...mapState(["userProfile"]),
procedures() {
return this.$store.getters.getFilteredProcedures();
},
},
Vuex code:
async likeProcedure({ dispatch }, postId) {
const userId = fb.auth.currentUser.uid;
// update user object
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: true,
});
dispatch("fetchUserProfile", { uid: userId });
},
Side note: I'm trying to remove the dispatch("fetchUserProfile") command, but this doesn't work, because then I'm calling dispatch without using it. And I cannot remove dispatch because then the object calling it is empty. And I cannot remove the object, because then the argument ('postId') isn't working. So if anyone knows how to deal with that, that would be extremely helpful.
Thank you :)
So this is the best solution I've come up yet. It kind of destroys the idea of a single source of truth, but at least it provides an immediate UI update:
async likeProcedure({ dispatch, state }, postId) {
console.log("likeProcedure");
const userId = fb.auth.currentUser.uid;
// line below provides immediate update to state and hence to the UI
state.userProfile.likedProcedures[postId.id] = true;
// line below updates Firebase database
await fb.usersCollection.doc(userId).update({
[`likedProcedures.${postId.id}`]: state.userProfile.likedProcedures[
postId.id
],
});
// line below then fetches the updated profile from Firebase and updates
// the profile in state. Kind of useless, but ensures that client and
// Firebase are in-sync
dispatch("fetchUserProfile", { uid: userId });
},
async fetchUserProfile({ commit }, user) {
// fetch user profile
const userProfile = await fb.usersCollection.doc(user.uid).get();
// set user profile in state
commit("setUserProfile", userProfile.data());
// change route to dashboard
if (router.currentRoute.path === "/login") {
router.push("/");
}
},
I was trying to restrict the user from navigating away from the current page by using CanDeactivate (if form is dirty and not saved). By the time we click on any link, Router_Navigation event is getting called and it is updating the router state in store and if I cancel the page navigation on modal pop up (from can deactivate), Router_Cancel event is being called, but the current router state is not getting updated (it’s still pointing to other page).
I saw this in ngrx documentation:
ROUTER_CANCEL and ROUTER_ERROR contain the store state before the
navigation. Use the previous state to restore the consistency of the
store.
Can someone please help me on how to get previous state from Router_cancel Action.
Thanks
I solved this by creating an applicationRouter state to maintain current and previous routes, whenever ngrx router dispatches a ROUTER_NAVIGATION Event i am listening to it and updating my applicationRouterState.
At each point applicationRouter will have only two router events (current and previous state).
and whenever Router_Cancel is triggered i am toggling the previous router and current router state.
PFB, the soln:
#Effect()
navigationListener$ = this.actions$.ofType('ROUTER_NAVIGATION')
.pipe(
switchMap((routerNavigationAction: RouterNavigationAction<RouterDefinition>) => {
return of(routerNavigationAction).pipe(
withLatestFrom(this._routerNavigationData$),
map(([action, routerNavData]: [RouterNavigationAction<RouterDefinition>, RouterState]) => {
// TODO: Move this logic to Reducer
if (!(routerNavData.currentRouter && routerNavData.currentRouter.url
&& routerNavData.previousRouter && routerNavData.previousRouter.url)) {
routerNavData.previousRouter = routerNavData.currentRouter = action.payload.routerState;
} else {
routerNavData.previousRouter = routerNavData.currentRouter;
routerNavData.currentRouter = action.payload.routerState;
}
return new fromActions.MaintainPrevCurrRouterStateAction(routerNavData);
})
);
})
);
And this is my state object:
export interface RouterDefinition {
url: string;
queryParams: Params;
params: Params;
segments: string[];
}
export interface RouterState {
currentRouter: RouterDefinition;
previousRouter: RouterDefinition;
}
I use a ngrx/effect to store the latest two ROUTER_NAVIGATION actions, and re-dispatch the previous one when I get a ROUTER_CANCEL or ROUTER_ERROR so that the router state is completely restored.
#Injectable()
export class RouterEffects {
private previousRouterNavigationAction: RouterNavigationAction<
RouterStateUrl
>;
private currentRouterNavigationAction: RouterNavigationAction<
RouterStateUrl
>;
#Effect({ dispatch: false })
save$: Observable<Action> = this.actions$.pipe(
ofType(ROUTER_NAVIGATION),
switchMap((action: RouterNavigationAction<RouterStateUrl>) => {
this.previousRouterNavigationAction = this.currentRouterNavigationAction;
this.currentRouterNavigationAction = { ...action };
return Observable.empty();
})
);
#Effect()
load$: Observable<Action> = this.actions$.pipe(
ofType(ROUTER_CANCEL, ROUTER_ERROR),
switchMap(action => Observable.of(this.previousRouterNavigationAction))
);
constructor(private actions$: Actions) {}
}
I'm writing a very simple app using redux-cycle, with 3 elements:
A text field to input url
A "connect" button to connect to a websocket end point
An output panel to show incoming messages
I have written a very simple websocket driver based on this answer https://stackoverflow.com/a/42926532/842860
The wire up code is like this:
const cycleMiddleware = createCycleMiddleware();
const {makeActionDriver} = cycleMiddleware;
function main(sources) {
return {
ACTION: sources.WEBSOCKET // The websocket driver emits commands which are consumed by reducers
}
}
const store = createStore(
reducer, // Update redux state
applyMiddleware(cycleMiddleware)
);
run(main, {
WEBSOCKET: WSDriver("ws://localhost:3000/something"), // The initial connection string
ACTION: makeActionDriver()
});
The question is how to make the websocket reconnect to another end point when the connect button is clicked? Do I need to modify my websocket driver to capture the event and reconnect, or does cyclejs provide a way to dynamically update the source/sink?
I think the appropriate method would be to, indeed, update the WSDriver to take a sink$ as input. This sink$ could contain the url you want to connect to.
Your driver would look like this
function WSDriver(endpoint$) {
var activeConnection;
return endpoint$
.map(endpointUrl => xs.create({
start: listener => {
activeConnection = new WebSocket('ws://localhost:4000');
/* no change here */
},
stop: () => {
activeConnection.close();
}
}))
.flatten() // it's a stream of stream, so we need to flatten it
}
On the app side you would need to change this
run(main, {
WEBSOCKET: WSDriver(), // < no initial connection
ACTION: makeActionDriver()
});
And in your main
function main(sources) {
return {
ACTION: sources.WEBSOCKET,
WEBSOCKET: newConnection$.startWith("ws://localhost:3000/something")
}
}
Given that the newConnection$ comes from the click events on the button.
Hope it helps
I am trying to communicate data received by the service worker back to webpage.
On the webpage 'navigator.serviceWorker.controller' is null. The sevice worker has self.client as empty.
Any samples or directions will help
What you can do is get a list of window clients which will return a list of the tabs for your origin and then post a message to each window client. (This code would be in the setBackgroundMessageHandler() ):
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true
})
.then((windowClients) => {
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
windowClient.postMessage(data);
}
})
.then(() => {
return registration.showNotification('my notification title');
});
return promiseChain;
Then to receive the message in the page, add a listener like so:
navigator.serviceWorker.addEventListener('message', function(event) {
console.log('Received a message from service worker: ', event.data);
});