Send multiple files to Slack via API - slack

According to Slack's documentation is only possible to send one file per time via API. The method is this: https://api.slack.com/methods/files.upload.
Using Slack's desktop and web applications we can send multiple files at once, which is useful because the files are grouped, helping in the visualization when we have more than one image with the same context. See the example below:
Do you guys know if it's possible, via API, to send multiple files at once or somehow achieve the same results as the image above?
Thanks in advance!

I've faced with the same problem. But I've tried to compose one message with several pdf files.
How I solved this task
Upload files without setting channel parameter(this prevents publishing) and collect permalinks from response. Please, check file object ref. https://api.slack.com/types/file. Via "files.upload" method you can upload only one file. So, you will need to invoke this method as many times as you have files to upload.
Compose message using Slack markdown <{permalink1_from_first_step}| ><{permalink2_from_first_step}| > - Slack parse links and automatically reformat message

Here is an implementation of the procedure recommended in the other answer in python
def postMessageWithFiles(message,fileList,channel):
import slack_sdk
SLACK_TOKEN = "slackTokenHere"
client = slack_sdk.WebClient(token=SLACK_TOKEN)
for file in fileList:
upload=client.files_upload(file=file,filename=file)
message=message+"<"+upload['file']['permalink']+"| >"
outP = client.chat_postMessage(
channel=channel,
text=message
)
postMessageWithFiles(
message="Here is my message",
fileList=["1.jpg", "1-Copy1.jpg"],
channel="myFavoriteChannel",
)

A Node.JS (es6) example using Slack's Bolt framework
import pkg from '#slack/bolt';
const { App } = pkg;
import axios from 'axios'
// In Bolt, you can get channel ID in the callback from the `body` argument
const channelID = 'C000000'
// Sample Data - URLs of images to post in a gallery view
const imageURLs = ['https://source.unsplash.com/random', 'https://source.unsplash.com/random']
const uploadFile = async (fileURL) {
const image = await axios.get(fileURL, { responseType: 'arraybuffer' });
return await app.client.files.upload({
file: image.data
// Do not use "channels" here for image gallery view to work
})
}
const permalinks = await Promise.all(imageURLs.map(async (imageURL) => {
return (await uploadImage(imageURL)).file.permalink
}))
const images = permalinks.map((permalink) => `<${permalink}| >`).join('')
const message = `Check out the images below: ${images}`
// Post message with images in a "gallery" view
// In Bolt, this is the same as doing `await say({`
// If you use say(, you don't need a channel param.
await app.client.chat.postMessage({
text: message,
channel: channelID,
// Do not use blocks here for image gallery view to work
})
The above example includes some added functionality - download images from a list of image URLs and then upload those images to Slack. Note that this is untested, I trimmed down the fully functioning code I'm using.
Slack's image.upload API docs will mention that the file format needs to be in multipart/form-data. Don't worry about that part, Bolt (via Slack's Node API), will handle that conversion automatically (and may not even work if you feed it FormData). It accepts file data as arraybuffer (used here), stream, and possibly other formats too.
If you're uploading local files, look at passing an fs readstream to the Slack upload function.

For Python you can use:
permalink_list = []
file_list=['file1.csv', 'file2.csv', 'file3.csv']
for file in file_list:
response = client.files_upload(file=file)
permalink = response['file']['permalink']
permalink_list.append(permalink)
text = ""
for permalink in permalink_list:
text_single_link = "<{}| >".format(permalink)
text = text + text_single_link
response = client.chat_postMessage(channel=channelid, text=text)
Here you can play around with the link logic - Slack Block Kit Builder

Python solution using new recommended client.files_upload_v2 (tested on slack-sdk-3.19.5 on 2022-12-21):
import slack_sdk
def slack_msg_with_files(message, file_uploads_data, channel):
client = slack_sdk.WebClient(token='your_slack_bot_token_here')
upload = client.files_upload_v2(
file_uploads=file_uploads_data,
channel=channel,
initial_comment=message,
)
print("Result of Slack send:\n%s" % upload)
file_uploads = [
{
"file": path_to_file1,
"title": "My File 1",
},
{
"file": path_to_file2,
"title": "My File 2",
},
]
slack_msg_with_files(
message='Text to add to a slack message along with the files',
file_uploads_data=file_uploads,
channel=SLACK_CHANNEL_ID # can be found in Channel settings in Slack. For some reason the channel names don't work with `files_upload_v2` on slack-sdk-3.19.5
)
(some additional error handling would not hurt)

Simply use Slack Blocks: Block Kit Builder. Great feature for the message customizations.

Related

Twilio sending multiple Images to WhatsApp

I am currently trying to send multiple pictures to WhatsApp via Twilio and got it working with one.
Already read the other Questions and this one might be an easy one.
How do I send multiple Images in one Message?
This is what I have currently and what I tried, but the second image is never sent:
exports.handler = function(context, event, callback) {
var client = context.getTwilioClient();
console.log("Sende Antwort")
client.messages.create({
to: event.From,
from: event.To,
body: "Sekunde, mache dir eben deinen Stoff klar."
}, function(err, res){
console.log("Sende Katzenbilder")
let twiml = new Twilio.twiml.MessagingResponse();
let message = twiml.message();
message.body("Hier ist dein wöchentlicher Cat-Content!")
message.media("https://images.unsplash.com/photo-1566927467984-6332be7377d0?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80");
message.media("https://images.unsplash.com/photo-1548247416-ec66f4900b2e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=663&q=80")
callback(null, twiml)
})
};
This is not possible via the underlying API
Sending Media Messages
https://developers.facebook.com/docs/whatsapp/api/messages/media
The above sample shows multiple different objects such as audio, document, image, sticker, and video for illustration purposes only. A valid request body contains only one of them.
Twilio developer evangelist here.
WhatsApp only supports sending one image at a time with a message.
In the Twilio API for WhatsApp documentation, this is pointed out (emphasis mine):
To send back a media in your WhatsApp reply, you need to include the media TwiML element with the URL to the media file. One media attachment is supported per message, with a size limit of 5MB.
You could try sending more than one message though, by using twiml.message more than once. Try:
exports.handler = function(context, event, callback) {
var client = context.getTwilioClient();
console.log("Sende Antwort")
client.messages.create({
to: event.From,
from: event.To,
body: "Sekunde, mache dir eben deinen Stoff klar."
}, function(err, res){
console.log("Sende Katzenbilder");
let twiml = new Twilio.twiml.MessagingResponse();
let message = twiml.message();
message.body("Hier ist dein wöchentlicher Cat-Content!");
message.media("https://images.unsplash.com/photo-1566927467984-6332be7377d0?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=1350&q=80");
let message2 = twiml.message();
message2.media("https://images.unsplash.com/photo-1548247416-ec66f4900b2e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=663&q=80");
callback(null, twiml)
})
};

how to use 'actions-on-google' libray in aws lambda

In actions-on-google , both the request and response object need to provide as input to this library. but in lambda function, only the request object exists.
So how can i override it ?
in aws lambda the format is
exports.handler = function (event, context, callback) { // event is the request object , the response is provided using the callback() functon
}
the actions-on-google object is created as :
const DialogflowApp = require('actions-on-google').DialogflowApp;
const app = new DialogflowApp({ request: request, response: response });
To get a Google Action to work on AWS Lambda, you need to do 2 things:
Code your app in a way that it's executable on Lambda
Create an API Gateway to your Lambda Function which you can then use for Dialogflow Fulfillment
I believe the first setp can't be done off-the-shelf with the Actions SDK. If you're using a framework like Jovo, you can create code that works for both Amazon Alexa and Google Assistant, and host it on AWS Lambda.
You can find a step by step tutorial about setting up a "Hello World" Google Action, host it on Lambda, and create an API Gateway here: https://www.jovo.tech/blog/google-action-tutorial-nodejs/
Disclaimer: I'm one of the founders of Jovo. Happy to answer any further questions.
This is only a half answer:
Ok, so I dont think I can tell you how to make the action on google sdk correct working on AWS Lambda.
Maybe its easy, I just dont know and need to read everything to know it.
My, "easy to go", but at the end you will maybe have more work solution, would be just interprete the request jsons by yourself and responde with a message as shown below
This here would be a extrem trivial javascript function to create a extrem trivial JSON response.
Parameters:
Message is the string you would like to add as answer.
Slots should be an array that can be used to bias the speech recognition.
(you can just give an empty array to this function if you dont want to bias the speech).
And State is any kind of serilizable javascript object this is for your self to maintain states or something else It will be transfered between all the intents.
This is an standard response on an speech request.
You can add other plattforms than speech for this, by adding different initial prompts please see the JSON tabs from the documentation:
https://developers.google.com/actions/assistant/responses#json
function answerWithMessage(message,slots,state){
let newmessage = message.toLowerCase();
let jsonResponse = {
conversationToken: JSON.stringify(state),
expectUserResponse: true,
expectedInputs: [
{
inputPrompt: {
initialPrompts: [
{
textToSpeech: newmessage
}
],
noInputPrompts: []
},
possibleIntents: [
{
intent: "actions.intent.TEXT"
}
],
speechBiasingHints: slots
}
]
};
return JSON.stringify(jsonResponse,null, 4);
}

Telegram Bot Random Images (How to send random images with Telegram-Bot)

const TeleBot = require('telebot');
const bot = new TeleBot({
token: 'i9NhrhCQGq7rxaA' // Telegram Bot API token.
});
bot.on(/^([Hh]ey|[Hh]oi|[Hh]a*i)$/, function (msg) {
return bot.sendMessage(msg.from.id, "Hello Commander");
});
var Historiepics = ['Schoolfotos/grr.jpg', 'Schoolfotos/boe.jpg',
'Schoolfotos/tobinsexy.jpg'];
console.log('Historiepics')
console.log(Math.floor(Math.random() * Historiepics.length));
var foto = Historiepics[(Math.floor(Math.random() * Historiepics.length))];
bot.on(/aap/, (msg) => {
return bot.sendPhoto(msg.from.id, foto);
});
bot.start();
The result I'm getting from this is just one picture everytime, but if I ask for another random picture it keeps showing me the same one without change.
I recently figured this out, so I'll drop an answer for anyone that runs into this issue.
The problem is with Telegram's cache. They cache images server side so that they don't have to do multiple requests to the same url. This protects them from potentially getting blacklisted for too many requests, and makes things snappier.
Unfortunately if you're using an API like The Cat API this means you will be sending the same image over and over again. The simplest solution is just to somehow make the link a little different every time. This is most easily accomplished by including the current epoch time as a part of the url.
For your example with javascript this can be accomplished with the following modifications
bot.on(/aap/, (msg) => {
let epoch = (new Date).getTime();
return bot.sendPhoto(msg.from.id, foto + "?time=" + epoch);
});
Or something similar. The main point is, as long as the URL is different you won't receive a cached result. The other option is to download the file and then send it locally. This is what Telebot does if you pass the serverDownload option into sendPhoto.

Detecting Gmail attachment downloads

Is there a way to detect if a particular file that is being downloaded is a Gmail attachment?
I am looking for a way to write a Greasemonkey script which would help me organize the downloads, based on their download sources, say Gmail email attachments would have a different behavior from other stuff.
So far, I've found out that attachments redirect to https://mail-attachment.googleusercontent.com/attachment/u/0/ , which I guess is not sufficient.
EDIT
Since an add-on would be more powerful than a userscript, I've decided to pursue the Add On idea. However, the problem of detection remains unsolved.
This is too complicated for just one question; it has at least these major parts:
Do you want to redirect downloads when the user clicks, or automatically download select files? Clarify the question.
Your GM script must identify the appropriate download links, and on which pages, and for which views? For gMail, this is not a trivial task, and the question needs to be clearer. It's worthy of a whole question just on this issue given the variety of views and AJAX involved.
Once identified, the script probably needs to intercept clicks on those links. (Depends on your goal (clarify!) and what the Firefox extension can do.)
Greasemonkey needs to interact with an extension that either intercepts the user-initiated download, or allows for an automatic download. I've detailed the auto-download approach, below.
Once your script has identified the appropriate file URLs and/or links (Open a new question for more help with that, and include pictures of the types of pages and links you want.), it can interface with a Firefox add-on, like the one below, to automatically save those files.
Automatically saving files from Greasemonkey with the help of an additional Add-on:
WARNING: The following is a working proof of concept for education only. It has no security features, and if you use it as-is, for actual surfing, some webpage or script writer or extension writer will use it to completely pwn your computer.
If you use the Add-on builder or SDK to install or "Test" the DANGER. DANGER. DANGER. File download utility,
Then you can use a Greasemonkey script, like this, to automatically save files:
// ==UserScript==
// #name _Call our File download add-on to trigger a file download.
// #include https://mail.google.com/mail/*
// #include https://stackoverflow.com/questions/14440362/*
// #require http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// #grant GM_addStyle
// ==/UserScript==
/*- The #grant directive is needed to work around a design change
introduced in GM 1.0. It restores the sandbox.
*/
var fileURL = "http://userscripts.org/scripts/source/29222.user.js";
var savePath = "D:\\temp\\";
var extensionLoaded = false;
window.addEventListener ("ImAlivefromExtension", function (zEvent) {
console.log ("The test extension appears to be loaded!", zEvent.detail);
extensionLoaded = true;
} );
window.addEventListener ("ReplyToDownloadRequest", function (zEvent) {
//var xxxx = JSON.parse (zEvent.detail);
console.log ("Extension replied: ", zEvent.detail);
} );
$("body").prepend ('<button id="gmFileDownloadBtn">Click to File download request.</button>');
$("#gmFileDownloadBtn").click ( function () {
if (extensionLoaded) {
detailVal = JSON.stringify (
{targFileURL: fileURL, targSavePath: savePath}
);
var zEvent = new CustomEvent (
"SuicidalDownloadRequestToAddOn",
{"detail": detailVal }
);
window.dispatchEvent (zEvent);
}
else {
alert ("The file download extension is not loaded!");
}
} );
You can test the script on this SO question page.
Note that any other extension, userscript, web page, or plugin can listen to or send spoof events, the only security, so far, is to limit which pages the extension runs on.
For reference, the extension source files are below. The rest is supplied by Firefox's Add-on SDK.
The content script:
var zEvent = new CustomEvent ("ImAlivefromExtension",
{"detail": "GM, DANGER, DANGER, DANGER, File download utility" }
);
window.dispatchEvent (zEvent)
window.addEventListener ("SuicidalDownloadRequestToAddOn", function (zEvent) {
console.log ("Extension received download request: ", zEvent.detail);
//-- Relay request to extension main.js
self.port.emit ("SuicidalDownloadRequestRelayed", zEvent.detail);
//-- Reply back to GM, or whoever is pretending to be GM.
var zEvent = new CustomEvent ("ReplyToDownloadRequest",
{"detail": "Your funeral!" }
);
window.dispatchEvent (zEvent)
} );
The background JS:
//--- For security, MAKE THESE AS RESTRICTIVE AS POSSIBLE!
const includePattern = [
'https://mail.google.com/mail/*',
'https://stackoverflow.com/questions/14440362/*'
];
let {Cc, Cu, Ci} = require ("chrome");
Cu.import ("resource://gre/modules/Services.jsm");
Cu.import ("resource://gre/modules/XPCOMUtils.jsm");
Cu.import ("resource://gre/modules/FileUtils.jsm");
let data = require ("sdk/self").data;
let pageMod = require ('sdk/page-mod');
let dlManageWindow = Cc['#mozilla.org/download-manager-ui;1'].getService (Ci.nsIDownloadManagerUI);
let fileURL = "";
let savePath = "";
let activeWindow = Services.wm.getMostRecentWindow ("navigator:browser");
let mod = pageMod.PageMod ( {
include: includePattern,
contentScriptWhen: 'end',
contentScriptFile: [ data.url ('ContentScript.js') ],
onAttach: function (worker) {
console.log ('DANGER download utility attached to: ' + worker.tab.url);
worker.port.on ('SuicidalDownloadRequestRelayed', function (message) {
var detailVal = JSON.parse (message);
fileURL = detailVal.targFileURL;
savePath = detailVal.targSavePath;
console.log ("Received request to \ndownload: ", fileURL, "\nto:", savePath);
downloadFile (fileURL, savePath);
} );
}
} );
function downloadFile (fileURL, savePath) {
dlManageWindow.show (activeWindow, 1);
try {
let newFile;
let fileURIToDownload = Services.io.newURI (fileURL, null, null);
let persistWin = Cc['#mozilla.org/embedding/browser/nsWebBrowserPersist;1']
.createInstance (Ci.nsIWebBrowserPersist);
let fileName = fileURIToDownload.path.slice (fileURIToDownload.path.lastIndexOf ('/') + 1);
let fileObj = new FileUtils.File (savePath);
fileObj.append (fileName);
if (fileObj.exists ()) {
console.error ('*** Error! File "' + fileName + '" already exists!');
}
else {
let newFile = Services.io.newFileURI (fileObj);
let newDownload = Services.downloads.addDownload (
0, fileURIToDownload, newFile, fileName, null, null, null, persistWin, false
);
persistWin.progressListener = newDownload;
persistWin.savePrivacyAwareURI (fileURIToDownload, null, null, null, "", newFile, false);
}
} catch (exception) {
console.error ("Error saving the file! ", exception);
dump (exception);
}
}
So far from what you are saying,the only thing you can do is making add-on(Firefox) and Extension(for chrome if you want).
If you have closer look at download of attachment,it happens when:
1) You click on icon of attachments
2) If you click download
For these two things you can find the click event of <a> tag containing download_url value.You can easily do that using js/jquery for creting extension.
So you can write the functionality when user tries to download attachment.
You could use Gmail contextual gadgets to modify the behavior on the Google side:
Gmail Contexual Gadgets
Contextual Gadgets don't have direct access to attachments but server side, you could use IMAP to access the attachment (based on the Gmail message ID identified by the gadget):
Gmail IMAP Extensions
Using gadgets and server-side IMAP has the advantage of being browser-agnostic.
It's not entirely clear what you want to do differently with the downloaded Gmail attachment as opposed to any given download (save it to a different location? Perform actions upon the attachment data?) But the contextual gadget and IMAP should give you some chance to modify the attachment data as needed before the browser download begins.

html5 multiple upload along with ajax

I am trying to use the multiple upload attribute of HTML5 to upload files.
I know it wouldn't work with IE and fall back to single file upload.
also I found some invalid html tag like min max allows opera to do the same.
I am trying to do the following:
The browse button be capable of selecting multiple files.
But the ajax should send files one by one.
My scenario is something like this:
the user selects 5 files and starts the upload . Now the ajax should firstfile send the first file, then second, and so on.
The server side script does something with the file and returns some data.
now as soon as one file upload is completed it must render that part of the result.
So as the user selects images and starts uploading the results come out as soon as each file is uploaded (and not after all the files are uploaded).
I tried something like this :
function handleFiles(files)
{ alert(files.length); //properly returns the number of files selected
for (var i = 0; i < files.length; i++) {
new FileUpload(files[i])
}
}
function FileUpload(file) {
var reader = new FileReader();
var xhr = new XMLHttpRequest();
this.xhr = xhr;
xhr.open("POST", "portfolio/add_media");
reader.onload = function(evt) {
xhr.sendAsBinary(evt.target.result);
};
reader.readAsBinaryString(file);
}
after reading the tutorial at mozilla but I end up with missing params.
. so can some one suggest me a clean solution to this
Some more details :
When I pass a single file ( with no multiple attribute ) my server recieves :
"image"=>[# < ActionDispatch::Http::UploadedFile:0x10d55be8
#tempfile=#< File:C:/Users/Gaurav/AppData/Local/Temp/RackMultipart20110701-1916-2ly4k2-0>,
#headers="Content-Disposition:
form-data; name=\"picture[image][]\";
filename=\"Desert.jpg\"\r\nContent-Type:
image/jpeg\r\n",
#content_type="image/jpeg",
#original_filename="Desert.jpg">]}}
But when I use multiple attribute and send using xhr I am able to get only one file param. How do I get the rest of the params ? esp the action dispatch thingy
You are simply sending the file data to the server, without encoding it in any way. For the server to know how to process it you need to encode your data properly (multipart/form-data encoding). Easiest way is using a FormData object: https://developer.mozilla.org/En/Using_XMLHttpRequest#Sending_files_using_a_FormData_object. Only that instead of data.append("CustomField", "This is some extra data") you would write data.append("file1", event.target.result).

Resources