Commit 709e2640 authored by Azizov Aziz 's avatar Azizov Aziz

Upd tgc giles-2

parent a676a83d
node_modules/
sessions/
tmp/
*.log
_td*
test
......
const { getFromJson, parseUrl, getBinarySize, rlog } = require('./../tools');
const axios = require('axios')
var moment = require('moment')
// import dateFormat, { masks } from "dateformat";
// const { Domains } = require('../models')
var DB
var _bxApps = {}
var _this
// var _bxAppId = 'app.61aa25df83c440.62179996'
// var _bxAppSecret = 'J64V70h2sKN0M6vbE5hn04sIMcRqwRPm0D5FUaOHh0cDUEJK6i'
var _bxAppId = 'app.615c3861d65128.01703947'
var _bxAppSecret = '1B0xH5c1YuBUXYVTmNOuQN94MKGWr6cTPsomUWDM1X0WwzGq0T'
var _oauthEndpoint = "https://oauth.bitrix.info/oauth/token/"
var _expiresIn = 3600
var _logTimeMask = 'YY_DD_MM_HH_mm_ss'
var selfDomain = 'https://tgc.4u.uz'
function BXAPI() {
this.isInited = false
this.odomain = false
_this = this
// this.bxAppId = _bxAppId
// this.bxAppSecret = _bxAppSecret
};
BXAPI.prototype.init = function(DBC,domain,datas,query) {
rlog("--= BXAPI Domain: ", domain, datas)
DB = DBC
this.domaindatas = {
serverEndpoint: "https:\/\/"+domain+"\/rest\/",
expiresIn: false,
access_token: false,
refresh_token: false,
domain: domain,
domainId: false,
}
return this.installApp(domain, datas, query)
}
BXAPI.prototype.bitrixRegisterEvents = function(cbk) {
this.call('event.bind', {
event: 'OnImConnectorMessageAdd',
handler: selfDomain
})
this.call('event.bind', {
event: 'OnImConnectorMessageDelete',
handler: selfDomain
})
this.call('event.bind', {
event: 'OnImConnectorMessageUpdate',
handler: selfDomain
})
this.call('event.bind', {
event: 'OnImConnectorStatusDelete',
handler: selfDomain
})
cbk()
}
BXAPI.prototype.bitrixUnregisterEvents = function(cbk) {
this.call('event.unbind', {
event: 'OnImConnectorMessageAdd',
handler: selfDomain
})
this.call('event.unbind', {
event: 'OnImConnectorMessageDelete',
handler: selfDomain
})
this.call('event.unbind', {
event: 'OnImConnectorMessageUpdate',
handler: selfDomain
})
this.call('event.unbind', {
event: 'OnImConnectorStatusDelete',
handler: selfDomain
})
}
BXAPI.prototype.installApp = function(domain,datas, query) {
return new Promise(function(resolve,reject){
rlog("---=== BXAPI Initing begin...");
var dateNow = Date.now()/1000;
if(datas && typeof datas.AUTH_ID != 'undefined' && query["INSTALLAPP"]=="YES"){
rlog("---=== BXAPI INSTALLAPP when Initing...");
// DB.Domains.findOne({ where: { name: domain } })
DB.Domains.findAll({where:{ name: domain }})
.then(o => o[0])
.then(function(odomain){
_this.odomain = odomain
if(odomain.id){
var insOptions = {
status: datas.status,
access_token: datas.AUTH_ID,
}
if(datas.REFRESH_ID){
insOptions.refresh_token = datas.REFRESH_ID
}
if(datas.member_id){
insOptions.member_id = datas.member_id
}
if(datas.AUTH_EXPIRES){
insOptions.expires = dateNow+datas.AUTH_EXPIRES
}
odomain.update(insOptions)
.then(function(resUpd){
console.log("--== Bx Install App Domain Update RES: ", resUpd, insOptions, odomain)
_this.setInited()
_this.bitrixRegisterEvents(function(){
resolve(odomain)
})
})
.catch(function(err){
console.error("--== ERROR Bx Update: ", err)
reject("--== ERROR Bx Update: ", err)
});
}
})
}
else {
// DB.Domains.findOne({ where: { name: domain } })
rlog("---=== BXAPI just when Initinted...");
DB.Domains.findOne({where:{ name: domain }})
// .then(o => o[0])
.then(function(odomain){
_this.odomain = odomain
rlog("--== Bx Domain info Found: ", _this.odomain.refresh_token)
_this.setInited()
resolve(odomain);
})
.catch(function(err){
console.error("--== Bx Domain info not Found: ", domain, err )
reject("--== Bx Domain info not Found: ", domain, err )
});
}
})
}
/**
*
* @param {string} openlineId
* @param {string} lineId
* @param {object[]} message
* @returns
*/
BXAPI.prototype.addMessage = async function(openlineId, lineId, message, chat) {
var node = {
CONNECTOR: openlineId,
LINE: lineId,
MESSAGES: [
{
user: {
id: message.sender_id.user_id,
name: chat.first_name,
first_name: chat.first_name
},
message: {
id: message.id,
date: message.date,
text: message.content.text.text,
},
chat: {
id: chat.id,
name: chat.title,
}
}
]
}
return new Promise((resolve) => {
_this.call('imconnector.send.messages', node, function (result) {
resolve(result)
})
})
}
BXAPI.prototype.setInited = function() {
this.isInited = true
}
BXAPI.prototype.isInit = function() {
rlog("--== BXAPI check Inited: ", this.isInited);
return this.isInited
}
BXAPI.prototype.getAppInfo = function(cbk) {
this.call('app.info',{},function(res){
rlog('--== App INFO BX: ', res)
})
}
BXAPI.prototype.call = function(method, params, cbk) {
var _this = this;
var domain = this.odomain;
var timeNow = Math.ceil(Date.now()/1000);
if(typeof params == 'undefined'){ params = {} }
rlog("--== BXAPI Call Method: ", method, params);
rlog("--== BXAPI Call Domain EXPR: ", domain.expires, timeNow, domain.expires<timeNow);
if(domain.expires<timeNow){
this.newAuth(function(){
_this.callRest(method, params).then(cbk)
})
return
}
// odomain.refresh_token
this.callRest(method, params)
.then(cbk)
.catch(function(data){
rlog("--== BXAPI Call ERROR: ", method, data)
if(data.error=="expired_token"){
this.newAuth(function(){
rlog("--=== ["+domain.name+"] RE RESTING from token refreshed... RES AUTH: ")
_this.callRest(method, params).then(cbk)
})
return
}
throw data
})
}
BXAPI.prototype.getToken = function(domain) {
}
BXAPI.prototype.newAuth = function(cbk) {
var _this = this
var refresh_token = this.odomain.refresh_token;
rlog("--== BXAPI Called Auth Proc: ", refresh_token);
var timeNow = Math.ceil(Date.now()/1000);
this.callRest('auth', {
'client_id': _bxAppId,
'grant_type': 'refresh_token',
'client_secret': _bxAppSecret,
'refresh_token': refresh_token,
}).then(function(resAuth){
rlog("--=== ["+_this.odomain.name+"] RES AUTH: ", resAuth)
_this.odomain.update({
access_token: resAuth.access_token,
refresh_token: resAuth.refresh_token,
expires: resAuth.expires
}).then(cbk)
// odomain.update({
// access_token: datas.AUTH_ID,
// expires: timeNow+datas.AUTH_EXPIRES
// })
// .then(function(resUpd){
// console.log("--== Bx Install App Domain Update RES: ", resUpd, insOptions, odomain)
// _this.odomain = odomain
// _this.setInited()
// resolve(odomain);
// })
// .catch(function(err){
// console.error("--== ERROR Bx Update: ", err)
// reject("--== ERROR Bx Update: ", err)
// });
// cbk()
})
}
BXAPI.prototype.callRest = function(method, params) {
var _this = this
var node = {...params}
var h = 'post'
return new Promise(function(resolve,reject){
rlog("--== BXAPI callRest Begin: ", method, params);
// console.log("--== BXAPI callRest domainDATAS: ", _this.domaindatas);
if(method=="auth"){
var url = _oauthEndpoint;
node = {}
h = 'get'
node.params = params
}
else {
var url = _this.domaindatas.serverEndpoint+''+method+ '.json';
node.access_token = _this.odomain.access_token
}
// this.domain
// axios.post('https://4uz.bitrix24.ru/rest/6/gkqq8twb8tw0rh05/profile.json', params)
axios[h](url, node)
.then((res) => {
rlog('callRest url, params ====>', url, node, res.status, res.data)
if (res.data.error) {
console.error('CALL_REST ERROR:',method, res)
return reject(res.data)
}
resolve(res.data)
}).catch((err) => {
rlog(`ERROR: callRest NAME: ${_this.odomain.name} ${method} MEMBER_ID: ${_this.odomain.member_id} URL: ${url} PARAMS: `, node, err.message)
console.error(err.message);
console.error('CALL_REST ERROR:',method, err)
reject(err)
});
})
}
module.exports = BXAPI
This diff is collapsed.
const http = require('http')
const url = require('url')
const process = require('process')
// const TGClient = require('./tgc')
var path = require('path')
const pm2 = require('pm2')
var allClients = {};
var pm2Connected = false;
var myNumber = 1;
const host = '192.168.130.10'
const port = 51000
function parseUrl(url)
{
var split = /^.*\/(getAuth|getMe|getUser|getUserFullInfo)_([^?]*)\??(.*)$/gi.exec(url);
var final_params = {};
split[3].split('&').forEach(function(pair){
var ps = pair.split('=');
final_params[ps[0]] = ps[1];
});
return {
name: split[1],
value: split[2],
params: final_params
};
}
function list(cbk){
if(pm2Connected){
pm2.list((err, list) => {
var processes = [];
if(typeof list != 'undefined' && list){
var pnn = 1;
list.forEach(function(app){
console.log("-= PM2 Listing processes EACH: ", app);
var currProcc = {
pm_id: app.pm_id,
pm_name: app.name,
pm_monit: app.monit,
pm_status: (app.pm2_env && app.pm2_env.status && app.pm2_env.status=="online")
};
if(app.name.substring(0,4)=="tgc_"){
var currClNumber = app.name.substring(4);
if(!allClients[currClNumber]){
allClients[currClNumber] = {
name: app.name,
pm_id: app.pm_id,
status: currProcc.pm_status
}
}
else {
allClients[currClNumber]['status'] = currProcc.pm_status
}
}
processes.push(currProcc);
if(pnn==list.length){
if(typeof cbk != 'undefined'){
cbk(processes);
}
}
else {
pnn = pnn+1;
}
})
}
});
}
}
function state(clientNumber){
console.log("-= Starting TGClient for allClients: ", allClients);
console.log("-= Starting TGClient for allClients NUM: ", allClients[clientNumber]);
return { result: (allClients[clientNumber] && allClients[clientNumber]['status']) ? true : false };
}
function start(clientNumber){
console.log("-= Starting TGClient for NUMBER: ", clientNumber);
allClients[clientNumber] = {}
if(pm2Connected){
list(function(){
if(!allClients[clientNumber]['status']){
pm2.start({
script : 'tgc.js',
name : 'tgc_'+clientNumber,
args : clientNumber
}, function(err, startedApp) {
if (err) {
console.error(err)
return pm2.disconnect()
}
if(startedApp) {
startedApp.forEach(function(app){
if(app.name=='tgc_'+clientNumber){
allClients[clientNumber]['name'] = app.name
allClients[clientNumber]['pm_id'] = app.pm_id
}
})
}
console.log("-= Starting for startedApp: ", startedApp);
senddata(clientNumber, {cmd:'startClient'})
})
}
})
}
// const tclient = new TGClient(clientNumber);
// tclient.start();
// allClients[clientNumber]['tgclient'] = tclient;
// console.log('---=======- CLIENT CNT [ '+Object.keys(allClients).length+' ] ---------')
return {result:true};
}
function stop(clientNumber){
console.log("--== STOPing COMMAND: ", pm2Connected, clientNumber, allClients);
// allClients[clientNumber]['tgclient'].stop();
if(pm2Connected){
console.log("--== STOPing pm ID: ", allClients[clientNumber]);
var pmIden = (allClients[clientNumber] && allClients[clientNumber]['pm_id']) ? allClients[clientNumber]['pm_id'] : 'tgc_'+clientNumber;
pm2.stop(pmIden,
function(err, stopedApp) {
if (err) {
console.error(err)
return pm2.disconnect()
}
console.log("-= Starting for stopedApp: ", stopedApp);
if(typeof stopedApp.status != 'undefined' && stopedApp.status=='stopped'){
allClients[clientNumber]['status'] = false;
}
else if(typeof stopedApp.pm2_env != 'undefined' && typeof stopedApp.pm2_env.status != 'undefined' && stopedApp.status=='stopped'){
allClients[clientNumber]['pm_id'] = stopedApp.pm_id;
allClients[clientNumber]['status'] = false;
}
})
}
return {result:true};
}
function restart(clientNumber){
console.log("--== RESTARTing COMMAND: ", clientNumber);
if(pm2Connected && allClients[clientNumber] && allClients[clientNumber]['pm_id']){
pm2.restart(allClients[clientNumber]['pm_id'], {},
function(err, restartedApp) {
if (err) {
console.error(err)
return pm2.disconnect()
}
console.log("-= Starting for restartedApp: ", restartedApp);
})
}
return {result:true};
}
function senddata(clientNumber, data){
console.log("--== SENDing Data to: ", clientNumber, data);
if(pm2Connected && allClients[clientNumber] && allClients[clientNumber]['pm_id']){
pm2.sendDataToProcessId({
// id of procces from "pm2 list" command or from pm2.list(errback) method
id : allClients[clientNumber]['pm_id'],
// process:msg will be send as 'message' on target process
type : 'process:msg',
// Data to be sent
data : data,
topic: true
}, function(err, res) {
console.log("-= SendData ERROR: ", err, res);
})
}
}
var getString = (o) => {
if (o !== null) {
if (typeof o === 'string') {
return o;
} else {
return JSON.stringify(o);
}
} else {
return null;
}
}
const server = http.createServer((req, res) => {
//getAuthorizationState
// let url=req.url.parse(req.originalUrl);
// let page = url.parse(uri).path?url.parse(uri).path.match('^[^?]*')[0].split('/').slice(1)[0] : '';
const path = url.parse(req.url).path.split('/');
var allMethods = ['start','stop','restart','state','list'];
myNumber = myNumber+1;
console.log(' ')
console.log(' ')
console.log('---=======- [ '+myNumber+' ] ---------')
console.log('This is the METHOD:' + req.method)
// console.log('This is the URL:' + url)
console.log('This is the path:', path)
console.log('This is the REQUEST Hdrs:')
console.log(req.headers)
var param = (path[2] && path[2].length) ? path[2].replace(/\D/g, '') : false;
console.log('This is the PARAM:', param)
if(path[1] && path[1].length && allMethods.indexOf(path[1])!='-1'){
console.log(' ')
console.log(' --------======== COMMAND ['+path[1]+'] =======-----------')
console.log("--== CLIENTS: ", allClients);
console.log(' ')
if(param){
var result = eval(path[1])(param);
console.log(' -= Cmd Params:', param)
}
else {
var result = eval(path[1]);
}
console.log(' --------======== COMMAND RES:', result)
if(result){
var resStr = getString(result)
console.log(' --------======== COMMAND RES TEXT:', resStr)
res.statusCode = 200
res.setHeader('Content-Type', 'application/json')
res.end(resStr)
}
}
else {
res.statusCode = 404
res.setHeader('Content-Type', 'text/plain')
res.end('Command not found\n')
}
})
server.listen(port, host, () => {
console.log(`Server listens http://${host}:${port}`)
pm2.connect(function(err) {
if (err) {
pm2Connected = false
console.error(err)
process.exit(2)
}
pm2Connected = true
console.log('-= PM2 connected!')
pm2.launchBus(function(err, pm2_bus) {
console.log("-= PM2 incom LaunchBUS Started!",err)
pm2_bus.on('process:msg', function(packet) {
console.log("-= PM2 incom PACKET:",packet)
})
})
list();
});
})
// server.listen(port, host, () => {
// console.log(`Server listens http://${host}:${port}`)
// })
const { Sequelize, DataTypes, Model } = require('sequelize');
class Accounts extends Model {
static init(sequelize) {
super.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
hash: { type: DataTypes.STRING(100) },
tg_id: { type: DataTypes.STRING(100) },
bx_id: { type: DataTypes.STRING(100) },
phone_number: { type: DataTypes.STRING(100) },
photo: { type: DataTypes.STRING(100) },
first_name: { type: DataTypes.STRING(100) },
last_name: { type: DataTypes.STRING(100) },
user_name: { type: DataTypes.STRING(100) },
last_status: { type: DataTypes.STRING(50) },
last_synch: { type: DataTypes.STRING(25) },
line_id: { type: DataTypes.STRING(5) },
status: { type: DataTypes.BOOLEAN },
disabled: { type: DataTypes.BOOLEAN, defaultValue:0 },
ignore: { type: DataTypes.BOOLEAN },
is_whitelist: { type: DataTypes.BOOLEAN },
is_blacklist: { type: DataTypes.BOOLEAN },
default: { type: DataTypes.BOOLEAN, defaultValue:0 },
domain_id: { type: DataTypes.INTEGER },
tg_status: { type: DataTypes.INTEGER, defaultValue:0 },
}, {
// Other model options go here
sequelize, // We need to pass the connection instance
// modelName: 'Accounts',
tableName: 'account',
timestamps: false
})
}
// successAuth(cbk){
// // const project = await this.findOne({ where: { title: 'My Title' } });
// //status
// //disabled
// //tg_status
// this.setDataValue('status', 1);
// this.setDataValue('disabled', 0);
// this.setDataValue('tg_status', 3);
// console.log("--== TG Status Before saving...");
// this.save().then(function(){
// console.log("--== TG Status SETTED: ", 9);
// if(typeof cbk == 'function'){ cbk() }
// })
// }
}
module.exports = Accounts
\ No newline at end of file
const { DataTypes, Model } = require('sequelize');
class Billing extends Model {
static init(sequelize) {
super.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
from: { type: DataTypes.STRING(100) },
to: { type: DataTypes.STRING(100) },
domain_id: { type: DataTypes.INTEGER },
blocked: { type: DataTypes.BOOLEAN },
created_at: { type: DataTypes.STRING(100) },
tg_user_info: { type: DataTypes.BOOLEAN }
}, {
// Other model options go here
sequelize, // We need to pass the connection instance
// modelName: 'Domains',
tableName: 'billing',
timestamps: false
})
}
}
module.exports = Billing
\ No newline at end of file
const { Sequelize, DataTypes, QueryTypes, Model } = require('sequelize');
const Accounts = require('./accounts')
var sqz;
class Chats extends Model {
static init(sequelize) {
sqz = sequelize;
super.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
chat_id: { type: DataTypes.BIGINT(20) },
username: { type: DataTypes.STRING(100) },
first_name: { type: DataTypes.STRING(150) },
last_name: { type: DataTypes.STRING(150) },
title: { type: DataTypes.STRING(150) },
phone_number: { type: DataTypes.STRING(30) },
photo_id: { type: DataTypes.STRING(250) },
type: { type: DataTypes.STRING(30) },
full: { type: DataTypes.TEXT },
}, {
// Other model options go here
sequelize, // We need to pass the connection instance
// modelName: 'Chats',
tableName: 'chats',
timestamps: false
})
}
}
Chats.chatsByAccountId = function(p,cbk){
if(!p.id && !p.id){ cbk([]); return; }
Accounts.findOne({ where:{id:p.id} })
.then(function(acc){
console.log("-= Got Account number: ", acc.phone_number);
if(!acc.phone_number){ cbk([]); return; }
var limit = (p.limit && p.limit<51) ? p.limit : 10;
var offset = (p.offset) ? p.offset : 0;
var selects = (p.select && p.select=="chat_ids") ? 'u.`tg_chat_id`' : 'u.`user_phone`, u.`tg_chat_id`, u.`bx_chat_id`, u.`ignoring`, u.`bx_agent_id`, c.*';
var order = ''; //TODO
var cquery = 'SELECT ';
var where = " WHERE u.`user_phone` = '"+acc.phone_number+"'"; // AND c.`chat_id`!=NULL
var whereToCountNumber = where; // . ' AND u.`tg_chat_id` = ' . $data['id'];
if(p.filter){
where = Object.entries(p.filter).reduce((prev, [fk, fv], i) => {
var fval = (fk=="chat_id") ? fv : "'"+fv+"'";
if(fk=="query"){
var retwr = prev + " AND (c.`chat_id` LIKE '%"+fv+"%' OR c.`username` LIKE '%"+fv+"%' OR c.`title` LIKE '%"+fv+"%' OR c.`phone_number` LIKE '%"+fv+"%')";
}
else{
var frFld = (fk=="type") ? ' u.`'+fk+'`' : ' c.`'+fk+'`';
var retwr = prev + " AND"+frFld+" LIKE '%"+fv+"%'"
}
return retwr
}, where)
// p.filter.forEach((item, index) => { })
}
cquery = cquery
+ selects
+ " "
+ "FROM `user_chats` u "
+ "LEFT JOIN chats c ON u.`tg_chat_id` = c.`chat_id` "
+ where
+ order;
if(limit){
var nlimit = limit+1;
cquery += " LIMIT "+nlimit;
}
if(offset){
cquery += " OFFSET "+offset;
}
console.log("--= READY Query:", cquery);
sqz.query(
// "SELECT u.`user_phone`, u.`tg_chat_id`, u.`bx_chat_id`, u.`ignoring`, u.`bx_agent_id`, c.* FROM `user_chats` u LEFT JOIN chats c ON u.`tg_chat_id` = c.`chat_id` WHERE u.`user_phone` = '998994412860' LIMIT 10 OFFSET 0",
cquery,
{ type: QueryTypes.SELECT }
)
.then(function(acChats){
console.log("-= Got AccountChats: ", acChats);
if(p.select && p.select=="chat_ids"){
var arReturn = acChats.map((achat, i) => {
return achat.tg_chat_id
}, [])
cbk(arReturn);
return;
}
var allCount = 0;
var queryCnt = '';
var IsNext = false;
var arResult = {
count: acChats.length,
limit: limit,
offset: offset
};
if(p.filter){
if(Object.keys(p.filter).length == 1 && p.filter.type){
queryCnt = "SELECT COUNT(`user_phone`) as chatcount FROM `user_chats` u" + where
}
}
else {
queryCnt = "SELECT COUNT(`user_phone`) as chatcount FROM `user_chats` u" + whereToCountNumber;
}
sqz.query(
queryCnt, //"SELECT COUNT(`user_phone`) as chatcount FROM `user_chats` u WHERE u.`user_phone` = 998994412860",
{ type: QueryTypes.SELECT }
)
.then(function(accCnt){
console.log("accCnt:", accCnt)
if(accCnt[0] && accCnt[0].chatcount){
allCount = accCnt[0].chatcount
arResult.allcount = allCount
var pagesCount = Math.round(allCount/limit);
var pgElmCnt = pagesCount*limit;
if( pgElmCnt < allCount ){
pagesCount = pagesCount+1;
}
}
if(pagesCount){
arResult.pages = pagesCount
}
if( nlimit && limit && arResult.count>limit){
acChats.pop();
arResult.count = arResult.count-1;
arResult.isnext = true;
}
else {
arResult.isnext = false;
}
arResult.list = acChats;
arResult.agents = [];
cbk(arResult)
})
})
.catch(console.error);
});
// Accounts.findOne({ where:{id:aid} })
// .then(function(acc){
// console.log("-= Got Account: ", acc.phone_number);
// }); //where: { title: 'My Title' }
// this.findOne().then(function(ress){
// cbk(ress.dataValues);
// }); //where: { title: 'My Title' }
}
module.exports = Chats
const { DataTypes, Model } = require('sequelize');
class Domains extends Model {
static init(sequelize) {
super.init({
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true
},
hash: {
type: DataTypes.STRING(100)
},
name: {
type: DataTypes.STRING(100)
},
first_install: {
type: DataTypes.BOOLEAN
},
created_at: {
type: DataTypes.STRING(100)
},
on_priority: {
type: DataTypes.BOOLEAN
},
access_token: { type: DataTypes.STRING(200) },
application_token: { type: DataTypes.STRING(200) },
refresh_token: { type: DataTypes.STRING(200) },
expires: { type: DataTypes.BIGINT },
member_id: { type: DataTypes.STRING(200) },
status: { type: DataTypes.STRING(10) },
user_id: { type: DataTypes.INTEGER },
}, {
// Other model options go here
sequelize, // We need to pass the connection instance
// modelName: 'Domains',
tableName: 'domains',
timestamps: false
})
}
setBxApiKeys(domain, req){
this.findOne({ where: { name: domain } })
.then(function(odomain){
console.log("--= DB Domain GET: ", odomain.id, odomain.name)
if(odomain.id){
req.domain_id = odomain.id
}
})
.catch(function(err){
console.error("--== DB Error:", err);
res.status(404).send({message: 'Домен не найден!'});
})
}
getBxApiKeys(domain){
return "Key_"+domain;
// this.update({
// status: 1
// }, {
// where: {
// name: domain
// }
// });
// Domains.findOne({ where: { name: domainId } })
// .then(function(odomain){
// console.log("--= getBxApiKeys Domain GETing: ")
// if(odomain && odomain.id){
// console.log("--= getBxApiKeys Domain GETED: ", odomain.id, odomain.name)
// // domain = odomain;
// // domainId = odomain.id;
// // domainName = odomain.name;
// }
// })
// .catch(console.error)
}
}
module.exports = Domains
\ No newline at end of file
const { getFromJson, parseUrl, getBinarySize, rlog } = require('./../tools');
const Sequelize = require('sequelize');
const Domains = require('./domains')
const Accounts = require('./accounts')
const Chats = require('./chats')
const Billing = require('./billing')
// rlog("---==== DOMAINs test:", Domains)
// const dbc = new Sequelize(
// 'tgc_dev',
// 'tug',
// 'mteam',
// {
// host: 'localhost',
// dialect: 'mysql'
// }
// );
const dbc = new Sequelize(
'tgcjs',
'tgcjs',
'tgcjsp@ss',
{
host: 'localhost',
dialect: 'mysql'
}
);
const dbn = {'name':333}
// async function initTables(dbc) {
// var dataTables = {}
// dataTables.Domains = await Domains.init(dbc);
// dataTables.Accounts = await Accounts.init(dbc);
// dataTables.Chats = await Chats.init(dbc);
// return dataTables;
// }
// export default initTables(dbc)
module.exports = async function (cbk) {
await dbc.authenticate()
rlog('DB Connection has been established successfully.');
await Domains.init(dbc)
await Accounts.init(dbc)
await Chats.init(dbc)
await Billing.init(dbc)
if(typeof cbk == 'function'){
cbk({
Domains,
Accounts,
Chats,
dbc,
Billing
});
}
return {
Domains,
Accounts,
Chats,
Billing,
dbc
}
}
// module.exports = {
// Domains,
// Accounts,
// Chats
// }
\ No newline at end of file
{
"test": "Test",
"Domain": "Domain"
}
\ No newline at end of file
{
"test": "Тест",
"Domain": "Домен",
"enter_phone": "Номер телефона аккаунта с Telegram",
"next": "Далее",
"ok": "Ок",
"cancel": "Отмена",
"edit": "Редактировать",
"edited": "Изменено",
"remove": "Удалить",
"removed": "Удалено",
"updated": "Обновлено",
"success": "Готово!",
"submit": "Отправить",
"page_main": "Главная",
"add_tg": "Добавить",
"on_off": "Вкл./Выкл.",
"page_billing": "Биллинг",
"billing_domain": "Домен",
"billing_status": "Статус",
"billing_reg_date": "Дата регистрации",
"billing_from": "Дата активации",
"billing_to": "Активен до",
"billing_remain": "Осталось",
"billing_on_proiritet": "В приоритете",
"no_phone_found": "Номер не указан",
"domain_time_expired": "Ошибка синхронизации. Свяжитесь с <a class=\"text-blue-600\" href=\"https:\/\/t.me\/technounit\" target=\"_blank\"> разработчиками <\/a>",
"domain_has_blocked": "Проблема с доменом. Свяжитесь с <a class=\"text-blue-600\" href=\"https:\/\/t.me/technounit\" target=\"_blank\"> разработчиками <\/a>",
"prev_page": "Назад",
"code_sent_call": "Ожидайте звонка",
"account_not_found": "Аккаунт не найден",
"tg_account_not_found": "Аккаунт с таким номером не найден",
"tg_auth_succes": "Авторизация успешна!",
"tg_status_1": "Включён",
"tg_status_0": "Отключен",
"tg_status_9": "Синхронизация",
"tg_account__add_form__title": "Добавить аккаунт",
"account_settings": "НАСТРОЙКИ",
"ignore_all_messages": "Игнорировать всех",
"whitelist": "Whitelist",
"blacklist": "Blacklist",
"tooltip_whitelist": "При включении этой опции, сообщения в отмеченных чатах будут регистрироваться в вашем Битрикс24, даже если включён режим <Отключить входящие>.",
"tooltip_blacklist": "При включении данной опции, сообщения в отмеченных чатах будут игнорироваться вашим Битрикс24, даже при неустановленном режиме <Отключить входящие>",
"tooltip_ignore_all_messages": "При включении этой опции будет отключён приём сообщений из всех чатов данного аккаунта, за исключением находящихся в WHITELIST, но при этом вы сможете писать от имени этого аккаунта. При отключении данной опции, будет разрешён приём сообщений из всех чатов данного аккаунта, за исключением находящихся в BLACKLIST.",
"tooltip_to_all": "Будут выделены все данные согласно типа вкладки, на которой вы находитесь. То есть если отметить этот пункт, находясь на вкладке БОТЫ, то будут выделены все боты. И так далее.",
"search_empty": "Везде",
"search_by_name": "По Названию",
"search_by_phone": "По Телефону",
"search_by_username": "По Юзернейм",
"code_sent_tg": "Код подтверждения отправлен в ваш Telegram",
"tg_code_error": "Неверный код! При повторной ошибке, этот аккаунт может быть заблокирован на несколько часов!",
"phone_need_signup": "Зайдите в Telegram с мобильного телефона",
"ajax__error": "Ошибка системы #678",
"ajax_error": "Возникла неизвестная ошибка!",
"send_code_timeout": "Проверьте в Telegram. Если код подтверждения не пришел, попробуйте снова позже.",
"enter_code_message": "Ожидается код подтверждения",
"enter_cloud_password": "Ожидается пароль двухфакторной аутентификации",
"enter_code": "Введите код подтверждения:",
"enter_password": "Введите пароль двухфакторной аутентификации",
"add_account": "Добавить аккаунт",
"add_to_contacts": "Добавить контакт",
"make_newsletter": "Рассылка",
"in_developing": "В разработке...",
"password_auth_error": "Неверный пароль двухфакторной аутентификации. При повторной ошибке этот аккаунт может быть заблокирован на несколько часов!",
"default_tg_account": "Аккаунт по умолчанию",
"set_default_tg_account": "Назначить ТГ-аккаунтом по умолчанию",
"search": "Поиск",
"are_you_sure": "Хотите удалить аккаунт?",
"chat_name": "Название",
"chat_phone": "Телефон",
"chat_username": "Юзернейм",
"chat_all": "Все",
"chat_private": "Контакты",
"chat_channel": "Каналы",
"chat_group": "Группы",
"chat_bot": "Боты",
"chat_selected": "Выбрано:",
"selected": "Выбрано",
"account_title": "Имя",
"avatar": "Аватар",
"phone": "Телефон",
"status": "Статус",
"account_is_default": "По умолчанию",
"add_to_whitelist": "В Whitelist",
"add_to_balcklist": "В Blacklist",
"remove_from_blacklist": "Удалить из Blacklist",
"remove_from_whitelist": "Убрать из Whitelist",
"select_all": "Для всех",
"choose_action": "Действие...",
"data_not_found": "Данные не найдены",
"download_begin": "Начался загрузка данных на ваш Битрикс!",
"rows_per_page": "Элементов на стр.:",
"rows_pagentaion": "${from}-${to} из ${count}",
"extend_by_days": "Продлить ${domain} (дни)",
"ajax__fail_error": "Неизвестная ошибка",
"ajax__error": "",
"monday": "Пн",
"tuesday": "Вт",
"wednesday": "Ср",
"thursday": "Чт",
"friday": "Пт",
"saturday": "Сб",
"sunday": "Вс",
"january": "Январь",
"february": "Февраль",
"march": "Март",
"april": "Апрель",
"may": "Май",
"june": "Июнь",
"july": "Июль",
"august": "Август",
"september": "Сентябрь",
"october": "Октябрь",
"november": "Ноябрь",
"december": "Декабрь",
"provider_status": "Состояние",
"provider_status_1": "Активен",
"provider_status_0": "Отключён",
"no_sctipt": "включите Javascript",
"local_storage_disabled": "Для корректной работы приложения необходимо включить localStorage в вашем браузере",
"page_how_to": "Инструкция",
"page_about": "О приложении",
"req_data_except": "Неверные данные",
"user_already_registered": "Этот аккаунт уже подключен",
"auth_success": "Вы успешно авторизованы",
"code_send_tg": "Код активации отправлен в Telegram",
"code_send_sms": "Код активации выслан по СМС",
"auth_impossible": "Авторизация невозможна",
"expect_user_code": "Введите код активации",
"param_except": "Нет нужных данных",
"account_has_reg": "Этот Telegram уже где-то подключен к Битрикс24",
"auth_code_resent": "Код подтверждения переотправлен",
"phone_number_invalid": "Некорректный номер телефона",
"phone_dont_have_telegram": "На этом номере Telegram нет!",
"open_bx_chat": "ЧАТ",
"success_message": "Готово!",
"add_contact": "Добавить в CRM",
"contact_add_sucess": "Контакт(ы) успешно добавлены",
"apply": "Применить",
"tg_user_info": "TG24 Info",
"create_openline_chat": "Чат с %name% %phone% создан",
"error_while_compiling": "Не корректный формат номера",
"banned": "Ваш аккаунт заблокирован на %time% секунд",
"responsible": "Ответственный",
"select_agent_title": "Выберите ответственного",
"select_agent": "Выберите ответственного",
"close": "Закрыть",
"ok": "Ок",
"file_size_more_then_expected": "Отправленный файл больше чем 5 мб. Для скачивании нажмите",
"here": "Сюда"
}
\ No newline at end of file
This diff is collapsed.
......@@ -2,9 +2,12 @@
"name": "tgc",
"version": "1.0.0",
"description": "TelegramClient for Bx24",
"main": "index.js",
"scripts": {
"test": "sndmsg"
"main": "./web/app.js",
"babel": {
"presets": [
"@babel/env",
"@babel/react"
]
},
"repository": {
"type": "git",
......@@ -19,17 +22,79 @@
"author": "Technounit-GROUP",
"license": "ISC",
"dependencies": {
"express": "^4.18.1",
"@emotion/react": "^11.10.4",
"@emotion/styled": "^11.10.4",
"@material-ui/core": "^4.12.4",
"@mui/icons-material": "^5.10.6",
"@mui/material": "^5.10.8",
"@mui/styled-engine-sc": "^5.10.6",
"@mui/x-data-grid": "^5.17.6",
"ag-grid-community": "^28.2.0",
"ag-grid-react": "^28.2.0",
"axios": "^0.24.0",
"bitrix24-promise": "^0.0.5",
"body-parser": "^1.20.1",
"cookie-parser": "^1.4.6",
"dateformat": "^5.0.3",
"express": "^4.18.2",
"express-session": "^1.17.3",
"express-ws": "^5.0.2",
"feather-icons": "^4.29.0",
"form_to_object": "^2.0.0",
"form-serializer": "^2.5.0",
"http": "^0.0.1-security",
"jquery": "^3.6.1",
"js-object-pretty-print": "^0.3.0",
"lodash": "^4.17.21",
"luxon": "^3.0.4",
"moment": "^2.29.4",
"morgan": "^1.10.0",
"morgan-json": "^1.1.0",
"mui-phone-input-ssr": "^1.2.5",
"mysql": "^2.18.1",
"mysql2": "^2.3.3",
"notistack": "^2.0.5",
"pm2": "^5.2.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-feather": "^2.0.10",
"react-icons": "^4.4.0",
"react-redux": "^8.0.4",
"react-router-dom": "^6.4.2",
"redux": "^4.2.0",
"sequelize": "^6.23.2",
"session-file-store": "^1.5.0",
"styled-components": "^5.3.6",
"tdl": "^7.1.0",
"tdl-tdlib-addon": "^1.2.1",
"url": "^0.11.0",
"uuid": "^3.4.0",
"webpack": "^5.74.0",
"winston": "^2.4.6"
},
"devDependencies": {
"logger-service": "^1.0.2"
"@babel/core": "^7.16.5",
"@babel/plugin-proposal-private-methods": "^7.16.5",
"@babel/plugin-transform-classes": "^7.16.5",
"@babel/preset-env": "^7.16.5",
"@babel/preset-react": "^7.16.5",
"autoprefixer": "^10.4.0",
"babel-loader": "^8.2.3",
"css-loader": "^6.5.1",
"logger-service": "^1.0.2",
"node-polyfill-webpack-plugin": "^1.1.4",
"path-browserify": "^1.0.1",
"postcss": "^8.4.5",
"style-loader": "^3.3.1",
"tailwindcss": "^3.0.5",
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production",
"start": "webpack --mode development",
"dev": "webpack --mode development"
}
}
This diff is collapsed.
logs/server_23_04_01.log
const express = require('express');
const app = express();
const path = require('path');
const router = express.Router();
const http = require('http')
const url = require('url')
const process = require('process')
var port="45454";
var host="0.0.0.0";
var testvar = 123;
app.use(express.static('web'))
app.use(express.json({extendet:true,limit:"1mb"}))
app.use(express.urlencoded({ extended: true }))
app.use((req, res, next) => {
var currentURL = req.protocol + '://' + req.get('host') + req.baseUrl + req.path;
console.log(" ");
console.log(" ");
console.log(" ");
console.log(" ");
console.log("-------========== A new path: ["+req.path+"] request: ["+currentURL+"] received at " + Date.now());
// console.log("--== REQUEST QUERY: ", req);
// console.log("--== REQUEST BODY: ", req.body);
// console.log("--== REQUEST HEADERS: ", req.headers);
// console.log("--== REQUEST SESSION: ", req.session);
// console.log("--== REQUEST Cookies: ", req.cookies._ym_uid);
// res.send("OK");
})
app.post('/',function(req, res){
console.log("--== REQUEST QUERY: ", req.query);
console.log("--== REQUEST BODY: ", req.body);
if(1==2){
testvar = 567;
}
res.send("OK");
});
app.listen(port, function(){
console.log(`Server listens http://0.0.0.0:${port}`)
})
// const server = http.createServer((req, res) => {
// // //getAuthorizationState
// // // let url=req.url.parse(req.originalUrl);
// // // let page = url.parse(uri).path?url.parse(uri).path.match('^[^?]*')[0].split('/').slice(1)[0] : '';
// // var allMethods = [
// // 'start',
// // 'stop',
// // 'restart',
// // 'state',
// // 'list',
// // 'installApp',
// // 'getAccounts'
// // ];
// const path = url.parse(req.url).path.split('/');
// // myNumber = myNumber+1;
// console.log(' ')
// console.log(' ')
// console.log('---=======- [ REQ ] ---------')
// console.log(req)
// console.log(req.method)
// console.log(req.body)
// res.statusCode = 200
// // res.setHeader('Content-Type', 'application/json')
// res.end('OK')
// })
// server.listen(port, host, () => {
// console.log(`Server listens http://${host}:${port}`)
// })
// while(true){
// setInterval(function(){
// console.log('-= Log Test Line 1');
// },3000)
// }
// const tgclient = new Client(new TDLib(path.join(__dirname, 'td/build/libtdjson.so')), {
// apiId: 234569,
// apiHash: 'ee77bf549e61d3de158916126c8ef7dd',
......
This diff is collapsed.
const fs = require('fs');
const path = require('path');
var moment = require('moment');
//getFromJson parseUrl getBinarySize
var currDate = moment().format("YY_DD_MM")
var logFileName = path.join(__dirname, '../logs/server_'+currDate+'.log')
// var logStream = fs.createWriteStream(path.join(__dirname, '../logs/server_'+currDate+'.log'))
var getString = (o) => {
if (o instanceof Error) {
return 'Object.keys(o)';
}
if (o !== null) {
if (typeof o === 'string') {
return o;
} else if (typeof o === 'object') {
return JSON.stringify(o);
} else {
return o.toString();
}
}
return o
}
module.exports.getString = getString
module.exports.getBinarySize = function (string) {
return Buffer.byteLength(string, 'utf8');
}
module.exports.getFromJson = function (data) {
var result = data
try {
result = JSON.parse(data);
} catch (e) {
result = data;
}
return result;
}
module.exports.parseUrl = function (url)
{
var split = /^.*\/(getAuth|getMe|getUser|getUserFullInfo)_([^?]*)\??(.*)$/gi.exec(url);
var final_params = {};
split[3].split('&').forEach(function(pair){
var ps = pair.split('=');
final_params[ps[0]] = ps[1];
});
return {
name: split[1],
value: split[2],
params: final_params
};
}
module.exports.rlog = function(...datas){
var nowDate = moment().format("YY_DD_MM")
var currTime = moment().format("HH:mm:ss")
// console.log(currTime,' ',datas)
var logline = (typeof datas != "string") ? getString(datas) : datas;
// var time = Math.floor(Date.now() / 1000);
if(nowDate!=currDate){
logFileName = path.join(__dirname, '../logs/server_'+nowDate+'.log')
}
fs.appendFile(logFileName, "\n--==["+currTime+"]__"+logline, (err) => {
if(err) throw err;
// console.log('Data has been added!');
});
// if(nowDate!=currDate){
// logStream = fs.createWriteStream(path.join(__dirname, '../logs/server_'+nowDate+'.log'))
// }
// logStream.write("\n--==["+currTime+"]__"+logline);
}
const axios = require('axios')
// const net = require('net');
// var SOCKETFILE = '/var/www/tgc/clients/998994412860/tgclient.sock';
// var client = net.createConnection(SOCKETFILE)
// .on('connect', ()=>{
// console.log("Connected.");
// client.write('{cmd:"testst",var:"var123"}')
// client.end();
// })
// var client = net.createConnection(51000, '192.168.130.4')
// .on('connect', ()=>{
// console.log("Connected.");
// client.write('{cmd:"testst",var:"var123"}')
// client.end();
// })
// const socket = net.createConnection(51000, '127.0.0.1', () => {
// socket.write('GET http://127.0.0.1:51000/test123 HTTP', () => {
// socket.destroy();
// // resolve();
// });
// });
axios.post('http://127.0.0.1:51000/tgccmd', {number:"998994412860",tgstatus:"checkonline"})
.then((res) => {
console.log(res.data)
}).catch((err) => {
console.log('callRest URL', url)
console.log('callRest PARAMS', params)
console.error(err.message);
});
\ No newline at end of file
This diff is collapsed.
const { Sequelize, DataTypes, QueryTypes, Model } = require('sequelize');
const DB = require('./models')
DB().then(async function(DBC){
console.log("-= Begin DB Testing...");
// SELECT * FROM `user_chats` WHERE `user_phone` = '998903469302' AND `tg_chat_id` = '1111'
// INSERT INTO `user_chats` (`user_phone`, `tg_chat_id`) VALUES ('998903469302', '1111')
// var a = await DBC.dbc.query(`SELECT * FROM \`user_chats\` WHERE \`user_phone\` = '998971041030' AND \`tg_chat_id\` = '1633705569'`, { type: QueryTypes.SELECT })
// console.log(a)
var resAcc = await DBC.Accounts.findOne({ where: { phone_number: "998903469302" } })
// console.log("-= Account RES: ", resAcc.dataValues);
resAcc.update({ tg_status: 1 })
// console.log("-= Account RES: ", resAcc.dataValues);
// DBC.Chats.chatsByAccountId({id:1229}, function(rrrr){ //,filter:{query:"Surxon"} //,select:'chat_ids'
// console.log("--== ChatRess: ", rrrr);
// });
// DBC.Accounts.findAndCountAll({
// where: {
// domain_id: 29
// },
// // offset: 10,
// // limit: 2
// })
// .then(function({ count, rows }){
// // { count, rows } = accres
// console.log(count);
// var arAccounts = [];
// rows.forEach(function(acc){
// arAccounts.push(acc.dataValues);
// if(arAccounts.length==count){
// console.log("--== arAccounts: ", arAccounts);
// }
// })
// });
// console.log(rows);
})
//var pretty = require('js-object-pretty-print').pretty,
// address,
// value;
// var test = {
// one: '123',
// two: {
// eyy:'222',
// boo:444
// }
// }
// if(test && test.two && test.two.orr && test.two.eyy==123){
// console.log("Tree nasholsya: ",test.two.orr);
// }
// else{
// console.log("Tree NETU! ");
// }
// class WST {
// constructor(test) {
// this.test = test;
// }
// }
// const wstest = new WST(333);
// wstest.myvar = 777;
// console.log(wstest);
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment