mirror of
https://github.com/stjornleysi/telegram_glpi.git
synced 2025-11-29 00:23:11 +00:00
Telegram-glpi 3.0
This commit is contained in:
37
README.md
37
README.md
@@ -1,21 +1,19 @@
|
||||
1. Клонировать репозиторий
|
||||
2. Получить токен телеграм бота с помощью botFather
|
||||
3. Создать телеграм-канал, включить в его настройках темы
|
||||
4. Добавить бота в администраторы канала и разрешить ему управление темами, остальным юзерам запретить
|
||||
5. В файле data/conf.json заменить параметры:
|
||||
|
||||
- telegramBotToken: токен телеграм бота
|
||||
- supportChatId: id телеграм канала
|
||||
- CompanyName: название компании (для приветствия)
|
||||
- glpiConfig:
|
||||
- apiurl: "http://[имя домена]/apirest.php"
|
||||
- app_token: это токен приложения, настраивается в админке
|
||||
- user_token: это "app-token" в настройках юзера
|
||||
- user_id: id юзера, через которого будет авторизироваться бот (видно в адресной строке)
|
||||
2. В файле data/conf.json заменить параметры:
|
||||
|
||||
6. Проверить в telegram_support.service путь к исполняемому файлу и добавить его в папку /etc/systemd/system/ (для debian)
|
||||
7. Для работы бота должен быть установлен node.js (все остальные зависимости находятся в папке node_modules)
|
||||
8. Обновить демоны командой:
|
||||
telegramBotToken: токен телеграм бота
|
||||
supportChatId: id чата для техподдержки
|
||||
CompanyName: название компании (для приветствия)
|
||||
glpiConfig:
|
||||
apiurl: "http://[имя домена]/apirest.php"
|
||||
app_token: это токен приложения, настраивается в админке
|
||||
user_token: это "app-token" в настройках юзера
|
||||
user_id: id юзера, через которого будет авторизироваться бот (видно в адресной строке)
|
||||
|
||||
3. Проверить в telegram_support.service путь к исполняемому файлу и добавить его в папку /etc/systemd/system/ (для debian)
|
||||
4. Для работы бота должен быть установлен node.js (все остальные зависимости находятся в папке node_modules)
|
||||
5. Обновить демоны командой:
|
||||
|
||||
> systemctl daemon-reload
|
||||
|
||||
@@ -26,10 +24,17 @@
|
||||
|
||||
**Создание образа и запуск его в docker-контейнере**
|
||||
|
||||
1. Выполнить первые 5 пунктов из инструкции выше
|
||||
1. Выполнить первые 2 пункта из инструкции выше
|
||||
2. Перейти в папку telegram-support-bot
|
||||
3. Запустить контейнер командой:
|
||||
|
||||
> docker compose up -d --build
|
||||
|
||||
|
||||
**Изменение пользовательских групп**
|
||||
|
||||
1. Чтобы добавить свои группы пользователей (это нужно для переназначения заявок в системе glpi прямо из телеграма), нужно ввести команду /configurationUserGroups в главной ветке канала для техподдержки и следовать инструкции в ответном сообщении
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,11 +1,27 @@
|
||||
{
|
||||
"telegramBotToken": "",
|
||||
"supportChatId": "",
|
||||
"TesttelegramBotToken": "",
|
||||
"TestsupportChatId": "",
|
||||
"CompanyName": "",
|
||||
"glpiConfig": {
|
||||
"apiurl": "",
|
||||
"app_token": "",
|
||||
"user_token": "",
|
||||
"user_id": ""
|
||||
"user_id": 5
|
||||
},
|
||||
"userGroups": {
|
||||
"Почта": [
|
||||
"115",
|
||||
"125"
|
||||
],
|
||||
"Хостинг": [
|
||||
"94",
|
||||
"169"
|
||||
],
|
||||
"Телефония": [
|
||||
"52",
|
||||
"67"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ticket": 0,
|
||||
"comment": 0,
|
||||
"history": {
|
||||
"data": {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
|
||||
}
|
||||
@@ -11,3 +11,4 @@ services:
|
||||
|
||||
volumes:
|
||||
data:
|
||||
|
||||
|
||||
13
lib/const.js
13
lib/const.js
@@ -28,11 +28,16 @@ exports.keyboards = {
|
||||
};
|
||||
|
||||
exports.inlineKeyboards = {
|
||||
open: [[{text: '✅', callback_data: 'CloseTicket'}, {text: '💬', callback_data: 'AddComment'}]],
|
||||
close: [[{text: '✔', callback_data: 'OpenTicket'}, {text: '💬', callback_data: 'AddComment'}]],
|
||||
open: [[{text: '✅', callback_data: 'CloseTicket'}, {text: '➡️', callback_data: 'AssignTicket'}, {text: '*️⃣', callback_data: 'ChangeStatus'}]],
|
||||
close: [[{text: '✔', callback_data: 'OpenTicket'}, {text: '➡️', callback_data: 'AssignTicket'}, {text: '*️⃣', callback_data: 'ChangeStatus'}]],
|
||||
confirmOpen: [[{text: 'Открыть заявку', callback_data: 'ConfirmOpen'}, {text: 'Отмена', callback_data: 'RefreshStatus'}]],
|
||||
confirmClose: [[{text: 'Закрыть заявку', callback_data: 'ConfirmClose'}, {text: 'Отмена', callback_data: 'RefreshStatus'}]],
|
||||
userAddComment: [[{text: '❓ Добавить комментарий', callback_data: 'UserAddComment'}]],
|
||||
threadPin: [[{text: 'Закрыть тему', callback_data: 'CloseThread'}]],
|
||||
threadPinConfirm: [[{text: 'Подтвердить', callback_data: 'ConfirmCloseThread'}, {text: 'Отмена', callback_data: 'CancelCloseThread'}]]
|
||||
changeStatus: [[{text: 'В ожидание', callback_data: 'WaitingStatus'}, {text: 'В работу', callback_data: 'WorkingStatus'}], [{text: 'Открыть тему', callback_data: 'OpenThread'}, {text: 'Отмена', callback_data: 'RefreshStatus'}]],
|
||||
configUserGroups: [
|
||||
[{text: 'Add new group', callback_data: 'AddNewGroup'}, {text: 'Add new user in group', callback_data: 'AddNewUser'}],
|
||||
[{text: 'Remove group', callback_data: 'RemoveGroup'}, {text: 'Remove user', callback_data: 'RemoveUser'}, {text: 'Exit', callback_data: 'ExitConfig'}]
|
||||
],
|
||||
confirmConfig: [[{text: 'Confirm', callback_data: 'ConfirmConfig'}, {text: 'Cancell', callback_data: 'CancellConfirm'}]]
|
||||
};
|
||||
|
||||
|
||||
160
lib/glpiParse.js
Normal file
160
lib/glpiParse.js
Normal file
@@ -0,0 +1,160 @@
|
||||
const glpm = require('./glpm.js');
|
||||
const cns = require('./const.js');
|
||||
const {
|
||||
sleep, htmlToText, createThread, closeThread, getTicketColor,
|
||||
parseMessageText, getKeyboardFromStatus, editMessageText, editMessageMarkup
|
||||
} = require('./utils.js');
|
||||
const fs = require('fs');
|
||||
|
||||
const dir = __dirname;
|
||||
let conf = JSON.parse(fs.readFileSync(dir + "/../data/conf.json"));
|
||||
const glpiUrl = conf.glpiConfig.apiurl.replace("apirest.php", "");
|
||||
|
||||
exports.parseTickets = async (bot, messageData) => {
|
||||
let listTickets = await glpm.getAllItems('Ticket', 5);
|
||||
for(let i = 4; i >= 0; i--){
|
||||
let ticketId;
|
||||
if(!listTickets[i]) break;
|
||||
ticketId = listTickets[i].id;
|
||||
if(ticketId <= messageData.ticket) continue;
|
||||
if(listTickets[i].users_id_recipient != conf.glpiConfig.user_id){
|
||||
let usersArray = await glpm.getUsers(ticketId);
|
||||
let authorEmail;
|
||||
if(!usersArray[0]) continue;
|
||||
if(usersArray[0].hasOwnProperty('alternative_email') && usersArray[0].alternative_email){
|
||||
authorEmail = usersArray[0].alternative_email;
|
||||
}else{
|
||||
let temp = await glpm.getItem("User", usersArray[0].users_id);
|
||||
authorEmail = temp.firstname + ' ' + temp.realname;
|
||||
}
|
||||
let text = await htmlToText(listTickets[i].content);
|
||||
let messageText = `🟢 <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">№${ticketId}</a></b>\n\n`;
|
||||
messageText += `<b>Автор заявки: </b>${authorEmail}\n`;
|
||||
messageText += `<b>Проблема: </b>${listTickets[i].name}\n<b>Описание: </b>`;
|
||||
messageText += text;
|
||||
if(messageText.length > 600){
|
||||
messageText = `${messageText.substring(0, 500)} + '\n\n<b><a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">Читать дальше</a></b>`;
|
||||
}
|
||||
let messg = await bot.telegram.sendMessage(conf.supportChatId, messageText, {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: { inline_keyboard: cns.inlineKeyboards.open }
|
||||
});
|
||||
await editMessageText(bot, messg.message_id, messageText, cns.inlineKeyboards.open);
|
||||
messageData.data[ticketId] = {
|
||||
messageId: messg.message_id,
|
||||
status: 1
|
||||
};
|
||||
let title = `🟢 ${ticketId} - ${listTickets[i].name}`;
|
||||
await createThread(bot, messageData, ticketId, title);
|
||||
await sleep(1000);
|
||||
}
|
||||
messageData.ticket = ticketId;
|
||||
}
|
||||
fs.writeFileSync(dir + "/../data/messageData.json", JSON.stringify(messageData, null, 3));
|
||||
}
|
||||
|
||||
exports.parseComments = async (bot, messageData) => {
|
||||
let listComments = await glpm.getAllItems('ITILFollowup', 5);
|
||||
for (let i = 4; i >= 0; i--) {
|
||||
if(!listComments[i]) break;
|
||||
let commentId = listComments[i].id;
|
||||
if(commentId <= messageData.comment) continue;
|
||||
if(listComments[i].users_id != conf.glpiConfig.user_id){
|
||||
let ticketId = listComments[i].items_id;
|
||||
if(!messageData.data.hasOwnProperty(ticketId) || !messageData.data[ticketId].hasOwnProperty("threadId")) continue;
|
||||
let comment = await htmlToText(listComments[i].content);
|
||||
let user;
|
||||
if(listComments[i].users_id){
|
||||
let temp = await glpm.getItem("User", listComments[i].users_id);
|
||||
user = temp.firstname + ' ' + temp.realname;
|
||||
}else{
|
||||
let temp = await glpm.getUsers(ticketId);
|
||||
user = temp[0].alternative_email;
|
||||
}
|
||||
if(!messageData.data.hasOwnProperty(ticketId) || !messageData.data[ticketId].hasOwnProperty('threadId')){
|
||||
return;
|
||||
}
|
||||
let messageText = `<b>Комментарий от ${user}:</b>\n\n${comment}`;
|
||||
if(messageText.length > 2400){
|
||||
messageText = `${messageText.substring(0, 2400)} + '\n\n<b><a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">Читать дальше</a></b>`;
|
||||
}
|
||||
await bot.telegram.sendMessage(conf.supportChatId, messageText, {parse_mode: "HTML", message_thread_id: messageData.data[ticketId].threadId});
|
||||
await sleep(1000);
|
||||
}
|
||||
messageData.comment = commentId;
|
||||
fs.writeFileSync(dir + "/../data/messageData.json", JSON.stringify(messageData, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
exports.refreshStatus = async (bot, messageData) => {
|
||||
let listTickets = await glpm.getAllItems('Ticket', 100);
|
||||
for(let i = 99; i >= 0; i--){
|
||||
let ticketId = listTickets[i].id;
|
||||
if(!listTickets[i] || !messageData.data.hasOwnProperty(ticketId)) continue;
|
||||
let td = messageData.data[ticketId];
|
||||
try{
|
||||
if(td.status != listTickets[i].status){
|
||||
let usersArray = await glpm.getUsers(ticketId);
|
||||
let message = await htmlToText(listTickets[i].content);
|
||||
let authorEmail;
|
||||
if(usersArray[0].hasOwnProperty('alternative_email') && usersArray[0].alternative_email){
|
||||
authorEmail = usersArray[0].alternative_email;
|
||||
}else if(usersArray[0].users_id == conf.glpiConfig.user_id){
|
||||
await editTicketStatus(bot, messageData, message);
|
||||
break;
|
||||
}else{
|
||||
let temp = await glpm.getItem("User", usersArray[0].users_id);
|
||||
authorEmail = temp.firstname + ' ' + temp.realname;
|
||||
}
|
||||
let color = await getTicketColor(listTickets[i].status);
|
||||
let messageText = `${color} <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">№${ticketId}</a></b>\n\n`;
|
||||
messageText += `<b>Автор заявки: </b>${authorEmail}\n`;
|
||||
messageText += `<b>Проблема: </b>${listTickets[i].name}\n<b>Описание: </b>`;
|
||||
messageText += message;
|
||||
if(messageText.length > 600){
|
||||
messageText = `${messageText.substring(0, 500)} + '\n\n<b><a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">Читать дальше</a></b>`;
|
||||
}
|
||||
let inKeyboard = await getKeyboardFromStatus(listTickets[i].status);
|
||||
if(td.hasOwnProperty('threadId')){
|
||||
if(listTickets[i].status == 5 || listTickets[i].status == 6){
|
||||
await closeThread(bot, messageData, ticketId);
|
||||
}else{
|
||||
let title = `${color} ${ticketId} - ${listTickets[i].name}`;
|
||||
await bot.telegram.editForumTopic(conf.supportChatId, td.threadId, { name: title });
|
||||
}
|
||||
}
|
||||
await editMessageText(bot, td.messageId, messageText, inKeyboard);
|
||||
await editMessageText(bot, td.pinMessageId, messageText, inKeyboard);
|
||||
messageData.data[ticketId].status = listTickets[i].status;
|
||||
}else if(td.hasOwnProperty('threadId') && (listTickets[i].status == 5 || listTickets[i].status == 6)){
|
||||
await closeThread(bot, messageData, ticketId);
|
||||
}
|
||||
}catch(e){
|
||||
fs.appendFileSync(dir + "/../logs/logs.json", JSON.stringify("refreshStatus function: " + e, null, 3));
|
||||
}
|
||||
}
|
||||
fs.writeFileSync(dir + "/../data/messageData.json", JSON.stringify(messageData, null, 3));
|
||||
}
|
||||
|
||||
exports.editTicketStatus = async (bot, messageData, message) => {
|
||||
let ticketId = message.text.split('№')[1].split('\n')[0];
|
||||
let ticket = await glpm.getItem('Ticket', ticketId);
|
||||
let inKeyboard = await getKeyboardFromStatus(ticket.status);
|
||||
if(messageData.data[ticketId].status != ticket.status){
|
||||
messageData.data[ticketId].status = ticket.status;
|
||||
let color = await getTicketColor(ticket.status);
|
||||
let title = ticket.name;
|
||||
if(messageData.data[ticketId].hasOwnProperty('userChatId')){
|
||||
title = `${title.split(' - ')[1]} - ${title.split(' - ')[2]}`;
|
||||
}
|
||||
title = `${color} ${ticketId} - ${title}`;
|
||||
if(messageData.data[ticketId].hasOwnProperty('threadId')){
|
||||
await bot.telegram.editForumTopic(conf.supportChatId, messageData.data[ticketId].threadId, { name: title });
|
||||
}
|
||||
let messageText = await parseMessageText(message, messageData, ticketId);
|
||||
await editMessageText(bot, messageData.data[ticketId].messageId, messageText, inKeyboard);
|
||||
await editMessageText(bot, messageData.data[ticketId].pinMessageId, messageText, inKeyboard);
|
||||
}
|
||||
await editMessageMarkup(bot, message.message_id, inKeyboard);
|
||||
fs.writeFileSync(dir + "/../data/messageData.json", JSON.stringify(messageData, null, 3));
|
||||
}
|
||||
60
lib/glpm.js
60
lib/glpm.js
@@ -25,8 +25,8 @@ exports.createTicket = async (title, description) => {
|
||||
})
|
||||
});
|
||||
return response.data.id;
|
||||
}catch(error){
|
||||
console.error('Failed to create ticket:', error);
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,34 @@ exports.changeStatusTicket = async (ticketId, statusId) => {
|
||||
})
|
||||
});
|
||||
return response;
|
||||
}catch(error){
|
||||
console.error('Failed to create ticket:', error);
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
exports.assignTicket = async (ticketId, userId) => {
|
||||
try{
|
||||
const session = await glpi.initSession();
|
||||
let token = session.data.session_token;
|
||||
const response = await axios(conf.glpiConfig.apiurl + '/Ticket_User', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'App-Token': conf.glpiConfig.app_token,
|
||||
'Authorization': conf.glpiConfig.user_token,
|
||||
'Session-Token': token
|
||||
},
|
||||
data: JSON.stringify({
|
||||
input: {
|
||||
tickets_id: ticketId,
|
||||
users_id: userId,
|
||||
type: 2
|
||||
}
|
||||
})
|
||||
});
|
||||
return response.data;
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,8 +95,8 @@ exports.getItem = async(item, id) => { // ITILFollowup = комментари
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
}catch(error){
|
||||
return 0;
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,12 +114,13 @@ exports.getAllItems = async(item, cnt) => {
|
||||
}
|
||||
});
|
||||
return response.data;
|
||||
}catch(error){
|
||||
return error;
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
exports.addComment = async(ticketId, comment) => {
|
||||
try{
|
||||
const session = await glpi.initSession();
|
||||
let token = session.data.session_token;
|
||||
const response = await axios(conf.glpiConfig.apiurl + '/ITILFollowup/', {
|
||||
@@ -111,11 +138,15 @@ exports.addComment = async(ticketId, comment) => {
|
||||
content: comment
|
||||
}
|
||||
})
|
||||
}).catch((err) => console.log(err));
|
||||
});
|
||||
return response.data;
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
exports.removeComment = async(ticketId, commentId) => {
|
||||
try{
|
||||
const session = await glpi.initSession();
|
||||
let token = session.data.session_token;
|
||||
const response = await axios(conf.glpiConfig.apiurl + '/ITILFollowup/' + commentId, {
|
||||
@@ -132,11 +163,15 @@ exports.removeComment = async(ticketId, commentId) => {
|
||||
itemtype: "Ticket"
|
||||
}
|
||||
})
|
||||
}).catch((err) => console.log(err));
|
||||
});
|
||||
return response.data;
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
exports.getUsers = async(ticketId) => {
|
||||
try{
|
||||
const session = await glpi.initSession();
|
||||
let token = session.data.session_token;
|
||||
const response = await axios(`${conf.glpiConfig.apiurl}/Ticket/${ticketId}/Ticket_User`, {
|
||||
@@ -147,6 +182,9 @@ exports.getUsers = async(ticketId) => {
|
||||
'Authorization': conf.glpiConfig.user_token,
|
||||
'Session-Token': token
|
||||
}
|
||||
}).catch((err) => console.log(err));
|
||||
});
|
||||
return response.data;
|
||||
}catch(e){
|
||||
fs.appendFileSync(__dirname + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
100
lib/utils.js
100
lib/utils.js
@@ -1,8 +1,10 @@
|
||||
const he = require('he');
|
||||
const html = require('html-to-text');
|
||||
const fs = require('fs');
|
||||
const cns = require('./const.js');
|
||||
|
||||
const conf = JSON.parse(fs.readFileSync(__dirname + "/../data/conf.json"));
|
||||
const dir = __dirname;
|
||||
const conf = JSON.parse(fs.readFileSync(dir + "/../data/conf.json"));
|
||||
const glpiUrl = conf.glpiConfig.apiurl.replace("apirest.php", "");
|
||||
|
||||
exports.htmlToText = async (text) => {
|
||||
@@ -16,48 +18,96 @@ exports.htmlToText = async(text) => {
|
||||
}
|
||||
let messageText = '';
|
||||
for(let k in textArray){
|
||||
if(textArray[k][0] != '>' && textArray[k].trim() && textArray[k].indexOf('cellpadding="0"') == -1){
|
||||
if(textArray[k][0] != '>' && textArray[k].trim() && textArray[k].indexOf('ts@krtech.ru писал') == -1 && textArray[k].indexOf('cellpadding="0"') == -1){
|
||||
messageText += textArray[k].trim().replace(/[<>]/g, '') + ' ';
|
||||
}
|
||||
}
|
||||
return messageText.replace(/\[.*?\]/g, '');
|
||||
}
|
||||
|
||||
exports.parseMessageText = async(message, addSilentInfo) => {
|
||||
let color = '🟢';
|
||||
if(message.hasOwnProperty('status')){
|
||||
switch(message.status){
|
||||
case 1: color = '🟢'; break;
|
||||
case 2: color = '🔵'; break;
|
||||
case 4: color = '🟠'; break;
|
||||
case 6: color = '⚫'; break;
|
||||
default: color = '⚫';
|
||||
}
|
||||
}
|
||||
let ticketId = message.text.split('\n')[0].split('№')[1];
|
||||
if(!addSilentInfo){
|
||||
addSilentInfo = '';
|
||||
}else addSilentInfo = '/' + addSilentInfo;
|
||||
let silentInfo = `<a href="${message.entities[0].url}${addSilentInfo}">​</a>`;
|
||||
exports.parseMessageText = async (message, messageData, ticketId) => {
|
||||
let color = await this.getTicketColor(messageData.data[ticketId].status);
|
||||
text = message.text.split('\n');
|
||||
messageText = `${silentInfo}${color} <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">№${ticketId}</a></b>\n\n`;
|
||||
messageText = `${color} <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">№${ticketId}</a></b>\n\n`;
|
||||
for(let i = 2; i < text.length; i++){
|
||||
if(text[i].indexOf(':') >= 0) messageText += `<b>${text[i].replace(':', ':</b>')}\n`;
|
||||
}
|
||||
for(let i in message.entities){
|
||||
if(message.entities[i].type == 'text_link' && message.entities[i].url.indexOf(glpiUrl + 'front/document.send.php?docid=') > 0){
|
||||
messageText = messageText.substring(0, messageText.length - 1).replace('screen', '');
|
||||
messageText += `<a href="${message.entities[i].url}">screen</a>`;
|
||||
}
|
||||
}
|
||||
if(text[text.length - 1].indexOf("Читать дальше") >= 0){
|
||||
messageText = `${messageText.replace('Читать дальше', '')}\n<b><a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">Читать дальше</a></b>`;
|
||||
}
|
||||
return messageText;
|
||||
}
|
||||
|
||||
exports.createThread = async (bot, messageData, ticketId, title) => {
|
||||
let td = messageData.data[ticketId];
|
||||
let thread = await bot.telegram.createForumTopic(conf.supportChatId, title, {
|
||||
icon_custom_emoji_id: '5357315181649076022'
|
||||
});
|
||||
let status = td.status;
|
||||
messageData.data[ticketId].threadId = thread.message_thread_id;
|
||||
let inKeyboard = await this.getKeyboardFromStatus(status);
|
||||
await this.editMessageMarkup(bot, td.messageId, inKeyboard);
|
||||
let msg = await bot.telegram.copyMessage(conf.supportChatId, conf.supportChatId, td.messageId, {
|
||||
parse_mode: 'HTML',
|
||||
disable_notification: true,
|
||||
message_thread_id: thread.message_thread_id,
|
||||
reply_markup: { inline_keyboard: inKeyboard }
|
||||
});
|
||||
await bot.telegram.pinChatMessage(conf.supportChatId, msg.message_id, { disable_notification: true });
|
||||
messageData.data[ticketId].pinMessageId = msg.message_id;
|
||||
fs.writeFileSync(dir + "/../data/messageData.json", JSON.stringify(messageData, null, 3));
|
||||
}
|
||||
|
||||
exports.closeThread = async (bot, messageData, ticketId) => {
|
||||
await bot.telegram.deleteForumTopic(conf.supportChatId, messageData.data[ticketId].threadId);
|
||||
delete messageData.data[ticketId].threadId;
|
||||
delete messageData.data[ticketId].pinMessageId;
|
||||
let jsonData = JSON.stringify(messageData, null, 3);
|
||||
fs.writeFileSync(dir + "/../data/messageData.json", jsonData);
|
||||
}
|
||||
|
||||
exports.getTicketColor = async (status) => {
|
||||
switch(status){
|
||||
case 1: color = '🟢'; break;
|
||||
case 2: color = '🔵'; break;
|
||||
case 3: color = '🔵'; break;
|
||||
case 4: color = '🟠'; break;
|
||||
case 6: color = '⚫'; break;
|
||||
default: color = '⚫';
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
exports.getKeyboardFromStatus = async (status) => {
|
||||
let keyboard = cns.inlineKeyboards.open;
|
||||
if(status == 5 || status == 6){
|
||||
keyboard = cns.inlineKeyboards.close;
|
||||
}
|
||||
return keyboard;
|
||||
}
|
||||
|
||||
exports.editMessageText = async (bot, messageId, messageText, keyboard) => {
|
||||
try{
|
||||
await bot.telegram.editMessageText(conf.supportChatId, messageId, undefined, messageText, {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: {inline_keyboard: keyboard}
|
||||
});
|
||||
}catch(e){
|
||||
fs.appendFileSync(dir + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
exports.editMessageMarkup = async (bot, messageId, keyboard) => {
|
||||
try{
|
||||
await bot.telegram.editMessageReplyMarkup(conf.supportChatId, messageId, undefined, {inline_keyboard: keyboard});
|
||||
}catch(e){
|
||||
fs.appendFileSync(dir + "/../logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
exports.sleep = async (ms) => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, ms);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
0
logs/logs.json
Normal file
0
logs/logs.json
Normal file
1
node_modules/.bin/he
generated
vendored
1
node_modules/.bin/he
generated
vendored
@@ -1 +0,0 @@
|
||||
../he/bin/he
|
||||
148
node_modules/.bin/he
generated
vendored
Executable file
148
node_modules/.bin/he
generated
vendored
Executable file
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env node
|
||||
(function() {
|
||||
|
||||
var fs = require('fs');
|
||||
var he = require('../he.js');
|
||||
var strings = process.argv.splice(2);
|
||||
var stdin = process.stdin;
|
||||
var data;
|
||||
var timeout;
|
||||
var action;
|
||||
var options = {};
|
||||
var log = console.log;
|
||||
|
||||
var main = function() {
|
||||
var option = strings[0];
|
||||
var count = 0;
|
||||
|
||||
if (/^(?:-h|--help|undefined)$/.test(option)) {
|
||||
log(
|
||||
'he v%s - https://mths.be/he',
|
||||
he.version
|
||||
);
|
||||
log([
|
||||
'\nUsage:\n',
|
||||
'\the [--escape] string',
|
||||
'\the [--encode] [--use-named-refs] [--everything] [--allow-unsafe] [--decimal] string',
|
||||
'\the [--decode] [--attribute] [--strict] string',
|
||||
'\the [-v | --version]',
|
||||
'\the [-h | --help]',
|
||||
'\nExamples:\n',
|
||||
'\the --escape \\<img\\ src\\=\\\'x\\\'\\ onerror\\=\\"prompt\\(1\\)\\"\\>',
|
||||
'\techo \'© 𝌆\' | he --decode'
|
||||
].join('\n'));
|
||||
return process.exit(option ? 0 : 1);
|
||||
}
|
||||
|
||||
if (/^(?:-v|--version)$/.test(option)) {
|
||||
log('v%s', he.version);
|
||||
return process.exit(0);
|
||||
}
|
||||
|
||||
strings.forEach(function(string) {
|
||||
// Process options
|
||||
if (string == '--escape') {
|
||||
action = 'escape';
|
||||
return;
|
||||
}
|
||||
if (string == '--encode') {
|
||||
action = 'encode';
|
||||
return;
|
||||
}
|
||||
if (string == '--use-named-refs') {
|
||||
action = 'encode';
|
||||
options.useNamedReferences = true;
|
||||
return;
|
||||
}
|
||||
if (string == '--everything') {
|
||||
action = 'encode';
|
||||
options.encodeEverything = true;
|
||||
return;
|
||||
}
|
||||
if (string == '--allow-unsafe') {
|
||||
action = 'encode';
|
||||
options.allowUnsafeSymbols = true;
|
||||
return;
|
||||
}
|
||||
if (string == '--decimal') {
|
||||
action = 'encode';
|
||||
options.decimal = true;
|
||||
return;
|
||||
}
|
||||
if (string == '--decode') {
|
||||
action = 'decode';
|
||||
return;
|
||||
}
|
||||
if (string == '--attribute') {
|
||||
action = 'decode';
|
||||
options.isAttributeValue = true;
|
||||
return;
|
||||
}
|
||||
if (string == '--strict') {
|
||||
action = 'decode';
|
||||
options.strict = true;
|
||||
return;
|
||||
}
|
||||
// Process string(s)
|
||||
var result;
|
||||
if (!action) {
|
||||
log('Error: he requires at least one option and a string argument.');
|
||||
log('Try `he --help` for more information.');
|
||||
return process.exit(1);
|
||||
}
|
||||
try {
|
||||
result = he[action](string, options);
|
||||
log(result);
|
||||
count++;
|
||||
} catch(error) {
|
||||
log(error.message + '\n');
|
||||
log('Error: failed to %s.', action);
|
||||
log('If you think this is a bug in he, please report it:');
|
||||
log('https://github.com/mathiasbynens/he/issues/new');
|
||||
log(
|
||||
'\nStack trace using he@%s:\n',
|
||||
he.version
|
||||
);
|
||||
log(error.stack);
|
||||
return process.exit(1);
|
||||
}
|
||||
});
|
||||
if (!count) {
|
||||
log('Error: he requires a string argument.');
|
||||
log('Try `he --help` for more information.');
|
||||
return process.exit(1);
|
||||
}
|
||||
// Return with exit status 0 outside of the `forEach` loop, in case
|
||||
// multiple strings were passed in.
|
||||
return process.exit(0);
|
||||
};
|
||||
|
||||
if (stdin.isTTY) {
|
||||
// handle shell arguments
|
||||
main();
|
||||
} else {
|
||||
// Either the script is called from within a non-TTY context, or `stdin`
|
||||
// content is being piped in.
|
||||
if (!process.stdout.isTTY) {
|
||||
// The script was called from a non-TTY context. This is a rather uncommon
|
||||
// use case we don’t actively support. However, we don’t want the script
|
||||
// to wait forever in such cases, so…
|
||||
timeout = setTimeout(function() {
|
||||
// …if no piped data arrived after a whole minute, handle shell
|
||||
// arguments instead.
|
||||
main();
|
||||
}, 60000);
|
||||
}
|
||||
data = '';
|
||||
stdin.on('data', function(chunk) {
|
||||
clearTimeout(timeout);
|
||||
data += chunk;
|
||||
});
|
||||
stdin.on('end', function() {
|
||||
strings.push(data.trim());
|
||||
main();
|
||||
});
|
||||
stdin.resume();
|
||||
}
|
||||
|
||||
}());
|
||||
1
node_modules/.bin/nodemon
generated
vendored
1
node_modules/.bin/nodemon
generated
vendored
@@ -1 +0,0 @@
|
||||
../nodemon/bin/nodemon.js
|
||||
16
node_modules/.bin/nodemon
generated
vendored
Executable file
16
node_modules/.bin/nodemon
generated
vendored
Executable file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const cli = require('../lib/cli');
|
||||
const nodemon = require('../lib/');
|
||||
const options = cli.parse(process.argv);
|
||||
|
||||
nodemon(options);
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
// checks for available update and returns an instance
|
||||
const pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json'));
|
||||
|
||||
if (pkg.version.indexOf('0.0.0') !== 0 && options.noUpdateNotifier !== true) {
|
||||
require('simple-update-notifier')({ pkg });
|
||||
}
|
||||
1
node_modules/.bin/nodetouch
generated
vendored
1
node_modules/.bin/nodetouch
generated
vendored
@@ -1 +0,0 @@
|
||||
../touch/bin/nodetouch.js
|
||||
112
node_modules/.bin/nodetouch
generated
vendored
Executable file
112
node_modules/.bin/nodetouch
generated
vendored
Executable file
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env node
|
||||
const touch = require("../index.js")
|
||||
|
||||
const usage = code => {
|
||||
console[code ? 'error' : 'log'](
|
||||
'usage:\n' +
|
||||
'touch [-acfm] [-r file] [-t [[CC]YY]MMDDhhmm[.SS]] file ...'
|
||||
)
|
||||
process.exit(code)
|
||||
}
|
||||
|
||||
const singleFlags = {
|
||||
a: 'atime',
|
||||
m: 'mtime',
|
||||
c: 'nocreate',
|
||||
f: 'force'
|
||||
}
|
||||
|
||||
const singleOpts = {
|
||||
r: 'ref',
|
||||
t: 'time'
|
||||
}
|
||||
|
||||
const files = []
|
||||
const args = process.argv.slice(2)
|
||||
const options = {}
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i]
|
||||
if (!arg.match(/^-/)) {
|
||||
files.push(arg)
|
||||
continue
|
||||
}
|
||||
|
||||
// expand shorthands
|
||||
if (arg.charAt(1) !== '-') {
|
||||
const expand = []
|
||||
for (let f = 1; f < arg.length; f++) {
|
||||
const fc = arg.charAt(f)
|
||||
const sf = singleFlags[fc]
|
||||
const so = singleOpts[fc]
|
||||
if (sf)
|
||||
expand.push('--' + sf)
|
||||
else if (so) {
|
||||
const soslice = arg.slice(f + 1)
|
||||
const soval = soslice.charAt(0) === '=' ? soslice : '=' + soslice
|
||||
expand.push('--' + so + soval)
|
||||
f = arg.length
|
||||
} else if (arg !== '-' + fc)
|
||||
expand.push('-' + fc)
|
||||
}
|
||||
if (expand.length) {
|
||||
args.splice.apply(args, [i, 1].concat(expand))
|
||||
i--
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const argsplit = arg.split('=')
|
||||
const key = argsplit.shift().replace(/^\-\-/, '')
|
||||
const val = argsplit.length ? argsplit.join('=') : null
|
||||
|
||||
switch (key) {
|
||||
case 'time':
|
||||
const timestr = val || args[++i]
|
||||
// [-t [[CC]YY]MMDDhhmm[.SS]]
|
||||
const parsedtime = timestr.match(
|
||||
/^(([0-9]{2})?([0-9]{2}))?([0-9]{2})([0-9]{2})([0-9]{2})([0-9]{2})(\.([0-9]{2}))?$/
|
||||
)
|
||||
if (!parsedtime) {
|
||||
console.error('touch: out of range or illegal ' +
|
||||
'time specification: ' +
|
||||
'[[CC]YY]MMDDhhmm[.SS]')
|
||||
process.exit(1)
|
||||
} else {
|
||||
const y = +parsedtime[1]
|
||||
const year = parsedtime[2] ? y
|
||||
: y <= 68 ? 2000 + y
|
||||
: 1900 + y
|
||||
|
||||
const MM = +parsedtime[4] - 1
|
||||
const dd = +parsedtime[5]
|
||||
const hh = +parsedtime[6]
|
||||
const mm = +parsedtime[7]
|
||||
const ss = +parsedtime[8]
|
||||
|
||||
options.time = new Date(Date.UTC(year, MM, dd, hh, mm, ss))
|
||||
}
|
||||
continue
|
||||
|
||||
case 'ref':
|
||||
options.ref = val || args[++i]
|
||||
continue
|
||||
|
||||
case 'mtime':
|
||||
case 'nocreate':
|
||||
case 'atime':
|
||||
case 'force':
|
||||
options[key] = true
|
||||
continue
|
||||
|
||||
default:
|
||||
console.error('touch: illegal option -- ' + arg)
|
||||
usage(1)
|
||||
}
|
||||
}
|
||||
|
||||
if (!files.length)
|
||||
usage()
|
||||
|
||||
process.exitCode = 0
|
||||
Promise.all(files.map(f => touch(f, options)))
|
||||
.catch(er => process.exitCode = 1)
|
||||
1
node_modules/.bin/nopt
generated
vendored
1
node_modules/.bin/nopt
generated
vendored
@@ -1 +0,0 @@
|
||||
../nopt/bin/nopt.js
|
||||
44
node_modules/.bin/nopt
generated
vendored
Executable file
44
node_modules/.bin/nopt
generated
vendored
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env node
|
||||
var nopt = require("../lib/nopt")
|
||||
, types = { num: Number
|
||||
, bool: Boolean
|
||||
, help: Boolean
|
||||
, list: Array
|
||||
, "num-list": [Number, Array]
|
||||
, "str-list": [String, Array]
|
||||
, "bool-list": [Boolean, Array]
|
||||
, str: String }
|
||||
, shorthands = { s: [ "--str", "astring" ]
|
||||
, b: [ "--bool" ]
|
||||
, nb: [ "--no-bool" ]
|
||||
, tft: [ "--bool-list", "--no-bool-list", "--bool-list", "true" ]
|
||||
, "?": ["--help"]
|
||||
, h: ["--help"]
|
||||
, H: ["--help"]
|
||||
, n: [ "--num", "125" ] }
|
||||
, parsed = nopt( types
|
||||
, shorthands
|
||||
, process.argv
|
||||
, 2 )
|
||||
|
||||
console.log("parsed", parsed)
|
||||
|
||||
if (parsed.help) {
|
||||
console.log("")
|
||||
console.log("nopt cli tester")
|
||||
console.log("")
|
||||
console.log("types")
|
||||
console.log(Object.keys(types).map(function M (t) {
|
||||
var type = types[t]
|
||||
if (Array.isArray(type)) {
|
||||
return [t, type.map(function (type) { return type.name })]
|
||||
}
|
||||
return [t, type && type.name]
|
||||
}).reduce(function (s, i) {
|
||||
s[i[0]] = i[1]
|
||||
return s
|
||||
}, {}))
|
||||
console.log("")
|
||||
console.log("shorthands")
|
||||
console.log(shorthands)
|
||||
}
|
||||
1
node_modules/.bin/semver
generated
vendored
1
node_modules/.bin/semver
generated
vendored
@@ -1 +0,0 @@
|
||||
../semver/bin/semver.js
|
||||
197
node_modules/.bin/semver
generated
vendored
Executable file
197
node_modules/.bin/semver
generated
vendored
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env node
|
||||
// Standalone semver comparison program.
|
||||
// Exits successfully and prints matching version(s) if
|
||||
// any supplied version is valid and passes all tests.
|
||||
|
||||
const argv = process.argv.slice(2)
|
||||
|
||||
let versions = []
|
||||
|
||||
const range = []
|
||||
|
||||
let inc = null
|
||||
|
||||
const version = require('../package.json').version
|
||||
|
||||
let loose = false
|
||||
|
||||
let includePrerelease = false
|
||||
|
||||
let coerce = false
|
||||
|
||||
let rtl = false
|
||||
|
||||
let identifier
|
||||
|
||||
let identifierBase
|
||||
|
||||
const semver = require('../')
|
||||
const parseOptions = require('../internal/parse-options')
|
||||
|
||||
let reverse = false
|
||||
|
||||
let options = {}
|
||||
|
||||
const main = () => {
|
||||
if (!argv.length) {
|
||||
return help()
|
||||
}
|
||||
while (argv.length) {
|
||||
let a = argv.shift()
|
||||
const indexOfEqualSign = a.indexOf('=')
|
||||
if (indexOfEqualSign !== -1) {
|
||||
const value = a.slice(indexOfEqualSign + 1)
|
||||
a = a.slice(0, indexOfEqualSign)
|
||||
argv.unshift(value)
|
||||
}
|
||||
switch (a) {
|
||||
case '-rv': case '-rev': case '--rev': case '--reverse':
|
||||
reverse = true
|
||||
break
|
||||
case '-l': case '--loose':
|
||||
loose = true
|
||||
break
|
||||
case '-p': case '--include-prerelease':
|
||||
includePrerelease = true
|
||||
break
|
||||
case '-v': case '--version':
|
||||
versions.push(argv.shift())
|
||||
break
|
||||
case '-i': case '--inc': case '--increment':
|
||||
switch (argv[0]) {
|
||||
case 'major': case 'minor': case 'patch': case 'prerelease':
|
||||
case 'premajor': case 'preminor': case 'prepatch':
|
||||
inc = argv.shift()
|
||||
break
|
||||
default:
|
||||
inc = 'patch'
|
||||
break
|
||||
}
|
||||
break
|
||||
case '--preid':
|
||||
identifier = argv.shift()
|
||||
break
|
||||
case '-r': case '--range':
|
||||
range.push(argv.shift())
|
||||
break
|
||||
case '-n':
|
||||
identifierBase = argv.shift()
|
||||
if (identifierBase === 'false') {
|
||||
identifierBase = false
|
||||
}
|
||||
break
|
||||
case '-c': case '--coerce':
|
||||
coerce = true
|
||||
break
|
||||
case '--rtl':
|
||||
rtl = true
|
||||
break
|
||||
case '--ltr':
|
||||
rtl = false
|
||||
break
|
||||
case '-h': case '--help': case '-?':
|
||||
return help()
|
||||
default:
|
||||
versions.push(a)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
options = parseOptions({ loose, includePrerelease, rtl })
|
||||
|
||||
versions = versions.map((v) => {
|
||||
return coerce ? (semver.coerce(v, options) || { version: v }).version : v
|
||||
}).filter((v) => {
|
||||
return semver.valid(v)
|
||||
})
|
||||
if (!versions.length) {
|
||||
return fail()
|
||||
}
|
||||
if (inc && (versions.length !== 1 || range.length)) {
|
||||
return failInc()
|
||||
}
|
||||
|
||||
for (let i = 0, l = range.length; i < l; i++) {
|
||||
versions = versions.filter((v) => {
|
||||
return semver.satisfies(v, range[i], options)
|
||||
})
|
||||
if (!versions.length) {
|
||||
return fail()
|
||||
}
|
||||
}
|
||||
return success(versions)
|
||||
}
|
||||
|
||||
const failInc = () => {
|
||||
console.error('--inc can only be used on a single version with no range')
|
||||
fail()
|
||||
}
|
||||
|
||||
const fail = () => process.exit(1)
|
||||
|
||||
const success = () => {
|
||||
const compare = reverse ? 'rcompare' : 'compare'
|
||||
versions.sort((a, b) => {
|
||||
return semver[compare](a, b, options)
|
||||
}).map((v) => {
|
||||
return semver.clean(v, options)
|
||||
}).map((v) => {
|
||||
return inc ? semver.inc(v, inc, options, identifier, identifierBase) : v
|
||||
}).forEach((v, i, _) => {
|
||||
console.log(v)
|
||||
})
|
||||
}
|
||||
|
||||
const help = () => console.log(
|
||||
`SemVer ${version}
|
||||
|
||||
A JavaScript implementation of the https://semver.org/ specification
|
||||
Copyright Isaac Z. Schlueter
|
||||
|
||||
Usage: semver [options] <version> [<version> [...]]
|
||||
Prints valid versions sorted by SemVer precedence
|
||||
|
||||
Options:
|
||||
-r --range <range>
|
||||
Print versions that match the specified range.
|
||||
|
||||
-i --increment [<level>]
|
||||
Increment a version by the specified level. Level can
|
||||
be one of: major, minor, patch, premajor, preminor,
|
||||
prepatch, or prerelease. Default level is 'patch'.
|
||||
Only one version may be specified.
|
||||
|
||||
--preid <identifier>
|
||||
Identifier to be used to prefix premajor, preminor,
|
||||
prepatch or prerelease version increments.
|
||||
|
||||
-l --loose
|
||||
Interpret versions and ranges loosely
|
||||
|
||||
-p --include-prerelease
|
||||
Always include prerelease versions in range matching
|
||||
|
||||
-c --coerce
|
||||
Coerce a string into SemVer if possible
|
||||
(does not imply --loose)
|
||||
|
||||
--rtl
|
||||
Coerce version strings right to left
|
||||
|
||||
--ltr
|
||||
Coerce version strings left to right (default)
|
||||
|
||||
-n <base>
|
||||
Base number to be used for the prerelease identifier.
|
||||
Can be either 0 or 1, or false to omit the number altogether.
|
||||
Defaults to 0.
|
||||
|
||||
Program exits successfully if any valid version satisfies
|
||||
all supplied ranges, and prints all satisfying versions.
|
||||
|
||||
If no satisfying versions are found, then exits failure.
|
||||
|
||||
Versions are printed in ascending order, so supplying
|
||||
multiple versions to the utility will just sort them.`)
|
||||
|
||||
main()
|
||||
1
node_modules/.bin/sshpk-conv
generated
vendored
1
node_modules/.bin/sshpk-conv
generated
vendored
@@ -1 +0,0 @@
|
||||
../sshpk/bin/sshpk-conv
|
||||
243
node_modules/.bin/sshpk-conv
generated
vendored
Executable file
243
node_modules/.bin/sshpk-conv
generated
vendored
Executable file
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env node
|
||||
// -*- mode: js -*-
|
||||
// vim: set filetype=javascript :
|
||||
// Copyright 2018 Joyent, Inc. All rights reserved.
|
||||
|
||||
var dashdash = require('dashdash');
|
||||
var sshpk = require('../lib/index');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var tty = require('tty');
|
||||
var readline = require('readline');
|
||||
var getPassword = require('getpass').getPass;
|
||||
|
||||
var options = [
|
||||
{
|
||||
names: ['outformat', 't'],
|
||||
type: 'string',
|
||||
help: 'Output format'
|
||||
},
|
||||
{
|
||||
names: ['informat', 'T'],
|
||||
type: 'string',
|
||||
help: 'Input format'
|
||||
},
|
||||
{
|
||||
names: ['file', 'f'],
|
||||
type: 'string',
|
||||
help: 'Input file name (default stdin)'
|
||||
},
|
||||
{
|
||||
names: ['out', 'o'],
|
||||
type: 'string',
|
||||
help: 'Output file name (default stdout)'
|
||||
},
|
||||
{
|
||||
names: ['private', 'p'],
|
||||
type: 'bool',
|
||||
help: 'Produce a private key as output'
|
||||
},
|
||||
{
|
||||
names: ['derive', 'd'],
|
||||
type: 'string',
|
||||
help: 'Output a new key derived from this one, with given algo'
|
||||
},
|
||||
{
|
||||
names: ['identify', 'i'],
|
||||
type: 'bool',
|
||||
help: 'Print key metadata instead of converting'
|
||||
},
|
||||
{
|
||||
names: ['fingerprint', 'F'],
|
||||
type: 'bool',
|
||||
help: 'Output key fingerprint'
|
||||
},
|
||||
{
|
||||
names: ['hash', 'H'],
|
||||
type: 'string',
|
||||
help: 'Hash function to use for key fingeprint with -F'
|
||||
},
|
||||
{
|
||||
names: ['spki', 's'],
|
||||
type: 'bool',
|
||||
help: 'With -F, generates an SPKI fingerprint instead of SSH'
|
||||
},
|
||||
{
|
||||
names: ['comment', 'c'],
|
||||
type: 'string',
|
||||
help: 'Set key comment, if output format supports'
|
||||
},
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Shows this help text'
|
||||
}
|
||||
];
|
||||
|
||||
if (require.main === module) {
|
||||
var parser = dashdash.createParser({
|
||||
options: options
|
||||
});
|
||||
|
||||
try {
|
||||
var opts = parser.parse(process.argv);
|
||||
} catch (e) {
|
||||
console.error('sshpk-conv: error: %s', e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (opts.help || opts._args.length > 1) {
|
||||
var help = parser.help({}).trimRight();
|
||||
console.error('sshpk-conv: converts between SSH key formats\n');
|
||||
console.error(help);
|
||||
console.error('\navailable key formats:');
|
||||
console.error(' - pem, pkcs1 eg id_rsa');
|
||||
console.error(' - ssh eg id_rsa.pub');
|
||||
console.error(' - pkcs8 format you want for openssl');
|
||||
console.error(' - openssh like output of ssh-keygen -o');
|
||||
console.error(' - rfc4253 raw OpenSSH wire format');
|
||||
console.error(' - dnssec dnssec-keygen format');
|
||||
console.error(' - putty PuTTY ppk format');
|
||||
console.error('\navailable fingerprint formats:');
|
||||
console.error(' - hex colon-separated hex for SSH');
|
||||
console.error(' straight hex for SPKI');
|
||||
console.error(' - base64 SHA256:* format from OpenSSH');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Key derivation can only be done on private keys, so use of the -d
|
||||
* option necessarily implies -p.
|
||||
*/
|
||||
if (opts.derive)
|
||||
opts.private = true;
|
||||
|
||||
var inFile = process.stdin;
|
||||
var inFileName = 'stdin';
|
||||
|
||||
var inFilePath;
|
||||
if (opts.file) {
|
||||
inFilePath = opts.file;
|
||||
} else if (opts._args.length === 1) {
|
||||
inFilePath = opts._args[0];
|
||||
}
|
||||
|
||||
if (inFilePath)
|
||||
inFileName = path.basename(inFilePath);
|
||||
|
||||
try {
|
||||
if (inFilePath) {
|
||||
fs.accessSync(inFilePath, fs.R_OK);
|
||||
inFile = fs.createReadStream(inFilePath);
|
||||
}
|
||||
} catch (e) {
|
||||
ifError(e, 'error opening input file');
|
||||
}
|
||||
|
||||
var outFile = process.stdout;
|
||||
|
||||
try {
|
||||
if (opts.out && !opts.identify) {
|
||||
fs.accessSync(path.dirname(opts.out), fs.W_OK);
|
||||
outFile = fs.createWriteStream(opts.out);
|
||||
}
|
||||
} catch (e) {
|
||||
ifError(e, 'error opening output file');
|
||||
}
|
||||
|
||||
var bufs = [];
|
||||
inFile.on('readable', function () {
|
||||
var data;
|
||||
while ((data = inFile.read()))
|
||||
bufs.push(data);
|
||||
});
|
||||
var parseOpts = {};
|
||||
parseOpts.filename = inFileName;
|
||||
inFile.on('end', function processKey() {
|
||||
var buf = Buffer.concat(bufs);
|
||||
var fmt = 'auto';
|
||||
if (opts.informat)
|
||||
fmt = opts.informat;
|
||||
var f = sshpk.parseKey;
|
||||
if (opts.private)
|
||||
f = sshpk.parsePrivateKey;
|
||||
try {
|
||||
var key = f(buf, fmt, parseOpts);
|
||||
} catch (e) {
|
||||
if (e.name === 'KeyEncryptedError') {
|
||||
getPassword(function (err, pw) {
|
||||
if (err)
|
||||
ifError(err);
|
||||
parseOpts.passphrase = pw;
|
||||
processKey();
|
||||
});
|
||||
return;
|
||||
}
|
||||
ifError(e);
|
||||
}
|
||||
|
||||
if (opts.derive)
|
||||
key = key.derive(opts.derive);
|
||||
|
||||
if (opts.comment)
|
||||
key.comment = opts.comment;
|
||||
|
||||
if (opts.identify) {
|
||||
var kind = 'public';
|
||||
if (sshpk.PrivateKey.isPrivateKey(key))
|
||||
kind = 'private';
|
||||
console.log('%s: a %d bit %s %s key', inFileName,
|
||||
key.size, key.type.toUpperCase(), kind);
|
||||
if (key.type === 'ecdsa')
|
||||
console.log('ECDSA curve: %s', key.curve);
|
||||
if (key.comment)
|
||||
console.log('Comment: %s', key.comment);
|
||||
console.log('SHA256 fingerprint: ' +
|
||||
key.fingerprint('sha256').toString());
|
||||
console.log('MD5 fingerprint: ' +
|
||||
key.fingerprint('md5').toString());
|
||||
console.log('SPKI-SHA256 fingerprint: ' +
|
||||
key.fingerprint('sha256', 'spki').toString());
|
||||
process.exit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts.fingerprint) {
|
||||
var hash = opts.hash;
|
||||
var type = opts.spki ? 'spki' : 'ssh';
|
||||
var format = opts.outformat;
|
||||
var fp = key.fingerprint(hash, type).toString(format);
|
||||
outFile.write(fp);
|
||||
outFile.write('\n');
|
||||
outFile.once('drain', function () {
|
||||
process.exit(0);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
fmt = undefined;
|
||||
if (opts.outformat)
|
||||
fmt = opts.outformat;
|
||||
outFile.write(key.toBuffer(fmt));
|
||||
if (fmt === 'ssh' ||
|
||||
(!opts.private && fmt === undefined))
|
||||
outFile.write('\n');
|
||||
outFile.once('drain', function () {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function ifError(e, txt) {
|
||||
if (txt)
|
||||
txt = txt + ': ';
|
||||
else
|
||||
txt = '';
|
||||
console.error('sshpk-conv: ' + txt + e.name + ': ' + e.message);
|
||||
if (process.env['DEBUG'] || process.env['V']) {
|
||||
console.error(e.stack);
|
||||
if (e.innerErr)
|
||||
console.error(e.innerErr.stack);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
1
node_modules/.bin/sshpk-sign
generated
vendored
1
node_modules/.bin/sshpk-sign
generated
vendored
@@ -1 +0,0 @@
|
||||
../sshpk/bin/sshpk-sign
|
||||
191
node_modules/.bin/sshpk-sign
generated
vendored
Executable file
191
node_modules/.bin/sshpk-sign
generated
vendored
Executable file
@@ -0,0 +1,191 @@
|
||||
#!/usr/bin/env node
|
||||
// -*- mode: js -*-
|
||||
// vim: set filetype=javascript :
|
||||
// Copyright 2015 Joyent, Inc. All rights reserved.
|
||||
|
||||
var dashdash = require('dashdash');
|
||||
var sshpk = require('../lib/index');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var getPassword = require('getpass').getPass;
|
||||
|
||||
var options = [
|
||||
{
|
||||
names: ['hash', 'H'],
|
||||
type: 'string',
|
||||
help: 'Hash algorithm (sha1, sha256, sha384, sha512)'
|
||||
},
|
||||
{
|
||||
names: ['verbose', 'v'],
|
||||
type: 'bool',
|
||||
help: 'Display verbose info about key and hash used'
|
||||
},
|
||||
{
|
||||
names: ['identity', 'i'],
|
||||
type: 'string',
|
||||
help: 'Path to key to use'
|
||||
},
|
||||
{
|
||||
names: ['file', 'f'],
|
||||
type: 'string',
|
||||
help: 'Input filename'
|
||||
},
|
||||
{
|
||||
names: ['out', 'o'],
|
||||
type: 'string',
|
||||
help: 'Output filename'
|
||||
},
|
||||
{
|
||||
names: ['format', 't'],
|
||||
type: 'string',
|
||||
help: 'Signature format (asn1, ssh, raw)'
|
||||
},
|
||||
{
|
||||
names: ['binary', 'b'],
|
||||
type: 'bool',
|
||||
help: 'Output raw binary instead of base64'
|
||||
},
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Shows this help text'
|
||||
}
|
||||
];
|
||||
|
||||
var parseOpts = {};
|
||||
|
||||
if (require.main === module) {
|
||||
var parser = dashdash.createParser({
|
||||
options: options
|
||||
});
|
||||
|
||||
try {
|
||||
var opts = parser.parse(process.argv);
|
||||
} catch (e) {
|
||||
console.error('sshpk-sign: error: %s', e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (opts.help || opts._args.length > 1) {
|
||||
var help = parser.help({}).trimRight();
|
||||
console.error('sshpk-sign: sign data using an SSH key\n');
|
||||
console.error(help);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!opts.identity) {
|
||||
var help = parser.help({}).trimRight();
|
||||
console.error('sshpk-sign: the -i or --identity option ' +
|
||||
'is required\n');
|
||||
console.error(help);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var keyData = fs.readFileSync(opts.identity);
|
||||
parseOpts.filename = opts.identity;
|
||||
|
||||
run();
|
||||
}
|
||||
|
||||
function run() {
|
||||
var key;
|
||||
try {
|
||||
key = sshpk.parsePrivateKey(keyData, 'auto', parseOpts);
|
||||
} catch (e) {
|
||||
if (e.name === 'KeyEncryptedError') {
|
||||
getPassword(function (err, pw) {
|
||||
parseOpts.passphrase = pw;
|
||||
run();
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.error('sshpk-sign: error loading private key "' +
|
||||
opts.identity + '": ' + e.name + ': ' + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var hash = opts.hash || key.defaultHashAlgorithm();
|
||||
|
||||
var signer;
|
||||
try {
|
||||
signer = key.createSign(hash);
|
||||
} catch (e) {
|
||||
console.error('sshpk-sign: error creating signer: ' +
|
||||
e.name + ': ' + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (opts.verbose) {
|
||||
console.error('sshpk-sign: using %s-%s with a %d bit key',
|
||||
key.type, hash, key.size);
|
||||
}
|
||||
|
||||
var inFile = process.stdin;
|
||||
var inFileName = 'stdin';
|
||||
|
||||
var inFilePath;
|
||||
if (opts.file) {
|
||||
inFilePath = opts.file;
|
||||
} else if (opts._args.length === 1) {
|
||||
inFilePath = opts._args[0];
|
||||
}
|
||||
|
||||
if (inFilePath)
|
||||
inFileName = path.basename(inFilePath);
|
||||
|
||||
try {
|
||||
if (inFilePath) {
|
||||
fs.accessSync(inFilePath, fs.R_OK);
|
||||
inFile = fs.createReadStream(inFilePath);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('sshpk-sign: error opening input file' +
|
||||
': ' + e.name + ': ' + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var outFile = process.stdout;
|
||||
|
||||
try {
|
||||
if (opts.out && !opts.identify) {
|
||||
fs.accessSync(path.dirname(opts.out), fs.W_OK);
|
||||
outFile = fs.createWriteStream(opts.out);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('sshpk-sign: error opening output file' +
|
||||
': ' + e.name + ': ' + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
inFile.pipe(signer);
|
||||
inFile.on('end', function () {
|
||||
var sig;
|
||||
try {
|
||||
sig = signer.sign();
|
||||
} catch (e) {
|
||||
console.error('sshpk-sign: error signing data: ' +
|
||||
e.name + ': ' + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var fmt = opts.format || 'asn1';
|
||||
var output;
|
||||
try {
|
||||
output = sig.toBuffer(fmt);
|
||||
if (!opts.binary)
|
||||
output = output.toString('base64');
|
||||
} catch (e) {
|
||||
console.error('sshpk-sign: error converting signature' +
|
||||
' to ' + fmt + ' format: ' + e.name + ': ' +
|
||||
e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
outFile.write(output);
|
||||
if (!opts.binary)
|
||||
outFile.write('\n');
|
||||
outFile.once('drain', function () {
|
||||
process.exit(0);
|
||||
});
|
||||
});
|
||||
}
|
||||
1
node_modules/.bin/sshpk-verify
generated
vendored
1
node_modules/.bin/sshpk-verify
generated
vendored
@@ -1 +0,0 @@
|
||||
../sshpk/bin/sshpk-verify
|
||||
167
node_modules/.bin/sshpk-verify
generated
vendored
Executable file
167
node_modules/.bin/sshpk-verify
generated
vendored
Executable file
@@ -0,0 +1,167 @@
|
||||
#!/usr/bin/env node
|
||||
// -*- mode: js -*-
|
||||
// vim: set filetype=javascript :
|
||||
// Copyright 2015 Joyent, Inc. All rights reserved.
|
||||
|
||||
var dashdash = require('dashdash');
|
||||
var sshpk = require('../lib/index');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Buffer = require('safer-buffer').Buffer;
|
||||
|
||||
var options = [
|
||||
{
|
||||
names: ['hash', 'H'],
|
||||
type: 'string',
|
||||
help: 'Hash algorithm (sha1, sha256, sha384, sha512)'
|
||||
},
|
||||
{
|
||||
names: ['verbose', 'v'],
|
||||
type: 'bool',
|
||||
help: 'Display verbose info about key and hash used'
|
||||
},
|
||||
{
|
||||
names: ['identity', 'i'],
|
||||
type: 'string',
|
||||
help: 'Path to (public) key to use'
|
||||
},
|
||||
{
|
||||
names: ['file', 'f'],
|
||||
type: 'string',
|
||||
help: 'Input filename'
|
||||
},
|
||||
{
|
||||
names: ['format', 't'],
|
||||
type: 'string',
|
||||
help: 'Signature format (asn1, ssh, raw)'
|
||||
},
|
||||
{
|
||||
names: ['signature', 's'],
|
||||
type: 'string',
|
||||
help: 'base64-encoded signature data'
|
||||
},
|
||||
{
|
||||
names: ['help', 'h'],
|
||||
type: 'bool',
|
||||
help: 'Shows this help text'
|
||||
}
|
||||
];
|
||||
|
||||
if (require.main === module) {
|
||||
var parser = dashdash.createParser({
|
||||
options: options
|
||||
});
|
||||
|
||||
try {
|
||||
var opts = parser.parse(process.argv);
|
||||
} catch (e) {
|
||||
console.error('sshpk-verify: error: %s', e.message);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
if (opts.help || opts._args.length > 1) {
|
||||
var help = parser.help({}).trimRight();
|
||||
console.error('sshpk-verify: sign data using an SSH key\n');
|
||||
console.error(help);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
if (!opts.identity) {
|
||||
var help = parser.help({}).trimRight();
|
||||
console.error('sshpk-verify: the -i or --identity option ' +
|
||||
'is required\n');
|
||||
console.error(help);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
if (!opts.signature) {
|
||||
var help = parser.help({}).trimRight();
|
||||
console.error('sshpk-verify: the -s or --signature option ' +
|
||||
'is required\n');
|
||||
console.error(help);
|
||||
process.exit(3);
|
||||
}
|
||||
|
||||
var keyData = fs.readFileSync(opts.identity);
|
||||
|
||||
var key;
|
||||
try {
|
||||
key = sshpk.parseKey(keyData);
|
||||
} catch (e) {
|
||||
console.error('sshpk-verify: error loading key "' +
|
||||
opts.identity + '": ' + e.name + ': ' + e.message);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
var fmt = opts.format || 'asn1';
|
||||
var sigData = Buffer.from(opts.signature, 'base64');
|
||||
|
||||
var sig;
|
||||
try {
|
||||
sig = sshpk.parseSignature(sigData, key.type, fmt);
|
||||
} catch (e) {
|
||||
console.error('sshpk-verify: error parsing signature: ' +
|
||||
e.name + ': ' + e.message);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
var hash = opts.hash || key.defaultHashAlgorithm();
|
||||
|
||||
var verifier;
|
||||
try {
|
||||
verifier = key.createVerify(hash);
|
||||
} catch (e) {
|
||||
console.error('sshpk-verify: error creating verifier: ' +
|
||||
e.name + ': ' + e.message);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
if (opts.verbose) {
|
||||
console.error('sshpk-verify: using %s-%s with a %d bit key',
|
||||
key.type, hash, key.size);
|
||||
}
|
||||
|
||||
var inFile = process.stdin;
|
||||
var inFileName = 'stdin';
|
||||
|
||||
var inFilePath;
|
||||
if (opts.file) {
|
||||
inFilePath = opts.file;
|
||||
} else if (opts._args.length === 1) {
|
||||
inFilePath = opts._args[0];
|
||||
}
|
||||
|
||||
if (inFilePath)
|
||||
inFileName = path.basename(inFilePath);
|
||||
|
||||
try {
|
||||
if (inFilePath) {
|
||||
fs.accessSync(inFilePath, fs.R_OK);
|
||||
inFile = fs.createReadStream(inFilePath);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('sshpk-verify: error opening input file' +
|
||||
': ' + e.name + ': ' + e.message);
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
inFile.pipe(verifier);
|
||||
inFile.on('end', function () {
|
||||
var ret;
|
||||
try {
|
||||
ret = verifier.verify(sig);
|
||||
} catch (e) {
|
||||
console.error('sshpk-verify: error verifying data: ' +
|
||||
e.name + ': ' + e.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
console.error('OK');
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.error('NOT OK');
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
1
node_modules/.bin/telegraf
generated
vendored
1
node_modules/.bin/telegraf
generated
vendored
@@ -1 +0,0 @@
|
||||
../telegraf/lib/cli.mjs
|
||||
105
node_modules/.bin/telegraf
generated
vendored
Executable file
105
node_modules/.bin/telegraf
generated
vendored
Executable file
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env node
|
||||
import d from 'debug';
|
||||
import parse from 'mri';
|
||||
import path from 'path';
|
||||
import { Telegraf } from './index.js';
|
||||
const debug = d('telegraf:cli');
|
||||
const helpMsg = `Usage: telegraf [opts] <bot-file>
|
||||
|
||||
-t Bot token [$BOT_TOKEN]
|
||||
-d Webhook domain [$BOT_DOMAIN]
|
||||
-H Webhook host [0.0.0.0]
|
||||
-p Webhook port [$PORT or 3000]
|
||||
-l Enable logs
|
||||
-h Show this help message
|
||||
-m Bot API method to run directly
|
||||
-D Data to pass to the Bot API method`;
|
||||
const help = () => console.log(helpMsg);
|
||||
/**
|
||||
* Runs the cli program and returns exit code
|
||||
*/
|
||||
export async function main(argv, env = {}) {
|
||||
const args = parse(argv, {
|
||||
alias: {
|
||||
// string params, all optional
|
||||
t: 'token',
|
||||
d: 'domain',
|
||||
m: 'method',
|
||||
D: 'data',
|
||||
// defaults exist
|
||||
H: 'host',
|
||||
p: 'port',
|
||||
// boolean params
|
||||
l: 'logs',
|
||||
h: 'help',
|
||||
},
|
||||
boolean: ['h', 'l'],
|
||||
default: {
|
||||
H: '0.0.0.0',
|
||||
p: env.PORT || '3000',
|
||||
},
|
||||
});
|
||||
if (args.help) {
|
||||
help();
|
||||
return 0;
|
||||
}
|
||||
const token = args.token || env.BOT_TOKEN;
|
||||
const domain = args.domain || env.BOT_DOMAIN;
|
||||
if (!token) {
|
||||
console.error('Please supply Bot Token');
|
||||
help();
|
||||
return 1;
|
||||
}
|
||||
const bot = new Telegraf(token);
|
||||
if (args.method) {
|
||||
const method = args.method;
|
||||
console.log(await bot.telegram.callApi(method, JSON.parse(args.data || '{}')));
|
||||
return 0;
|
||||
}
|
||||
let [, , file] = args._;
|
||||
if (!file) {
|
||||
try {
|
||||
const packageJson = (await import(path.resolve(process.cwd(), 'package.json')));
|
||||
file = packageJson.main || 'index.js';
|
||||
// eslint-disable-next-line no-empty
|
||||
}
|
||||
catch (err) { }
|
||||
}
|
||||
if (!file) {
|
||||
console.error('Please supply a bot handler file.\n');
|
||||
help();
|
||||
return 2;
|
||||
}
|
||||
if (file[0] !== '/')
|
||||
file = path.resolve(process.cwd(), file);
|
||||
try {
|
||||
if (args.logs)
|
||||
d.enable('telegraf:*');
|
||||
const mod = await import(file);
|
||||
const botHandler = mod.botHandler || mod.default;
|
||||
const httpHandler = mod.httpHandler;
|
||||
const tlsOptions = mod.tlsOptions;
|
||||
const config = {};
|
||||
if (domain) {
|
||||
config.webhook = {
|
||||
domain,
|
||||
host: args.host,
|
||||
port: Number(args.port),
|
||||
tlsOptions,
|
||||
cb: httpHandler,
|
||||
};
|
||||
}
|
||||
bot.use(botHandler);
|
||||
debug(`Starting module ${file}`);
|
||||
await bot.launch(config);
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Error launching bot from ${file}`, err === null || err === void 0 ? void 0 : err.stack);
|
||||
return 3;
|
||||
}
|
||||
// Enable graceful stop
|
||||
process.once('SIGINT', () => bot.stop('SIGINT'));
|
||||
process.once('SIGTERM', () => bot.stop('SIGTERM'));
|
||||
return 0;
|
||||
}
|
||||
process.exitCode = await main(process.argv, process.env);
|
||||
1
node_modules/.bin/uuid
generated
vendored
1
node_modules/.bin/uuid
generated
vendored
@@ -1 +0,0 @@
|
||||
../uuid/bin/uuid
|
||||
65
node_modules/.bin/uuid
generated
vendored
Executable file
65
node_modules/.bin/uuid
generated
vendored
Executable file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env node
|
||||
var assert = require('assert');
|
||||
|
||||
function usage() {
|
||||
console.log('Usage:');
|
||||
console.log(' uuid');
|
||||
console.log(' uuid v1');
|
||||
console.log(' uuid v3 <name> <namespace uuid>');
|
||||
console.log(' uuid v4');
|
||||
console.log(' uuid v5 <name> <namespace uuid>');
|
||||
console.log(' uuid --help');
|
||||
console.log('\nNote: <namespace uuid> may be "URL" or "DNS" to use the corresponding UUIDs defined by RFC4122');
|
||||
}
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
|
||||
if (args.indexOf('--help') >= 0) {
|
||||
usage();
|
||||
process.exit(0);
|
||||
}
|
||||
var version = args.shift() || 'v4';
|
||||
|
||||
switch (version) {
|
||||
case 'v1':
|
||||
var uuidV1 = require('../v1');
|
||||
console.log(uuidV1());
|
||||
break;
|
||||
|
||||
case 'v3':
|
||||
var uuidV3 = require('../v3');
|
||||
|
||||
var name = args.shift();
|
||||
var namespace = args.shift();
|
||||
assert(name != null, 'v3 name not specified');
|
||||
assert(namespace != null, 'v3 namespace not specified');
|
||||
|
||||
if (namespace == 'URL') namespace = uuidV3.URL;
|
||||
if (namespace == 'DNS') namespace = uuidV3.DNS;
|
||||
|
||||
console.log(uuidV3(name, namespace));
|
||||
break;
|
||||
|
||||
case 'v4':
|
||||
var uuidV4 = require('../v4');
|
||||
console.log(uuidV4());
|
||||
break;
|
||||
|
||||
case 'v5':
|
||||
var uuidV5 = require('../v5');
|
||||
|
||||
var name = args.shift();
|
||||
var namespace = args.shift();
|
||||
assert(name != null, 'v5 name not specified');
|
||||
assert(namespace != null, 'v5 namespace not specified');
|
||||
|
||||
if (namespace == 'URL') namespace = uuidV5.URL;
|
||||
if (namespace == 'DNS') namespace = uuidV5.DNS;
|
||||
|
||||
console.log(uuidV5(name, namespace));
|
||||
break;
|
||||
|
||||
default:
|
||||
usage();
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -11,8 +11,8 @@
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
"./data/dataId.json",
|
||||
"./data/threads.json"
|
||||
"./data/messageData.json",
|
||||
"./logs/logs.json"
|
||||
],
|
||||
"delay": "10"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Service]
|
||||
User=root
|
||||
ExecStart=node /home/user/telegram_support_bot/telegram_support_bot.js
|
||||
ExecStart=node /root/telegram_support_bot/telegram_support_bot.js
|
||||
Restart=always
|
||||
|
||||
[Install]
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
const {Telegraf, Markup} = require('telegraf');
|
||||
const glpm = require('./lib/glpm.js');
|
||||
const cns = require('./lib/const.js');
|
||||
const {sleep, parseMessageText, htmlToText} = require('./lib/utils.js');
|
||||
const {parseTickets, parseComments, refreshStatus, editTicketStatus} = require('./lib/glpiParse.js');
|
||||
const {sleep, createThread, closeThread, getTicketColor, editMessageMarkup, editMessageText} = require('./lib/utils.js');
|
||||
const fs = require('fs');
|
||||
|
||||
const dir = __dirname;
|
||||
let ticketData = {};
|
||||
const conf = JSON.parse(fs.readFileSync(dir + "/data/conf.json"));
|
||||
let conf = JSON.parse(fs.readFileSync(dir + "/data/conf.json"));
|
||||
const glpiUrl = conf.glpiConfig.apiurl.replace("apirest.php", "");
|
||||
let threadsData = JSON.parse(fs.readFileSync(dir + "/data/threads.json"));
|
||||
let messageData = JSON.parse(fs.readFileSync(dir + "/data/messageData.json"));
|
||||
let configData = {};
|
||||
|
||||
const bot = new Telegraf(conf.telegramBotToken, {
|
||||
handlerTimeout: 90000 * 5
|
||||
@@ -27,7 +29,19 @@ bot.hears('Подать заявку', async (ctx) => {
|
||||
await ctx.reply('Выберите, пожалуйста, наиболее подходящую категорию', cns.keyboards.main);
|
||||
await deleteMessage(ctx, ctx.message.message_id);
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
});
|
||||
|
||||
// Настройка кнопок для переназначения заявки
|
||||
|
||||
bot.hears('/configurationUserGroups', async (ctx) => {
|
||||
if(ctx.chat.id == conf.supportChatId){
|
||||
await bot.telegram.sendMessage(conf.supportChatId, 'Выберите действие', {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: {inline_keyboard: cns.inlineKeyboards.configUserGroups}
|
||||
});
|
||||
await deleteMessage(ctx, ctx.message.message_id);
|
||||
}
|
||||
});
|
||||
|
||||
// Создаём кнопки основного меню
|
||||
|
||||
@@ -67,7 +81,7 @@ bot.hears('Назад', async (ctx) => {
|
||||
}catch{
|
||||
ctx.reply('Добро пожаловать в чат-бот технической поддержки ' + conf.CompanyName, cns.keyboards.start);
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
});
|
||||
|
||||
bot.hears('Отменить заявку', async (ctx) => {
|
||||
if(ctx.chat.id != conf.supportChatId){
|
||||
@@ -76,7 +90,7 @@ bot.hears('Отменить заявку', async (ctx) => {
|
||||
await deleteMessage(ctx, ctx.message.message_id - 1);
|
||||
await deleteMessage(ctx, ctx.message.message_id);
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
});
|
||||
|
||||
bot.hears('Отправить заявку', async (ctx) => {
|
||||
if(ctx.chat.id != conf.supportChatId && ticketData.hasOwnProperty(ctx.chat.id) && ticketData[ctx.chat.id].data["Кабинет"].length > 1){
|
||||
@@ -90,9 +104,8 @@ bot.hears('Отправить заявку', async (ctx) => {
|
||||
for(key in ticketData[ctx.chat.id]['data']){
|
||||
textToGLPI += '<b>' + key + ':</b> ' + ticketData[ctx.chat.id]['data'][key].replace(/[<>/]/g, '') + '<br>';
|
||||
}
|
||||
let res = await glpm.createTicket(title, textToGLPI);
|
||||
let messageText = `<a href="http://example.com/${ctx.chat.id}/${ctx.message.message_id+2}">​</a>`;
|
||||
messageText += `🟢 <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${res}">№${res}</a></b>\n\n`;
|
||||
let ticketId = await glpm.createTicket(title, textToGLPI);
|
||||
let messageText = `🟢 <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">№${ticketId}</a></b>\n\n`;
|
||||
let userLogin = '';
|
||||
if(ctx.chat.username) userLogin = ' (@' + ctx.chat.username + ')';
|
||||
ticketData[ctx.chat.id]['data']['Автор заявки'] += userLogin;
|
||||
@@ -103,17 +116,25 @@ bot.hears('Отправить заявку', async (ctx) => {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: {inline_keyboard: cns.inlineKeyboards.open}
|
||||
});
|
||||
messageData.data[ticketId] = {
|
||||
messageId: messg.message_id,
|
||||
userMassageId: ctx.message.message_id + 2,
|
||||
userChatId: ctx.chat.id,
|
||||
status: 1
|
||||
}
|
||||
let threadTitle = `🟢 ${ticketId}${title.replace(ticketData[ctx.chat.id].data["Кабинет"], '')}`;
|
||||
await createThread(bot, messageData, ticketId, threadTitle);
|
||||
await ctx.reply('Заявка отправлена', cns.keyboards.start);
|
||||
await deleteMessage(ctx, ctx.message.message_id - 1);
|
||||
await deleteMessage(ctx, ctx.message.message_id);
|
||||
let messageUserText = messageText.replace('">​</a>', `/${messg.message_id}">​</a>`);
|
||||
await ctx.telegram.sendMessage(ctx.message.chat.id, messageUserText, {
|
||||
await ctx.telegram.sendMessage(ctx.chat.id, messageText, {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: {inline_keyboard: cns.inlineKeyboards.userAddComment}
|
||||
});
|
||||
fs.writeFileSync(dir + "/data/messageData.json", JSON.stringify(messageData, null, 3));
|
||||
delete ticketData[ctx.chat.id];
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
});
|
||||
|
||||
// Обработка сообщений
|
||||
|
||||
@@ -124,13 +145,17 @@ bot.on('text', async (ctx) => {
|
||||
ticketId = ctx.message.reply_to_message.text.split('№')[1].split('\n')[0];
|
||||
}catch{}
|
||||
if(!ticketData.hasOwnProperty(ctx.chat.id) && ticketId){
|
||||
if(!threadsData.hasOwnProperty(ticketId)){
|
||||
let generalMessageId = ctx.message.reply_to_message.entities[0].url.split('/')[5];
|
||||
await createThread(ticketId, generalMessageId, ctx.chat.id, ctx.message.reply_to_message.message_id);
|
||||
if(!messageData.data[ticketId].hasOwnProperty('threadId')){
|
||||
let ticket = await glpm.getItem('Ticket', ticketId);
|
||||
let title = `🟢 ${ticketId} - ${ticket.name}`;
|
||||
await createThread(bot, messageData, ticketId, title);
|
||||
}
|
||||
if(!ctx.chat.last_name) ctx.chat.last_name = '';
|
||||
let userName = ctx.chat.first_name + ' ' + ctx.chat.last_name;
|
||||
let messageText = `<b>Комментарий от ${userName}:</b>\n\n${ctx.message.text}`;
|
||||
await bot.telegram.sendMessage(conf.supportChatId, messageText, {parse_mode: "HTML", message_thread_id: threadsData[ticketId]["threadId"]});
|
||||
await bot.telegram.sendMessage(conf.supportChatId, messageText, {
|
||||
parse_mode: "HTML", message_thread_id: messageData.data[ticketId].threadId
|
||||
});
|
||||
let discript = `<b><font color="blue">Комментарий от ${userName}:</font></b><br><br>`;
|
||||
await glpm.addComment(ticketId, discript + ctx.message.text);
|
||||
}else if(ticketId){
|
||||
@@ -155,40 +180,56 @@ bot.on('text', async (ctx) => {
|
||||
await deleteMessage(ctx, ctx.message.message_id);
|
||||
}
|
||||
}else if(ctx.message.message_thread_id){
|
||||
let thread = {};
|
||||
let ticket;
|
||||
for(let i in threadsData){
|
||||
if(threadsData[i].threadId == ctx.message.message_thread_id){
|
||||
thread = threadsData[i];
|
||||
for(let i in messageData.data){
|
||||
if(messageData.data[i].threadId == ctx.message.message_thread_id){
|
||||
ticket = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(thread.hasOwnProperty('threadId') && thread.userChatId){
|
||||
await bot.telegram.sendMessage(thread.userChatId, ctx.message.text, {reply_parameters: {message_id: thread.userMessgId}});
|
||||
if(messageData.data[ticket].hasOwnProperty('userChatId')){
|
||||
try{
|
||||
await bot.telegram.sendMessage(messageData.data[ticket].userChatId, ctx.message.text, {
|
||||
reply_parameters: {message_id: messageData.data[ticket].userMassageId}
|
||||
});
|
||||
}catch{
|
||||
await bot.telegram.sendMessage(messageData.data[ticket].userChatId, ctx.message.text);
|
||||
}
|
||||
}
|
||||
await glpm.addComment(ticket, ctx.message.text);
|
||||
}else if(configData.hasOwnProperty("flag")){
|
||||
configData["text"] = ctx.message.text.trim();
|
||||
configData["id"] = ctx.message.message_id;
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
});
|
||||
|
||||
// Обработка изображений из приватных чатов
|
||||
|
||||
bot.on('photo', async (ctx) => {
|
||||
if(ctx.chat.id != conf.supportChatId){
|
||||
if(!ticketData.hasOwnProperty(ctx.chat.id)){
|
||||
let ticketId;
|
||||
let messg = ctx.message.reply_to_message.text;
|
||||
try{
|
||||
ticketId = ctx.message.reply_to_message.text.split('№')[1].split('\n')[0];
|
||||
ticketId = messg.split('№')[1].split('\n')[0];
|
||||
}catch{}
|
||||
if(!ticketId) return;
|
||||
if(!threadsData.hasOwnProperty(ticketId)){
|
||||
let generalMessageId = ctx.message.reply_to_message.entities[0].url.split('/')[5];
|
||||
await createThread(ticketId, generalMessageId, ctx.chat.id, ctx.message.reply_to_message.message_id);
|
||||
if(!ticketData.hasOwnProperty(ctx.chat.id) && ticketId){
|
||||
if(!messageData.data[ticketId].hasOwnProperty(threadId)){
|
||||
let ticket = await glpm.getItem('Ticket', ticketId);
|
||||
let title = `🟢 ${ticketId}${ticket.name.split('-')[1]}-${ticket.name.split('-')[2]}`;
|
||||
await createThread(bot, messageData, ticketId, title);
|
||||
}
|
||||
await bot.telegram.sendPhoto(conf.supportChatId, ctx.message.photo[0].file_id, {message_thread_id: threadsData[ticketId]["threadId"]});
|
||||
await bot.telegram.sendPhoto(conf.supportChatId, ctx.message.photo[0].file_id, {message_thread_id: messageData.data[ticketId]["threadId"]});
|
||||
}
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
});
|
||||
|
||||
// Сохраняем ID последнего сообщения в чате
|
||||
|
||||
bot.on('message', async (ctx) => {
|
||||
if(ctx.message.forum_topic_edited){
|
||||
await deleteMessage(ctx, ctx.message.message_id);
|
||||
}
|
||||
});
|
||||
|
||||
// Функция для создания кнопок основного меню с похожим функционалом
|
||||
|
||||
@@ -218,71 +259,60 @@ async function deleteMessage(ctx, message_id){
|
||||
}catch{}
|
||||
}
|
||||
|
||||
async function editMessage(message, keyboard, silent){
|
||||
try{
|
||||
let ticketId = message.text.split('\n')[0].split('№')[1];
|
||||
let chatId = message.chat.id;
|
||||
let ticketData = await glpm.getItem('Ticket', ticketId);
|
||||
message['status'] = ticketData.status;
|
||||
if(message.entities[0].url.split('/')[5]) silent = undefined;
|
||||
messageText = await parseMessageText(message, silent);
|
||||
let inKeyboard = JSON.parse(JSON.stringify(keyboard));
|
||||
if(inKeyboard[0][0].text == "✔" || inKeyboard[0][0].text == '✅'){
|
||||
if(threadsData.hasOwnProperty(ticketId)){
|
||||
delete inKeyboard[0][1].callback_data;
|
||||
inKeyboard[0][1]["url"] = `t.me/c/${conf.supportChatId.substring(4)}/${threadsData[ticketId].threadId}`;
|
||||
}else{
|
||||
delete inKeyboard[0][1].url;
|
||||
inKeyboard[0][1].callback_data = 'AddComment';
|
||||
}
|
||||
}
|
||||
await bot.telegram.editMessageText(chatId, message.message_id, undefined, messageText, {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: {inline_keyboard: inKeyboard},
|
||||
entities: message.entities
|
||||
});
|
||||
}catch{}
|
||||
}
|
||||
|
||||
// Создаём обработчики инлайн кнопок
|
||||
|
||||
createAction('ConfirmOpen', 1, cns.inlineKeyboards.open);
|
||||
createAction('ConfirmClose', 6, cns.inlineKeyboards.close);
|
||||
createAction('OpenTicket', null, cns.inlineKeyboards.confirmOpen);
|
||||
createAction('CloseTicket', null, cns.inlineKeyboards.confirmClose);
|
||||
createAction('CloseThread', null, cns.inlineKeyboards.threadPinConfirm);
|
||||
createAction('CancelCloseThread', null, cns.inlineKeyboards.threadPin);
|
||||
createAction('OpenTicket', cns.inlineKeyboards.confirmOpen);
|
||||
createAction('CloseTicket', cns.inlineKeyboards.confirmClose);
|
||||
createAction('ChangeStatus', cns.inlineKeyboards.changeStatus);
|
||||
createAction('ConfirmOpen', cns.inlineKeyboards.open, 1);
|
||||
createAction('ConfirmClose', cns.inlineKeyboards.close, 6);
|
||||
createAction('WaitingStatus', cns.inlineKeyboards.open, 4);
|
||||
createAction('WorkingStatus', cns.inlineKeyboards.open, 2);
|
||||
createAction('OpenThread', cns.inlineKeyboards.open);
|
||||
|
||||
function createAction(action, status, keyboard){
|
||||
function createAction(action, keyboard, status){
|
||||
try{
|
||||
bot.action(action, async (ctx) => {
|
||||
let message = ctx.update.callback_query.message;
|
||||
if(status){
|
||||
let ticketId = message.text.split('\n')[0].split('№')[1];
|
||||
let td = messageData.data[ticketId];
|
||||
if(status || action == 'OpenThread'){
|
||||
await glpm.changeStatusTicket(ticketId, status);
|
||||
if(dataId.hasOwnProperty(ticketId)) dataId[ticketId].status = status;
|
||||
await editTicketStatus(bot, messageData, message);
|
||||
if(status == 6){
|
||||
let userChatId = message.entities[0].url.split('/')[3];
|
||||
let userMessgId = message.entities[0].url.split('/')[4];
|
||||
if(userChatId){
|
||||
await bot.telegram.sendMessage(userChatId, "<b>Заявка закрыта</b>", {
|
||||
reply_parameters: {message_id: userMessgId},
|
||||
if(td.hasOwnProperty('userChatId')){
|
||||
try{
|
||||
await bot.telegram.sendMessage(td.userChatId, "<b>Заявка закрыта</b>", {
|
||||
reply_parameters: {message_id: td.userMassageId},
|
||||
parse_mode: "HTML"
|
||||
});
|
||||
}catch{
|
||||
await bot.telegram.sendMessage(td.userChatId, "<b>Заявка закрыта</b>", {
|
||||
parse_mode: "HTML"
|
||||
});
|
||||
}
|
||||
if(threadsData.hasOwnProperty(ticketId)){
|
||||
let thread = threadsData[ticketId].threadId;
|
||||
await bot.telegram.deleteForumTopic(conf.supportChatId, thread);
|
||||
delete threadsData[ticketId];
|
||||
let jsonData = JSON.stringify(threadsData, null, 3);
|
||||
fs.writeFileSync(dir + "/data/threads.json", jsonData);
|
||||
}
|
||||
if(td.hasOwnProperty('threadId')){
|
||||
await closeThread(bot, messageData, ticketId);
|
||||
}
|
||||
}else if(!td.hasOwnProperty('threadId') && (status == 1 || action == 'OpenThread')){
|
||||
let color = await getTicketColor(status || td.status);
|
||||
let title;
|
||||
if(td.hasOwnProperty('userChatId')){
|
||||
let problem = message.text.split('\n')[4].replace('Категория: ', '');
|
||||
let author = message.text.split('\n')[2].replace('Автор заявки: ', '').replace(/\(@[^)]+\)/g, '');
|
||||
title = `${color} ${ticketId} - ${problem} - ${author}`;
|
||||
}else{
|
||||
title = `${color} ${ticketId} - ${message.text.split('\n')[3].replace('Проблема: ', '')}`;
|
||||
}
|
||||
await createThread(bot, messageData, ticketId, title);
|
||||
}
|
||||
}else{
|
||||
await editMessageMarkup(bot, message.message_id, keyboard);
|
||||
}
|
||||
await editMessage(message, keyboard);
|
||||
});
|
||||
}catch(error){
|
||||
console.error('Failed to action: ' + action, error);
|
||||
}catch(e){
|
||||
fs.appendFileSync(dir + "/logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,80 +320,102 @@ function createAction(action, status, keyboard){
|
||||
|
||||
bot.action('RefreshStatus', async (ctx) => {
|
||||
let message = ctx.update.callback_query.message;
|
||||
let ticketId = message.text.split('\n')[0].split('№')[1];
|
||||
let ticketData = await glpm.getItem('Ticket', ticketId);
|
||||
switch(ticketData.status){
|
||||
case 1: await editMessage(message, cns.inlineKeyboards.open); break;
|
||||
case 2: await editMessage(message, cns.inlineKeyboards.open); break;
|
||||
case 4: await editMessage(message, cns.inlineKeyboards.open); break;
|
||||
case 6: await editMessage(message, cns.inlineKeyboards.close); break;
|
||||
default: await editMessage(message, cns.inlineKeyboards.close); break;
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
|
||||
// Обработчик для кнопки с облачком, которая создает новую тему
|
||||
|
||||
bot.action('AddComment', async (ctx) => {
|
||||
let message = ctx.update.callback_query.message;
|
||||
let userChatId = message.entities[0].url.split('/')[3];
|
||||
let userMessgId = message.entities[0].url.split('/')[4];
|
||||
let generalMessageId = message.entities[0].url.split('/')[5];
|
||||
if(!generalMessageId) generalMessageId = message.message_id;
|
||||
let ticketId = message.text.split('\n')[0].split('№')[1];
|
||||
if(!threadsData.hasOwnProperty(ticketId)){
|
||||
await editMessage(message, message.reply_markup.inline_keyboard, generalMessageId);
|
||||
await createThread(ticketId, generalMessageId, userChatId, userMessgId);
|
||||
}
|
||||
}).catch(error => console.log(error));
|
||||
|
||||
// Создание новой темы
|
||||
|
||||
async function createThread(ticketId, generalMessageId, userChatId, userMessageId){
|
||||
let thread = await bot.telegram.createForumTopic(conf.supportChatId, ticketId);
|
||||
threadsData[ticketId] = {
|
||||
"userChatId": userChatId,
|
||||
"userMessgId": userMessageId,
|
||||
"threadId": thread.message_thread_id
|
||||
};
|
||||
let ticket = await glpm.getItem('Ticket', ticketId);
|
||||
let inKeyboard = JSON.parse(JSON.stringify(cns.inlineKeyboards.open));
|
||||
if (ticket.status == 6 || ticket.status == 5){
|
||||
inKeyboard = JSON.parse(JSON.stringify(cns.inlineKeyboards.close));
|
||||
}
|
||||
delete inKeyboard[0][1].callback_data;
|
||||
inKeyboard[0][1]["url"] = `t.me/c/${conf.supportChatId.substring(4)}/${thread.message_thread_id}`;
|
||||
await bot.telegram.editMessageReplyMarkup(conf.supportChatId, generalMessageId, undefined, { inline_keyboard: inKeyboard });
|
||||
let msg = await bot.telegram.copyMessage(conf.supportChatId, conf.supportChatId, generalMessageId, {
|
||||
parse_mode: 'HTML',
|
||||
disable_notification: true,
|
||||
message_thread_id: thread.message_thread_id,
|
||||
reply_markup: { inline_keyboard: cns.inlineKeyboards.threadPin }
|
||||
await editTicketStatus(bot, messageData, message);
|
||||
});
|
||||
await bot.telegram.pinChatMessage(conf.supportChatId, msg.message_id, { disable_notification: true });
|
||||
let jsonData = JSON.stringify(threadsData, null, 3);
|
||||
fs.writeFileSync(dir + "/data/threads.json", jsonData);
|
||||
}
|
||||
|
||||
// Обработчик для кнопки с подсказкой о добавлении комментария
|
||||
|
||||
bot.action('UserAddComment', async (ctx) => {
|
||||
await ctx.reply('Чтобы отправить комментарий, <b>ответьте на сообщение с номером заявки</b> (которое начинается с 🟢)', {parse_mode: "HTML"});
|
||||
});
|
||||
|
||||
// Закрыть тему
|
||||
// Изменить конфигурацию пользовательских групп
|
||||
|
||||
bot.action('ConfirmCloseThread', async (ctx) => {
|
||||
createConfigButtons('AddNewGroup', 'Укажите название новой группы');
|
||||
createConfigButtons('AddNewUser', 'Укажите группу и glpi id пользователя в формате:\n[Группа]: [id]');
|
||||
createConfigButtons('RemoveGroup', 'Укажите название удаляемой группы');
|
||||
createConfigButtons('RemoveUser', 'Укажите группу и glpi id пользователя в формате:\n[Группа]: [id]');
|
||||
|
||||
function createConfigButtons(action, messageText){
|
||||
bot.action(action, async (ctx) => {
|
||||
let message = ctx.update.callback_query.message;
|
||||
await editMessageText(bot, message.message_id, messageText, cns.inlineKeyboards.confirmConfig)
|
||||
configData["flag"] = action;
|
||||
});
|
||||
}
|
||||
|
||||
bot.action('ConfirmConfig', async (ctx) => {
|
||||
if(configData.hasOwnProperty('id')){
|
||||
let messageId = ctx.update.callback_query.message.message_id;
|
||||
switch(configData.flag){
|
||||
case "AddNewGroup": conf.userGroups[configData.text] = []; break;
|
||||
case "AddNewUser": let gu = configData.text.split(':'); conf.userGroups[gu[0].trim()].push(gu[1].trim()); break;
|
||||
case "RemoveGroup": delete conf.userGroups[configData.text]; break;
|
||||
case "RemoveUser": let rgu = configData.text.split(':'); let index = conf.userGroups[rgu[0].trim()].indexOf(rgu[1].trim());
|
||||
conf.userGroups[rgu[0].trim()].splice(index, 1); break;
|
||||
}
|
||||
let jsonData = JSON.stringify(conf, null, 3);
|
||||
fs.writeFileSync(dir + "/data/conf.json", jsonData);
|
||||
createAssignActions();
|
||||
await deleteMessage(ctx.update.callback_query, configData.id);
|
||||
configData = {};
|
||||
await editMessageMarkup(bot, messageId, cns.inlineKeyboards.configUserGroups);
|
||||
}
|
||||
});
|
||||
|
||||
bot.action('ExitConfig', async (ctx) => {
|
||||
configData = {};
|
||||
let message = ctx.update.callback_query.message;
|
||||
await bot.telegram.deleteMessage(conf.supportChatId, message.message_id);
|
||||
});
|
||||
|
||||
bot.action('CancellConfirm', async (ctx) => {
|
||||
await deleteMessage(ctx.update.callback_query, configData.id);
|
||||
configData = {};
|
||||
let message = ctx.update.callback_query.message;
|
||||
await editMessageMarkup(bot, message.message_id, cns.inlineKeyboards.configUserGroups);
|
||||
});
|
||||
|
||||
// Обработчики для кнопок с переназначением заявок
|
||||
|
||||
bot.action('AssignTicket', async (ctx) => {
|
||||
let message = ctx.update.callback_query.message;
|
||||
let keyboard = [[]];
|
||||
let row = 0;
|
||||
for(let key in conf.userGroups){
|
||||
if(row == 3){
|
||||
keyboard.push([]);
|
||||
row = 0;
|
||||
}
|
||||
keyboard[keyboard.length-1].push({text: key, callback_data: 'ButtonFor_' + key});
|
||||
row++;
|
||||
}
|
||||
keyboard[keyboard.length-1].push({text: 'Отмена', callback_data: 'RefreshStatus'});
|
||||
await editMessageMarkup(bot, message.message_id, keyboard);
|
||||
});
|
||||
|
||||
// Создание обработчиков событий для переназначения заявок
|
||||
|
||||
createAssignActions();
|
||||
|
||||
function createAssignActions(){
|
||||
for(let key in conf.userGroups){
|
||||
bot.action('ButtonFor_' + key, async (ctx) => {
|
||||
let message = ctx.update.callback_query.message;
|
||||
let ticketId = message.text.split('№')[1].split('\n')[0];
|
||||
let messageId = message.entities[0].url.split('/')[5];
|
||||
message.message_id = messageId;
|
||||
delete threadsData[ticketId];
|
||||
await bot.telegram.deleteForumTopic(conf.supportChatId, message.message_thread_id);
|
||||
let ticket = await glpm.getItem("Ticket", ticketId);
|
||||
let inKeyboard = cns.inlineKeyboards.open;
|
||||
if(ticket.status == 6 || ticket.status == 5) inKeyboard = cns.inlineKeyboards.close;
|
||||
await editMessage(message, inKeyboard);
|
||||
let jsonData = JSON.stringify(threadsData, null, 3);
|
||||
fs.writeFileSync(dir + "/data/threads.json", jsonData);
|
||||
}).catch(error => console.log(error));
|
||||
for(let i in conf.userGroups[key]){
|
||||
await glpm.assignTicket(ticketId, conf.userGroups[key][i]);
|
||||
}
|
||||
if(message.text.indexOf("⚫") < 0){
|
||||
await glpm.changeStatusTicket(ticketId, 2);
|
||||
}
|
||||
await editTicketStatus(bot, messageData, message);
|
||||
if(messageData.data[ticketId].hasOwnProperty('threadId')){
|
||||
await closeThread(bot, messageData, ticketId);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.log(error);
|
||||
@@ -373,163 +425,21 @@ bot.launch();
|
||||
|
||||
// Сборщик заявок и комментариев из GLPI
|
||||
|
||||
let dataId = JSON.parse(fs.readFileSync(dir + "/data/dataId.json"));
|
||||
|
||||
(async () => {
|
||||
let counter = 0; // счетчик для выполнения функции refreshStatus()
|
||||
while (true) {
|
||||
try{
|
||||
|
||||
// Собираем заявки
|
||||
|
||||
let listTickets = await glpm.getAllItems('Ticket', 4);
|
||||
for(let i = 5; i >= 0; i--){
|
||||
let ticketId;
|
||||
if(!listTickets) break;
|
||||
if(!listTickets[i]) continue;
|
||||
ticketId = listTickets[i].id;
|
||||
if(ticketId <= dataId.ticket) continue;
|
||||
if(listTickets[i].users_id_recipient != conf.glpiConfig.user_id){
|
||||
let usersArray = await glpm.getUsers(ticketId);
|
||||
let authorEmail;
|
||||
if(!usersArray[0]) continue;
|
||||
if(usersArray[0].hasOwnProperty('alternative_email') && usersArray[0].alternative_email){
|
||||
authorEmail = usersArray[0].alternative_email;
|
||||
}else{
|
||||
let temp = await glpm.getItem("User", usersArray[0].users_id);
|
||||
authorEmail = temp.firstname + ' ' + temp.realname;
|
||||
}
|
||||
let text = await htmlToText(listTickets[i].content);
|
||||
let messageText = `🟢 <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">№${ticketId}</a></b>\n\n`;
|
||||
messageText += `<b>Автор заявки: </b>${authorEmail}\n`;
|
||||
messageText += `<b>Проблема: </b>${listTickets[i].name}\n<b>Описание: </b>`;
|
||||
messageText += text;
|
||||
if(messageText.length > 600){
|
||||
messageText = `${messageText.substring(0, 500)} + '\n\n<b><a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">Читать дальше</a></b>`;
|
||||
}
|
||||
let messg = await bot.telegram.sendMessage(conf.supportChatId, messageText, {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: { inline_keyboard: cns.inlineKeyboards.open }
|
||||
});
|
||||
let silentInfo = `<a href="http://example.com///${messg.message_id}">​</a>`;
|
||||
await bot.telegram.editMessageText(conf.supportChatId, messg.message_id, undefined, silentInfo + messageText, {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: { inline_keyboard: cns.inlineKeyboards.open }
|
||||
});
|
||||
dataId.history[ticketId] = {};
|
||||
dataId.history[ticketId]["messageId"] = messg.message_id;
|
||||
dataId.history[ticketId]["status"] = 1;
|
||||
dataId.ticket = ticketId;
|
||||
}
|
||||
fs.writeFileSync(dir + "/data/dataId.json", JSON.stringify(dataId, null, 3));
|
||||
}
|
||||
|
||||
// Собираем комментарии
|
||||
|
||||
let listComments = await glpm.getAllItems('ITILFollowup', 4);
|
||||
for (let i = 5; i >= 0; i--) {
|
||||
if(!listComments) break;
|
||||
if(!listComments[i]) continue;
|
||||
let commentId = listComments[i].id;
|
||||
if (commentId <= dataId.comment) continue;
|
||||
if (listComments[i].users_id != conf.glpiConfig.user_id){
|
||||
let ticketId = listComments[i].items_id;
|
||||
let text = await htmlToText(listComments[i].content);
|
||||
let user;
|
||||
if (listComments[i].users_id) {
|
||||
let temp = await glpm.getItem("User", listComments[i].users_id);
|
||||
user = temp.firstname + ' ' + temp.realname;
|
||||
} else {
|
||||
let temp = await glpm.getUsers(ticketId);
|
||||
user = temp[0].alternative_email;
|
||||
}
|
||||
await addComment(text, ticketId, user);
|
||||
}
|
||||
dataId.comment = commentId;
|
||||
fs.writeFileSync(dir + "/data/dataId.json", JSON.stringify(dataId, null, 3));
|
||||
}
|
||||
}catch(err){ console.log(err) }
|
||||
await sleep(10000);
|
||||
counter++;
|
||||
await parseTickets(bot, messageData);
|
||||
await parseComments(bot, messageData);
|
||||
if(counter >= 60){
|
||||
await refreshStatus();
|
||||
await refreshStatus(bot, messageData);
|
||||
counter = 0;
|
||||
}
|
||||
}catch(e){
|
||||
fs.appendFileSync(dir + "/logs/logs.json", JSON.stringify(e, null, 3));
|
||||
}
|
||||
await sleep(10000);
|
||||
counter++;
|
||||
}
|
||||
})();
|
||||
|
||||
// Отправка комментария в Telegram
|
||||
|
||||
async function addComment(comment, ticketId, user){
|
||||
try{
|
||||
threadsData = JSON.parse(fs.readFileSync(dir + "/data/threads.json"));
|
||||
if(!threadsData.hasOwnProperty(ticketId)){
|
||||
if(!dataId.history.hasOwnProperty(ticketId)) return;
|
||||
let generalMessageId = dataId.history[ticketId].messageId;
|
||||
await createThread(ticketId, generalMessageId);
|
||||
}
|
||||
let messageText = `<b>Комментарий от ${user}:</b>\n\n${comment}`;
|
||||
if(messageText.length > 2400){
|
||||
messageText = `${messageText.substring(0, 2400)} + '\n\n<b><a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">Читать дальше</a></b>`;
|
||||
}
|
||||
await bot.telegram.sendMessage(conf.supportChatId, messageText, {parse_mode: "HTML", message_thread_id: threadsData[ticketId]["threadId"]});
|
||||
}catch(err){console.log(err)}
|
||||
}
|
||||
|
||||
// обновляет статусы последних 50 заявок
|
||||
|
||||
async function refreshStatus(){
|
||||
let listTickets = await glpm.getAllItems('Ticket', 49);
|
||||
dataId = JSON.parse(fs.readFileSync(dir + "/data/dataId.json"));
|
||||
for(let i = 50; i >= 0; i--){
|
||||
if(!listTickets) break;
|
||||
if(!listTickets[i]) continue;
|
||||
let ticketId = listTickets[i].id;
|
||||
try{
|
||||
if(dataId["history"][ticketId].status != listTickets[i].status && listTickets[i].users_id_recipient != conf.glpiConfig.user_id){
|
||||
let messageId = dataId["history"][ticketId].messageId;
|
||||
let color = '🟢';
|
||||
switch(listTickets[i].status){
|
||||
case 1: color = '🟢'; break;
|
||||
case 2: color = '🔵'; break;
|
||||
case 4: color = '🟠'; break;
|
||||
case 6: color = '⚫'; break;
|
||||
default: color = '⚫';
|
||||
}
|
||||
let usersArray = await glpm.getUsers(ticketId);
|
||||
let authorEmail;
|
||||
if(usersArray[0].hasOwnProperty('alternative_email') && usersArray[0].alternative_email){
|
||||
authorEmail = usersArray[0].alternative_email;
|
||||
}else{
|
||||
let temp = await glpm.getItem("User", usersArray[0].users_id);
|
||||
authorEmail = temp.firstname + ' ' + temp.realname;
|
||||
}
|
||||
let text = await htmlToText(listTickets[i].content);
|
||||
let messageText = `${color} <b>ЗАЯВКА <a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">№${ticketId}</a></b>\n\n`;
|
||||
messageText += `<b>Автор заявки: </b>${authorEmail}\n`;
|
||||
messageText += `<b>Проблема: </b>${listTickets[i].name}\n<b>Описание: </b>`;
|
||||
messageText += text;
|
||||
if(messageText.length > 600){
|
||||
messageText = `${messageText.substring(0, 500)} + '\n\n<b><a href="${glpiUrl}front/ticket.form.php?id=${ticketId}">Читать дальше</a></b>`;
|
||||
}
|
||||
let silentInfo = `<a href="http://example.com///${messageId}">​</a>`;
|
||||
let inKeyboard;
|
||||
if(listTickets[i].status == 5 || listTickets[i].status == 6){
|
||||
inKeyboard = JSON.parse(JSON.stringify(cns.inlineKeyboards.close));
|
||||
}else{
|
||||
inKeyboard = JSON.parse(JSON.stringify(cns.inlineKeyboards.open));
|
||||
}
|
||||
if(threadsData.hasOwnProperty(ticketId)){
|
||||
delete inKeyboard[0][1].callback_data;
|
||||
inKeyboard[0][1]["url"] = `t.me/c/${conf.supportChatId.substring(4)}/${threadsData[ticketId].threadId}`;
|
||||
}
|
||||
await bot.telegram.editMessageText(conf.supportChatId, messageId, undefined, silentInfo + messageText, {
|
||||
parse_mode: 'HTML',
|
||||
reply_markup: { inline_keyboard: inKeyboard }
|
||||
});
|
||||
dataId.history[ticketId]["status"] = listTickets[i].status;
|
||||
}
|
||||
}catch(err){}
|
||||
}
|
||||
fs.writeFileSync(dir + "/data/dataId.json", JSON.stringify(dataId, null, 3));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user