Skip to content

Instantly share code, notes, and snippets.

@Max-Makhrov
Last active August 10, 2022 10:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Max-Makhrov/a8f763c6de9640c0e8dcc060dc59a818 to your computer and use it in GitHub Desktop.
Save Max-Makhrov/a8f763c6de9640c0e8dcc060dc59a818 to your computer and use it in GitHub Desktop.
Send a message to Telegram with Bot from WebApp. Google Apps Script
// 1️⃣ change settings
// Telegram token
var token = "826438459:AAH2hEf7YZq...";
// Spreadseet id for storing memory
// Sample:
// https://docs.google.com/spreadsheets/d/1pI12qRVgIY7i03tVdd9jNd2GwPDgOi1RLthdpVpdMD8
var memory_sets = {
id: '1pI12qRVgIY7i03tVdd9...',
memory_type: 'spreadsheet',
sheet_name: 'memory'
}
// test chat id
var test_chat_id = -123456789;
// 2️⃣ set Web App and save URL
function doPost(e) {
try {
return send2bot_(e);
} catch(err) {
return getXmlTable_([[err]]);
}
}
function doGet(e) {
return doPost(e);
}
function send2bot_(e) {
// check message exists
var msg = e.parameter.msg;
if (!msg || msg === '') {
return getXmlTable_([['🥴 no msg']]);
}
var chat_id = e.parameter.chat_id;
if (!chat_id || chat_id === '') {
return getXmlTable_([['🥴 no chat_id']]);
}
/** lock - use lock to prevent conflicting access to memory */
var lock = LockService.getScriptLock();
try {
lock.waitLock(30000); // wait 30 seconds for others' use of the code section and lock to stop and then proceed
} catch (e) {
Logger.log('Could not obtain lock after 30 seconds.');
return getXmlTable_([[new Date, ' Server Busy... please try after some time', chat_id]]);
}
// prevent same messages
Utilities.sleep(500 * Math.random() + 500); // to let the script run if lock does not work...
var timestamp = getTimeStamp_();
/** release lock */
lock.releaseLock();
// try get sent stamp from memory
memory_sets.value = timestamp;
memory_sets.key = '' +
'💬 ' + chat_id +
' 📢 ' + msg;
var savedstamp = inMemory_(memory_sets);
// send result as XML
var result = getXmlTable_([[savedstamp, msg, chat_id]]);
// use:
// =IMPORTXML("https://script.google.com/macros/s/.../exec?msg=beep&chat_id=-154555889","/table/row")
// exit function if message was sent
if (savedstamp !== timestamp) {
return result;
}
// send message to telegtam Bot
var telegtamUrl = "https://api.telegram.org/bot" + token;
sendMessage2Bot_(msg, chat_id, telegtamUrl);
return result;
}
// _______ _
// |__ __| | |
// | | ___| | ___ __ _ _ __ __ _ _ __ ___
// | |/ _ \ |/ _ \/ _` | '__/ _` | '_ ` _ \
// | | __/ | __/ (_| | | | (_| | | | | | |
// |_|\___|_|\___|\__, |_| \__,_|_| |_| |_|
// __/ |
// |___/
/**
* sendMessage2Bot_
* send message to Telegram
*
*
* Telegram Bot API
* supports only these tags:
* b, i, a, code, pre
*
* @param {string} msg
* @param {int} chat_id
* @param {string} baseurl - telegram url
*
*/
function test_sendMessage2Bot() {
var telegtamUrl = "https://api.telegram.org/bot" + token;
sendMessage2Bot_(
'Hello,\n' +
'<b>bold</b>\n' +
'<strong>strong💪🏼</strong>\n' +
'<a href = "https://twitter.com/max__makhrov">url</a>\n' +
'<i>italic</i>\n' +
'<em>emphasized</em>\n\n' +
'<code>code\nis multiline</code><br><br>' +
'<pre>pre\nis multiline</pre>',
test_chat_id, telegtamUrl);
}
function sendMessage2Bot_(msg, chat_id, baseurl) {
var text = tg_br2newline_(msg);
chat_id = chat_id || GROUP0_CHAT_ID;
var data = {
"text" : text,
"parse_mode": 'html'
};
var payload = JSON.stringify(data);
var options = {
"method" : "POST",
"contentType" : "application/json",
"payload" : payload,
"muteHttpExceptions": true
};
var url = baseurl + "/sendMessage?chat_id=" + chat_id;
var response = UrlFetchApp.fetch(url, options);
console.log(response.getContentText());
}
// function testtg_br2newline() {
// var text = 'hello<br/>there!';
// var res = tg_br2newline_(text);
// console.log(res);
// }
function tg_br2newline_(text) {
var regex = /<br\s*[\/]?>/gi;
return text.replace(regex, "\n");
}
// __ __
// | \/ |
// | \ / | ___ _ __ ___ ___ _ __ _ _
// | |\/| |/ _ \ '_ ` _ \ / _ \| '__| | | |
// | | | | __/ | | | | | (_) | | | |_| |
// |_| |_|\___|_| |_| |_|\___/|_| \__, |
// __/ |
// |___/
/**
* return saved value
* from memory
*
* saves new value if it was new
*
*
* @param {string} id of memory slot or Spreadhseet
* @param {string} key
* @param {string} value
* @param {string} memory_type:
* cache
* spreadsheet
*
* @param {string} sheet_name
* @param {string} key_tag (optional)
* @param {string} value_tag (optional)
*
*
*/
// function test_inmemory() {
// /** 1 */
// var options = {
// id: memory_id,
// memory_type: 'spreadsheet',
// sheet_name: 'memory',
// value: 'hi',
// key: 'test1'
// }
// var res = inMemory_(options);
// console.log(res); // hi
// options.value = 'hell';
// console.log(inMemory_(options)); // hi
// /** 2 */
// var options = {
// id: 'cache_test',
// memory_type: 'cache',
// value: 'hi',
// key: 'test1'
// }
// var res = inMemory_(options);
// console.log(res);
// }
function inMemory_(options) {
// work with memory
var getCacheMemory_ = function() {
var c = CacheService.getScriptCache();
var cashed = c.get(options.id);
if (cashed) {
return JSON.parse(cashed);
}
return {};
}
var getSheetMemory_ = function() {
var sets = {
file: options.id,
memory: {
sheet: options.sheet_name,
key: options.key_tag || 'key',
value: options.value_tag || 'value',
func: data2keyvalues_
}
};
var res = getSetsFromSheets_(sets);
return res.memory;
}
var saveCacheValue_ = function(key, value, memory) {
if (!(key in memory)) {
memory[key] = value;
}
// prolongate cache life
var c = CacheService.getScriptCache();
c.put(
options.id,
JSON.stringify(memory),
21600);
}
var saveSheetValue_ = function(key, value, memory) {
if (key in memory) {
// do not write to sheet twice
return;
}
var ss = SpreadsheetApp.openById(options.id);
var s = ss.getSheetByName(options.sheet_name);
var key_tag = options.key_tag || 'key';
var value_tag = options.value_tag || 'value';
var tags = s.getRange('1:1').getValues()[0];
var data = [];
for (var i = 0; i < tags.length; i++) {
if (tags[i] === key_tag) {
data.push(key);
} else if (tags[i] === value_tag) {
data.push(value);
} else {
data.push('');
}
}
s.appendRow(data);
}
// config/sets
var config = {
'cache':
{getMemory: getCacheMemory_,
saveValue: saveCacheValue_},
'spreadsheet':
{getMemory: getSheetMemory_,
saveValue: saveSheetValue_}
}
var sets = config[options.memory_type];
if (!sets) {
throw '🥴 memory_type = ' + options.memory_type +
'; allowed types = ' + Object.keys(config).join(', ');
}
// memory
var memory = sets.getMemory();
// console.log(memory);
// result
// default result is new value
// it remains if memory has no
// item of this key in memory
var res = options.value;
if (options.key in memory) {
res = memory[options.key];
}
// save result to memory
sets.saveValue(
options.key,
options.value,
memory
);
return res;
}
// ______
// | ____|
// | |__ _ _ _ __ ___ ___
// | __| | | | '_ \ / __/ __|
// | | | |_| | | | | (__\__ \
// |_| \__,_|_| |_|\___|___/
/**
* converts data to object with keys
*
* param {array} options.data
* param {array} options.header
* param {number} options.row_data_starts
*
*/
function data2jsonarray_(options) {
var tags = options.header;
// loop rows
var res = [], chunk = {}, num_vals, val;
var start = options.row_data_starts -1;
for (var i = start; i < options.data.length; i++) {
chunk = {};
num_vals = 0;
for (var ii = 0; ii < tags.length; ii++) {
if (tags[ii] !== '') {
val = options.data[i][ii];
chunk[tags[ii]] = val;
if (val !== '') { num_vals++; }
}
}
if (num_vals > 0) {
res.push(chunk);
}
}
return res;
}
/**
* convert data to arrays of json grouped by key
*
* param {array} options.data
* param {array} options.header
* param {string} options.key
* param {number} options.row_data_starts
*
*/
function data2jsongroups_(options) {
var tags = options.header;
var key = options.key;
var start = options.row_data_starts -1;
// loop rows
var res = {}, chunk = {}, num_vals, val, k;
for (var i = start; i < options.data.length; i++) {
chunk = {};
num_vals = 0;
for (var ii = 0; ii < tags.length; ii++) {
if (tags[ii] !== '' && tags[ii] !== key) {
val = options.data[i][ii];
chunk[tags[ii]] = val;
if (val !== '') { num_vals++; }
} else if (tags[ii] !== '' && tags[ii] === key) {
k = options.data[i][ii];
}
}
if (num_vals > 0) {
if (!(k in res)) {
res[k] = [chunk];
} else {
res[k].push(chunk);
}
}
}
return res;
}
/**
* converts data to key-value pairs
*
* param {array} options.data
* param {array} options.header
* param {string} options.key
* param {string} options.value
* param {number} options.row_data_starts
*
*/
function data2keyvalues_(options) {
var tags = options.header;
// loop rows
var start = options.row_data_starts -1;
var res = {};
var keyIndex = tags.indexOf(options.key);
var valueIndex = tags.indexOf(options.value);
var k, v;
for (var i = start; i < options.data.length; i++) {
k = options.data[i][keyIndex];
v = options.data[i][valueIndex];
if (k !== '') {
res[k] = v;
}
}
return res;
}
/**
* getSetsFromSheet_
*
* gets sets from sheet
* or from multiple sheets
*
* @param {object} options
*/
function getSetsFromSheets_(options) {
var ini_sets = options;
var file = SpreadsheetApp.openById(ini_sets.file);
var sheetname, sheet, range;
var sets_i, data, res = {};
for (var k in ini_sets) {
if (k !== 'file') {
// get sets
sets_i = ini_sets[k];
// get sheet + range
sheetname = sets_i.sheet || k;
sheet = file.getSheetByName(sheetname);
range = sheet.getDataRange();
// get data
data = range.getValues();
sets_i.data = data;
setSheetsDataEndpoints_(sets_i);
// add to the result
res[k] = sets_i.func(sets_i);
}
}
// console.log(JSON.stringify(res, null, 4));
return res;
}
/**
* sets data endpoints:
* {
* header: ['id', 'name', 'sum'],
* row_data_starts: 2
* }
*
* @param {array} options.data - sheet.getDataRange().getvalues()
* @param {int} options.row_tags
* @param {int} options.row_data_starts
* @param {int} options.row_marker
* @param {string} options.marker_data_starts
*/
function setSheetsDataEndpoints_(options) {
// #1. set header
// by default starts with row 1
var row_tags = options.row_tags || 1;
options.header = options.data[row_tags - 1];
// #2. set row_data_starts
// #2.1.
if (options.row_data_starts) {
// it was set by the script
return 0;
}
// #2.2. concept of self-descriptive tables
if (options.row_marker) {
var marker_data_starts = options.marker_data_starts || 'rowstart: ';
var markers = options.data[options.row_marker - 1];
for (var i = 0; i < markers.length; i++) {
// find text in a row where marker is ↓
if (markers[i].startsWith(marker_data_starts)) {
options.row_data_starts = parseInt(
markers[i].replace(
marker_data_starts, ''));
return 0;
}
}
}
// #2.3. defaults
options.row_data_starts = 2;
return 0;
}
// _______ _
// |__ __| | |
// | | _____ _| |_ ___
// | |/ _ \ \/ / __/ __|
// | | __/> <| |_\__ \
// |_|\___/_/\_\\__|___/
/**
* get textual timestamp
* from cuttent or from
* selected date
*
* @param {date} t
*
*/
// function test_getTimeStamp() {
// console.log(getTimeStamp_());
// }
function getTimeStamp_(t) {
var t = t || new Date();
var dd = Utilities.formatDate(
t,
Session.getTimeZone(),
"yyyy-MM-dd HH:mm:ss"
);
return dd;
}
/***
* get xml table
*
* @param {array} data - array of arrays
*
*/
// function test_getXmlTable() {
// var data = [
// [1, 2],
// ['ssss', 'ffff']
// ];
// console.log(getXmlTable_(data).getContent())
// }
function getXmlTable_(data) {
var output = '<table>';
for (var i = 0; i < data.length; i++) {
output += '<row>';
for (var ii = 0; ii < data[0].length; ii++) {
// <![CDATA[some stuff]]>
// https://stackoverflow.com/questions/2784183/what-does-cdata-in-xml-mean
output += '<col><![CDATA[' + data[i][ii] + ']]></col>';
}
output += '</row>';
}
output += '</table>';
return ContentService.createTextOutput(output).setMimeType(
ContentService.MimeType.XML
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment