Using a bot to open embed hyperlinks sent in a discord channel by another bot - json

Situation: 3rd Party Discord Bot sends masked URL in case of certain events into a private discord channel as an embedded message, instead of clicking on them manually the goal is to have another bot opening those hyperlinks automatically.
Current Status: With a lot of research (also on stack overflow) I managed to get to the following state that will open hyperlinks that are sent as normal text in the respective discord channel or that are included in the description of an embedded message (Kudos to Zach C & Daemon Beast):
client.on("message", message => {
if (message.channel.id == config.channelIds) {
//first part analyses normal messages
if (message.content.includes("https")) {
var link = message.content.split("https")[1]
console.log(link)
var linktest = `https${link}`
console.log(`opening ${linktest}`)
open(linktest)
}
//second part analyses embeded messsages
else if (message.embeds) {
message.embeds.forEach(embed => {
if (embed.description.includes("https")){
var link = embed.description.split("https")[1];
link = link.replace(")", "");
console.log(link);
var linktest = `https${link}`;
console.log(`opening ${linktest}`);
open(linktest);
}
});
}
}
})
Testing: Testing was done using another Bot sending embedded hyperlinks. When they were embedded in the Body/Description the hyperlinks are being opened just fine.
//Testing Bot:
{"content": null,
"embeds": [
{
"title": "Test Title",
"description": "Test Description",
"color": 2108322,
"fields": [
{
"name": "Test Name",
"value": "Test Value\n[Click here to test](https://google.com)"
}]}]}
Problem: In this particular use case hyperlinks are not included in the body/description but rather in the field value which currently not being recognized by the bot and thus not opened.
I already went tough a couple of hours of research & trial/error but was not able to change the code in a way that it would work.
I have tried to use "some" functionality
if (embed.fields.some(f => f.value.includes("https")))
and "includes"
if(message.content.toLowerCase().includes("https"))
But while with the some functionality I was able to make some progress by getting a return value "true" I struggle in adjusting the "var link =" in a way to then get to a proper link.
I have used the replace function to remove the closing bracket ) from the hyperlink.
I feel like I have reached 95% and there is only a small adjustment necessary that the code actually targets the right fields in the embedded message.
Your support is very much appreciated, thank you in advance!

For the sake of completion I would like to share the found solution, there would be better ones with loops but this one worked for me as the link is always at the same place in the embed:
else if (message.embeds) {
message.embeds.forEach(embed => {
console.log(message.embeds[0].fields[8].value);
if (embed.fields[6].value.includes("https")){
var link = embed.fields[6].value.split("https")[1];
link = link.replace(")", "");
console.log(link);
var linktest = `https${link}`;
console.log(`opening ${linktest}`);
open(linktest);

Related

Update Google Calendar UI after changing visability setting via Workspace Add-On

I have a very basic Google Workspace Add-on that uses the CalendarApp class to toggle the visabilty of a calendar’s events when a button is pressed, using the setSelected() method
The visabilty toggling works, but the change in only reflected in the UI when the page is refreshed. Toggling the checkbox manually in the UI reflects the change immediately without needing to refresh the page.
Is there a method to replicate this immediate update behaviour via my Workspace Add-On?
A mwe is below.
function onDefaultHomePageOpen() {
// create button
var action = CardService.newAction().setFunctionName('toggleCalVis')
var button = CardService.newTextButton()
.setText("TOGGLE CAL VIS")
.setOnClickAction(action)
.setTextButtonStyle(CardService.TextButtonStyle.FILLED)
var buttonSet = CardService.newButtonSet().addButton(button)
// create CardSection
var section = CardService.newCardSection()
.addWidget(buttonSet)
// create card
var card = CardService.newCardBuilder().addSection(section)
// call CardBuilder.call() and return card
return card.build()
}
function toggleCalVis() {
// fetch calendar with UI name "foo"
var calendarName = "foo"
var calendarsByName = CalendarApp.getCalendarsByName(calendarName)
var namedCalendar = calendarsByName[0]
// Toggle calendar visabilty in the UI
if (namedCalendar.isSelected()) {
namedCalendar.setSelected(false)
}
else {
namedCalendar.setSelected(true)
}
}
In short: Create a chrome extension
(2021-sep-2)Reason: The setSelected() method changes ONLY the data on server. To apply the effect of it, you need to refresh the page. But Google Workspace Extension "for security reason" does not allow GAS to do that. However in an Chrome Extension you can unselect the checkbox of visibility by plain JS. (the class name of the left list is encoded but stable for me.) I have some code for Chrome Extension to select the nodes although I didn't worked it out(see last part).
(2021-jul-25)Worse case: Default calendars won't be selected by getAllCalendars(). I just tried the same thing as you mentioned, and the outcome is worse. I wanted to hide all calendars, and I am still pretty sure the code is correct, since I can see the calendar names in the console.
const allCals = CalendarApp.getAllCalendars()
allCals.forEach(cal => {console.log(`unselected ${cal.setSelected(false).getName()}`)})
Yet, the principle calendar, reminder calendar, and task calendar are not in the console.
And google apps script dev should ask themselves: WHY DO PEOPLE USE Calendar.setSelected()? We don't want to hide the calendar on the next run.
In the official document, none of these two behaviour is mentioned.
TL;DR part (My reason for not using GAS)
GAS(google-apps-script) has less functionality. For what I see, google is trying to build their own eco-system, but everything achievable in GAS is also available via javascript. I can even use typescript and do whatever I want by creating an extension.
GAS is NOT easy to learn. The learning was also painful, I spent 4 hours to build the first sample card, and I can interact correctly with the opened event after 9 hours. The documentation is far from finished.
GAS is poorly supported. The native web-based code editor (https://script.google.com/) is not build for coding real apps, it loses the version control freedom in new interface. And does not support cross-file search. Instead of import, codes run from top to bottom in the list, which you need to find that by yourself. (pass along no extension, no prettier, I can tolerate these)
In comparison with other online JS code editors, like codepen / code sandbox / etcetera it does so less function. Moreover, VSCode also has a online version now(github codespaces).
I hope my 13 hours in GAS are not totally wasted. As least whoever read this can just avoid suffering the same painful test.
Here's the code(typescript) for disable all the checks in Chrome.
TRACKER_CAL_ID_ENCODED is the calendar ID of which I don't want to uncheck. Since it is not the major part of this question, it is not very carefully commented.
(line update: 2022-jan-31) Aware that the mutationsList.length >= 3 is not accurate, I cannot see how mutationsList.length works.
Extension:
getSelectCalendarNode()
.then(unSelectCalendars)
function getSelectCalendarNode() {
return new Promise((resolve) => {
document.onreadystatechange = function () {
if (document.readyState == "complete") {
const leftSidebarNode = document.querySelector(
"div.QQYuzf[jsname=QA0Szd]"
)!;
new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.target) {
let _selectCalendarNode = document.querySelector("#dws12b.R16x0");
// customized calendars will start loading on 3th+ step, hence 3, but when will they stop loading? I didn't work this out
if (mutationsList.length >= 3) {
// The current best workaround I saw is setTimeout after loading event... There's no event of loading complete.
setTimeout(() => {
observer.disconnect();
resolve(_selectCalendarNode);
}, 1000);
}
}
}
}).observe(leftSidebarNode, { childList: true, subtree: true });
}
};
});
}
function unSelectCalendars(selectCalendarNode: unknown) {
const selcar = selectCalendarNode as HTMLDivElement;
const calwrappers = selcar.firstChild!.childNodes; // .XXcuqd
for (const calrow of calwrappers) {
const calLabel = calrow.firstChild!.firstChild as HTMLLabelElement;
const calSelectWrap = calLabel.firstChild!;
const calSelcted =
(calSelectWrap.firstChild!.firstChild! as HTMLDivElement).getAttribute(
"aria-checked"
) == "true"
? true
: false;
// const calNameSpan = calSelectWrap.nextSibling!
// .firstChild! as HTMLSpanElement;
// const calName = calNameSpan.innerText;
const encodedCalID = calLabel.getAttribute("data-id")!; // const decodedCalID = atob(encodedCalID);
if ((encodedCalID === TRACKER_CAL_ID_ENCODED) !== calSelcted) {
//XOR
calLabel.click();
}
}
console.log(selectCalendarNode);
return;
}
There is no way to make a webpage refresh with Google Apps Script
Possible workarounds:
From the sidebar, provide users a link that redirects them to the Calendar UI webpage (thus a new, refreshed version of it will be opened)
Install a Goole Chrome extension that refreshes the tab in specified intervals

How can I relaunch/update/refresh the card again using Apps Script

I'm having a nightmare doing a lot of scenarios using Apps Script, but nothing works! I have a function that makes a GET request returns an array of cards. Now, sometimes I need this card refreshes again to fetch the new content.
function listTemplatesCards(){
var getAllTemplates = getTemplates();
var allTemplates = getAllTemplates.templates;
var theUserSlug = getAllTemplates.user_slug;
var templateCards = [];
//There are templates
if(allTemplates.length > 0){
allTemplates.forEach(function(template){
templateCards.push(templateCard(template, theUserSlug).build());
});
return templateCards;
}
}
This function is called on onTriggerFunction. Now, if I moved to another card and I wanted to back again to the root but in clean and clear way, I use this but it doesn't work:
//Move the user to the root card again
var refreshNav = CardService.newNavigation().popToRoot();
return CardService.newActionResponseBuilder().setStateChanged(true).setNavigation(refreshNav).build();
Simply, what I want is once the user clicks on Refresh button, the card refreshes/updates itself to make the call again and get the new data.
The only way I've found to do this is to always use a single card for the root. In the main function (named in the appscript.json onTriggerFunction), return only a single card, not an array of cards. You can then use popToRoot().updateCard(...) and it works.
I struggled with this for over a day, improving on Glen Little's answer so that its a bit more clear.
I have my root card to be refreshed defined in a funciton called: onHomepage.
I update the appscript.json manifest to set the homepageTrigger and onTriggerFunction to return the function that builds my root card.
"gmail": {
"homepageTrigger": {
"enabled": true,
"runFunction":"onHomepage"
},
"contextualTriggers":[
{
"unconditional":{},
"onTriggerFunction": "onHomepage"
}
]
}
Then it is as simple as building a gotoRoot nav button function that will always refresh the root page.
function gotoRootCard() {
var nav = CardService.newNavigation()
.popToRoot()
.updateCard(onHomepage());
return CardService.newActionResponseBuilder()
.setNavigation(nav)
.build();
}
As far as gmail addons are considered, cards are not refreshed but updated with new cards. And it is pretty simple.
//lets assume you need a form to be updated
function updateProfile() {
//ajax calls
//...
//recreate the card again.
var card = CardService.newCardBuilder();
//fill it with widgets
//....
//replace the current outdated card with the newly created card.
return CardService.newNavigation().updateCard(card.build());
}
A bad hack that works for my Gmail add-on:
return CardService.newActionResponseBuilder()
.setStateChanged(true) // this doesn't seem to do much. Wish it would reload the add-on
.setNotification(CardService.newNotification()
.setText('Created calendar event')
)
// HACK! Open a URL which closes itself in order to activate the RELOAD_ADD_ON side-effect
.setOpenLink(CardService.newOpenLink()
.setUrl("https://some_site.com/close_yoself.html")
.setOnClose(CardService.OnClose.RELOAD_ADD_ON))
.build();
The contents of close_yoself.html is just:
<!DOCTYPE html>
<html><body onload="self.close()"></body></html>
So, it looks like Google has considered and solved this issue for an ActionResponse which uses OpenLink, but not for one using Navigation or Notification. The hack above is definitely not great as it briefly opens and closes a browser window, but at least it refreshes the add-on without the user having to do so manually.

chrome selection text to keep formatting

I have a context menu, so when you selection some text from a page they can send to my extension. I am using
var child1 = chrome.contextMenus.create(
{"title": "Send To Box" , contexts:["selection"], "parentId": id, "id":"box", "contexts":[context], "onclick": sendToMyBox});
And in my sendToMyBox
function sendToMyBox(info, tab)
{
if (info.menuItemId == "box")
{
mainData = info.selectionText;
}
}
So the issue is selectionText is missing all the formatting. What ever selected its coming as a single line text, is there anyway I can get the current format from the selected. Basically I want to keep all the new lines tabs, etc...
Thanks
I think maybe you can get the html element first (you can achieve that by register a mouse event, then get event.target), then use
element.innerHTML
to get the rich text.

Push JSON File to Firebase

I've got a large JSON file that I'd like to push to my Firebase. It's currently in a specific format that I'd like to slightly change when pushed.
My current JSON file looks a bit like this:
"item": [
{
"title": "Hernia Repair",
"dc:creator": "realph",
"content:encoded": "A hernia occurs when an internal part of the body pushes through a weakness in the muscle or surrounding tissue wall. Your muscles are usually strong and tight enough to keep your intestines and organs in place, but a hernia can develop if there are any weak spots.",
},
...
]
While my Firebase items look like this:
"services" : {
"-JfTLQsxlZr6W2JWwMMd" : {
"description" : "Hernia repair refers to a surgical operation for the correction of a hernia (a bulging of internal organs or tissues through the wall that contains itself.",
"title" : "Hernia Repair",
},
...
}
I'm trying to push each one of these items to the services object that's already set up in my Firebase. But I'd like to push each item to a new unique id (i.e. -JfTLQsxlZr6W2JWwMMd), just like it is in my Firebase object (above). I also want push the title to title and push content:encoded to description.
Is this even possible? Doing this would potentially save me a lot of time going forward.
Any help from someone that is familiar with this sort of thing would be appreciated. Thanks in advance!
Update
This is what I was thinking to do, but I don't believe it's wired up correctly. I'm getting back a unique key with the console.log, but no item is being added to the services object:
$scope.convertItems = function() {
for(var i=0; i < $scope.items.length; i++) {
var newService = {
title: title,
description: 'content:encoded'
};
}
var promise = ServiceService.add(newService);
promise.then(function(data) {
console.log(data.name());
});
};

Tweet clickable links on website?

Right, based on this question Tweet clickable links with twitteroauth?,
How do I actually parse the entire string and replace the t.co link portion with a
portion?
For example i tweet this -
Hey check out this link www.google.com
and in my website currently it shows
Hey check out this link http://t.co/generatedlink
So how do i parse it and make it into this
Hey check out this link http://t.co/generatedlink
which would display like this in my website:
Hey check out this link http://t.co/generatedlink
How am I able to detect that a certain portion of the tweet text has a link inside it? Or am I going about this wrong?
You need to understand Twitter Entities.
When you request the tweet, make sure you use include_entities=true
For example:
https://api.twitter.com/1/statuses/show.json?id=220197158798897155&include_entities=true
In the response, you will see an element called "entities" inside that, you will see "urls".
That will contain all the URLs and their position (indices) within the tweet.
"text": "Twitter for Mac is now easier and faster, and you can open multiple windows at once http://t.co/0JG5Mcq",
"entities": {
"urls": [
{
"url": "http://t.co/0JG5Mcq",
"display_url": "blog.twitter.com/2011/05/twitte…",
"expanded_url": "http://blog.twitter.com/2011/05/twitter-for-mac-update.html",
"indices": [
84,
103
]
}
],
}
function urlify(text) {
var urlRegex = /(https?:\/\/[^\s]+)/g;
return text.replace(urlRegex, function(url) {
return '' + url + '';
})}
var text = "Hey check out this link http://t.co/generatedlink";
var a = urlify(text);
alert(a);
The above solution is perfect for your needs. I have a Fiddle link too Check it out http://jsfiddle.net/UhzCx/
I got this answer here Detect URLs in text with JavaScript
If you can use javascript, here is an example http://jsfiddle.net/3VF96/13/
Function
function generateURL(text){
var str=text;
var n=str.indexOf("http");
var strv=str.substring(0,n);
var link=str.substring(n,str.length);
strv=strv+" <a href='"+link+"'>"+link+"</a>";
$("#Link").append(strv);
}
​