Get meeting link with Google calendar api - google-api

I'm looking to use the Google calendar API for an application, but I'd like to get a Google meet link (for a G Suite account). As far as I can see that option is not possible, or am I wrong?
Thanks in advance!

As per https://cloud.google.com/blog/products/application-development/hangouts-meet-now-available-in-google
You need to search for element with entryPointType = "video" in ConferenceData.entryPoints[] and then you can use matchedObject.uri to get the Google meet link.
In Javascript / nodejs the solution will be something similar to:
var {google} = require('googleapis');
const eventId = "<yourEventId>";
const calendarId = "<yourCalendarId>";
const calendar = google.calendar({
version : "v3",
auth : auth
});
calendar.events.get({
calendarId,
eventId
}, ( err, res ) => {
if( err ) {
console.log( err );
} else {
const conferenceData = res.conferenceData;
if( conferenceData ) {
const entryPoints = conferenceData.entryPoints;
if( entryPoints ) {
const videoDetails = entryPoints.find( entryPoint => entryPoint.entryPointType == "video" );
if( videoDetails ) {
console.log( "Google Meet link", videoDetails.uri );
} else {
console.log( "No video details available" );
}
} else {
console.log( "No entry points available" );
}
} else {
console.log( "No conference data available" );
}
}
});

As of now, there's only hangoutLink in calendar API. If you'd like to know how to use it in Javascript, check this guide.

Related

ParseServer object not found

Parse.Query in cloud code cant find object
Running this code cloud function as a user who can reject or accept Invites. The code gets the invite (works) and checks for the given deckId, where the user should be added as a collaborator.
But for some reason i can't explain the Query for the Deck always returns:
https://pastebin.com/XjLjvzXZ
I have checked:
the docs for syntax -> should be fine
the Invite object exists and has a deckId
The Deck Objects exist and the deckId and Deck's objectId are matching
Parse.Cloud.define("inviteAction", async (request) =>
{
const user = request.user;
const inviteId = request.params.invite;
const action = request.params.actiom;
let inviteQuery = new Parse.Query("Invite");
const invite = await inviteQuery.get(inviteId, { useMasterKey: true } );
if (invite != null && invite != undefined)
{
const deckId = invite.get("deckId");
console.log(deckId);
if (invite.get("invited") === user.getUsername())
{
if (action === "accept")
{
let deckQuery = new Parse.Query("Deck");
await deckQuery.get(deckId, { useMasterKey: true } ).then((deck) =>
{
console.log(deck);
deck.addUnique("collaborators", user.getUsername());
deck.save();
}).catch((error) =>
{
console.log(error); // is always error
});
}
invite.destroy();
return true;
}
}
return false;
});
This gives me the same error:
let deckQuery = new Parse.Query("Deck");
await deckQuery.find({ useMasterKey: true } ).then((deck) =>
{
console.log(deck.length);
}).catch((error) =>
{
console.log(error);
});
OMG im so dumb,
apparently you get this error if you have a typo as well.
I just changed
const action = request.params.actiom;
to
const action = request.params.action;

Create An Event and push to MailChimp using MailChimp Net V3

Here's an (php) example of creating an Event and pushing it to MailChimp.
Anybody knows how this can be accomplished using C# and MailChimp Net V3?
PHP-example:
https://mailchimp.com/developer/marketing/guides/track-outside-activity-events/#create-an-event
I've found a way, though you'll need to create a new Logic class.
public class MemberEventResponse
{
}
public class MailChimpMemberEventLogic : BaseLogic
{
public MailChimpMemberEventLogic( MailChimpOptions options )
: base( options )
{
}
public async Task<MemberEventResponse> CreateMemberEventAsync( string listId, string email, string eventName )
{
var baseUrl = $"/lists/{listId}/members/{Hash(email)}/events";
using ( var client = CreateMailClient( baseUrl ) )
{
var response = await client.PostAsJsonAsync( "", new { name = eventName } ).ConfigureAwait( false );
await response.EnsureSuccessMailChimpAsync().ConfigureAwait( false );
var listActivityResponse = await response.Content.ReadAsAsync<MemberEventResponse>().ConfigureAwait( false );
return listActivityResponse;
}
}
}
Then you just need to instantiate it, and create the event.
var logic = new MailChimpMemberEventLogic( _mailChimpOptions );
await logic.CreateMemberEventAsync( _mailChimpListId, email, eventName );
NB, I haven't looked into the response contents...

Create custom Web Chat for Microsoft Bot Framework

Is there a guide on how to create my own Web Chat (I don't want to be using/customizing the one that is in react). I would like to make it from scratch using Blazor or Angular, but I haven't found a good guide on how to do that.
No guides that I am aware of.
It also depends on what you mean. If you are wanting to build the tool that would implement something like Web Chat, then I'd recommend installing different tools to use while referencing their respective SDKs.
BotFramework-WebChat is impressive but it is also quite sophisticated in its build and features.
Botkit's Web Client, while presently deprecated, would serve as a different perspective to tackling the same problem.
Botkit's botbuilder-adapter-web offers a different method by communicating via webhooks and websocket with a site.
Regardless, you will need to have a method for capturing the incoming messages as well as posting messages back. This means you will need to utilize the BotFramework-DirectLineJS SDK which is what BotFramework-WebChat relies on. You will then need to consider how you will want to handle the different activity types and messages that could be sent by the bot or user. For instance, how to handle:
Messages:
Simple text messages
Messages with attachments: cards, adaptive cards, images, audio/speech, etc.
Events
ConversationUpdates, including membersAdded
Traces
Typing
There are accessibility considerations, as well, for the hearing- and sight-impaired, etc., plus a host of other things to think of.
Here is a custom chat I created some time ago. It does the absolute basic and is not pretty. In it I call a local token server I run for getting a valid Directline token.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="favicon.ico" type="image/x-icon" />
<title>Custom Chat Using Direct Line</title>
<script crossorigin="anonymous" src="https://unpkg.com/#babel/standalone#7.8.7/babel.min.js"></script>
<script crossorigin="anonymous" integrity="sha384-7aeOL7r9BM1QyTIsoLYJYNsRLfVnQCtLmwgXlxteDNhJf0xUGj9PKP8F5w2Fx92/"
src="https://unpkg.com/botframework-directlinejs#0.11.6/dist/directline.js"></script>
</head>
<body>
<h2>Custom Chat Using Direct Line</h2>
<div class="input-container">
<input type="text" class="input-box" name="input-box" value="Hi">
<button type="button" class="input-button">Send</button>
</div>
<div class="response-container">
</div>
<script type="text/babel" data-presets="es2015,react,stage-3">
( async function () {
const { ConnectionStatus, DirectLine } = window.DirectLine;
const renderStatus = {
DISPLAY_ACTIVITIES: 'display',
HIDE_ACTIVITIES: 'hide',
RESET_VIEW: 'reset',
MAINTAIN_VIEW: 'maintain'
}
let previousWatermark = 0;
let currentWatermark = 0;
let displayStatus = renderStatus.DISPLAY_ACTIVITIES;
let viewStatus = renderStatus.MAINTAIN_VIEW;
let responseHistory;
// Custom 'token' server retrieves a Direct Line token
// Server stores the Direct Line secret exchanging it for a token when requested
const res = await fetch( 'http://localhost:3500/directline/conversations', { method: 'POST' } );
const { token } = await res.json();
var directLine = new DirectLine( {
token: token
} )
// Function posts activity to Direct Line, when called
const postActivity = ( dl, text ) => {
dl.postActivity(
{
from: { id: 'abc123', name: 'JohnDoe' }, // required (from.name is optional)
type: 'message',
text: `${ text }`
}
)
// As all message activities are logged below, there is no need to log the posted activity
.subscribe(
id => id,
error => console.log( "Error posting activity", error )
);
}
// Posts user message on button click
const inputButton = document.querySelector( '.input-button' );
const inputBox = document.querySelector( '.input-box' );
inputButton.addEventListener( 'click', ( e ) => {
e.preventDefault();
const text = inputBox.value;
postActivity( directLine, text );
} );
inputBox.onkeyup = ( e ) => {
const keyCode = e ? ( e.which ? e.which : e.keyCode ) : event.keyCode;
if ( keyCode === 13 ) {
const text = inputBox.value;
postActivity( directLine, text );
}
};
// Updates UI with all response activity
let responseContainer = document.querySelector( '.response-container' );
const subscribeToActivities = (dl) => {
dl.activity$
.filter( activity => {
return activity.type === 'message';
} )
.subscribe(
activity => {
const text = activity.text;
if (!activity.attachments) {
const id = activity.from.id;
currentWatermark = Number(dl.watermark);
if ( viewStatus === renderStatus.RESET_VIEW && currentWatermark <= previousWatermark && responseHistory.length > 0) {
displayStatus = renderStatus.HIDE_ACTIVITIES;
viewStatus = renderStatus.MAINTAIN_VIEW;
}
else if ( displayStatus === renderStatus.DISPLAY_ACTIVITIES && currentWatermark >= previousWatermark ) {
switch ( id ) {
case 'botberg':
responseContainer.innerHTML += `<ul class="chat-list"><li>From Bot: ${ text } </li></ul>`;
displayStatus = renderStatus.HIDE_ACTIVITIES;
viewStatus = renderStatus.MAINTAIN_VIEW;
break;
}
}
else if ( displayStatus === renderStatus.HIDE_ACTIVITIES && currentWatermark >= previousWatermark ) {
switch ( id ) {
case 'botberg':
break;
default:
responseContainer.innerHTML += `<ul class="chat-list"><li>From User: ${ text } </li></ul>`;
displayStatus = renderStatus.DISPLAY_ACTIVITIES;
viewStatus = renderStatus.MAINTAIN_VIEW;
}
}
}
else {
responseContainer.innerHTML += `<ul class="chat-list"><li>From Bot: Client received unsuppported attachment type </li></ul>`;
}
}
);
}
subscribeToActivities(directLine);
directLine.connectionStatus$
.subscribe( async connectionStatus => {
switch ( connectionStatus ) {
case ConnectionStatus.Uninitialized:
console.log( 'CONNECTION_STATUS => UNINITIALIZED ', directLine );
break;
case ConnectionStatus.Connecting:
console.log( 'CONNECTION_STATUS => CONNECTING ', directLine );
break;
case ConnectionStatus.Online:
console.log( 'CONNECTION_STATUS => ONLINE ', directLine );
break;
case ConnectionStatus.ExpiredToken:
console.log( 'CONNECTION_STATUS => EXPIRED TOKEN ', directLine );
break;
case ConnectionStatus.FailedToConnect:
console.log( 'CONNECTION_STATUS => FAILED TO CONNECT ', directLine );
break;
case ConnectionStatus.Ended:
console.log( 'CONNECTION_STATUS => ENDED ', directLine );
break;
}
} );
} )()
</script>
</body>
</html>

Shopify 2.0 Dawn - How to hide unavailable variants?

PROBLEM
Unavailable variant combinations are different from sold-out because customers don't understand it's the selects which make certain combinations 'not possible'.
Shopify's way of handling this is to display 'Unavailable' in the buy button. But customers think this means sold-out when in reality, they've chosen the wrong combination of variants...
The previous JS workarounds to remove unavailable or 'not possible' variants don't work in Shopify 2.0's new default/flagship theme, Dawn because the JS is different.
As far as I can tell, Dawn's variant JS was recently moved from /asstes/variants.js to line 497 in /assets/global.js.
SKILL
My CSS is decent but my JS is lame, I'm a designer sorry.
QUESTIONS
Based on user interaction with the first variant, how do you hide unavailable variants (not sold-out) in Shopify 2.0 Dawn?
How do you make one variant option set a checkbox instead of a radio button or radio?
What's the best way to add custom text as the first option in selects? e.g. 'Choose a size...' or 'Choose a color...' etc. Is it best to hard-code or use JS for this as well?
RESOURCES/EXAMPLES
Here's a pull request which grabs sold-out from the new Dawn JS but I don't understand how to adapt it for 'Unavailable' sorry (which is a different exception from sold-out): https://github.com/Shopify/dawn/pull/105
Here's an example of how to hide unavailable variants in the older Debut theme which doesn’t seem to work in the newer Dawn JS: https://www.youtube.com/watch?v=vspWDu_POYA
Here's a link to the JS gist referenced in that video: https://gist.github.com/jonathanmoore/c0e0e503aa732bf1c05b7a7be4230c61
And finally, here's the new code from Dawn at line 497 in /assets/global.js
class VariantSelects extends HTMLElement {
constructor() {
super();
this.addEventListener('change', this.onVariantChange);
}
onVariantChange() {
this.updateOptions();
this.updateMasterId();
this.toggleAddButton(true, '', false);
this.updatePickupAvailability();
if (!this.currentVariant) {
this.toggleAddButton(true, '', true);
this.setUnavailable();
} else {
this.updateMedia();
this.updateURL();
this.updateVariantInput();
this.renderProductInfo();
}
}
updateOptions() {
this.options = Array.from(this.querySelectorAll('select'), (select) => select.value);
}
updateMasterId() {
this.currentVariant = this.getVariantData().find((variant) => {
return !variant.options.map((option, index) => {
return this.options[index] === option;
}).includes(false);
});
}
updateMedia() {
if (!this.currentVariant || !this.currentVariant?.featured_media) return;
const newMedia = document.querySelector(
`[data-media-id="${this.dataset.section}-${this.currentVariant.featured_media.id}"]`
);
if (!newMedia) return;
const parent = newMedia.parentElement;
parent.prepend(newMedia);
window.setTimeout(() => { parent.scroll(0, 0) });
}
updateURL() {
if (!this.currentVariant) return;
window.history.replaceState({ }, '', `${this.dataset.url}?variant=${this.currentVariant.id}`);
}
updateVariantInput() {
const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment`);
productForms.forEach((productForm) => {
const input = productForm.querySelector('input[name="id"]');
input.value = this.currentVariant.id;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
}
updatePickupAvailability() {
const pickUpAvailability = document.querySelector('pickup-availability');
if (!pickUpAvailability) return;
if (this.currentVariant?.available) {
pickUpAvailability.fetchAvailability(this.currentVariant.id);
} else {
pickUpAvailability.removeAttribute('available');
pickUpAvailability.innerHTML = '';
}
}
renderProductInfo() {
fetch(`${this.dataset.url}?variant=${this.currentVariant.id}&section_id=${this.dataset.section}`)
.then((response) => response.text())
.then((responseText) => {
const id = `price-${this.dataset.section}`;
const html = new DOMParser().parseFromString(responseText, 'text/html')
const destination = document.getElementById(id);
const source = html.getElementById(id);
if (source && destination) destination.innerHTML = source.innerHTML;
document.getElementById(`price-${this.dataset.section}`)?.classList.remove('visibility-hidden');
this.toggleAddButton(!this.currentVariant.available, window.variantStrings.soldOut);
});
}
toggleAddButton(disable = true, text, modifyClass = true) {
const addButton = document.getElementById(`product-form-${this.dataset.section}`)?.querySelector('[name="add"]');
if (!addButton) return;
if (disable) {
addButton.setAttribute('disabled', true);
if (text) addButton.textContent = text;
} else {
addButton.removeAttribute('disabled');
addButton.textContent = window.variantStrings.addToCart;
}
if (!modifyClass) return;
}
setUnavailable() {
const addButton = document.getElementById(`product-form-${this.dataset.section}`)?.querySelector('[name="add"]');
if (!addButton) return;
addButton.textContent = window.variantStrings.unavailable;
document.getElementById(`price-${this.dataset.section}`)?.classList.add('visibility-hidden');
}
getVariantData() {
this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent);
return this.variantData;
}
}
customElements.define('variant-selects', VariantSelects);
class VariantRadios extends VariantSelects {
constructor() {
super();
}
updateOptions() {
const fieldsets = Array.from(this.querySelectorAll('fieldset'));
this.options = fieldsets.map((fieldset) => {
return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value;
});
}
}
customElements.define('variant-radios', VariantRadios);
Any help or pointers in the right direction would be much appreciated. Cheers

Disable user typing text box c#

Is it possible to disable the user typing input text area in the V4 bot framework in any channel ? I have this as part of customer requirement Can someone please help me
The box you refer to is called the send box. If you are using BotFramework-Web Chat, you can disable it by passing the value via styleOptions like so:
<script>
(async function () {
const styleOptions = {
hideSendBox = true
}
[...]
window.ReactDOM.render(
<ReactWebChat
directLine={directLine},
styleOptions={styleOptions}
/>,
document.getElementById( 'webchat' )
);
})
</script>
If you are using the iFrame embedded version of Web Chat, it is not configurable.
Hope of help!
Edit
If you are wanting the send box to be responsive according to the type of activity received from the bot, then you will want to use a combination of the activityMiddleware() function as well as an event emitter/listener. In the following example, I am hiding/showing the send box when suggestedActions is an activity property.
Please be aware that the data values should be "none" and "flex". In particular, the latter value when it is not suggestedActions in order to maintain the current code.
<script>
(async function () {
[...]
const activityMiddleware = () => next => card => {
const { activity: { suggestedActions } } = card;
const toggleSendBoxEvent = new Event('ToggleSendBoxEvent')
if (suggestedActions) {
toggleSendBoxEvent.data = "none";
window.dispatchEvent(toggleSendBoxEvent);
} else {
toggleSendBoxEvent.data = "flex";
window.dispatchEvent(toggleSendBoxEvent);
}
return next(card);
)
[...]
window.ReactDOM.render(
<ReactWebChat
directLine={ window.WebChat.createDirectLine({ token }) }
activityMiddleware={ activityMiddleware }
/>,
document.getElementById( 'webchat' )
);
window.addEventListener('ToggleSendBoxEvent', ( { data } ) => {
const sendBoxes = document.getElementsByClassName("main");
let send_Box;
for (let sendBox of sendBoxes) {
send_Box = sendBox;
}
send_Box.setAttribute('style', `display:${ data }`)
})
});
</script>
Hope of help!

Resources