import { WebAPICallResult, WebClient } from '@slack/client';
import Winston from '../logging/app.logger';
import { prettyJson } from '../logging/format';
import { Client } from '../borzoo/client';
import { getUserBySlackId } from '../server/authorization/authorization.service';
import Axios from 'axios';
import { User } from '../data/schema/user';
import { privateDecrypt } from 'crypto';
/**
* __Provides a Login button to the user.__
* @param {WebClient} web The instance of the Slack WebClient.
* @param {string} userId The Slack ID or Slack username of whom to send the message to.
* @param {string} url The URL that you wish to redirect a user to after logging in.
* @returns {Promise<WebAPICallResult>}
*/
export async function loginPrompt(web: WebClient, userId: string, channelId: string, url: string): Promise<WebAPICallResult> {
try {
let webAppUrl = process.env.ZEVERE_WEB_APP_URL + '/login';
let redirectUrl = `${url}?id=${userId}`;
webAppUrl = `${webAppUrl}?redirect_uri=${encodeURIComponent(redirectUrl)}`;
return await web.chat.postEphemeral({
attachments: [
{
actions: [
{
text: 'Login',
type: 'button',
url: webAppUrl,
}
],
fallback: `Click here to login: ${webAppUrl}`,
text: 'Click here to login:',
thumb_url: 'https://avatars3.githubusercontent.com/u/33139090?s=200&v=4',
}
],
channel: channelId,
user: userId,
text: 'Please login to Zevere.',
});
} catch (exception) {
Winston.error('Exception caught in messaging#loginPrompt.');
throw exception;
}
}
//#region Tasks
/**
* Displays a message of a User's task, as well as the functionality to delete it.
*
* @export
* @param {WebClient} web The instance of the Slack WebClient.
* @param {string} userId The Slack ID or Slack username of whom to send the message to.
* @param {string} channelId The ID of the channel you wish to send the message to.
* @param {string} listId The ID of the List that the task belongs to.
* @param {string} taskId The ID of the task that you wish to display.
* @returns {Promise}
*/
export async function showTask(web: WebClient, user: User, channelId: string, listId: string, taskId: string) {
try {
const bz = new Client();
const task = await bz.getTask(user.zevereId, listId, taskId);
const taskUrl = `${process.env.ZEVERE_WEB_APP_URL}/users/${encodeURIComponent(user.zevereId)}/lists/${encodeURIComponent(listId)}/tasks/${encodeURIComponent(taskId)}`;
const fields = [];
if (task.due)
fields.push({ title: 'Due on:', value: task.due, short: false });
if (task.tags && task.tags.length > 0)
fields.push({ title: 'Tags', value: task.tags.reduce((p, c) => `${p} ${c}`, ''), short: true });
if (task.createdAt)
fields.push({ title: 'Created On', value: task.createdAt, short: true });
const attachments = [
{
fallback: taskUrl,
pretext: 'Here is your task!',
color: 'good',
title: task.title,
title_link: taskUrl,
text: task.description,
fields,
callback_id: 'deletetask',
actions: [
{
type: 'button',
text: 'Hide',
name: 'taskAction',
value: 'hide'
},
{
type: 'button',
text: 'Delete',
name: 'taskAction',
value: JSON.stringify({ user, listId, taskId }),
confirm: {
dismiss_text: 'Cancel',
ok_text: 'Delete',
title: 'Delete Task',
text: 'Once it is deleted, it is gone forever!'
}
}
]
}
];
return await web.chat.postEphemeral({
channel: channelId,
user: user.slackId,
attachments
});
} catch (err) {
Winston.info(err);
throw err;
}
}
//#endregion Tasks
//#region Lists
/**
* Shows the list details to a user.
*
* @export
* @param {WebClient} web The instance of the Slack WebClient.
* @param {string} userId The Slack ID or Slack username of whom to send the message to.
* @param {string} channelId The ID of the channel you wish to send the message to.
* @param {string} listId The ID of the List that the task belongs to.
* @returns {Promise} Awaitable.
*/
export async function showList(web: WebClient, userId: string, channelId: string, listId: string): Promise {
const bz = new Client();
const user = await getUserBySlackId(userId);
const list = await bz.getList(user.zevereId, listId);
const listUrl = `${process.env.ZEVERE_WEB_APP_URL}/users/${encodeURIComponent(user.zevereId)}/lists/${encodeURIComponent(list.id)}`;
try {
const fields = [];
if (list.updatedAt)
fields.push({ title: 'Last Updated', value: list.updatedAt, short: true });
if (list.tags && list.tags.length > 0)
fields.push({ title: 'Tags', value: list.tags.reduce((p, c) => `${p} ${c}`, ''), short: true });
if (list.createdAt)
fields.push({ title: 'Created On', value: list.createdAt, short: true });
const attachments = [
{
fallback: listUrl,
pretext: 'Here is your task list!',
color: 'good',
title: list.title,
title_link: listUrl,
text: list.description,
fields,
callback_id: 'viewtask',
actions: list.tasks && list.tasks.length > 0 ?
list.tasks.map(t => {
return {
type: 'button',
name: 'task',
text: t.title,
value: JSON.stringify({ listId: listId, taskId: t.id })
};
}) : [],
}
];
return await web.chat.postEphemeral({
as_user: false,
channel: channelId,
user: userId,
attachments
});
} catch (err) {
Winston.info(err);
throw err;
}
}
/**
* Displays a select menu of the user's lists so they may view one.
*
* @export
* @param {WebClient} web The instance of the Slack WebClient.
* @param {string} userId The Slack ID or Slack username of whom to send the message to.
* @param {string} channelId The ID of the channel you wish to send the message to.
* @returns {Promise<WebAPICallResult>}
*/
export async function showListsForSelection(web: WebClient, userId: string, channelId: string): Promise<WebAPICallResult> {
return await showListsWith({
text: 'Select a list to view:',
color: 'good',
callbackId: 'viewlist'
})(web, userId, channelId);
}
/**
* Displays a select menu of the user's lists so that the user may
* choose to delete one.
* @export
* @param {WebClient} web The instance of the Slack WebClient.
* @param {string} userId The Slack ID or Slack username of whom to send the message to.
* @param {string} channelId The ID of the channel you wish to send the message to.
* @returns {Promise<WebAPICallResult>}
*/
export async function showListsForDeletion(web: WebClient, userId: string, channelId: string): Promise<WebAPICallResult> {
return await showListsWith({
text: 'Select a list to delete:',
color: 'danger',
callbackId: 'deletelist',
footer: 'Note: Once it is deleted, it is gone forever!',
confirm: {
text: 'Are you sure you want to delete the list?',
title: 'Confirm Deletion',
dismiss_text: 'Cancel',
ok_text: 'Delete'
}
})(web, userId, channelId);
}
/**
* Sends a message to confirm that a list has been deleted.
*
* @export
* @param {WebClient} web The instance of the Slack WebClient.
* @param {string} responseUrl A response URL given from Slack which you may use to reply back to within
* 5 to 30 minutes.
* @see https://api.slack.com/interactive-messages
* @returns {Promise<any>}
*/
export async function confirmListDeletion(web: WebClient, responseUrl: string): Promise<any> {
return await Axios.post(responseUrl, {
response_type: 'ephemeral',
replace_original: true,
text: 'Deleted successfully.'
});
}
//#endregion Lists
//#region General
/**
* Deletes a message using the response url.
*
* @export
* @param {WebClient} web The instance of the Slack WebClient.
* @param {string} responseUrl A response URL given from Slack which you may use to reply back to within
* 5 to 30 minutes.
* @returns {Promise<any>}
*/
export async function deleteMessage(web: WebClient, responseUrl: string): Promise<any> {
return await Axios.post(responseUrl, {
response_type: 'ephemeral',
text: '',
replace_original: true,
delete_original: true,
});
}
/**
* __Sends the supplied message to the #General channel.__
* @param {WebClient} web The instance of the Slack Web Client.
* @param {string} message The message you wish to send to #General.
* @returns {Promise<WebAPICallResult>}
*/
export async function messageGeneralChat(web: WebClient, message: string): Promise<WebAPICallResult> {
try {
const channelsResponse = await web.channels.list();
Winston.debug('Channels:');
channelsResponse.channels |> prettyJson |> Winston.debug;
const general = channelsResponse.channels.find(c => c.name === 'general');
Winston.debug(`General chat found? ${general ? 'yes' : 'no'}`);
return await web.chat.postMessage({
channel: general.id,
text: message || 'Hello World!"'
});
}
catch (exception) {
Winston.error('Exception caught in messaging#messageGeneralChat.');
throw exception;
}
}
/**
* __Sends a direct message to a user.__
* @param {WebClient} web The instance of the Slack Web Client.
* @param {string} userId The Slack ID or Slack username of whom the message should be sent to.
* @param {string} message The message you wish to send to the aforementioned user.
* @returns {Promise<WebAPICallResult>}
*/
export async function messageUser(web: WebClient, userId: string, message: string): Promise<WebAPICallResult> {
try {
return await web.chat.postMessage({
channel: userId,
text: message
});
}
catch (exception) {
Winston.error('Exception caught in messaging#messageUser.');
throw exception;
}
}
/**
*
* __Sends an ephemeral message to the user.__
* @export
* @param {WebClient} web The instance of the Slack Web Client.
* @param {string} userId The Slack ID or Slack username of whom the message should be sent to.
* @param {string} channelId The ID of the channel you wish to send the message to.
* @param {string} message The message you wish to send to the aforementioned user.
* @returns {Promise<WebAPICallResult>}
*/
export async function messageUserEphemeral(web: WebClient, userId: string, channelId: string, message: string): Promise<WebAPICallResult> {
try {
return await web.chat.postEphemeral({
channel: channelId,
user: userId,
text: message,
});
}
catch (exception) {
Winston.error('Exception caught in messaging#messageUserEphemeral.');
throw exception;
}
}
//#endregion General
//#region Private
type ListOptions = {
callbackId: string,
text: string,
color?: string,
footer?: string,
confirm?: {
text: string,
title: string,
dismiss_text: string,
ok_text: string
}
}
function showListsWith(opts: ListOptions) {
return async function (web: WebClient, userId: string, channelId: string): Promise<WebAPICallResult> {
const bz = new Client();
const user = await getUserBySlackId(userId);
const lists = await bz.getLists(user.zevereId);
if (lists.length < 1) {
return await web.chat.postEphemeral({
channel: channelId,
as_user: false,
user: userId,
text: 'You currently do not have any lists to choose from.'
});
}
const listOptions = lists.map(list => { return { text: list.title, value: list.id }; });
return await web.chat.postEphemeral({
channel: channelId,
as_user: false,
user: userId,
attachments: [
{
text: opts.text,
color: opts.color || '#777777',
callback_id: opts.callbackId,
actions: [
{
id: 'lists',
type: 'select',
data_source: 'static',
options: listOptions,
name: 'list',
confirm: opts.confirm || undefined
},
],
footer: opts.footer || undefined
}
]
});
}
}
//#endregion Private