I'm developing a bot for MS Teams and I'm looking to know the timezone of a user, to deliver messages at an appropriate time (e.g. not in the middle of the night).
I didn't found something appropriate in the bot framework REST API. Although messages we receive include a 'clientInfo.country' property, which is a start, but definitely not enough to time messages as we would like.
On every message to a user, there is an entities[] collection, one of which is details of the user's locale. For example (copied/pasted from here):
"entities": [
{
"locale": "en-US",
"country": "US",
"platform": "Windows",
"timezone": "America/Los_Angeles",
"type": "clientInfo"
}
],
And the answer is: there’s a localTimestamp property that can be used to get the time offset, which is good enough for what I need.
From #Savageman 's answer
And the answer is: there’s a localTimestamp property that can be used to get the time offset, which is good enough for what I need.
We can solve the issue "NOT Receive the timezone" by mapping the utcOffset of localTimestamp and country in entities to timezone.
I wrote a javascript code to get timezone such as "Asia/shanghai" by using the "localTimestamp": "2019-08-06T18:23:44.259+08:00" and "country": "CN" from Session in Teams message.
More details in my github readme.md.
let moment = require("moment-timezone");
let ct = require("countries-and-timezones");
let partOfSampleSession = {
"message": {
"entities": [
{
"country": "CN",
"locale": "zh-CN",
"platform": "Web",
"type": "clientInfo"
}
],
"localTimestamp": "2019-08-06T18:23:44.259+08:00"
}
}
function getTimezoneFromSession(session) {
// Get the name of country, such as "CN", "JP", "US"
let country = session.message.entities[0].country;
// Get the localTimestamp from message in session, such as "2019-08-06T18:23:44.259+08:00"
let localTimestamp = session.message.localTimestamp;
// Caculate the utfOffset of "localTimestamp", such as "480" by "2019-08-06T18:23:44.259+08:00"
let utcOffsetOfLocalTime = moment().utcOffset(localTimestamp).utcOffset();
// Mapping country to an object array which contains utcOffsets and it's corresponding timezones
// One element from mxTimezones is {"utcOffset": "480", "name": "Asia/Shanghai"}
let mxTimezones = ct.getTimezonesForCountry(country);
// get the same timezone as localtime utcOffset from timezones in a country
let timezone = "";
mxTimezones.forEach(mxTimezone => {
if (mxTimezone.utcOffset == utcOffsetOfLocalTime) {
timezone = mxTimezone.name;
}
});
return timezone;
}
let timezone = getTimezoneFromSession(partOfSampleSession);
// timezone = "Asia/Shanghai"
console.log(timezone);
// example of ct.getTimezonesForCountry("US")
// mxTimezones = [
// {
// "name": "America/New_York",
// "utcOffset": "-300",
// },
// {
// "name": "America/Los_Angeles",
// "utcOffset": "-480",
// }
// ...
// 27 elements
// ...
// ]
Related
I am building a script that pushes adaptive card messages to an MS Teams channel connector via a webhook requesting the approval of a supervisor.
I want to put two buttons on the bottom of the message, one to decline and another to approve the request. For this, I need to push a boolean value to an API endpoint. My initial idea was to create an API endpoint accepting a JSON body and looking into the JSON body for approval over a POST request but apparently, this is no longer supported in MS Teams and then I was thinking to use Action.OpenUrl to push this as a query parameter in the URL, but unfortunately this opens a new tab with the query parameter.
Is there any other way to achieve this? This is a sample JSON for the MS Teams webhook:
{
"type": "message",
"attachments": [
{
"contentType": "application/vnd.microsoft.card.adaptive",
"contentUrl": null,
"content": {
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
"type": "AdaptiveCard",
"version": "1.5",
"msTeams": {
"width": "full"
},
"actions": [
{
"type": "Action.OpenUrl",
"title": "Approve",
"url": "https://<api_endpoint_url>?approved=true"
},
{
"type": "Action.OpenUrl",
"title": "Decline",
"url": "https://<api_endpoint_url>?approved=false"
}
]
}
}
]
}
This sample shows a feature where user can send task request to his manager and manager can approve/reject the request in group chat.You can try like below code:-
case "approved":
string[] approvedCard = { ".", "Cards", "ApprovedCard.json" };
var approvedAttachment = GetResponseAttachment(approvedCard, data, out cardJson);
Activity approvedActivity = new Activity();
approvedActivity.Type = "message";
approvedActivity.Id = turnContext.Activity.ReplyToId;
approvedActivity.Attachments = new List<Attachment> { approvedAttachment };
await turnContext.UpdateActivityAsync(approvedActivity);
response = JObject.Parse(cardJson);
adaptiveCardResponse = new AdaptiveCardInvokeResponse()
{
StatusCode = 200,
Type = "application/vnd.microsoft.card.adaptive",
Value = response
};
return CreateInvokeResponse(adaptiveCardResponse);
Ref Sample-https://github.com/OfficeDev/Microsoft-Teams-Samples/tree/main/samples/bot-request-approval/csharp
I need to query the partial fields for courseWork.list and courseWork.get so I am passing this value in fields as described in the documentation. But courseWork(individualStudentsOptions) API call returns a error:
{
"error": {
"code": 400,
"message": "field_mask: Unknown field mask values: individual_students_options",
"status": "INVALID_ARGUMENT",
"details": [
{
"#type": "type.googleapis.com/google.rpc.BadRequest",
"fieldViolations": [
{
"field": "field_mask",
"description": "Unknown field mask values: individual_students_options"
}
]
}
]
}
}
In other experiments, for example courseWork(id) everything is fine and the API call returns this:
{
"courseWork": [
{
"id": "93359557635"
},
{
"id": "93359557700"
},
...
]
}
So please help me how to fill in the field of individualStudentsOptions ?
How exactly are you calling this api?
This is how we do it here:
String studentUser = "student#gmail.com";
IndividualStudentsOptions individualStudentsOptions = new IndividualStudentsOptions();
ArrayList<String> studentIdList = new ArrayList<>();
studentIdList.add(studentUser);
individualStudentsOptions.setStudentIds(studentIdList);
CourseWork courseWork = new CourseWork()
.setCourseId(course.getId())
.setTitle("My course work")
.setDescription("desc")
.setMaxPoints(100.0)
.setDueDate(date)
.setDueTime(timeOfDay)
.setAssigneeMode("INDIVIDUAL_STUDENTS")
.setIndividualStudentsOptions(individualStudentsOptions)
.setWorkType("ASSIGNMENT")
.setState("PUBLISHED");
courseWork = service.courses().courseWork().create(course.getId(), courseWork).execute();
I'm trying to decide upon the best format of response for my API. I need to return a reports response which provides information on the report itself and the fields contained on it. Fields can be of differing types, so there can be: SelectList; TextArea; Location etc..
They each use different properties, so "SelectList" might use "Value" to store its string value and "Location" might use "ChildItems" to hold "Longitude" "Latitude" etc.
Here's what I mean:
"ReportList": [
{
"Fields": [
{
"Id": {},
"Label": "",
"Value": "",
"FieldType": "",
"FieldBankFieldId": {},
"ChildItems": [
{
"Item": "",
"Value": ""
}
]
}
]
}
The problem with this is I'm expecting the users to know when a value is supposed to be null. So I'm expecting a person looking to extract the value from "Location" to extract it from "ChildItems" and not "Value". The benefit to this however, is it's much easier to query for things than the alternative which is the following:
"ReportList": [
{
"Fields": [
{
"SelectList": [
{
"Id": {},
"Label": "",
"Value": "",
}
]
"Location": [
{
"Id": {},
"Label": "",
"Latitude": "",
"Longitude": "",
"etc": "",
}
]
}
]
}
So this one is a reports list that contains a list of fields which on it contains a list of fieldtype for every fieldtype I have (15 or something like that). This is opposed to just having a list of reports which has a list of fields with a "fieldtype" enum which I think is fairly easy to manipulate.
So the Question: Which format is best for a response? Any alternatives and comments appreciated.
EDIT:
To query all fields by fieldtype in a report and get values with the first way it would go something like this:
foreach(field in fields)
{
switch(field.fieldType){
case FieldType.Location :
var locationValue = field.childitems;
break;
case FieldType.SelectList:
var valueselectlist = field.Value;
break;
}
The second one would be like:
foreach(field in fields)
{
foreach(location in field.Locations)
{
var latitude = location.Latitude;
}
foreach(selectList in field.SelectLists)
{
var value= selectList.Value;
}
}
I think the right answer is the first one. With the switch statement. It makes it easier to query on for things like: Get me the value of the field with the id of this guid. It just means putting it through a big switch statement.
I went with the first one because It's easier to query for the most common use case. I'll expect the client code to put it into their own schema if they want to change it.
Good morning to all and thank you for your help.
I'm working in a map page (map.html) create by leaflet library that take data from a external geojson file called water_well.js. This file, previously generated by overpass service is just a list of markers. every Marker have some proprerties. Follow an exemple:
"properties": {
"operator:type": "international",
"is_in:district": "west_mamprusi",
"is_in:region": "northern",
"source:date": "2012-02-11",
"source:ele": "gps",
"water_wells:source_type": "borehole"
},
The main page extract those data from the file before with this javascript:
var wwMarker = L.geoJson(water_well, {
pointToLayer : function (feature, latlng) {
lat = feature.geometry.coordinates[0];
lng = feature.geometry.coordinates[1];
//following code that make error
op_type = feature.properties.operator_type;
district = feature.properties.is_in:district;
region = feature.properties.is_in:region;
source_date = feature.properties.source:date;
source_ele = feature.properties.source:ele;
source_type = feature.properties.water_wells:source_type;
.....
I'm sure the problem is my Zero javascript knowledge, but I'm not a programmer and I do this map for my NGO engaged in water wells in Burkina Faso.
The script for extraction of the data don't work in this point:
op_type = feature.properties.operator:type;
The problem is ":" because is invalid character.
The second question is that not all markers in the first file called water_well.js have the same "properties" filled ad actually it is possible that someone have different group of "properties like those two:
{
"type": "Feature",
"id": "node/1606958159",
"properties": {
"#id": "node/1606958159",
"amenity": "drinking_water",
"man_made": "water_well",
"name": "puits 4"
},
"geometry": {
"type": "Point",
"coordinates": [
-3.6235696,
12.02171
]
}
},
{
"type": "Feature",
"id": "node/1913126817",
"properties": {
"#id": "node/1913126817",
"ele": "170.8000030517578",
"grid_proximity": "grid_further_500_m",
"is_in:district": "builsa",
"is_in:region": "upper_east",
"man_made": "water_well",
"operational_status": "open",
"operator:type": "individual",
"pipe_connection": "no",
"pump": "manual",
"seasonal": "another_pattern",
"source": "MVP,Columbia University",
"source:date": "2012-02-14",
"source:ele": "gps",
"water_wells:source_type": "unprotected_well"
},
"geometry": {
"type": "Point",
"coordinates": [
-1.2430456,
10.3233693
]
}
},
maybe it is possible to extract all properties of each item independently from which one is present or not..... This can be de better way to solve the problem but I've no idea how to do that.
This is what I do (ckick the water tap to see pop-up): www.h2openmap.org/map
This is almost what I would like to do (ckick the water tap to see pop-up): overpass-turbo.eu/s/7Ov
Thank you for spending your time reading my question.
Have a nice day everyone, Francesco
You can access those properties using the bracketnotation, instead of using:
district = feature.properties.is_in:district;
Use bracketnotation:
district = feature.properties['is_in:district'];
Reference on property-accessors: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_Accessors
Now if you want to do something based on if a property exists there is a hasOwnProperty method available on objects. Since feature.properties is an object you can use that in a condition:
if (features.properties.hasOwnProperty('is_in:district')) {
// Property exists, do stuff
}
// or
if (!features.properties.hasOwnProperty('is_in:district')) {
// Property does not exist, do stuff
}
If you want to do something base on wether multiple properties exist you can use the && (and) operator:
if (features.properties.hasOwnProperty('is_in:district') &&
features.properties.hasOwnProperty('source:data')) {
// Properties exist, do stuff
}
// Or
if (!features.properties.hasOwnProperty('is_in:district') &&
!features.properties.hasOwnProperty('source:data')) {
// Properties do not exist, do stuff
}
You could use the || (or) operator to see if at least one of the conditions matches:
if (features.properties.hasOwnProperty('is_in:district') ||
features.properties.hasOwnProperty('source:data')) {
// At least one of the properties exist, do stuff
}
// Or
if (!features.properties.hasOwnProperty('is_in:district') ||
!features.properties.hasOwnProperty('source:data')) {
// At least one of the properties does not exist, do stuff
}
Reference for this can be found here under "Logical operators": https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators
You can use something like to build (or don't build) the data object that you need for your popup. Hope that helps.
How do I check whether user's profile picture is default or uploaded in Google?
Note: When you get user details from APIs.
All of default profile pictures have the following URL:
https://lh3.googleusercontent.com/-XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg
You can just compare string equality of profile picture with the given one.
people.get includes a isDefault value in the image object. E.g. if you try it for +Google you will get;
"image": {
"url": "https://lh4.googleusercontent.com/-v0soe-ievYE/AAAAAAAAAAI/AAAAAAACyas/yR1_yhwBcBA/photo.jpg?sz=50",
"isDefault": false
}
people.get no longer has isDefault as a property.
https://developers.google.com/+/web/api/rest/latest/people#resource
Updating this answer for 2020: it's now possible to get the user's profile picture by sending a request to the people.get API with photos as the personFields to request.
You'll get back an array of images, and whenever default: true is present, it means it's a default (not user-set) image:
Example (if you're using this with OAuth):
GET https://people.googleapis.com/v1/people/me
Sample response (with profile picture)
{
"resourceName": "people/117055959617985617271",
"etag": "%EgQBAzcuGgQBAgUHIgxHamp6MG9wZ3hOcz0=",
"photos": [
{
"metadata": {
"primary": true,
"source": {
"type": "PROFILE",
"id": "117055959617985617271"
}
},
"url": "https://lh3.googleusercontent.com/a-/AOh14Gg_-udXd3O6K1URTBTEK2rLa2DOh6G2dgmOHcOBOtE=s100"
},
{
"metadata": {
"source": {
"type": "CONTACT",
"id": "2"
}
},
"url": "https://lh3.googleusercontent.com/a/default-user=s100",
"default": true
}
]
}
Sample response (default only)
{
"resourceName": "people/113009277022343767972",
"etag": "%EgQBAzcuGgQBAgUHIgxxdHVTY3IxZVJIUT0=",
"photos": [
{
"metadata": {
"primary": true,
"source": {
"type": "PROFILE",
"id": "113009277022343767972"
}
},
"url": "https://lh6.googleusercontent.com/-6NS3awoYRXw/AAAAAAAAAAI/AAAAAAAAAAA/AMZuucnTo-mElIpcAEazDV9DAs1VyYDEIw/s100/photo.jpg",
"default": true
}
]
}
in ruby with devise and omniauth-google-oauth2
in your devise.rb
config.omniauth :google_oauth2 (Other options....), skip_image_info: false
then in your user.rb / other place:
def self.parse_auth_image(auth)
if auth.provider == "google_oauth2"
return nil if auth.dig("extra", "raw_info", "image", "isDefault")
end
auth.info.image
end
The best way to do this in FULL DETAIL:
require 'open-uri'
Default image:
default = "https://lh3.googleusercontent.com/-
XdUIqdMkCWA/AAAAAAAAAAI/AAAAAAAAAAA/4252rscbv5M/photo.jpg"
Image to check:
image_to_check = "https://lh3.googleusercontent.com/-
uh4wK1JDtCI/AAAAAAAAAAI/AAAAAAAAAAA/huieBSbV4zg/s64-
c/100695019739939096169.jpg"
Comparison check
blob_for_default_image = open(default).read
blob_for_image_to_check = open(image_to_check).read
Then you compare the 2 image blobs:
blob_for_default_image == blob_for_image_to_check
If you are using PHP, It's fairly simple, just use this code
$profile_img_end = explode("/", $userData['picture']); // Exploding the URL and Dividing it into several terms
if (end($profile_img_end) === "photo.jpg") { // Now Simply Check if last part of array is equal to "photo.jpg" which is common in all default images
$profile_img = null; // If it's default then set it equal to null
} else {
$profile_img = $userData['picture']; // Else get the Link of the Image
}
Alternative in JavaScript
var url = ""; // Image URL
var img_split = url.split("/"); // Split it Again from / (forward slash)
if (img_split[img_split.length - 1] === 'photo.jpg') { // Check if last array is equal to photo.jpg (By Default Image)
var image = null;
} else {
var image = url;
}
HOPE IT HELPS!