├── .gitignore
├── commonLib.js
├── random.js
├── Lang.js
├── qiwi.js
├── CooldownLib.js
├── AmoCRM.js
├── currencyConverter.js
├── Guard.js
├── GoogleSpreadSheet.js
├── GoogleApp.js
├── GoogleAppSync.gs
├── BlocIO.js
├── refLib.js
├── DateTimeFormat.js
├── webhooks.js
├── OxaPayLibV1.js
├── GoogleTableSync.js
├── FreeKassa.js
├── Coinbase.js
├── OxaPayLib.js
├── ResourcesLib.js
├── CoinPayments.js
└── MembershipChecker.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /.vscode/launch.json
2 |
--------------------------------------------------------------------------------
/commonLib.js:
--------------------------------------------------------------------------------
1 | function getNameFor(member){
2 | let haveAnyNames = member.username||member.first_name||member.last_name;
3 | if(!haveAnyNames){ return ""}
4 |
5 | if(member.username){
6 | return "@" + member.username
7 | }
8 |
9 | return member.first_name ? member.first_name : member.last_name
10 | }
11 |
12 | function getLinkFor(member, parse_mode){
13 | let name = getNameFor(member);
14 | if(name==""){
15 | name = member.telegramid;
16 | }
17 |
18 | if(!parse_mode){
19 | return "[" + name + "](tg://user?id=" + member.telegramid + ")";
20 | }
21 |
22 | return "" + name + "";
23 | }
24 |
25 | publish({
26 | getNameFor: getNameFor,
27 | getLinkFor: getLinkFor
28 | })
29 |
30 |
--------------------------------------------------------------------------------
/random.js:
--------------------------------------------------------------------------------
1 | function verifyMinAndMax(min, max){
2 | if(!min||!max){
3 | Bot.sendMessage('Need pass min and max for random value');
4 | return
5 | }
6 | if((typeof(min)!='number')||(typeof(max)!='number')){
7 | Bot.sendMessage('Min and max must be Number type');
8 | return
9 | }
10 | if(min>max){
11 | Bot.sendMessage('Max must be creater then min');
12 | return
13 | }
14 | return true;
15 | }
16 |
17 | function rndFloat(min, max){
18 | if(!verifyMinAndMax(min, max)){ return }
19 | return (Math.random() * (max - min + 1)) + min
20 | }
21 |
22 | function rndInt(min, max){
23 | if(!verifyMinAndMax(min, max)){ return }
24 | return Math.floor(rndFloat(min, max))
25 | }
26 |
27 | publish({
28 | sendMessage: function (messages){
29 | var err_msg = 'need pass messages array. E.g: [\"Hello\", \"Hi!\"]';
30 | if(!messages){
31 | Bot.sendMessage(err_msg);
32 | }
33 | if(!messages.length || messages.length==0){
34 | Bot.sendMessage(err_msg);
35 | }
36 |
37 | let random_int = rndInt(0, messages.length-1);
38 |
39 | Bot.sendMessage(messages[random_int]);
40 | },
41 |
42 | randomInt: rndInt,
43 | randomFloat: rndFloat
44 | })
--------------------------------------------------------------------------------
/Lang.js:
--------------------------------------------------------------------------------
1 | const LIB_PREFIX = "lang_lib_"
2 |
3 | function setUserLanguage(curLangName){
4 | User.setProperty(LIB_PREFIX + "curLangName", curLangName, "string");
5 | }
6 |
7 | function getUserLanguage(){
8 | if(user){
9 | let lng = User.getProperty(LIB_PREFIX + "curLangName");
10 | if(lng){ return lng }
11 | }
12 | return getDefaultLanguage();
13 | }
14 |
15 | function setDefaultLanguage(langName){
16 | Bot.setProperty(LIB_PREFIX + "default", langName, "string");
17 | }
18 |
19 | function getDefaultLanguage(){
20 | return Bot.getProperty(LIB_PREFIX + "default");
21 | }
22 |
23 | function setupLanguage(langName, keys){
24 | Bot.setProperty(LIB_PREFIX + langName, keys, "json");
25 | let def = getDefaultLanguage();
26 | if(!def){ setDefaultLanguage(langName) }
27 | }
28 |
29 | function get(lang){
30 | let curLng;
31 | if(lang){ curLng = lang }
32 | else{
33 | curLng = getUserLanguage();
34 | }
35 |
36 | let json = Bot.getProperty(LIB_PREFIX + curLng);
37 | if(!json){
38 | throw new Error("Language is not configured: " + curLng);
39 | }
40 |
41 | return json;
42 | }
43 |
44 | function get_trans_item(item, lang){
45 | var result;
46 | var json = get(lang);
47 | try{ result = eval("json." + item) }
48 | catch(err){}
49 |
50 | return result
51 | }
52 |
53 | function t(item, lang){
54 | // for lang
55 | var result = get_trans_item(item, lang);
56 | if(result){ return result }
57 |
58 | // for default language
59 | return get_trans_item(item, getDefaultLanguage());
60 | }
61 |
62 | function getCommandByAlias(alias, lang){
63 | if(!alias){ return }
64 | var json = get(lang)
65 | if(!json){ return }
66 | if(!json.aliases){ return }
67 |
68 | var aliases;
69 | for(var key in json.aliases){
70 | // aliases separated with ",". Can have spaces - so remove spaces:
71 | aliases = key.split(" ,").join(",").split(", ").join(",");
72 | aliases = aliases.split(",");
73 | for(var ind in aliases){
74 | if(aliases[ind].toLowerCase()==alias.toLowerCase()){
75 | return json.aliases[key]
76 | }
77 | }
78 | }
79 | }
80 |
81 | publish({
82 | user:{
83 | setLang: setUserLanguage,
84 | getCurLang: getUserLanguage
85 | },
86 | default:{
87 | setLang: setDefaultLanguage,
88 | getCurLang: getDefaultLanguage
89 | },
90 | setup: setupLanguage,
91 | get: get,
92 | t: t,
93 | getCommandByAlias: getCommandByAlias
94 | })
--------------------------------------------------------------------------------
/qiwi.js:
--------------------------------------------------------------------------------
1 | let libPrefix = 'LibQiwiPayment_';
2 |
3 | function getPaymentLink(options = {}){
4 | return 'https://qiwi.com/payment/form/99?' +
5 | 'extra%5B%27account%27%5D='+ options.account +
6 | '&amountInteger=' + options.amount +
7 | '&extra%5B%27comment%27%5D=' + options.comment +
8 | '¤cy=643'+
9 | '&blocked[0]=account&blocked[1]=comment'
10 | }
11 |
12 | function setQiwiApiTokent(token){
13 | Bot.setProperty(libPrefix + 'ApiToken', token, 'string');
14 | }
15 |
16 | function accept(item){
17 | if(item.status!='SUCCESS'){ return false }
18 | if(item.type!='IN'){ return false }
19 | let accepted_payments = Bot.getProperty(libPrefix + 'accepted_payments');
20 | if(!accepted_payments){ accepted_payments = {} }
21 |
22 | if(accepted_payments[item.txnId]==true){
23 | /* already accepted */
24 | return false;
25 | }
26 |
27 | accepted_payments[item.txnId]=true
28 |
29 | Bot.setProperty(libPrefix + 'accepted_payments', accepted_payments, 'json');
30 | return true;
31 | }
32 |
33 | function onAcceptPayment(){
34 | if(http_status=='401'){
35 | Bot.sendMessage('Please verify API token');
36 | return
37 | }
38 | let history = JSON.parse(content).data;
39 | let prms = params.split(' ');
40 | let comment = prms[0];
41 | let onSuccess = prms[1];
42 | let onNoPaymentYet = prms[2];
43 |
44 | let payment;
45 | for(it in history){
46 | if(history[it].comment==comment){
47 | payment = history[it];
48 | if(accept(payment)){
49 | Bot.runCommand(onSuccess + ' ' + String(payment.sum.amount));
50 | return
51 | }
52 | }
53 | }
54 |
55 | Bot.runCommand(onNoPaymentYet);
56 | }
57 |
58 | function acceptPayment(options){
59 | let apiToken = Bot.getProperty(libPrefix + 'ApiToken');
60 |
61 | let headers = { 'Authorization': 'Bearer ' + apiToken,
62 | 'Content-type': 'application/json',
63 | 'Accept': 'application/json'
64 | }
65 |
66 | let url = 'https://edge.qiwi.com/payment-history/v2/persons/' +
67 | options.account + '/payments?'+
68 | 'rows=50&operation=IN'
69 |
70 | HTTP.get( {
71 | url: url,
72 | success: libPrefix + 'onAcceptPayment ' +
73 | options.comment + ' ' + options.onSuccess +
74 | ' ' + options.onNoPaymentYet,
75 | error: options.onError,
76 | headers: headers
77 | } )
78 | }
79 |
80 |
81 |
82 | publish({
83 | getPaymentLink: getPaymentLink,
84 | setQiwiApiTokent: setQiwiApiTokent,
85 | acceptPayment: acceptPayment
86 | })
87 |
88 | on(libPrefix + 'onAcceptPayment', onAcceptPayment );
--------------------------------------------------------------------------------
/CooldownLib.js:
--------------------------------------------------------------------------------
1 | let LIB_PREFIX = "CooldownLib"
2 | let curCooldown;
3 |
4 | function loadCurCooldown(name, chat){
5 | var resLib = Libs.ResourcesLib;
6 | var name = LIB_PREFIX + "-" + name
7 | curCooldown = chat ? resLib.anotherChatRes(name, chat.chatid) : resLib.userRes(name);
8 | return curCooldown;
9 | }
10 |
11 | function resetCooldown(){
12 | var time = curCooldown.growth.info().max;
13 | curCooldown.set(time);
14 | }
15 |
16 | function setupCooldown(time){
17 | if(curCooldown.growth.isEnabled()){
18 | if(curCooldown.growth.info().max == time){
19 | // already setuped
20 | return
21 | }
22 | }
23 |
24 | curCooldown.set(time);
25 |
26 | curCooldown.growth.add({
27 | value: -1, // just add negative value
28 | interval: 1, // -1 once at 1 sec
29 | min: 0,
30 | max: time
31 | });
32 |
33 | return true;
34 | }
35 |
36 | function isCooldown(){
37 | return curCooldown.value() > 0;
38 | }
39 |
40 | function require(name, value){
41 | if(!value){
42 | throw new Error(LIB_PREFIX + ": need param " + name)
43 | }
44 | }
45 |
46 | function checkOptions(options){
47 | require("name", options.name);
48 | require("time", options.time);
49 | require("onEnding", options.onEnding);
50 | }
51 |
52 | function checkErrors(options){
53 | if(!Libs.ResourcesLib){
54 | throw new Error("Cooldown Lib: Please install ResourcesLib")
55 | }
56 |
57 | checkOptions(options);
58 | }
59 |
60 | function watch(options, chat){
61 | checkErrors(options);
62 | loadCurCooldown(options.name, chat);
63 |
64 | if(setupCooldown(options.time)){
65 | // just started
66 | if(options.onStarting){
67 | options.onStarting();
68 | }
69 |
70 | return
71 | }
72 |
73 | if(isCooldown()&&(options.onWaiting)){
74 | options.onWaiting(curCooldown.value());
75 | }else{
76 | let result = options.onEnding();
77 | if(result){
78 | resetCooldown();
79 | }
80 | }
81 | }
82 |
83 |
84 | // watching
85 | function watchUserCooldown(options){
86 | watch(options);
87 | }
88 |
89 | function watchChatCooldown(options){
90 | watch(options, chat);
91 | }
92 |
93 | function watchCooldown(options){
94 | watch(options, { chatid: "global" });
95 | }
96 |
97 | // getting
98 | function getUserCooldown(name){
99 | return loadCurCooldown(name);
100 | }
101 |
102 | function getChatCooldown(name){
103 | return loadCurCooldown(name, chat);
104 | }
105 |
106 | function getCooldown(name){
107 | return loadCurCooldown(name, { chatid: "global" });
108 | }
109 |
110 | publish({
111 | user: {
112 | watch: watchUserCooldown,
113 | getCooldown: getUserCooldown
114 | },
115 | chat: {
116 | watch: watchChatCooldown,
117 | getCooldown: getChatCooldown
118 | },
119 | watch: watchCooldown,
120 | getCooldown: getCooldown
121 | })
122 |
--------------------------------------------------------------------------------
/AmoCRM.js:
--------------------------------------------------------------------------------
1 | let libPrefix = 'LibAMO_CRM_'
2 |
3 | let setUserLogin = function(login){
4 | Bot.setProperty( libPrefix + 'userLogin', login, 'string');
5 | }
6 |
7 | let setApiKey = function(hash){
8 | Bot.setProperty( libPrefix + 'apiKey', hash, 'string');
9 | }
10 |
11 | let setSubDomain = function(subDomain){
12 | Bot.setProperty( libPrefix + 'subDomain', subDomain, 'string');
13 | }
14 |
15 | let loadOptions = function(){
16 | return {
17 | user: {
18 | USER_LOGIN: Bot.getProperty( libPrefix + 'userLogin'),
19 | USER_HASH: Bot.getProperty( libPrefix + 'apiKey' )
20 | },
21 | subDomain: Bot.getProperty( libPrefix + 'subDomain')
22 | }
23 | }
24 |
25 | let loadCRMCredentials = function(){
26 | // need cookie loading...
27 | let credentials = loadOptions();
28 | credentials.cookies = Bot.getProperty( libPrefix + 'cookies');
29 |
30 | return credentials;
31 | }
32 |
33 | function verifyCallback(prms){
34 | if(typeof(prms.onSuccess)!='string'){
35 | throw 'Need handler callback command';
36 | }
37 | return true
38 | }
39 |
40 | function apiCallAs(apiMethod, options){
41 | // if(!verifyCallback(prms)){ return }
42 |
43 | let credentials = loadCRMCredentials();
44 |
45 | let url = 'https://' + credentials.subDomain + '.amocrm.ru/api/v2/' +
46 | options.method;
47 |
48 |
49 | let body = options.params;
50 | if(!body){ body = "" }
51 |
52 | params = {
53 | url: url,
54 | body: body,
55 | cookies: credentials.cookies,
56 | success: libPrefix + 'onApiResponse ' + options.onSuccess,
57 | // error: callbacks.onError
58 | }
59 |
60 | if(apiMethod=="get"){ return HTTP.get( params ) }
61 | HTTP.post( params )
62 | }
63 |
64 | function apiGet(options = {}){
65 | apiCallAs("get", options)
66 | }
67 |
68 | function apiPost(options = {}){
69 | apiCallAs("post", options)
70 | }
71 |
72 |
73 | function onApiResponse(){
74 | let json = JSON.parse(content);
75 | Bot.runCommand(params, {body: json} );
76 | }
77 |
78 |
79 | function auth(){
80 | let options = loadOptions();
81 |
82 | let url = 'https://' + options.subDomain + '.amocrm.ru/private/api/auth.php?type=json'
83 |
84 | HTTP.post( {
85 | url: url,
86 | body: options.user,
87 | success: libPrefix + "SaveCookies",
88 | // error: callbacks.onError
89 | } )
90 |
91 | }
92 |
93 | function onSaveCookies(){
94 | let json = JSON.parse(content);
95 |
96 | if(json.response.auth){
97 | Bot.setProperty( libPrefix + 'cookies', cookies, 'text');
98 | }else{
99 | Bot.sendMessage( "Error with autorization to AmoCRM. Invalid credentials?" );
100 | }
101 | }
102 |
103 | publish({
104 | setUserLogin: setUserLogin,
105 | setApiKey: setApiKey,
106 | setSubDomain: setSubDomain,
107 | auth: auth,
108 |
109 | apiGet: apiGet,
110 | apiPost: apiPost
111 | })
112 |
113 | on(libPrefix + "SaveCookies", onSaveCookies);
114 |
115 | on(libPrefix + 'onApiResponse', onApiResponse);
--------------------------------------------------------------------------------
/currencyConverter.js:
--------------------------------------------------------------------------------
1 | let libPrefix = 'currencyConverter';
2 |
3 | function storeRate(){
4 | if(content.split(' ').join('')=='{}'){
5 | throw new Error('Seems we have limits with Free Plan - https://free.currencyconverterapi.com/')
6 | }
7 |
8 | let json = JSON.parse(content);
9 |
10 | if(json.error){
11 | Bot.sendMessage('Error: ' + json.error);
12 | return
13 | }
14 |
15 | let rate, key;
16 | for (var attr in json){
17 | rate = json[attr].val;
18 | key = attr.toUpperCase();
19 | break;
20 | }
21 |
22 | let amount;
23 |
24 | let prms = params.split(' ');
25 | // possible passing same user params
26 | // last param - amount
27 | let cmd = prms.slice(0, prms.length-1).join(' ');
28 | for(var attr in prms){ amount = prms[attr] }
29 |
30 | let val = { value: rate, updated_at: Date.now()};
31 |
32 | if(key){
33 | Bot.setProperty(key, val, 'json');
34 | }
35 |
36 | let result = calcResult(rate, amount);
37 |
38 | Bot.run({ command: cmd + ' ' + result, options: {content: content} });
39 | };
40 |
41 | function calcResult(rate, amount){
42 | return String(parseFloat(rate) * parseFloat(amount));
43 | }
44 |
45 | function isHaveError(query, onSuccess){
46 | let example_code = 'Libs.CurrencyConverter.convert("USD_EUR", "onconvert")';
47 | let err_msg;
48 |
49 | if(typeof(query)!='string'){
50 | err_msg = 'Need currencies! For example: ' + example_code
51 | }
52 |
53 | else if(query.split('_').length!=2){
54 | err_msg = 'Need TWO currencies separated with _. ! For example: EUR_USD'
55 | }
56 |
57 | else if(typeof(onSuccess)!='string'){
58 | err_msg = 'Need handler command! For example: ' + example_code;
59 | }
60 |
61 | if(err_msg){
62 | Bot.sendMessage(err_msg);
63 | return true;
64 | }
65 |
66 | return false;
67 | }
68 |
69 | function getApiUrl(query, onSuccess){
70 | if(isHaveError(query, onSuccess)){ return }
71 |
72 | let apiKey = Bot.getProperty(libPrefix + 'ApiKey', '9e878aebb95bf44aba20');
73 |
74 | return 'http://free.currencyconverterapi.com/api/v5/convert?compact=y&q=' + query +
75 | '&apiKey=' + apiKey;
76 | }
77 |
78 | function setupApiKey(apiKey){
79 | Bot.setProperty(libPrefix + 'ApiKey', apiKey, 'string');
80 | }
81 |
82 | publish({
83 | getApiUrl: getApiUrl,
84 | setupApiKey: setupApiKey,
85 |
86 | convert: function(query, amount, onSuccess){
87 | if(isHaveError(query, onSuccess)){ return }
88 |
89 | var now = Date.now();
90 |
91 | var rate = Bot.getProperty(query.toUpperCase());
92 |
93 | if(rate){
94 | var minutes = ( now - rate.updated_at ) / 60000;
95 | if(minutes<30){
96 | let result = calcResult(rate.value, amount);
97 | Bot.runCommand(onSuccess + ' ' + result);
98 | return
99 | }
100 | }
101 | let url = getApiUrl(query, onSuccess);
102 |
103 | HTTP.get( { url: url, success: libPrefix + '_lib_on_http_success ' + onSuccess + ' ' + amount } )
104 | }
105 | })
106 |
107 | on(libPrefix + '_lib_on_http_success', storeRate );
108 |
109 |
110 |
--------------------------------------------------------------------------------
/Guard.js:
--------------------------------------------------------------------------------
1 | const PANEL_NAME = "AdminGuardSettings";
2 | const IDS_FIELD_NAME = "adminIds";
3 | const FOLDER_FIELD_NAME = "adminFolder";
4 | const UNAUTHORIZED_COMMAND_NAME = "unauthorizedAccessCommand";
5 |
6 | let guardSettings;
7 |
8 | function setup(){
9 | if (AdminPanel.getPanel(PANEL_NAME)){
10 | return Bot.sendMessage(
11 | "Guard Lib: You have already activated the panel. To make changes, go to the admin panel on the bot page.");
12 | }
13 |
14 | let panelData = {
15 | title: "Guard",
16 | description: "",
17 | index: 0,
18 | icon: "person",
19 | button_title: "SAVE",
20 | fields: [
21 | {
22 | name: IDS_FIELD_NAME,
23 | title: "Admin IDs",
24 | description: "A list of authorized users (you can specify multiple user BB IDs separated by commas).",
25 | type: "string",
26 | value: user.id
27 | },
28 | {
29 | name: "adminIdsDiscr",
30 | description: "Information about BB ID is stored in user.id."
31 | },
32 | {
33 | name: FOLDER_FIELD_NAME,
34 | title: "Commands folder",
35 | description: "The folder for commands is only for authorized users (whose BB IDs are listed above).",
36 | type: "string",
37 | placeholder: "admins",
38 | value: "admins"
39 | },
40 | {
41 | name: UNAUTHORIZED_COMMAND_NAME,
42 | title: "Command in case of unauthorized access",
43 | description: "If, when attempting unauthorized access, you want to output a message to the user or execute some other code.",
44 | type: "string",
45 | placeholder: "/unauthorized_access",
46 | }
47 | ]
48 | };
49 |
50 | AdminPanel.setPanel({ panel_name: PANEL_NAME, data: panelData });
51 |
52 | Bot.sendMessage("Guard Lib: The admin panel has been successfully activated.");
53 | };
54 |
55 | function getPanelSettings(){
56 | if (guardSettings) return;
57 | guardSettings = AdminPanel.getPanelValues(PANEL_NAME);
58 | return;
59 | };
60 |
61 | function isAdmin(userId){
62 | getPanelSettings();
63 |
64 | if (!guardSettings) return;
65 | if (!guardSettings[IDS_FIELD_NAME]) return ;
66 | if (isNaN(userId)) return ;
67 |
68 | let adminIdsList = guardSettings[IDS_FIELD_NAME].toString().replace(/\s/g, "").split(",");
69 | let adminIds = [];
70 | for (let adminId of adminIdsList){
71 | if (adminId == userId) return true;
72 | };
73 | return false;
74 | };
75 |
76 | function isAdminFolder(){
77 | if (!guardSettings[FOLDER_FIELD_NAME]) return;
78 |
79 | let adminFolder = guardSettings[FOLDER_FIELD_NAME];
80 | if (command.folder == adminFolder) return true ;
81 | return;
82 | };
83 |
84 | function unauthorizedAccess(){
85 | if (!guardSettings[UNAUTHORIZED_COMMAND_NAME]) return;
86 |
87 | Bot.run({ command: guardSettings[UNAUTHORIZED_COMMAND_NAME] });
88 | };
89 |
90 | function verifyAccess(){
91 | getPanelSettings();
92 |
93 | if (!guardSettings) return true;
94 |
95 | if(!isAdminFolder()) return true;
96 | if(!isAdmin(user.id)){
97 | unauthorizedAccess();
98 | return;
99 | };
100 | return true;
101 | };
102 |
103 | publish({
104 | setup: setup,
105 | isAdmin: isAdmin,
106 | verifyAccess: verifyAccess
107 | });
--------------------------------------------------------------------------------
/GoogleSpreadSheet.js:
--------------------------------------------------------------------------------
1 | // this Lib deprecated!
2 | // Use GoogleTableSync Lib!
3 |
4 | let libPrefix = "SpreadSheet-Lib-"
5 |
6 | function throwError(err){
7 | throw "Google SpreadSheet Lib: " + err;
8 | }
9 |
10 | function setUrlApp(appUrl){
11 | Bot.sendMessage("This Lib deprecated! Please use GoogleTableSync Lib");
12 |
13 | if(typeof(appUrl)!="string"){
14 | throwError("Need pass Google App url")
15 | }
16 |
17 | if(appUrl.indexOf("https://script.google.com/")+1>0){
18 | return Bot.setProperty(libPrefix + "app-url", appUrl, "string");
19 | }
20 |
21 | throwError("Seems it is not url for Google App: " + appUrl);
22 | }
23 |
24 | function getAppUrl(){
25 | let result = Bot.getProperty(libPrefix + "app-url");
26 | if(!result){
27 | throwError("Need set Google App url before using")
28 | }
29 | return result;
30 | }
31 |
32 | function checkOptions(options){
33 | if(!options){ throwError("Need pass options") }
34 | if(!options.sheetName){ throwError("Need pass sheetName") }
35 | if(!options.onSuccess){ throwError("Need pass onSuccess command name") }
36 | }
37 |
38 | function getCallback(options){
39 | let onError = " ";
40 | if(onError){ onError = options.onError }
41 |
42 | return libPrefix + "onSuccess " + options.onSuccess + " " + onError;
43 | }
44 |
45 | function getErrCallback(options){
46 | let onError = " ";
47 | if(onError){ onError = options.onError }
48 |
49 | return libPrefix + "onError " + onError;
50 | }
51 |
52 | function getHeader(options){
53 | let appUrl = getAppUrl();
54 | checkOptions(options);
55 |
56 | let qrow = ""
57 | if(options.rowIndex){ qrow = "&rowIndex=" + options.rowIndex }
58 |
59 | HTTP.get({
60 | url: appUrl + "?sheetName=" + options.sheetName + qrow,
61 | success: getCallback(options),
62 | error: getErrCallback(options),
63 | })
64 | }
65 |
66 | function getRow(options){
67 | getHeader(options);
68 | }
69 |
70 | function postRow(options, isEdit){
71 | let appUrl = getAppUrl();
72 | checkOptions(options);
73 |
74 | // row: { "User": "Ivan", "Task":"create task", "Desc": "My cool DESC." }
75 | if(!options.row){ throwError("Need pass table row data") }
76 |
77 | if(isEdit&&(!options.rowIndex)){
78 | throwError("Need pass rowIndex for editing")
79 | }
80 |
81 | HTTP.post( {
82 | url: appUrl,
83 | success: getCallback(options),
84 | error: getErrCallback(options),
85 | body: options
86 | })
87 | }
88 |
89 | function addRow(options){
90 | postRow(options, false);
91 | }
92 |
93 | function editRow(options){
94 | postRow(options, true)
95 | }
96 |
97 | function onSuccess(){
98 | let callback = params.split(" ")[0];
99 | let errCalback = params.split(" ")[1];
100 |
101 | var result = content.split("APP-RESULT")[1];
102 |
103 | if(!result){
104 | // error
105 | var arr = content.split("width:600px");
106 | var error = arr[1].split("<")[0]
107 | return Bot.runCommand(errCalback, {error: error});
108 | }
109 |
110 | result = decodeURI(result);
111 | result = JSON.parse(result)
112 | Bot.runCommand(callback, result);
113 | }
114 |
115 | function onError(){
116 | let errCalback = params;
117 | Bot.sendMessage("Download error");
118 | Bot.runCommand(errCalback);
119 | }
120 |
121 | publish({
122 | setUrl: setUrlApp,
123 | getHeader: getHeader,
124 |
125 | getRow: getRow,
126 | addRow: addRow,
127 | editRow: editRow
128 | })
129 |
130 | on(libPrefix + "onSuccess", onSuccess );
131 | on(libPrefix + "onError", onError);
--------------------------------------------------------------------------------
/GoogleApp.js:
--------------------------------------------------------------------------------
1 | var libPrefix = "GoogleAppLib_";
2 |
3 | function setUrl(url){
4 | Bot.setProperty(libPrefix + "AppUrl", url, "string");
5 | }
6 |
7 | function getUrl(){
8 | return Bot.getProperty(libPrefix + "AppUrl");
9 | }
10 |
11 | function isWebhookLibInstalled(){
12 | if(Libs.Webhooks){ return }
13 | throwError("Please install Webhook Lib. It is required by GoogleApp Lib.")
14 | }
15 |
16 | function getWebhookUrl(isDebug){
17 | var cmd = libPrefix + "onRun";
18 | if(isDebug){
19 | Bot.sendMessage("GoogleAppLib: Debug mode - ON");
20 | cmd = libPrefix + "onDebugRun"
21 | }
22 | return Libs.Webhooks.getUrlFor({
23 | command: cmd,
24 | user_id: user.id
25 | })
26 | }
27 |
28 | function throwError(title){
29 | throw new Error("GoogleApp Lib error: " + title);
30 | }
31 |
32 | function isOptionsCorrect(options){
33 | if(!options){
34 | throwError("on run - need object param")
35 | }
36 | if(typeof(options)!="object"){
37 | throwError("on run - param must be object")
38 | }
39 | if(!options.code){
40 | throwError("on run - need passed code in params")
41 | }
42 | if(!options.code.name){
43 | throwError("on run - code must be function with name")
44 | }
45 | }
46 |
47 | function run(options){
48 | isWebhookLibInstalled();
49 | isOptionsCorrect(options)
50 |
51 | var webhookUrl = getWebhookUrl(options.debug);
52 | var func = options.code;
53 | var url = getUrl() + "?hl=en";
54 |
55 | if(options.debug){
56 | Bot.sendMessage(
57 | "GoogleAppLib: post data to [url](" + url + ")." +
58 | "\n\nYou can open this link only on incognito mode without Google autorization"
59 | );
60 | }
61 |
62 | HTTP.post( {
63 | url: url,
64 | // success: "" - no success
65 | error: libPrefix + "onHttpError",
66 | body: {
67 | code: func + ";" + func.name + "()",
68 | webhookUrl: webhookUrl,
69 | email: options.email,
70 | onRun: options.onRun,
71 | // pass all BJS variables to Google App script
72 | data: getData()
73 | },
74 | folow_redirects: true,
75 | // headers: { "Content-Type": "text/plain;charset=utf-8" }
76 | } )
77 | }
78 |
79 | function getData(){
80 | return {
81 | message: message,
82 | user: user,
83 | chat: chat,
84 | bot: bot,
85 | params: params,
86 | options: options,
87 | admins: admins,
88 | owner: owner,
89 | iteration_quota: iteration_quota,
90 | payment_plan: payment_plan,
91 | completed_commands_count: completed_commands_count,
92 | request: request,
93 | content: content,
94 | http_status: http_status,
95 | cookies: cookies,
96 | http_headers: http_headers,
97 | command: command,
98 | BB_API_URL: BB_API_URL
99 | }
100 | }
101 |
102 | function inspectError(json){
103 | var error = json.error;
104 | if(!error){ return }
105 |
106 | Bot.sendMessage("Error on Google App script: " +
107 | inspect(error.name) + "\n\n" + inspect(error.message) );
108 | Bot.sendMessage("Code: " + inspect(error.code))
109 | return true
110 | }
111 |
112 | function parseContent(){
113 | if(typeof(content)=="object"){
114 | return content
115 | }
116 |
117 | try{
118 | return JSON.parse(content);
119 | }catch(e){
120 | throwError("Error on content parsing: " + content)
121 | }
122 | }
123 |
124 | function doUserOnRun(data){
125 | if(!data.onRun){ return }
126 | if(data.onRun==""){ return }
127 | Bot.run({ command: data.onRun, options: data.result })
128 | }
129 |
130 | function onRun(){
131 | var json = parseContent();
132 | doUserOnRun(json);
133 | }
134 |
135 | function onDebugRun(){
136 | var json = parseContent();
137 | if(inspectError(json.result)){ return }
138 | doUserOnRun(json);
139 | }
140 |
141 | function onHttpError(){
142 | throwError("app error. Please check app url and script installation.")
143 | }
144 |
145 | publish({
146 | setUrl: setUrl,
147 | run: run
148 | })
149 |
150 | on(libPrefix + "onRun", onRun);
151 | on(libPrefix + "onDebugRun", onDebugRun)
152 | on(libPrefix + "onHttpError", onHttpError)
153 |
--------------------------------------------------------------------------------
/GoogleAppSync.gs:
--------------------------------------------------------------------------------
1 | // Bots.Business 2022
2 | // Use this code to connect Bots.Business BJS with Google App Script
3 | // https://help.bots.business/libs/googleapp
4 |
5 | // BJS data;
6 | var message, chat, bot, params, options, statistics, admins, owner, iteration_quota, payment_plan, completed_commands_count, request, content, http_status, cookies, http_headers, user;
7 |
8 | function debug(){ run() }
9 |
10 | function run(data){
11 | var is_debug = false;
12 | if(!data){
13 | data = loadData();
14 | is_debug = true;
15 | }
16 | data = JSON.parse(data);
17 | if(!data){ return }
18 | setBJSData(data.data);
19 |
20 | var result = {};
21 |
22 | try {
23 | var code = data.code;
24 | result = eval(code);
25 | }catch (e){
26 | result.error = { name: e.name, message: e.message, stack: e.stack, code: data.code}
27 | if(is_debug){ sendMail(data, result) }
28 | }
29 |
30 | if(!is_debug){
31 | saveData(data, result);
32 | }
33 |
34 | if(is_debug){ sendMail(data, result) }
35 |
36 | result = { result: result, onRun: data.onRun};
37 |
38 | callWebhook(result, data);
39 | }
40 |
41 | //this is a function that fires when the webapp receives a POST request
42 | function doPost(e){
43 | var data = e.postData.contents;
44 | run(data);
45 |
46 | return HtmlService.createHtmlOutput("BBGoogleAppLib:ok");
47 | }
48 |
49 | function getFormatted(json){
50 | try{
51 | json = JSON.parse(json);
52 | return "
" + JSON.stringify(json, null, 4) + "
";
53 | }catch{
54 | return String(json)
55 | }
56 | }
57 |
58 | //this is a function that fires when the webapp receives a GET request
59 | function doGet(e){
60 | var lastData = loadData();
61 | var lastResult = CacheService.getScriptCache().get("LastResult");
62 |
63 | return HtmlService.createHtmlOutput(
64 | "This is BB Google App Lib connector
" +
65 | "Last result:
" +
66 | getFormatted(lastResult) +
67 | "Last data:
" +
68 | "
" + getFormatted(lastData)
69 | )
70 | }
71 |
72 | function setBJSData(data){
73 | message = data.message;
74 | chat = data.chat;
75 | bot = data.bot;
76 | params = data.params;
77 | options = data.options;
78 | admins = data.admins;
79 | owner = data.owner;
80 | iteration_quota = data.iteration_quota;
81 | payment_plan = data.payment_plan;
82 | completed_commands_count = data.completed_commands_count;
83 | request = data.request;
84 | content = data.content;
85 | http_status = data.http_status;
86 | cookies = data.cookies;
87 | http_headers = data.http_header;
88 | user = data.user;
89 | command = data.command;
90 | BB_API_URL = data.BB_API_URL;
91 | }
92 |
93 | function sendMail(data, result){
94 | if(!data.email){ return }
95 | MailApp.sendEmail({
96 | to: data.email,
97 | subject: "Google App Script Error",
98 | htmlBody: "code:" +
99 | "
" + JSON.stringify(data.code) +
100 | "
result:" +
101 | "
" + JSON.stringify(result)
102 | });
103 | }
104 |
105 | function sendWebhookErrorMail(data, result){
106 | if(!data.email){ return }
107 | MailApp.sendEmail({
108 | to: data.email,
109 | subject: "Google App Script Error",
110 | htmlBody: "Try call webhook:" +
111 | "
" + data.webhookUrl +
112 | "
result:" +
113 | "
" + JSON.stringify(result)
114 | });
115 | }
116 |
117 | function saveData(data, result){
118 | CacheService.getScriptCache().put("LastData", JSON.stringify(data));
119 | CacheService.getScriptCache().put("LastResult", JSON.stringify(result));
120 | }
121 |
122 | function loadData(){
123 | return CacheService.getScriptCache().get("LastData");
124 | }
125 |
126 | function callWebhook(result, data){
127 | var options = {
128 | 'method' : 'post',
129 | 'contentType': 'application/json',
130 | 'payload' : JSON.stringify(result)
131 | };
132 |
133 | try{
134 | UrlFetchApp.fetch(data.webhookUrl, options);
135 | }catch(e){
136 | result.error = { name: e.name, message: e.message, stack: e.stack, code: data.code}
137 | sendWebhookErrorMail(data, result)
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/BlocIO.js:
--------------------------------------------------------------------------------
1 | let API_URL = 'https://block.io/api/v2/';
2 | let libPrefix = 'libblockio_'
3 |
4 | function getCredential(options, prms){
5 | let apiKey = ( prms.api_key ? prms.api_key :
6 | Bot.getProperty( libPrefix + options.coin.toLowerCase() + 'apikey')
7 | );
8 |
9 | let pin = '';
10 | if(options.method.indexOf('withdraw') + 1) {
11 |
12 | pin = (prms.pin ? prms.pin :
13 | Bot.getProperty( libPrefix + 'secretpin')
14 | )
15 |
16 | if(pin!=''){ pin = '&pin=' + pin }
17 | }
18 | return 'api_key=' + apiKey + pin;
19 | }
20 |
21 | function apiGet(options = {}, prms){
22 | if(!verifyCallback(prms)){ return }
23 |
24 | let callbacks = getCallbacks(prms, 'on_' + options.method);
25 | let credential = getCredential(options, prms);
26 |
27 | let url = API_URL + options.method + '?' + credential +
28 | '&' + options.query;
29 |
30 | HTTP.get( {
31 | url: url,
32 | success: callbacks.onSuccess,
33 | error: callbacks.onError
34 | } )
35 | }
36 |
37 | function onApiResponse(){
38 | let json
39 | try{
40 | json = JSON.parse(content);
41 | }catch(err){
42 | throw 'Error with downloaded JSON content: ' + content
43 | }
44 |
45 | let arr = params.split(' ');
46 | if(json.status=='success'){
47 | let user_callback_cmd = arr[1];
48 | Bot.runCommand(user_callback_cmd, json.data);
49 | }else{
50 | let user_callback_err_cmd = arr[arr.length-1];
51 | Bot.runCommand(user_callback_err_cmd, json);
52 | }
53 | }
54 |
55 | function onApiError(){
56 | let json;
57 | let user_callback_cmd = params.split(' ')[0];
58 |
59 | if(content){
60 | json = JSON.parse(content);
61 |
62 | if(json.status=='fail'){
63 | Bot.runCommand(
64 | user_callback_cmd + ' ' + json.data.error_message
65 | );
66 | return
67 | }
68 | }else
69 |
70 | Bot.runCommand(user_callback_cmd);
71 | }
72 |
73 | function verifyCallback(prms){
74 | if(typeof(prms.onSuccess)!='string'){
75 | throw 'Need handler callback command';
76 | }
77 | return true
78 | }
79 |
80 | function getCallbacks(prms, successKey){
81 | return {
82 | onSuccess: (libPrefix + 'callback ' + successKey +
83 | ' ' + prms.onSuccess + ' ' + prms.onError),
84 | onError: (libPrefix + 'callback ' + 'on_api_error ' + prms.onError)
85 | }
86 | }
87 |
88 | function snakeToCamel(string) {
89 | return string.replace(/(_\w)/g, function(m){
90 | return m[1].toUpperCase();
91 | });
92 | }
93 |
94 | function doApiGetFor(coin, method, prms){
95 | let query = '';
96 | let i = 0;
97 | let keys = Object.keys(prms);
98 | for(let ind in prms){
99 | query+= keys[i] + '=' + prms[ind] + '&'
100 | i+=1;
101 | }
102 |
103 | apiGet( { method: method, coin: coin, query: query}, prms);
104 | }
105 |
106 | function getApiFunctions(coin, funcs_names){
107 | let result = {};
108 | let funcName;
109 |
110 | for(let ind in funcs_names){
111 | funcName = snakeToCamel(funcs_names[ind]);
112 | result[funcName] = function(prms){ doApiGetFor(coin, funcs_names[ind], prms) }
113 | }
114 | return result;
115 | }
116 |
117 | function getMethodsForCoin(coin){
118 | let result = getApiFunctions(coin, [
119 | 'get_new_address',
120 | 'get_balance',
121 | 'get_address_balance',
122 | 'get_my_addresses',
123 | 'get_address_by_label',
124 | 'is_valid_address',
125 | 'is_green_transaction',
126 | 'archive_addresses',
127 | 'unarchive_addresses',
128 | 'get_my_archived_addresses',
129 | 'get_transactions',
130 | 'get_raw_transaction',
131 | 'get_network_fee_estimate',
132 | 'get_current_price',
133 | 'withdraw',
134 | 'withdraw_from_addresses',
135 | 'withdraw_from_labels'
136 | ]);
137 |
138 | result['setApiKey'] = function(apiKey){
139 | Bot.setProperty( libPrefix + coin.toLowerCase() + 'apikey', apiKey, 'string');
140 | return coin;
141 | }
142 |
143 | return result;
144 | }
145 |
146 | let setSecretPin = function(pin){
147 | Bot.setProperty( libPrefix + 'secretpin', pin, 'string');
148 | }
149 |
150 | publish({
151 | setSecretPin: setSecretPin,
152 | testNet: {
153 | Bitcoin: getMethodsForCoin('TestNetBitcoin'),
154 | Litecoin: getMethodsForCoin('TestNetLitecoin'),
155 | Dogecoin: getMethodsForCoin('TestNetDogecoin'),
156 | },
157 | Bitcoin: getMethodsForCoin('Bitcoin'),
158 | Litecoin: getMethodsForCoin('Litecoin'),
159 | Dogecoin: getMethodsForCoin('Dogecoin'),
160 | })
161 |
162 | on(libPrefix + 'callback', onApiResponse);
--------------------------------------------------------------------------------
/refLib.js:
--------------------------------------------------------------------------------
1 | let LIB_PREFIX = 'REFLIB_';
2 |
3 | let trackOptions = {};
4 |
5 | function emitEvent(eventName, prms = {}){
6 | let evenFun = trackOptions[eventName]
7 | if(evenFun){
8 | evenFun(prms)
9 | return true;
10 | }
11 | }
12 |
13 | function getProp(propName){
14 | return User.getProperty(LIB_PREFIX + propName);
15 | }
16 |
17 | function getList(userId){
18 | let listName = LIB_PREFIX + 'refList' + String(userId);
19 | return new List({ name: listName, user_id: userId })
20 | }
21 |
22 | function getTopList(){
23 | var list = new List({ name: LIB_PREFIX + 'TopList' });
24 | if(!list.exist){ list.create() }
25 | return list;
26 | }
27 |
28 | function getRefList(userId){
29 | if(!userId){ userId = user.id }
30 |
31 | let refList = getList(userId);
32 | return refList;
33 | }
34 |
35 | function addFriendFor(userId){
36 | // save RefList
37 | let refList = getList(userId)
38 | if(!refList.exist){ refList.create() }
39 |
40 | refList.addUser(user);
41 | }
42 |
43 | function getRefCount(userId){
44 | if(!userId){ userId = user.id }
45 | userId = parseInt(userId);
46 |
47 | var refsCount = User.getProperty({
48 | name: LIB_PREFIX + 'refsCount',
49 | user_id: userId
50 | });
51 |
52 | if(!refsCount){ refsCount = 0 }
53 | return refsCount;
54 | }
55 |
56 | function updateRefsCountFor(userId){
57 | var topList = getTopList();
58 |
59 | var refsCount = getRefCount(userId);
60 |
61 | User.setProperty({
62 | name: LIB_PREFIX + 'refsCount',
63 | value: refsCount + 1,
64 | list: topList,
65 | user_id: userId
66 | });
67 | }
68 |
69 | function setReferral(userId){
70 | addFriendFor(userId);
71 | updateRefsCountFor(userId);
72 |
73 | let userKey = LIB_PREFIX + 'user' + String(userId);
74 | let refUser = Bot.getProperty(userKey);
75 |
76 | if(!refUser){ return }
77 |
78 | User.setProperty(LIB_PREFIX + 'attracted_by_user', refUser, 'json');
79 |
80 | if(emitEvent('onAtractedByUser', refUser )){ return true } // Deprecated
81 | emitEvent('onAttracted', refUser)
82 | }
83 |
84 | function isAlreadyAttracted(){
85 | return getProp('attracted_by_user') || getProp('old_user')
86 | }
87 |
88 | function trackRef(){
89 | let prefix = 'user'
90 |
91 | let uprefix = Bot.getProperty(LIB_PREFIX + 'refList_link_prefix');
92 | if(uprefix){ prefix = uprefix }
93 |
94 | let arr = params.split(prefix);
95 | if(arr[0]!=''){ return }
96 | let userId=arr[1];
97 | if(!userId){ return }
98 | userId = parseInt(userId)
99 |
100 | // own link was touched
101 | if(userId==user.id){ return emitEvent('onTouchOwnLink') }
102 |
103 | // it is affiliated by another user
104 | return setReferral(userId);
105 | }
106 |
107 | function clearRefList(){
108 | // TODO
109 | }
110 |
111 | function getAttractedBy(){
112 | var prop = getProp('attracted_by_user');
113 | if(prop){
114 | // support for old code
115 | prop.chatId = prop.telegramid;
116 | }
117 | return prop;
118 | }
119 |
120 | function getRefLink(botName, prefix){
121 | if(!prefix){
122 | prefix = 'user'
123 | }else{
124 | Bot.setProperty(LIB_PREFIX + 'refList_' + 'link_prefix', prefix, 'string');
125 | }
126 |
127 | if(!botName){ botName = bot.name }
128 |
129 | // TODO: we need something like User.get({ user_id, xxx, bot_id: yyy })
130 | // because this data in database already and we don't need this bot prop
131 | let userKey = LIB_PREFIX + 'user' + user.id;
132 | Bot.setProperty(userKey, user, 'json');
133 |
134 | return 'https://t.me/' + botName + '?start=' + prefix + user.id;
135 | }
136 |
137 | function isDeepLink(){
138 | return (message.split(' ')[0]=='/start')&¶ms;
139 | }
140 |
141 | function track(_trackOptions={}){
142 | trackOptions = _trackOptions;
143 |
144 | if(isAlreadyAttracted() ){
145 | return emitEvent('onAlreadyAttracted');
146 | }
147 |
148 | if(isDeepLink()&&trackRef()){
149 | return
150 | }
151 |
152 | return User.setProperty(LIB_PREFIX + 'old_user', true, 'boolean');
153 | }
154 |
155 | publish({
156 | getLink: getRefLink,
157 | track: track,
158 | getRefList: getRefList,
159 | getRefCount: getRefCount,
160 | getTopList: getTopList,
161 | getAttractedBy: getAttractedBy,
162 |
163 | // DEPRECATED
164 | currentUser:{
165 | getRefLink: getRefLink,
166 | track: track,
167 | refList:{
168 | get: getRefList,
169 | clear: clearRefList
170 | },
171 | attractedByUser: getAttractedBy,
172 | },
173 | topList:{
174 | get: getTopList
175 | }
176 | })
--------------------------------------------------------------------------------
/DateTimeFormat.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Date Format 1.2.3
3 | * (c) 2007-2009 Steven Levithan
4 | * MIT license
5 | *
6 | * Includes enhancements by Scott Trenda
7 | * and Kris Kowal
8 | *
9 | * Accepts a date, a mask, or a date and a mask.
10 | * Returns a formatted version of the given date.
11 | * The date defaults to the current date/time.
12 | * The mask defaults to dateFormat.masks.default.
13 | */
14 |
15 | function format(date, mask, utc){
16 | var dateFormat = function() {
17 | var token = /d{1,4}|m{1,4}|s{1,2}|M{1,2}|m{1,4}|H{1,2}|yyyy{1,4}|y{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
18 | timezone = /\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) (?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
19 | timezoneClip = /[^-+\dA-Z]/g,
20 | pad = function(val, len) {
21 | val = String(val);
22 | len = len || 2;
23 | while (val.length < len) val = "0" + val;
24 | return val;
25 | };
26 |
27 | // Regexes and supporting functions are cached through closure
28 | return function(date, mask, utc) {
29 | var dF = dateFormat;
30 |
31 | // You can't provide utc if you skip other args (use the "UTC:" mask prefix)
32 | if (arguments.length == 1 && Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
33 | mask = date;
34 | date = undefined;
35 | }
36 |
37 | // Passing date through Date applies Date.parse, if necessary
38 | date = date ? new Date(date) : new Date;
39 | if (isNaN(date)) throw SyntaxError("invalid date");
40 |
41 | mask = String(dF.masks[mask] || mask || dF.masks["default"]);
42 |
43 | // Allow setting the utc argument via the mask
44 | if (mask.slice(0, 4) == "UTC:") {
45 | mask = mask.slice(4);
46 | utc = true;
47 | }
48 |
49 | var _ = utc ? "getUTC" : "get",
50 | d = date[_ + "Date"](),
51 | D = date[_ + "Day"](),
52 | m = date[_ + "Month"](),
53 | y = date[_ + "FullYear"](),
54 | H = date[_ + "Hours"](),
55 | M = date[_ + "Minutes"](),
56 | s = date[_ + "Seconds"](),
57 | L = date[_ + "Milliseconds"](),
58 | o = utc ? 0 : date.getTimezoneOffset(),
59 | flags = {
60 | d: d,
61 | dd: pad(d),
62 | ddd: dF.i18n.dayNames[D],
63 | dddd: dF.i18n.dayNames[D + 7],
64 | m: m + 1,
65 | mm: pad(m + 1),
66 | mmm: dF.i18n.monthNames[m],
67 | mmmm: dF.i18n.monthNames[m + 12],
68 | yy: String(y).slice(2),
69 | yyyy: y,
70 | h: H % 12 || 12,
71 | hh: pad(H % 12 || 12),
72 | H: H,
73 | HH: pad(H),
74 | M: M,
75 | MM: pad(M),
76 | s: s,
77 | ss: pad(s),
78 | l: pad(L, 3),
79 | L: pad(L > 99 ? Math.round(L / 10) : L),
80 | t: H < 12 ? "a" : "p",
81 | tt: H < 12 ? "am" : "pm",
82 | T: H < 12 ? "A" : "P",
83 | TT: H < 12 ? "AM" : "PM",
84 | Z: utc ? "UTC" : (String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
85 | o: (o > 0 ? "-" : "+") + pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
86 | S: ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : (d % 100 - d % 10 != 10) * d % 10]
87 | };
88 |
89 | return mask.replace(token, function($0) {
90 | return $0 in flags ? flags[$0] : $0.slice(1, $0.length - 1);
91 | });
92 | };
93 | }();
94 |
95 | // Some common format strings
96 | dateFormat.masks = {
97 | "default": "ddd mmm dd yyyy HH:MM:ss",
98 | shortDate: "m/d/yy",
99 | mediumDate: "mmm d, yyyy",
100 | longDate: "mmmm d, yyyy",
101 | fullDate: "dddd, mmmm d, yyyy",
102 | shortTime: "h:MM TT",
103 | mediumTime: "h:MM:ss TT",
104 | longTime: "h:MM:ss TT Z",
105 | isoDate: "yyyy-mm-dd",
106 | isoTime: "HH:MM:ss",
107 | isoDateTime: "yyyy-mm-dd'T'HH:MM:ss",
108 | isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
109 | };
110 |
111 | // Internationalization strings
112 | dateFormat.i18n = {
113 | dayNames: [
114 | "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
115 | "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"
116 | ],
117 | monthNames: [
118 | "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
119 | "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"
120 | ]
121 | };
122 |
123 | return dateFormat(date, mask, utc);
124 | }
125 |
126 | publish({
127 | format: format
128 | })
129 |
--------------------------------------------------------------------------------
/webhooks.js:
--------------------------------------------------------------------------------
1 | let LIB_PREFIX = 'webhook_lib_'
2 | let API_URL = 'https://' + BB_API_URL + '/v1/bots/';
3 |
4 |
5 | function MD5(d){
6 | var result = M(V(Y(X(d),8*d.length)));
7 | return result.toLowerCase()};
8 | function M(d){for(var _,m='0123456789ABCDEF',f='',r=0;r>>4&15)+m.charAt(15&_);
9 | return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;
10 | for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<>5]>>>m%32&255);
12 | return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;
13 | for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
15 |
16 |
17 |
18 | function setApiKey(apiKey){
19 | Bot.setProperty(LIB_PREFIX + 'api_key', apiKey, 'string');
20 | }
21 |
22 | function checkCommand(options){
23 | if(options.command){ return true }
24 | throw 'WebhooksLib: Need pass command options in function getGlobalUrl'
25 | }
26 |
27 | function buildUrl(options){
28 | checkCommand(options);
29 | let _wbUrl = API_URL + String(bot.id) + '/new-webhook?&command=' + encodeURIComponent(options.command)
30 |
31 | if(options.api_key){ _wbUrl+= '&api_key=' + options.apiKey }
32 | if(options.public_user_token){ _wbUrl+= '&public_user_token=' + options.public_user_token }
33 | if(options.user_id){ _wbUrl+='&user_id=' + options.user_id }
34 | if(options.content){ _wbUrl+='&content=' + encodeURIComponent(options.content) }
35 | if(options.redirect_to){ _wbUrl+='&redirect_to=' + encodeURIComponent(options.redirect_to) }
36 |
37 | return _wbUrl;
38 | }
39 |
40 | function getGlobalUrl(options){
41 | if(!options){ throw 'WebhooksLib: need pass options for function getGlobalUrl' }
42 |
43 | options.api_key = Bot.getProperty(LIB_PREFIX + 'api_key');
44 |
45 | if(!options.api_key){
46 | throw 'WebhooksLib: Api Key is not defined. Need define it before'
47 | }
48 |
49 | return buildUrl(options);
50 | }
51 |
52 | function getUrlFor(options){
53 | if(!options.user_id){ options.user_id = '' }
54 | if(!options){ throw 'WebhooksLib: need pass options for function getUrlFor' }
55 |
56 | let _saltedParams = bot.token + '-' + options.user_id + '-' +
57 | options.command + ' Salt is very salty!'
58 |
59 | options.public_user_token = MD5(_saltedParams)
60 |
61 | return buildUrl(options);
62 | }
63 |
64 | function getUrl(){
65 | throw 'WebhooksLib: This function deprecated.'
66 | }
67 |
68 | publish({
69 | setApiKey: setApiKey,
70 | getGlobalUrl: getGlobalUrl,
71 | getUrlFor: getUrlFor,
72 | getUrl: getUrl // Deprecated
73 | })
--------------------------------------------------------------------------------
/OxaPayLibV1.js:
--------------------------------------------------------------------------------
1 | // Declare variables for the library prefix and the API URL
2 | let libPrefix = 'oxapaylibv1';
3 | let API_URL = 'https://api.oxapay.com/v1';
4 |
5 | // Functions to set and get the merchant api key
6 | function setMerchantApiKey(key) {
7 | Bot.setProperty(libPrefix + 'merchantapikey', key, 'string');
8 | }
9 |
10 | function getMerchantApiKey() {
11 | var merchantApiKey = Bot.getProperty(libPrefix + 'merchantapikey');
12 | if (!merchantApiKey) { throw new Error('OxaPay Lib V1: no Merchant API Key. You need to setup it') }
13 |
14 | return merchantApiKey
15 | }
16 |
17 | // Functions to set and get the payout api key
18 | function setPayoutApiKey(key) {
19 | Bot.setProperty(libPrefix + 'payoutapikey', key, 'string');
20 | }
21 |
22 | function getPayoutApiKey() {
23 | var payoutApiKey = Bot.getProperty(libPrefix + 'payoutapikey');
24 | if (!payoutApiKey) { throw new Error('OxaPay Lib V1: no Payout Api Key. You need to setup it') }
25 |
26 | return payoutApiKey
27 | }
28 |
29 | // Functions to set and get the general api key
30 | function setGeneralApiKey(key) {
31 | Bot.setProperty(libPrefix + 'generalapikey', key, 'string');
32 | }
33 |
34 | function getGeneralApiKey() {
35 | var generalapikey = Bot.getProperty(libPrefix + 'generalapikey');
36 | if (!generalapikey) { throw new Error('OxaPay Lib V1: no General Api Key. You need to setup it') }
37 |
38 | return generalapikey
39 | }
40 |
41 | // Function to make an API call
42 | function apiCall(options) {
43 | if (!options) throw 'OxaPay Lib V1: apiCall need options';
44 | if (!options.url) throw 'OxaPay Lib V1: apiCall need options.url';
45 | if (!options.fields) options.fields = {};
46 | if (!options.method) options.method = 'post';
47 |
48 | // Initialize default headers for the API request
49 | let headers = {
50 | 'cache-control': 'no-cache',
51 | 'Content-type': 'application/json',
52 | 'Accept': 'application/json',
53 | }
54 |
55 | if (options.url.includes('payment')) {
56 | // Get the merchant api key from the 'getMerchantApiKey' function and add it to the options object
57 | let merchantApiKey = getMerchantApiKey();
58 | headers['merchant_api_key'] = merchantApiKey;
59 | }
60 | else if (options.url.includes('payout')) {
61 | // Get the Payout Api key from the 'getPayoutApiKey' function and add it to the options object
62 | let payoutApiKey = getPayoutApiKey();
63 | headers['payout_api_key'] = payoutApiKey;
64 | }
65 | else if (options.url.includes('general')) {
66 | // Get the General Api key from the 'getGeneralApiKey' function and add it to the options object
67 | let generalApiKey = getGeneralApiKey();
68 | headers['general_api_key'] = generalApiKey;
69 | }
70 |
71 | // Define the callback URL for payments (not needed for general)
72 | if ( options.fields.on_callback && !options.fields.callback_url && !options.url.includes('general'))
73 | {
74 | options.fields.callback_url = Libs.Webhooks.getUrlFor({
75 | command: libPrefix + 'onCallback ' + options.fields.on_callback,
76 | user_id: user.id,
77 | });
78 | }
79 |
80 | let method = options.method;
81 |
82 | // Define parameters for the HTTP request
83 | let params = {
84 | url: API_URL + options.url,
85 | body: options.fields,
86 | headers: headers,
87 |
88 | // Set success and error callback functions for the API call
89 | success: libPrefix + 'onApiResponse ' + options.on_success,
90 | error: libPrefix + 'onApiResponseError'
91 | }
92 |
93 | if (method === 'post') {
94 | HTTP.post(params);
95 | } else {
96 | HTTP.get(params);
97 | }
98 | }
99 |
100 | // Function called when an API response is received
101 | function onApiResponse() {
102 |
103 | // Parse the content of the response, which is in JSON format
104 | let json = JSON.parse(content);
105 |
106 | // Execute the request onSuccess command and pass 'options' objects as arguments
107 | Bot.runCommand(params, json);
108 | }
109 |
110 | // Function called when an API request results in an error
111 | function onApiResponseError() {
112 | throw content
113 | }
114 |
115 | // Function called when a transaction status is updated
116 | function onCallback(e) {
117 | // Parse the JSON data contained in the callback content
118 | let data = JSON.parse(content);
119 | const merchantTypes = ['invoice', 'white_label', 'static_address'];
120 | const apiSecretKey = merchantTypes.includes(data.type) ? getMerchantApiKey() : getPayoutApiKey();
121 | const calculatedHmac = CryptoJS.HmacSHA512(content, apiSecretKey).toString(CryptoJS.enc.Hex);
122 | const receivedHmac = options.headers.Hmac;
123 |
124 | if (calculatedHmac !== receivedHmac) {
125 | throw 'OxaPay Lib V1: Invalid HMAC signature!';
126 | }
127 |
128 | Bot.run({ command: params, options: data })
129 | }
130 |
131 | // Export functions to be used elsewhere
132 | publish({
133 |
134 | // These lines of code are defining properties for an object, named 'setMerchantApiKey' , 'setPayoutApiKey' and 'setGeneralApiKey' respectively.
135 | // These two functions set some sort of API key for a payment processing system.
136 | // These functions should only be called by admin
137 |
138 | setMerchantApiKey: setMerchantApiKey,
139 |
140 | setPayoutApiKey: setPayoutApiKey,
141 |
142 | setGeneralApiKey: setGeneralApiKey,
143 |
144 | // This function can call all api
145 | // It is not clear what this nested 'apiCall' object or function does based on this code alone.
146 | apiCall: apiCall,
147 | })
148 |
149 | // Set up event listeners for various events
150 | on(libPrefix + 'onApiResponse', onApiResponse);
151 | on(libPrefix + 'onApiResponseError', onApiResponseError);
152 | on(libPrefix + 'onCallback', onCallback);
--------------------------------------------------------------------------------
/GoogleTableSync.js:
--------------------------------------------------------------------------------
1 | //lib id - 32520
2 |
3 | var libPrefix = "GoogleSheetSync";
4 |
5 | function GACode() {
6 | var datas = options.gaTableSyncLib.datas;
7 |
8 | var data = null;
9 |
10 | var sheet;
11 | var headers = [];
12 |
13 | var suncResult = { newCount: 0, updatedCount: 0 };
14 |
15 | var already_filled = {}
16 |
17 | function fillOnExistHeaders(headers, rowIndex) {
18 | already_filled = {}
19 |
20 | var columnIndex = 1
21 |
22 | headers.forEach(function (column) {
23 | var value = data[column]
24 | if (value) {
25 | sheet.getRange(rowIndex, columnIndex).setValue(value)
26 | already_filled[column] = true
27 | }
28 | columnIndex += 1
29 | })
30 |
31 | return { lastColumn: columnIndex - 1, lastRow: rowIndex }
32 | }
33 |
34 | function fillOnNewHeaders(coors) {
35 | if (coors.lastRow == 1) {
36 | // we have totally new header
37 | coors.lastRow = 2
38 | }
39 |
40 | for (var ind in data) {
41 | if (already_filled[ind]) {
42 | continue
43 | }
44 |
45 | var value = data[ind];
46 | if(!value){
47 | continue
48 | }
49 |
50 | // fill Header
51 | sheet.getRange(1, coors.lastColumn + 1).setValue(ind)
52 |
53 | // fill value
54 | sheet.getRange(coors.lastRow, coors.lastColumn + 1).setValue(value)
55 | coors.lastColumn += 1
56 | }
57 | }
58 |
59 | function updateData(headers, rowIndex) {
60 | var filled = {}
61 |
62 | // fill new data on exists headers
63 | var coors = fillOnExistHeaders(headers, rowIndex)
64 |
65 | // fill new data on new headers
66 | fillOnNewHeaders(coors)
67 | }
68 |
69 | function updateExistData() {
70 | var rows = sheet.getDataRange().getValues()
71 | var alreadyUpdated = false
72 |
73 | // get headers
74 | headers = rows[0];
75 |
76 | var curRowIndex = 1;
77 | rows.forEach(function (row) {
78 | // find row by index
79 | for(var cellInd in row){
80 | if (row[cellInd] == data[options.gaTableSyncLib.index]) {
81 | // finded - need update value
82 | updateData(headers, curRowIndex)
83 | alreadyUpdated = true
84 | return
85 | }
86 | }
87 |
88 | curRowIndex += 1
89 | })
90 |
91 | return alreadyUpdated
92 | }
93 |
94 | function openSheet(){
95 | var table;
96 | var tableID = options.gaTableSyncLib.tableID;
97 | try{
98 | table = SpreadsheetApp.openById(tableID)
99 | }catch(e){
100 | throw new Error(
101 | "Table with id: " + tableID + " not found. " +
102 | "Or you need to add permissions. See: https://help.bots.business/libs/googleapp#permissions")
103 | }
104 |
105 | sheet = table.getSheetByName(options.gaTableSyncLib.sheetName);
106 | if(!sheet){
107 | throw new Error("Can not open sheet with name: " + options.gaTableSyncLib.sheetName);
108 | }
109 | }
110 |
111 | function syncData() {
112 | if (updateExistData()) {
113 | suncResult.updatedCount += 1
114 | return
115 | }
116 |
117 | var lastRowIndex = sheet.getLastRow()
118 | updateData(headers, lastRowIndex + 1)
119 | suncResult.newCount += 1
120 | }
121 |
122 | function syncDatas() {
123 | datas.forEach(function (it) {
124 | data = it;
125 | syncData();
126 | })
127 |
128 | return suncResult;
129 | }
130 |
131 | // READING
132 | function buildData(headers, item){
133 | var new_item = {};
134 | for(var key in item){
135 | new_item[headers[key]] = item[key]
136 | }
137 | return new_item;
138 | }
139 |
140 | function findData(data, rows){
141 | var item = null;
142 | for(var indRow in rows){
143 | var row = rows[indRow]
144 | // find row by index
145 | for(var cellInd in row){
146 | var key = data[options.gaTableSyncLib.index];
147 | var tableValue = row[cellInd];
148 | if (tableValue == key) {
149 | item = row;
150 | break;
151 | }
152 | }
153 | if(item){
154 | return buildData(headers, item);
155 | }
156 | }
157 | }
158 |
159 | function readData(){
160 | var rows = sheet.getDataRange().getValues()
161 | var result = [];
162 |
163 | // get headers
164 | headers = rows[0];
165 |
166 | datas.forEach(function (data) {
167 | var item = findData(data, rows);
168 | if(item){
169 | result.push(item)
170 | }
171 | })
172 |
173 | return result;
174 | }
175 | // end READING
176 |
177 | openSheet();
178 |
179 | if(options.gaTableSyncLib.isRead){
180 | return readData()
181 | }else{
182 | return syncDatas();
183 | }
184 |
185 | // end GACode
186 | }
187 |
188 | function checkErrors(syncOptions) {
189 | if (!Libs.GoogleApp) {
190 | throw new Error(libPrefix + ": need install GoogleApp Lib");
191 | }
192 |
193 | if (!options) {
194 | options = {}
195 | }
196 |
197 | if (!syncOptions) {
198 | throw new Error(libPrefix + ": need pass options");
199 | }
200 | if (!syncOptions.tableID) {
201 | throw new Error(libPrefix + ": need pass options.tableID");
202 | }
203 | if (!syncOptions.sheetName) {
204 | throw new Error(libPrefix + ": need pass options.sheetName");
205 | }
206 | if (!syncOptions.datas) {
207 | throw new Error(libPrefix + ": need pass array options.datas");
208 | }
209 | }
210 |
211 | function sync(syncOptions, reading) {
212 | checkErrors(syncOptions);
213 | options.gaTableSyncLib = syncOptions;
214 | options.gaTableSyncLib.isRead = reading;
215 |
216 | Libs.GoogleApp.run({
217 | code: GACode, // Function with Google App code
218 | onRun: syncOptions.onRun, // Optional. This command will be executed after run
219 | email: syncOptions.email, // Optional. Email for errors,
220 | debug: syncOptions.debug // For debug. Default is false
221 | })
222 | }
223 |
224 | function read(readOptions){
225 | sync(readOptions, true)
226 | }
227 |
228 | publish({
229 | sync: sync,
230 | read: read
231 | })
--------------------------------------------------------------------------------
/FreeKassa.js:
--------------------------------------------------------------------------------
1 | let LIB_PREFIX = 'free_kassa_lib_'
2 |
3 | function getSetupPage(){
4 | return 'https://api.bots.business/web-payments/' +
5 | bot.id + '/free-kassa';
6 | }
7 |
8 | function MD5(d){
9 | result = M(V(Y(X(d),8*d.length)));
10 | return result.toLowerCase()};
11 | function M(d){for(var _,m='0123456789ABCDEF',f='',r=0;r>>4&15)+m.charAt(15&_);
12 | return f}function X(d){for(var _=Array(d.length>>2),m=0;m<_.length;m++)_[m]=0;
13 | for(m=0;m<8*d.length;m+=8)_[m>>5]|=(255&d.charCodeAt(m/8))<>5]>>>m%32&255);
15 | return _}function Y(d,_){d[_>>5]|=128<<_%32,d[14+(_+64>>>9<<4)]=_;
16 | for(var m=1732584193,f=-271733879,r=-1732584194,i=271733878,n=0;n>16)+(_>>16)+(m>>16)<<16|65535&m}function bit_rol(d,_){return d<<_|d>>>32-_}
18 |
19 |
20 | function getMD5Sign(options){
21 | let secretKey = getSecretKey();
22 | let storeId = getStoreId();
23 |
24 | return MD5(
25 | storeId + ':' +
26 | options.amount + ':' +
27 | secretKey + ':' +
28 | options.comment
29 | )
30 | }
31 |
32 | function getOption(name){
33 | let prop = Bot.getProperty(LIB_PREFIX + name);
34 | if(!prop){ throw name + ' is not defined. Need define it.' }
35 | return prop;
36 | }
37 |
38 | function setOption(name, secret){
39 | Bot.setProperty(LIB_PREFIX + name, secret, 'string');
40 | }
41 |
42 | function getSecretKey(){ return getOption('Secret Key') }
43 | function setSecretKey(secret){ setOption('Secret Key', secret) }
44 |
45 | function getSecretKey2(){ return getOption('Secret Key 2') }
46 | function setSecretKey2(secret){ setOption('Secret Key 2', secret) }
47 |
48 | function getStoreId(){ return getOption('Store Id') }
49 | function setStoreId(storeId){ setOption('Store Id', storeId) }
50 |
51 | function getStoreId(){ return getOption('Store Id') }
52 | function setStoreId(storeId){ setOption('Store Id', storeId) }
53 |
54 | function getOnPaymentCommand(){ return getOption('OnPayment') }
55 | function setOnPaymentCommand(command){ setOption('OnPayment', command) }
56 |
57 | function getPaymentLink(options){
58 | if(!options.amount){ throw 'amount is not passed. Need define it.' }
59 | if(!options.comment){ throw 'comment is not passed. Need define it.' }
60 |
61 | return 'https://www.free-kassa.ru/merchant/cash.php?' +
62 | 'm=' + getStoreId() +
63 | '&oa=' + options.amount +
64 | '&o=' + options.comment +
65 | '&lang=' + options.language +
66 | '&s=' + getMD5Sign(options)
67 | }
68 |
69 | function acceptPayment(options){
70 | return
71 | /// not worked!
72 | let secretKey2 = getSecretKey2();
73 | let storeId = getStoreId();
74 |
75 | let url = 'http://www.free-kassa.ru/api.php?' +
76 | 'merchant_id=' + storeId + '&' +
77 | 's=' + MD5(storeId + '.' + secretKey2)
78 | 'intid'
79 | HTTP.get( {
80 | url: url,
81 | success: LIB_PREFIX + 'onAcceptPayment ' +
82 | options.comment + ' ' + options.onSuccess +
83 | ' ' + options.onNoPaymentYet,
84 | } )
85 | }
86 |
87 | function onPayment(){
88 | /// TODO: нужно проверить подпись!
89 | let result = decodeResult(content);
90 | let alreadyPaid = Bot.getProperty(LIB_PREFIX + 'alreadyPaid');
91 |
92 | if(!alreadyPaid){ alreadyPaid = {} }
93 | if(alreadyPaid[result.intid]){ return }
94 |
95 | alreadyPaid[result.intid] = true;
96 | Bot.setProperty(LIB_PREFIX + 'alreadyPaid', true, 'boolean')
97 |
98 |
99 | let cmd = getOnPaymentCommand();
100 | Bot.runCommand(cmd + ' ' + content);
101 | }
102 |
103 | function decodeResult(result){
104 | var obj = {};
105 | var pairs = result.split('&');
106 | for(i in pairs){
107 | var split = pairs[i].split('=');
108 | obj[decodeURIComponent(split[0])] = decodeURIComponent(split[1]);
109 | }
110 | return obj;
111 | }
112 |
113 | publish({
114 | getSetupPage: getSetupPage,
115 | options:{
116 | secret:{
117 | get: getSecretKey,
118 | set: setSecretKey
119 | },
120 | secret2:{
121 | get: getSecretKey,
122 | set: setSecretKey
123 | },
124 | storeId:{
125 | get: getStoreId,
126 | set: setStoreId
127 | },
128 | onPayment:{
129 | get: getOnPaymentCommand,
130 | set: setOnPaymentCommand
131 | }
132 | },
133 | getPaymentLink: getPaymentLink,
134 | decodeResult: decodeResult
135 | })
136 |
137 | on('lib-on-payment-free-kassa', onPayment)
--------------------------------------------------------------------------------
/Coinbase.js:
--------------------------------------------------------------------------------
1 | let libPrefix = "CoinbaseLib";
2 |
3 | let lib = {
4 | endpoint: "https://api.coinbase.com",
5 | apiVersion: "/v2/",
6 | commands: {
7 | onNotification: libPrefix + "_onNotification",
8 | onApiCall: libPrefix + "_onApiCall",
9 | onApiError: libPrefix + "_onApiCallError"
10 | },
11 | panelName: libPrefix + "Options"
12 | }
13 |
14 | function setupAdminPanel(){
15 | var webhookUrl = Libs.Webhooks.getUrlFor({
16 | command: lib.commands.onNotification
17 | })
18 |
19 | var panel = {
20 | title: "Coinbase options",
21 | description: "Options for Coinbase Lib",
22 | icon: "logo-bitcoin",
23 |
24 | fields: [
25 | {
26 | name: "APIKey",
27 | title: "API Key",
28 | description: "you can get your API key in https://www.coinbase.com/settings/api",
29 | type: "password",
30 | placeholder: "API Key",
31 | icon: "key"
32 | },
33 | {
34 | name: "SecretAPIKey",
35 | title: "Secret API Key",
36 | description: "you can get your Secret API key in https://www.coinbase.com/settings/api",
37 | type: "password",
38 | placeholder: "Secret API Key",
39 | icon: "key"
40 | },
41 | {
42 | name: "WebhookUrl",
43 | title: "Notifications url. Fill this notifications url on API key creation",
44 | description: webhookUrl,
45 | icon: "flash"
46 | },
47 | {
48 | name: "OnNotification",
49 | title: "Command to be called on notifications",
50 | description: "this command will be executed on notification",
51 | type: "string",
52 | placeholder: "/onCoinbaseNotify",
53 | icon: "notifications"
54 | },
55 | ]
56 | }
57 |
58 | AdminPanel.setPanel({
59 | panel_name: lib.panelName,
60 | data: panel,
61 | force: false // default false - save fields values
62 | });
63 | }
64 |
65 | function sendNoLibMessage(libName){
66 | Bot.sendMessage("Please install " + libName +
67 | " from the Store. It is required by CoinbaseLib");
68 | }
69 |
70 | function setup(){
71 | if(!Libs.Webhooks){
72 | return sendNoLibMessage("Webhook Lib");
73 | }
74 |
75 | setupAdminPanel();
76 | }
77 |
78 | function getOptions(){
79 | return AdminPanel.getPanelValues(lib.panelName);
80 | }
81 |
82 | function timestamp(){
83 | const now = new Date()
84 | return Math.round(now.getTime() / 1000)
85 | }
86 |
87 | function generateSIGN(options){
88 | // timestamp + method + requestPath + body
89 | let body = "";
90 | if(options.body){
91 | body = JSON.stringify(options.body);
92 | }
93 |
94 | let sign = options.timestamp + options.method + options.path + body;
95 |
96 | let key = options.secretApiKey || getOptions().SecretAPIKey;
97 | if(!key){
98 | throw new Error(libPrefix +
99 | ": Please setup secretApiKey https://help.bots.business/libs/coinbase#initial-setup");
100 | }
101 |
102 | let hmac = CryptoJS.HmacSHA256(sign, key);
103 |
104 | return String(hmac);
105 | }
106 |
107 | function getCredentials(options){
108 | // SEE: https://developers.coinbase.com/docs/wallet/api-key-authentication
109 | options.timestamp = timestamp();
110 | var apiKey = options.apiKey || getOptions().APIKey;
111 |
112 | if(!apiKey){
113 | throw new Error(libPrefix +
114 | ": Please setup ApiKey https://help.bots.business/libs/coinbase#initial-setup");
115 | }
116 |
117 | return {
118 | "CB-ACCESS-KEY": apiKey,
119 | "CB-ACCESS-SIGN": generateSIGN(options),
120 | "CB-ACCESS-TIMESTAMP": options.timestamp,
121 | "CB-VERSION": "2019-11-15"
122 | }
123 | }
124 |
125 | function withSupportForParams(command){
126 | if(!command){ return "" }
127 | // user can pass params here
128 | return command.split(" ").join("%%");
129 | }
130 |
131 | function buildQueryParams(options){
132 | options.path = lib.apiVersion + options.path;
133 | let headers = getCredentials(options);
134 |
135 | let url = lib.endpoint + options.path;
136 |
137 | let onSuccess = withSupportForParams(options.onSuccess);
138 | let onError = withSupportForParams(options.onError);
139 |
140 | return {
141 | url: url,
142 | success: lib.commands.onApiCall + " " + onSuccess + " " + onError,
143 | error: lib.commands.onApiCallError + " " + onError,
144 | background: options.background, // if you have timeout error
145 | headers: headers,
146 | body: options.body
147 | }
148 | }
149 |
150 | function getCorrectedPath(path){
151 | if(path[0]=="/"){
152 | path = path.substring(1, path.length)
153 | }
154 | if(path[path.length-1]=="/"){
155 | path = path.substring(0, path.length - 1)
156 | }
157 |
158 | return path
159 | }
160 |
161 | function apiCall(options){
162 | // options:
163 | // method - GET, POST. GET is default
164 | // path - is the full path and query parameters of the URL, e.g.: exchange-rates?currency=USD
165 | // body - The body is the request body string. It is omitted if there is no request body (typically for GET requests).
166 | // background - perform request in background for more timeout
167 | // onSuccess - commnad onSuccess
168 | // onError - commnad onError
169 | // apiKey - if you need custom Api Key
170 | // secretApiKey - if you need custom Api Key
171 | if(!options.method){
172 | options.method = "GET" // by default
173 | }
174 |
175 | options.method = options.method.toUpperCase();
176 |
177 | if(!options.path){
178 | throw new Error(libPrefix + ": need pass API options.path for Api Call")
179 | }
180 |
181 | options.path = getCorrectedPath(options.path);
182 |
183 | let reqParams = buildQueryParams(options);
184 |
185 | if(options.method == "GET"){
186 | return HTTP.get(reqParams)
187 | }
188 |
189 | if(options.method == "POST"){
190 | return HTTP.post(reqParams)
191 | }
192 | }
193 |
194 | function getError(code){
195 | code = parseInt(code);
196 | var errors = [
197 | {id: "two_factor_required", code: 402, description: "When sending money over 2fa limit"},
198 | {id: "param_required", code: 400, description: "Missing parameter"},
199 | {id: "validation_error", code: 400, description: "Unable to validate POST/PUT"},
200 | {id: "invalid_request", code: 400, description: "Invalid request"},
201 | {id: "personal_details_required", code: 400, description: "User’s personal detail required to complete this request"},
202 | {id: "identity_verification_required", code: 400, description: "Identity verification is required to complete this request"},
203 | {id: "jumio_verification_required", code: 400, description: "Document verification is required to complete this request"},
204 | {id: "jumio_face_match_verification_required", code: 400, description: "Document verification including face match is required to complete this request"},
205 | {id: "unverified_email", code: 400, description: "User has not verified their email"},
206 | {id: "authentication_error", code: 401, description: "Invalid auth (generic)"},
207 | {id: "invalid_scope", code: 403, description: "User hasn’t authenticated necessary scope"},
208 | {id: "not_found", code: 404, description: "Resource not found"},
209 | {id: "rate_limit_exceeded", code: 429, description: "Rate limit exceeded"},
210 | {id: "internal_server_error", code: 500, description: "Internal server error"}
211 | ];
212 |
213 | var err_msg = "";
214 | var err;
215 | for(var ind in errors){
216 | err = errors[ind];
217 | if(err.code==code){
218 | err_msg += err.id + ": " + err.description + "; "
219 | }
220 | }
221 | return err_msg;
222 | }
223 |
224 | function getCommandFromParam(param){
225 | if(!param){ return }
226 | return param.split("%%").join(" ");
227 | }
228 |
229 | function getResultOptions(){
230 | return ( {
231 | result: JSON.parse(content),
232 | http_status: http_status
233 | } )
234 | }
235 |
236 | function onApiCall(){
237 | var cmds = params.split(" ");
238 | var onSuccess = getCommandFromParam(cmds[0]);
239 | var onError = getCommandFromParam(cmds[1]);
240 |
241 | if(!http_status){ http_status = 0 }
242 | http_status = parseInt(http_status);
243 |
244 | if((http_status>299)||(http_status<200)){
245 | var error = libPrefix + " Api error. " + getError(http_status);
246 | if(!onError){ throw new Error(error) }
247 |
248 | return onApiCallError(onError, error);
249 | }
250 |
251 | Bot.run({ command: onSuccess, options: getResultOptions() })
252 | }
253 |
254 | function onApiCallError(onError, error){
255 | if(!onError){ onError = params }
256 | var opts = getResultOptions();
257 | opts.error = error;
258 | Bot.run({ command: onError, options: opts })
259 | }
260 |
261 | function onNotification(){
262 | var onNotify = getOptions().OnNotification;
263 | if(!onNotify){ return }
264 | Bot.run({ command: onNotify, options: getResultOptions() })
265 | }
266 |
267 | publish({
268 | setup: setup,
269 | apiCall: apiCall
270 | })
271 |
272 |
273 | on(lib.commands.onNotification, onNotification)
274 | on(lib.commands.onApiCall, onApiCall)
275 | on(lib.commands.onApiError, onApiCallError)
--------------------------------------------------------------------------------
/OxaPayLib.js:
--------------------------------------------------------------------------------
1 | // Declare variables for the library prefix and the API URL
2 | let libPrefix = "oxapaylib";
3 | let API_URL = "https://api.oxapay.com/";
4 |
5 | // Functions to set and get the merchant key
6 | function setMerchantKey(key) {
7 | Bot.setProperty(libPrefix + "merchantkey", key, "string");
8 | }
9 |
10 | function getMerchantKey() {
11 | var merchantKey = Bot.getProperty(libPrefix + "merchantkey");
12 | if (!merchantKey) { throw new Error("OxaPay lib: no merchantKey. You need to setup it") }
13 |
14 | return merchantKey
15 | }
16 |
17 | // Functions to set and get the payment api key
18 | function setPaymentApiKey(key) {
19 | Bot.setProperty(libPrefix + "paymentapikey", key, "string");
20 | }
21 |
22 | function getPaymentApiKey() {
23 | var paymentApiKey = Bot.getProperty(libPrefix + "paymentapikey");
24 | if (!paymentApiKey) { throw new Error("OxaPay lib: no paymentApiKey. You need to setup it") }
25 |
26 | return paymentApiKey
27 | }
28 |
29 | // Function to make an API call
30 | function apiCall(options) {
31 |
32 | // Set the headers for the API request
33 | let headers = {
34 | "cache-control": "no-cache",
35 | "Content-type": "application/json",
36 | "Accept": "application/json",
37 | }
38 |
39 | // Define parameters for the HTTP request
40 | params = {
41 | url: API_URL + options.url,
42 | body: options.fields,
43 | headers: headers,
44 |
45 | // Set success and error callback functions for the API call
46 | success: libPrefix + "onApiResponse " + options.onSuccess,
47 | error: libPrefix + "onApiResponseError"
48 | }
49 |
50 | HTTP.post(params)
51 | }
52 |
53 | // Function called when an API response is received
54 | function onApiResponse() {
55 |
56 | // Parse the content of the response, which is in JSON format
57 | let options = JSON.parse(content);
58 |
59 | // Execute the request onSuccess command and pass "options" objects as arguments
60 | Bot.runCommand(params, options);
61 | }
62 |
63 | // Function called when an API request results in an error
64 | function onApiResponseError() {
65 | throw content
66 | }
67 |
68 | function createTransaction(options) {
69 |
70 | // Throw an error if no options are passed or if there are no fields specified in the options
71 | if (!options) { throw "OxaPayLib: createTransaction need options" }
72 | if (!options.fields) { throw "OxaPayLib: createTransaction need options.fields" }
73 |
74 | // Get the merchant key from the "getMerchantKey" function and add it to the options object
75 | let merchantKey = getMerchantKey();
76 | options.fields.merchant = merchantKey
77 |
78 | // Define the callback URL for the transaction
79 | let callbackUrl = Libs.Webhooks.getUrlFor({
80 | command: libPrefix + "onCallback",
81 | user_id: user.id,
82 | })
83 |
84 | // Add the callback URL to the fields in the options
85 | options.fields.callbackUrl = callbackUrl;
86 |
87 | // Set the URL for the API request and the success callback function
88 | options.url = "merchants/request"
89 | options.onSuccess = options.onCreatePayment;
90 |
91 | // Make the API call using the "apiCall" function
92 | apiCall(options);
93 | }
94 |
95 | // Function called when a transaction status is updated
96 | function onCallback() {
97 |
98 | // Parse the JSON data contained in the callback content
99 | let data = JSON.parse(content);
100 |
101 | // If the transaction status is 2
102 | if (data.status == 2) {
103 | // Call the "verifyPayment" function with an options object that includes "fields.trackId", using the trackId from the callback data
104 | verifyPayment({ fields: { trackId: data.trackId } });
105 | }
106 | }
107 |
108 | function verifyPayment(options) {
109 |
110 | // Throw an error if no options are passed or if there are no fields specified in the options or if the trackId field is missing
111 | if (!options) { throw "OxaPayLib: verifyPayment need options" }
112 | if (!options.fields) { throw "OxaPayLib: verifyPayment need options.fields" }
113 | if (!options.fields.trackId) { throw "OxaPayLib: verifyPayment need options.fields.trackId" }
114 |
115 | // Get the merchant key from the "getMerchantKey" function and add it to the options object
116 | let merchantKey = getMerchantKey();
117 | options.fields.merchant = merchantKey
118 |
119 | // Set the URL for the API request and the success callback function
120 | options.url = "merchants/verify"
121 | options.onSuccess = libPrefix + "onVerifyPayment " + options.fields.trackId;
122 |
123 | // Make the API call using the "apiCall" function
124 | apiCall(options);
125 | }
126 |
127 | // Function called when a payment is successfully verified
128 | function onVerifyPayment() {
129 |
130 | // If the status property of the response object equals 1
131 | if (options.result == 100 && options.status == 1) {
132 |
133 | // Call the "runCommand" function on the "Bot" object to execute the specified command and pass the "params" and "options" objects as arguments
134 | Bot.runCommand("/onCompletePayment", options);
135 | }
136 | }
137 |
138 |
139 | function transfer(options) {
140 |
141 | // Throw an error if no options are passed or if there are no fields specified in the options
142 | if (!options) { throw "OxaPayLib: transfer need options" }
143 | if (!options.fields) { throw "OxaPayLib: transfer need options.fields" }
144 | if (!options.fields.currency) { throw "OxaPayLib: transfer need options.fields.currency" }
145 | if (!options.fields.amount) { throw "OxaPayLib: transfer need options.fields.amount" }
146 | if (!options.fields.address) { throw "OxaPayLib: transfer need options.fields.address" }
147 |
148 | // Get the payment api key from the "getPaymentApiKey" function and add it to the options object
149 | let key = getPaymentApiKey();
150 | options.fields.key = key
151 |
152 | // Set the URL for the API request and the success callback function
153 | options.url = "api/send"
154 | options.onSuccess = options.onTransfer;
155 |
156 | // Make the API call using the "apiCall" function
157 | apiCall(options);
158 | }
159 |
160 | // Function to get information about a transaction using the trackId
161 | function getTxInfo(options) {
162 |
163 | // Throw an error if no options are passed or if there are no fields specified in the options or if the trackId field is missing
164 | if (!options) { throw "OxaPayLib: getTxInfo need options" }
165 | if (!options.fields) { throw "OxaPayLib: getTxInfo need options.fields" }
166 | if (!options.fields.trackId) { throw "OxaPayLib: getTxInfo need options.fields.trackId" }
167 |
168 | // Get the merchant key from the "getMerchantKey" function and add it to the options object
169 | let merchantKey = getMerchantKey();
170 | options.fields.merchant = merchantKey
171 |
172 | // Set the URL for the API request
173 | options.url = "merchants/inquiry"
174 |
175 | // Make the API call using the "apiCall" function
176 | apiCall(options);
177 | }
178 |
179 | // Function to get a list of accepted coins
180 | function getAcceptedCoins(options) {
181 |
182 | // If no options are passed, set options to an empty object
183 | if (!options) options = {}
184 |
185 | // Get the merchant key from the "getMerchantKey" function and add it to the options object
186 | let merchantKey = getMerchantKey();
187 | if (!options.fields) options.fields = {}
188 | options.fields.merchant = merchantKey
189 |
190 | // Set the URL for the API request
191 | options.url = "merchants/allowedCoins"
192 |
193 | // Make the API call using the "apiCall" function
194 | apiCall(options);
195 | }
196 |
197 | // Export functions to be used elsewhere
198 | publish({
199 |
200 | // These lines of code are defining properties for an object, named "setMerchantKey" and "setPaymentApiKey" respectively.
201 | // These two functions set some sort of API key for a payment processing system.
202 | // These functions should only be called by admin
203 |
204 | setMerchantKey: setMerchantKey,
205 |
206 | setPaymentApiKey: setPaymentApiKey,
207 |
208 | // This function can call all api
209 | // It is not clear what this nested "apiCall" object or function does based on this code alone.
210 | apiCall: apiCall,
211 |
212 | // This function creates a new transaction.
213 | // This means that when createTransaction is called, it will send order information and register it in the OxaPay system.
214 | createTransaction: createTransaction,
215 |
216 | // This function send the assets in your wallet balance to another OxaPay account with zero fees.
217 | transfer: transfer,
218 |
219 | // This function retrieves information about a particular transaction.
220 | // This means that when getTxInfo is called with a transaction trackId as a parameter,
221 | // it will inquire about payment and receive a report of a payment session.
222 | getTxInfo: getTxInfo,
223 |
224 | // This function retrieves the list of all coins that are accepted for payment.
225 | // This means that when getAcceptedCoins is called, it will get the list of your merchant"s accepted coins.
226 | getAcceptedCoins: getAcceptedCoins
227 | })
228 |
229 | // Set up event listeners for various events
230 | on(libPrefix + "onApiResponse", onApiResponse);
231 | on(libPrefix + "onApiResponseError", onApiResponseError);
232 | on(libPrefix + "onCallback", onCallback);
233 | on(libPrefix + "onVerifyPayment", onVerifyPayment);
234 |
--------------------------------------------------------------------------------
/ResourcesLib.js:
--------------------------------------------------------------------------------
1 | let libPrefix = 'ResourcesLib_';
2 |
3 | let growthResource = function(resource){
4 | return {
5 | resource: resource,
6 |
7 | /// auto growthing or decreasing
8 | propName: function(){ return this.resource.propName() + '_growth'},
9 |
10 | info: function(){
11 | return Bot.getProperty(this.propName()) || {}
12 | },
13 |
14 | title: function(){
15 | if(!this.isEnabled){ return }
16 |
17 | let growth = this.info();
18 | let start_text = 'add ' + String(growth.increment);
19 | let middle_text = ' once at ' + String(growth.interval) + ' secs';
20 |
21 | if(growth.type=='simple'){
22 | return start_text + middle_text
23 | }
24 | if(growth.type=='percent'){
25 | return start_text + '%' + middle_text
26 | }
27 | if(growth.type=='compound_interest'){
28 | return start_text + '%' + middle_text + ' with reinvesting'
29 | }
30 | },
31 |
32 | have: function(){ return this.info() },
33 |
34 | isEnabled: function(){
35 | let growth = this.info();
36 | if(growth){ return growth.enabled }
37 | return false;
38 | },
39 |
40 | _toggle: function(status){
41 | let growth = this.info();
42 | if(!growth){ return }
43 |
44 | growth.enabled = status;
45 | return Bot.setProperty(this.propName(), growth, 'json');
46 | },
47 |
48 | stop: function(){
49 | return this._toggle(false);
50 | },
51 |
52 | progress: function(){
53 | let growth = this.info();
54 | if(!growth){ return }
55 |
56 | let total_iterations = this.totalIterations(growth);
57 | let fraction = total_iterations % 1;
58 | return fraction*100;
59 | },
60 |
61 | willCompletedAfter: function(){
62 | return this.info().interval - this.progress()/100 * this.info().interval;
63 | },
64 |
65 | totalIterations: function(growth){
66 | if(!growth){ growth = this.info() }
67 |
68 | let now = (new Date().getTime());
69 | let duration_in_seconds = ( now - growth.started_at ) / 1000;
70 | return duration_in_seconds / growth.interval;
71 | },
72 |
73 | _calcMinMax(result, growth){
74 | if((growth.min)&&(growth.min > result)){
75 | return growth.min
76 | }
77 |
78 | if((growth.max)&&(growth.max < result)){
79 | return growth.max
80 | }
81 |
82 | return result
83 | },
84 |
85 | _calcByTotalIterations(value, total_iterations, growth){
86 | var result;
87 | if(growth.type=='simple'){
88 | result = value + total_iterations * growth.increment
89 | }
90 | if(growth.type=='percent'){
91 | let percent = growth.increment / 100;
92 | let all_percents = percent * growth.base_value * total_iterations
93 | result = value + all_percents;
94 | }
95 | if(growth.type=='compound_interest'){
96 | let percent = (1 + growth.increment / 100);
97 | result = value * Math.pow(percent, total_iterations)
98 | }
99 | return result;
100 | },
101 |
102 | _getTotalIterationsWithLimit(growth){
103 | let total_iterations = this.totalIterations(growth);
104 |
105 | if(!growth.max_iterations_count){ return total_iterations }
106 |
107 | let total = total_iterations + growth.completed_iterations_count;
108 | if(total < growth.max_iterations_count){
109 | return total_iterations
110 | }
111 |
112 | return growth.max_iterations_count - growth.completed_iterations_count;
113 | },
114 |
115 | _calcValue(value, growth){
116 | let total_iterations = this._getTotalIterationsWithLimit(growth);
117 |
118 | if(total_iterations<1){ return }
119 |
120 | let fraction = total_iterations % 1;
121 | total_iterations = total_iterations - fraction;
122 |
123 | var result = this._calcByTotalIterations(value, total_iterations, growth)
124 |
125 | growth.completed_iterations_count+= total_iterations;
126 |
127 | result = this._calcMinMax(result, growth);
128 |
129 | this._updateIteration(growth, fraction * 1000);
130 |
131 | return result;
132 | },
133 |
134 | getValue: function(value){
135 | let growth = this.info();
136 | if(!growth){ return value }
137 | if(!growth.enabled){ return value }
138 |
139 | let new_value = this._calcValue(value, growth);
140 |
141 | if(!new_value){ return value }
142 |
143 | this.resource._set(new_value); /// update value
144 |
145 | return new_value;
146 | },
147 |
148 | _updateIteration: function(growth, fraction){
149 | if(!growth){ growth = this.info() }
150 | if(!growth){ return }
151 |
152 | let started_at = (new Date().getTime());
153 | /// started same early
154 | if(fraction){ started_at = started_at - fraction }
155 |
156 | growth.started_at = started_at;
157 |
158 | return Bot.setProperty(this.propName(), growth, 'json');
159 | },
160 |
161 | _updateBaseValue: function(base_value){
162 | var growth = this.info();
163 | if(!growth){ return }
164 |
165 | growth.base_value = base_value;
166 | return Bot.setProperty(this.propName(), growth, 'json');
167 | },
168 |
169 | _newGrowth: function(options){
170 | return {
171 | base_value: this.resource.baseValue(),
172 | increment: options.increment,
173 | interval: options.interval,
174 | type: options.type,
175 | min: options.min,
176 | max: options.max,
177 | max_iterations_count: options.max_iterations_count,
178 | enabled: true,
179 | completed_iterations_count: 0
180 | }
181 | },
182 |
183 | _addAs: function(options){
184 | let growth = this._newGrowth(options);
185 | return this._updateIteration(growth);
186 | },
187 |
188 | add: function(options){
189 | /// absolute growth value
190 | options.type = 'simple';
191 | options.increment = options.value;
192 | return this._addAs(options);
193 | },
194 |
195 | addPercent: function(options){
196 | /// percent
197 | options.type = 'percent';
198 | options.increment = options.percent;
199 | return this._addAs(options);
200 | },
201 |
202 | addCompoundInterest: function(options){
203 | /// compound percent
204 | options.type = 'compound_interest';
205 | options.increment = options.percent;
206 | return this._addAs(options);
207 | }
208 |
209 | }
210 | }
211 |
212 | let commonResource = function(objName, objID, resName){
213 | return {
214 | objName: objName,
215 | objID: objID,
216 | name: resName,
217 | growth: null,
218 |
219 | _setGrowth: function(growth){
220 | this.growth = growth;
221 | },
222 |
223 | propName: function(){
224 | return libPrefix + this.objName + this.objID + '_' + this.name
225 | },
226 |
227 | isNumber: function(value){ return typeof(value)=='number' },
228 |
229 | verifyNumber: function(value){
230 | if(!this.isNumber(value)){
231 | let evalue = '';
232 | if(typeof(value)!='undefined'){ evalue = JSON.stringify(value) }
233 | throw 'ResLib: value must be number only. It is not number: ' + typeof(value) + ' ' + evalue;
234 | }
235 | },
236 |
237 | removeRes: function(res_amount){
238 | this.set(this.value() - res_amount);
239 | return true;
240 | },
241 |
242 | baseValue: function(){
243 | let cur_value = Bot.getProperty(this.propName());
244 | if(typeof(cur_value)=='undefined'){ return 0 }
245 |
246 | return cur_value;
247 | },
248 |
249 | value: function(){
250 | let cur_value = this.baseValue();
251 |
252 | if(this._withEnabledGrowth()){
253 | return this.growth.getValue(cur_value);
254 | }
255 | return cur_value;
256 | },
257 |
258 | add: function(res_amount){
259 | this.verifyNumber(res_amount);
260 | this.set(this.value() + res_amount)
261 | return true;
262 | },
263 |
264 | have: function(res_amount){
265 | this.verifyNumber(res_amount);
266 | // can not have negative or null amount
267 | if(res_amount < 0){ return false }
268 | if(res_amount == 0){ return false }
269 |
270 | return this.value() >= res_amount;
271 | },
272 |
273 | remove: function(res_amount){
274 | if(!this.have(res_amount)){
275 | throw 'ResLib: not enough resources'
276 | }
277 | return this.removeRes(res_amount);
278 | },
279 |
280 | removeAnyway: function(res_amount){
281 | this.verifyNumber(res_amount);
282 | return this.removeRes(res_amount)
283 | },
284 |
285 | _withEnabledGrowth: function(){
286 | return (this.growth && this.growth.isEnabled())
287 | },
288 |
289 | _set: function(res_amount){
290 | Bot.setProperty(this.propName(), res_amount, 'float');
291 | },
292 |
293 | set: function(res_amount){
294 | this.verifyNumber(res_amount);
295 |
296 | if( this._withEnabledGrowth() ){
297 | this.growth._updateBaseValue(res_amount)
298 | }
299 | return this._set(res_amount);
300 | },
301 |
302 | anywayTakeFromAndTransferTo: function(fromResource, toResource, res_amount){
303 | if(fromResource.name!=toResource.name){
304 | throw 'ResLib: can not transfer different resources'
305 | }
306 |
307 | if(fromResource.removeAnyway(res_amount)){
308 | return toResource.add(res_amount)
309 | }
310 | return false
311 | },
312 |
313 | anywayTakeFromAndTransferToDifferent: function(fromResource, toResource, remove_amount, add_amount){
314 | if(fromResource.removeAnyway(remove_amount)){
315 | return toResource.add(add_amount)
316 | }
317 | return false
318 | },
319 |
320 | takeFromAndTransferTo: function(fromResource, toResource, res_amount){
321 | if(!fromResource.have(res_amount)){
322 | throw 'ResLib: not enough resources for transfer'
323 | }
324 |
325 | return this.anywayTakeFromAndTransferTo(fromResource, toResource, res_amount)
326 | },
327 |
328 | takeFromAndTransferToDifferent: function(fromResource, toResource, remove_amount, add_amount){
329 | if(!fromResource.have(remove_amount)){
330 | throw 'ResLib: not enough resources for transfer'
331 | }
332 |
333 | return this.anywayTakeFromAndTransferToDifferent(fromResource, toResource, remove_amount, add_amount)
334 | },
335 |
336 | takeFromAnother: function(anotherResource, res_amount){
337 | return this.takeFromAndTransferTo(anotherResource, this, res_amount);
338 | },
339 |
340 | transferTo: function(anotherResource, res_amount){
341 | return this.takeFromAndTransferTo(this, anotherResource, res_amount);
342 | },
343 |
344 | exchangeTo: function(anotherResource, options){
345 | return this.takeFromAndTransferToDifferent(this,
346 | anotherResource, options.remove_amount, options.add_amount );
347 | },
348 |
349 | takeFromAnotherAnyway: function(anotherResource, res_amount){
350 | return this.anywayTakeFromAndTransferTo(anotherResource, this, res_amount);
351 | },
352 |
353 | transferToAnyway: function(anotherResource, res_amount){
354 | return this.anywayTakeFromAndTransferTo(this, anotherResource, res_amount);
355 | }
356 |
357 | }
358 | }
359 |
360 | let growthFor = function(resource){
361 | let growth = growthResource(resource);
362 | resource._setGrowth(growth);
363 | return growth;
364 | }
365 |
366 | let getResourceFor = function(object, object_id, resName){
367 | let res = commonResource(object, object_id, resName);
368 | growthFor(res);
369 |
370 | return res;
371 | }
372 |
373 | let userResource = function(resName){
374 | return getResourceFor('user', user.telegramid, resName);
375 | }
376 |
377 | let chatResource = function(resName){
378 | return getResourceFor('chat', chat.chatid, resName);
379 | }
380 |
381 | let anotherUserResource = function(resName, telegramid){
382 | return getResourceFor('user', telegramid, resName);
383 | }
384 |
385 | let anotherChatResource = function(resName, chatid){
386 | return getResourceFor('chat', chatid, resName);
387 | }
388 |
389 |
390 |
391 | publish({
392 | userRes: userResource,
393 | chatRes: chatResource,
394 |
395 | anotherUserRes: anotherUserResource,
396 | anotherChatRes: anotherChatResource,
397 |
398 | growthFor: growthFor
399 |
400 | })
--------------------------------------------------------------------------------
/CoinPayments.js:
--------------------------------------------------------------------------------
1 | let libPrefix = "coinpaymentslib";
2 | let API_URL = "https://www.coinpayments.net/api.php";
3 |
4 | function cryptHmacSHA512(body, privateKey){
5 | return String(CryptoJS.HmacSHA512(body, privateKey));
6 | }
7 |
8 | function setPrivateKey(key){
9 | Bot.setProperty(libPrefix + "privatekey", key, "string");
10 | }
11 |
12 | function setPublicKey(key){
13 | Bot.setProperty(libPrefix + "publickey", key, "string");
14 | }
15 |
16 | function setBBApiKey(apiKey){
17 | Bot.setProperty(libPrefix + "bb_api_key", apiKey, "string");
18 | }
19 |
20 | function loadKey(){
21 | var publicKey = Bot.getProperty(libPrefix + "publickey");
22 | var privateKey = Bot.getProperty(libPrefix + "privatekey");
23 |
24 | if(!publicKey){ throw new Error("CP lib: no publicKey. You need to setup it") }
25 | if(!privateKey){ throw new Error("CP lib: no privateKey. You need to setup it") }
26 |
27 | return {
28 | publicKey: publicKey,
29 | privateKey: privateKey
30 | }
31 | }
32 |
33 | function getQueryParams(json){
34 | return Object.keys(json).map(function(key) {
35 | return encodeURIComponent(key) + "=" +
36 | encodeURIComponent(json[key]);
37 | }).join("&");
38 | }
39 |
40 | function apiCall(options){
41 | canCallApi();
42 |
43 | let key = loadKey();
44 |
45 | let body = "version=1&format=json&key=" + key.publicKey +
46 | "&" + getQueryParams(options.fields)
47 |
48 | let hmac = cryptHmacSHA512(body, key.privateKey);
49 |
50 | let headers = {
51 | "Content-type": "application/x-www-form-urlencoded",
52 | "Accept": "application/json",
53 | "HMAC": hmac
54 | }
55 |
56 | params = {
57 | url: API_URL,
58 | body: body,
59 | headers: headers,
60 | success: libPrefix + "onApiResponse " + options.onSuccess,
61 | // error: callbacks.onError
62 | }
63 |
64 | HTTP.post( params )
65 | }
66 |
67 | function canCallApi(){
68 | // Bug workaround for:
69 | // Too many errors in the last two minutes from XXX - please fix your code and try again
70 |
71 | let lastErr = Bot.getProperty(libPrefix + "lastError");
72 | if(!lastErr){ return true }
73 |
74 | let curTime = Date.now();
75 | if(curTime-lastErr.time > 300000){
76 | // 5 minutes have passed
77 | return true;
78 | }
79 |
80 | throw new Error(
81 | "CoinPayments Lib: Can not make Api request because you have error in last 5 minutes. " +
82 | "Please fix error and try again. Last error on message: " + lastErr.message + ". Last error: " + lastErr.error
83 | )
84 | }
85 |
86 | function saveError(json){
87 | if(json.error=="ok"){ return }
88 |
89 | Bot.setProperty(
90 | libPrefix + "lastError",
91 | {
92 | time: Date.now(),
93 | error: json.error,
94 | message: message
95 | }
96 | )
97 | }
98 |
99 | function onApiResponse(){
100 | let json = JSON.parse(content);
101 | saveError(json)
102 | Bot.runCommand(params, {body: json} );
103 | }
104 |
105 | function haveError(options, errCallback){
106 | let err = options.body.error;
107 |
108 | if(err=="ok"){ return false }
109 |
110 | saveError(options.body);
111 |
112 | if(errCallback){
113 | Bot.runCommand(errCallback, {result: result, error: err });
114 | }else{
115 | Bot.sendMessage("CoinPaymentsLib error:\n\n" + err);
116 | }
117 | return true
118 | }
119 |
120 | function runCallbackAndGetResult(){
121 | let arr = params.split(" ");
122 | let payment_index = arr[0];
123 |
124 | let callback
125 | let errCallback;
126 |
127 | if(arr[1]){
128 | callback = arr[1].split("#==#").join(" ");
129 | }
130 |
131 | if(arr[2]){
132 | errCallback = arr[2].split("#==#").join(" ");
133 | }
134 |
135 | if(haveError(options, errCallback)){ return }
136 |
137 | let result = options.body.result;
138 |
139 | if(callback&&(callback!="")){
140 | Bot.runCommand(callback, {result: result, payment_index: payment_index});
141 | }
142 |
143 | return { body: result, payment_index: payment_index }
144 | }
145 |
146 | function onCreateTransaction(){
147 | let result = runCallbackAndGetResult();
148 |
149 | if(!result){ return }
150 |
151 | let payments = getPayments();
152 | payments.list[result.payment_index].txn_id = result.body.txn_id;
153 | User.setProperty(libPrefix + "_payments", payments, "json");
154 | }
155 |
156 | function getJsonFromQuery(query){
157 | if(!query){
158 | throw new Error("CP lib: no query in getJsonFromQuery. May be it is GET request? Need post!")
159 | }
160 | if(typeof(query)!="string"){ throw new Error("CP lib: query must be string") }
161 |
162 | let arr = query.split("&");
163 | let result = {}
164 |
165 | let floatItems = ["amount1", "amount2", "fee", "status", "amount", "amounti",
166 | "feei", "confirms", "fiat_amount", "fiat_amounti", "fiat_fee", "fiat_feei" ]
167 |
168 | let item;
169 | let value;
170 | for(let i in arr){
171 | item = arr[i].split("=")
172 | value = unescape(item[1]);
173 | if(floatItems.includes(item[0])){
174 | value = parseFloat(value);
175 | }
176 | result[item[0]] = value;
177 | }
178 |
179 | return result;
180 | }
181 |
182 | function getParsedCustom(result){
183 | // user.id + ":" + payment_index + ":" + options.onPaymentCompleted
184 | if(!result.custom){
185 | throw new Error("CP lib: no result.custom in onIPN")
186 | }
187 |
188 | let arr = result.custom.split(":");
189 | let user_id = arr[0];
190 | let payment_index = arr[1];
191 | let onPaymentCompletedCallback = arr[2].split("#==#").join(" ");
192 |
193 | return { user_id: user_id, payment_index: payment_index,
194 | onPaymentCompletedCallback: onPaymentCompletedCallback }
195 | }
196 |
197 | function callPaymentCompleted(result, callback){
198 | if(!callback){
199 | throw new Error("CP lib: onIPN - onPaymentCompleted is undefined")
200 | }
201 |
202 | Bot.runCommand(callback, result);
203 | }
204 |
205 | function acceptPaymentIfNeed(payments, result, opts){
206 | // accept payment
207 | let cur_status = payments.list[opts.payment_index].status;
208 | if(cur_status=="paid"){ return }
209 |
210 | let new_status = parseInt(result.status)
211 |
212 | if(new_status>=100){
213 | // payment done
214 | payments.list[opts.payment_index].status="paid"
215 | User.setProperty(libPrefix + "_payments", payments, "json");
216 |
217 | callPaymentCompleted(result, opts.onPaymentCompletedCallback);
218 | }
219 | }
220 |
221 | function onIPN(){
222 | let result = getJsonFromQuery(content);
223 | let opts = getParsedCustom(result);
224 |
225 | if(String(opts.user_id)!=String(user.id)){ return }
226 |
227 | let payments = getPayments();
228 |
229 | payments.list[opts.payment_index].status_text = result.status_text
230 | User.setProperty(libPrefix + "_payments", payments, "json");
231 |
232 | // IPN bot callback
233 | Bot.runCommand(params, result);
234 |
235 | acceptPaymentIfNeed(payments, result, opts);
236 | }
237 |
238 | function callTestPaymentCompleted(options){
239 | let result = getTestIPNResult(options);
240 | let opts = getParsedCustom(result);
241 |
242 | callPaymentCompleted(result, opts.onPaymentCompletedCallback);
243 | }
244 |
245 | function getTestIPNResult(options){
246 | return {
247 | ipn_version :"1.0",
248 | ipn_id :"230c6ea3eb116716b79914a3b9ee19f4",
249 | ipn_mode :"hmac",
250 | merchant :"5418303a5fc165090ee8a9177a3982de",
251 | ipn_type :"api",
252 | txn_id :"CPDF3OEBF2FBBABD8WQMBQS368",
253 | status :"100",
254 | status_text :"Complete",
255 | currency1 : ( options.currency1 || "USD" ),
256 | currency2 : ( options.currency1 || "BTC" ),
257 | amount1 : ( options.amount || "0.75" ),
258 | amount2 : ( options.amount || "0.29721357" ),
259 | fee :"0.00148607",
260 | net :"0.2947275",
261 | send_tx :"39rCRQWzX4ZxvQJuQYLXELzp2YCbrcnXg6A1xeGcHkgE",
262 | buyer_name :"CoinPayments API",
263 | email :"test@example.com",
264 | custom : user.id + ":5:" + options.onPaymentCompleted,
265 | received_amount :"0.29721357",
266 | received_confirms :"4"
267 | }
268 | }
269 |
270 | function getTestIPNContentForPermanentWallet(options){
271 | let amount = "0.01000000" || options.amount;
272 |
273 | // Generate random txn_id
274 | let txn_id = "5rRdg9Urrems6V" + parseInt(Math.random(1) * 5000000000) + "pYMQh1dVxcufjfUQYhCP"
275 | if(options.txn_id){ txn_id = options.txn_id }
276 |
277 | return "ipn_version=1.0&"+
278 | "ipn_id=aaf82c3063db89b55dd9505b6bd62648&" +
279 | "ipn_mode=hmac&" +
280 | "merchant=5418303a5fc165090ee8a9177a3982de&" +
281 | "ipn_type=deposit&" +
282 | "address=3P6kW4BEy9vwn3bPCtnDjpzAkJcJj3amExT&" +
283 | "txn_id=" + txn_id + "&" +
284 | "label=myLabel&" +
285 | "status=100&" +
286 | "status_text=Deposit confirmed&" +
287 | "currency=WAVES&" +
288 | "amount=" + amount + "&" +
289 | "amounti=1000000&" +
290 | "fee=0.00005000&" +
291 | "feei=5000&" +
292 | "confirms=5&" +
293 | "fiat_coin=USD&" +
294 | "fiat_amount=0.02365218&" +
295 | "fiat_amounti=2365218&" +
296 | "fiat_fee=0.00011826&" +
297 | "fiat_feei=11826"
298 | }
299 |
300 | function onTxInfo(){
301 | let result = runCallbackAndGetResult();
302 |
303 | let payments = getPayments();
304 | payments.list[result.payment_index].status_text = result.body.status_text;
305 |
306 | User.setProperty(libPrefix + "_payments", payments, "json");
307 | }
308 |
309 | function getWebhookUrl(command){
310 | let apiKey = Bot.getProperty(libPrefix + "bb_api_key");
311 | if(!apiKey){
312 | throw "BB Api Key is not defined. Need define it with function setBBApiKey."
313 | }
314 |
315 | let url = "https://" + BB_API_URL + "/v1/bots/" + String(bot.id) +
316 | "/new-webhook?api_key=" + apiKey + "&command=" +
317 | encodeURIComponent(command) +
318 | "&user_id=" + user.id;
319 |
320 | return url;
321 | }
322 |
323 | function getPayments(){
324 | return User.getProperty(libPrefix + "_payments");
325 | }
326 |
327 | function getNewPaymentIndex(){
328 | let payments = getPayments();
329 | if(!payments){ payments = { count: 0, list:{} } };
330 |
331 | let payment_index = payments.count + 1;
332 |
333 | payments.count = payment_index;
334 | payments.list[payment_index] = { status: "new" }
335 | User.setProperty(libPrefix + "_payments", payments, "json");
336 |
337 | return payment_index
338 | }
339 |
340 | function getUserCallback(options){
341 | let userCallback = "";
342 | if(!options.onSuccess){ options.onSuccess = "" }
343 |
344 | if(options.onSuccess){
345 | userCallback = options.onSuccess.split(" ").join("#==#");
346 | }
347 |
348 | if(options.onError){
349 | userCallback+= options.onError.split(" ").join("#==#");
350 | }
351 |
352 | return userCallback;
353 | }
354 |
355 | function createTransaction(options){
356 | if(!options){ throw "CoinPaymentsLib: need options" }
357 | if(!options.fields){ throw "CoinPaymentsLib: need options.fields" }
358 |
359 | if(options.fields.currency){
360 | options.fields.currency1 = options.fields.currency;
361 | options.fields.currency2 = options.fields.currency;
362 | }
363 |
364 | let payment_index = getNewPaymentIndex();
365 | if(!payment_index){ throw "CoinPaymentsLib: LIB error" }
366 |
367 | options.fields.cmd = "create_transaction";
368 |
369 | if(!options.onIPN){ options.onIPN = "" }
370 | options.fields.ipn_url = getWebhookUrl(libPrefix + "onIPN " + options.onIPN);
371 |
372 | let userCompletedCallback = ""
373 | if(options.onPaymentCompleted){
374 | userCompletedCallback = options.onPaymentCompleted.split(" ").join("#==#"); // bug workaround with params
375 | }
376 |
377 | options.fields.custom = user.id + ":" + payment_index + ":" + userCompletedCallback;
378 |
379 | let userCallback = getUserCallback(options);
380 |
381 | options.onSuccess = libPrefix + "onCreateTransaction " + payment_index + " " + userCallback;
382 |
383 | apiCall(options);
384 | }
385 |
386 | function getTxInfo(options){
387 | if(!options){ throw "CoinPaymentsLib: need options" }
388 | if(!options.payment_index){ throw "CoinPaymentsLib: need options.payment_index" }
389 |
390 | let payment_index = options.payment_index;
391 |
392 | let txn_id = getPayments().list[payment_index].txn_id;
393 |
394 | apiCall({
395 | fields: {
396 | cmd: "get_tx_info",
397 | txid: txn_id
398 | },
399 | onSuccess: libPrefix + "onTxInfo " + payment_index + " " + options.onSuccess
400 | })
401 | }
402 |
403 | function createPermanentWallet(options){
404 | if(!options){ throw "CoinPaymentsLib: need options" }
405 | if(!options.currency){ throw "CoinPaymentsLib: need options.currency" }
406 | if(!options.user_id){ options.user_id = user.id }
407 |
408 | if(!options.onIPN){ options.onIPN="" }
409 | if(!options.onIncome){ options.onIncome="" }
410 | let ipn_url = getWebhookUrl(libPrefix + "onPermanentWalletIPN " + options.onIPN + "%%%" + options.onIncome);
411 |
412 | let userCallback = getUserCallback(options);
413 |
414 | apiCall({
415 | fields: {
416 | cmd: "get_callback_address",
417 | currency: options.currency,
418 | label: options.label,
419 | ipn_url: ipn_url
420 | },
421 | onSuccess: libPrefix + "onGetCallbackAddress " + userCallback
422 | })
423 | }
424 |
425 | function onGetCallbackAddress(){
426 | let arr = params.split(" ");
427 | let callback
428 | let errCallback;
429 |
430 | if(arr[0]){
431 | callback = arr[0].split("#==#").join(" ");
432 | }
433 |
434 | if(arr[1]){
435 | errCallback = arr[1].split("#==#").join(" ");
436 | }
437 |
438 | if(haveError(options, errCallback)){ return }
439 |
440 | let result = options.body.result;
441 |
442 | if(callback&&(callback!="")){
443 | Bot.runCommand(callback, {result: result});
444 | }
445 |
446 | if(!result){ return }
447 | // TODO - is need stored in prop?
448 | }
449 |
450 | function acceptPaymentForPermanentWalletIfNeed(result, incomeCallback){
451 | // accept payment
452 |
453 | let payments = Bot.getProperty(libPrefix + "_permanentWallets");
454 | if(!payments){ payments = { list: {} } }
455 |
456 | let item = payments.list[result.txn_id];
457 |
458 | if(item&&(item.status == "completed")){
459 | return
460 | }
461 |
462 | let new_status = parseInt(result.status)
463 |
464 | if(new_status>=100){
465 | // payment done
466 | payments.list[result.txn_id] = { status: "completed" }
467 | User.setProperty(libPrefix + "_permanentWallets", payments, "json");
468 |
469 | callPaymentCompleted(result, incomeCallback);
470 | }
471 | }
472 |
473 | function onPermanentWalletIPN(){
474 | let result = getJsonFromQuery(content);
475 |
476 | let arr = params.split("%%%")
477 | let calbackIPN = arr[0]
478 | let incomeCallback = arr[1]
479 |
480 | // IPN bot callback
481 | Bot.runCommand(calbackIPN, result);
482 |
483 | acceptPaymentForPermanentWalletIfNeed(result, incomeCallback);
484 | }
485 |
486 | function callTestPermanentWalletIncome(options){
487 | content = getTestIPNContentForPermanentWallet(options);
488 | params = (options.onIPN || "") + "%%%" + ( options.onIncome || "")
489 |
490 | onPermanentWalletIPN();
491 | }
492 |
493 | publish({
494 | setPrivateKey: setPrivateKey,
495 | setPublicKey: setPublicKey,
496 | setBBApiKey: setBBApiKey, // API key from Bots.Business
497 |
498 | apiCall: apiCall,
499 | getIpnUrl: getWebhookUrl,
500 |
501 | createTransaction: createTransaction,
502 | createPermanentWallet: createPermanentWallet,
503 |
504 | getTxInfo: getTxInfo,
505 |
506 | callTestPaymentCompleted: callTestPaymentCompleted,
507 | callTestPermanentWalletIncome: callTestPermanentWalletIncome
508 | })
509 |
510 | on(libPrefix + "onApiResponse", onApiResponse);
511 | on(libPrefix + "onCreateTransaction", onCreateTransaction);
512 | on(libPrefix + "onIPN", onIPN);
513 | on(libPrefix + "onTxInfo", onTxInfo);
514 | on(libPrefix + "onGetCallbackAddress", onGetCallbackAddress);
515 | on(libPrefix + "onPermanentWalletIPN", onPermanentWalletIPN);
--------------------------------------------------------------------------------
/MembershipChecker.js:
--------------------------------------------------------------------------------
1 | // 24443 - lib id
2 |
3 | const LIB_PREFIX = "MembershipChecker_";
4 |
5 | function _setupAdminPanel(){
6 | const panel = {
7 | // Panel title
8 | title: "Membership checker options",
9 | description: "use these options to check your user channel membership",
10 | icon: "person-add",
11 |
12 | fields: [
13 | {
14 | name: "chats",
15 | title: "Chats or channels for checking",
16 | description: "must be separated by commas",
17 | type: "string",
18 | placeholder: "@myChannel, @myChat",
19 | icon: "chatbubbles"
20 | },
21 | {
22 | name: "checkTime",
23 | title: "checking delay in minutes",
24 | description: "the bot will check the user membership for all incoming messages once at this time",
25 | type: "integer",
26 | placeholder: "10",
27 | value: 20,
28 | icon: "time"
29 | },
30 | {
31 | name: "onNeedJoining",
32 | title: "onNeedJoining command",
33 | description: "if the user does not have a membership to ANY chat or channel, this command will be executed",
34 | type: "string",
35 | placeholder: "/onNeedJoining",
36 | icon: "warning"
37 | },
38 | {
39 | name: "onNeedAllJoining",
40 | title: "onNeedAllJoining command",
41 | description: "if the user does not have a membership to ALL chats and channels, this command will be executed",
42 | type: "string",
43 | placeholder: "/onNeedAllJoining",
44 | icon: "alert"
45 | },
46 | {
47 | name: "onJoining",
48 | title: "onJoining command",
49 | description: "if the user just received a membership for ANY chat or channel this command will be executed",
50 | type: "string",
51 | placeholder: "/onJoining",
52 | icon: "person-add"
53 | },
54 | {
55 | name: "onAllJoining",
56 | title: "onAllJoining command",
57 | description: "if the user just received a membership for ALL chats and channels this command will be executed",
58 | type: "string",
59 | placeholder: "/onAllJoining",
60 | icon: "happy"
61 | },
62 | {
63 | name: "onStillJoined",
64 | title: "onStillJoined command",
65 | description: "if the user still have membership this command will be executed. Only with check() method",
66 | type: "string",
67 | placeholder: "/onStillJoined",
68 | icon: "checkmark"
69 | },
70 | {
71 | name: "onError",
72 | title: "onError command",
73 | description: "if an error occurs during verification, this command will be executed",
74 | type: "string",
75 | placeholder: "/onCheckingError",
76 | icon: "bug"
77 | },
78 | {
79 | name: "debug",
80 | title: "debug info",
81 | description: "turn on for debug info",
82 | type: "checkbox",
83 | value: false,
84 | icon: "hammer"
85 | }
86 | ]
87 | }
88 |
89 | AdminPanel.setPanel({
90 | panel_name: "MembershipChecker",
91 | data: panel
92 | });
93 | }
94 |
95 | function setup(){
96 | _setupAdminPanel();
97 | Bot.sendMessage("MembershipChecker Panel: Setup - OK");
98 | }
99 |
100 | function _getLibOptions(){
101 | return AdminPanel.getPanelValues("MembershipChecker");
102 | }
103 |
104 | function _getActualChatsOnly(userData){
105 | // admin can remove chats in any time. We need to remove it from userData too
106 | let chatsInSettings = _getChatsArr();
107 | let actualChats = {};
108 | for(let chat_id in userData.chats){
109 | let chatStillInSettings = chatsInSettings.includes(chat_id)
110 | if(!chatStillInSettings){ continue }
111 | actualChats[chat_id] = userData.chats[chat_id]
112 | }
113 | return actualChats
114 | }
115 |
116 | function _getUserData(){
117 | if(!user){
118 | throw new Error("MembershipChecker: user is not exist. Seems it is background task")
119 | }
120 |
121 | let userData = User.getProperty(LIB_PREFIX + "Data");
122 | if(!userData){ userData = { chats: {} } }
123 | if(!userData.chats){ userData.chats = {} }
124 |
125 | userData.chats = _getActualChatsOnly(userData);
126 | return userData;
127 | }
128 |
129 | function _saveUserData(userData){
130 | _debugInfo("_saveUserData: " + JSON.stringify(userData));
131 | User.setProperty(LIB_PREFIX + "Data", userData, "json");
132 | }
133 |
134 | function _debugInfo(info){
135 | if(!_getLibOptions().debug){ return }
136 | Api.sendMessage({
137 | text: "MCLDebug" +
138 | "\n turn off debug in AdminPanel " +
139 | "\n message: " + message +
140 | "\n\n⚡ " + info,
141 | parse_mode: "HTML"
142 | })
143 | }
144 |
145 | function _msgIncludes(subStr){
146 | if(!subStr){ return false }
147 | if(subStr == ""){ return false }
148 |
149 | return message.includes(subStr)
150 | }
151 |
152 | function _isInternalCommands(opts){
153 | if(!message){ return false }
154 |
155 | return (
156 | _msgIncludes(LIB_PREFIX)||
157 | _msgIncludes(opts.onJoining)||
158 | _msgIncludes(opts.onAllJoining)||
159 | _msgIncludes(opts.onNeedJoining)||
160 | _msgIncludes(opts.onNeedAllJoining)||
161 | _msgIncludes(opts.onError)
162 | )
163 | }
164 |
165 | function _isHandleNeeded(){
166 | if(!user){ return } // can check only for user
167 |
168 | let opts = _getLibOptions();
169 | if(!opts.chats){
170 | throw new Error("MembershipChecker: please install chats for checking in Admin Panel");
171 | }
172 |
173 | // ignore internal commands
174 | if(_isInternalCommands(opts)){
175 | _debugInfo("ignore internal commands in handle()")
176 | return
177 | }
178 |
179 | if(completed_commands_count > 0){
180 | _debugInfo("handle can not be run on sub commands")
181 | return
182 | }
183 |
184 | return true;
185 | }
186 |
187 | function handle(passed_options){
188 | if(!_isHandleNeeded()){ return }
189 |
190 | _debugInfo("handle()")
191 |
192 | let lastCheckTime = _getUserData().lastCheckTime;
193 | const opts = _getLibOptions();
194 | if(_canRunHandleAgain(lastCheckTime, opts)){
195 | return check(passed_options, true);
196 | }
197 |
198 | // check is not needed now
199 | _debugInfo(
200 | "Checking is not required since the delay time has not come yet.\nCurrent delay: " +
201 | String(opts.checkTime) + " min"
202 | )
203 | }
204 |
205 | function _isItSpamCall(lastCheckTime){
206 | // only 1 check per 2 second for one user
207 | if(lastCheckTime){
208 | let duration = Date.now() - lastCheckTime;
209 | return duration < 2000
210 | }
211 | return false
212 | }
213 |
214 | function check(passed_options, noNeedOnStillJoined){
215 | let userData = _getUserData();
216 |
217 | _debugInfo("check() for user Data: " + JSON.stringify(userData));
218 |
219 | if(_isItSpamCall(userData.lastCheckTime)){ return }
220 |
221 | userData.lastCheckTime = Date.now();
222 | _saveUserData(userData);
223 |
224 | _debugInfo("create task for checking");
225 |
226 | // create task for checking
227 | Bot.run({
228 | command: LIB_PREFIX + "checkMemberships",
229 | options: {
230 | time: userData.lastCheckTime, // current time value for this checking
231 | needStillJoinedCallback: !noNeedOnStillJoined, // if true - we need to call still joined callback
232 | bb_options: passed_options, // customized passed options
233 | },
234 | // TODO: need decrease this time to 0.01
235 | run_after: 1 // just for run in background
236 | })
237 | }
238 |
239 | function checkMembership(chat_id){
240 | if(!chat_id){ chat_id = params }
241 |
242 | Api.getChatMember({
243 | chat_id: chat_id,
244 | user_id: user.telegramid,
245 | on_result: LIB_PREFIX + "onCheckMembership " + chat_id,
246 | on_error: LIB_PREFIX + "onError " + chat_id,
247 | bb_options: options // here we have all options lib + admin passed_options
248 | })
249 | }
250 |
251 | function getChats(){
252 | return _getLibOptions().chats;
253 | }
254 |
255 | function getNotJoinedChats(){
256 | return _getNotJoinedChats().join(", ");
257 | }
258 |
259 | function checkMemberships(){
260 | let chats = _getChatsArr();
261 | _debugInfo("run checking for " + JSON.stringify(chats));
262 |
263 | for(let ind in chats){
264 | // several chats
265 | let chat_id = chats[ind];
266 | Bot.run({
267 | command: LIB_PREFIX + "checkMembership " + chat_id,
268 | options: options, // passed options
269 | // TODO: need decrease this time to 0.01
270 | run_after: 1 // just for run in background
271 | })
272 | }
273 | }
274 |
275 | // need remove?
276 | function _isJoined(response){
277 | let status = response.result.status;
278 | return ["member", "administrator", "creator"].includes(status);
279 | }
280 |
281 |
282 | function _isSameChatsCheckingTime(chats, needNegative){
283 | let lastCheckTime = options.bb_options.time;
284 | if(needNegative){ lastCheckTime = -lastCheckTime }
285 | const sameTime = Object.values(chats).every(
286 | value => value === lastCheckTime
287 | );
288 |
289 | return sameTime;
290 | }
291 |
292 | function _needStillJoinedCallback(userData) {
293 | // we need callback only with check() method not in handle()
294 | if(!options.bb_options.needStillJoinedCallback){ return false }
295 | // callback must be installed
296 | if(!_getLibOptions().onStillJoined){ return false }
297 |
298 | return _isSameChatsCheckingTime(userData.chats);
299 | }
300 |
301 | function _runCallback(callbackName, chat_id){
302 | const opts = _getLibOptions();
303 | const command = opts[callbackName];
304 |
305 | if(!command){
306 | _debugInfo("callback is not installed: " + callbackName + ". Chat: " + chat_id +
307 | "\n\n> " + JSON.stringify(options));
308 | return false;
309 | }
310 |
311 | _debugInfo(
312 | "run callback: " + callbackName + ">" + command + ", for chat: " + chat_id +
313 | "\n\n> " + JSON.stringify(options)
314 | );
315 |
316 | Bot.run({
317 | command: command,
318 | options: {
319 | result: options.result,
320 | bb_options: options.bb_options.passed_options,
321 | chat_id: chat_id
322 | }
323 | })
324 | return true;
325 | }
326 |
327 | function _proccessOldChat(userData){
328 | // it is still joined chat
329 | _debugInfo("skip old chat");
330 |
331 | const needCallback = _needStillJoinedCallback(userData);
332 |
333 | if(!needCallback){
334 | _debugInfo("still joined callback is not needed: " + JSON.stringify(options));
335 | return true
336 | }
337 |
338 | return _runCallback("onStillJoined");
339 | }
340 |
341 | function handleMembership(chat_id, userData){
342 | const checkTime = userData.chats[chat_id];
343 | // we have negative time - it is not joined chat
344 | const isOld = ( checkTime && checkTime > 0 );
345 |
346 | // we use same time - because need to track still joined callback
347 | userData.chats[chat_id] = options.bb_options.time;
348 |
349 | // skip old chats
350 | if(isOld){
351 | _proccessOldChat(userData)
352 | _saveUserData(userData);
353 | return
354 | }
355 |
356 | // we do not need stillJoinedCallback on new chat
357 | // set different time for new chat
358 | userData.chats[chat_id]+= 10;
359 | _saveUserData(userData);
360 |
361 | let opts = _getLibOptions();
362 | const needCallback = ( !isOld && opts.onJoining);
363 |
364 | if(!needCallback){
365 | _debugInfo(
366 | "on Joining callbacks is not needed: it is old joining in: " + chat_id +
367 | "\n\n> " + JSON.stringify(userData)
368 | );
369 | return
370 | }
371 |
372 | _runCallback("onJoining", chat_id);
373 |
374 | // is all chats joined?
375 | if(isMember()){
376 | return _runCallback("onAllJoining");
377 | }
378 | }
379 |
380 | function _handleNoneMembership(chat_id, userData){
381 | userData.chats[chat_id] = -options.bb_options.time // we use negative time for not joined chats
382 | _saveUserData(userData);
383 | _runCallback("onNeedJoining", chat_id);
384 |
385 | if(_needToJoinAll(userData.chats)){
386 | _runCallback("onNeedAllJoining");
387 | }
388 | }
389 |
390 | function onCheckMembership(){
391 | let chat_id = params.split(" ")[0];
392 |
393 | let userData = _getUserData();
394 | userData.lastCheckTime = options.bb_options.time;
395 |
396 | _debugInfo("check response: " + JSON.stringify(options) + "\n\n> " + JSON.stringify(userData));
397 |
398 | if(_isJoined(options)){
399 | _debugInfo("user is (still?) joined to " + chat_id + " chat")
400 | return handleMembership(chat_id, userData)
401 | }
402 |
403 | return _handleNoneMembership(chat_id, userData)
404 | }
405 |
406 | function onError(){
407 | _debugInfo("onError for " + params + " >" + JSON.stringify(options))
408 |
409 | let opts = _getLibOptions();
410 | if(!opts.onError){ return } // no action
411 | opts.chat_id = params;
412 | Bot.run({ command: opts.onError, options: options })
413 | }
414 |
415 | function _canRunHandleAgain(curTime){
416 | if(!curTime){ return false }
417 |
418 | let options = _getLibOptions();
419 | if(!options.checkTime){
420 | throw new Error("MembershipChecker: please install checking delay time in Admin Panel");
421 | }
422 |
423 | let duration = Date.now() - curTime; // in ms
424 | duration = duration / 1000 / 60; // in minutes
425 |
426 | return duration > parseInt(options.checkTime);
427 | }
428 |
429 | function _isActualMembership(chat_id){
430 | if(!chat_id){ return false }
431 | let userData = _getUserData()
432 | return userData.chats[chat_id] > 0
433 | }
434 |
435 | function _getNotJoinedChats(){
436 | let result;
437 | let notJoined = [];
438 | const chats = _getChatsArr();
439 |
440 | for(let ind in chats){
441 | result = _isActualMembership(chats[ind]);
442 | if(!result){
443 | notJoined.push(chats[ind])
444 | }
445 | }
446 | return notJoined
447 | }
448 |
449 | function _needToJoinAll(chats){
450 | let result;
451 | for(let ind in chats){
452 | result = _isActualMembership(chats[ind]);
453 | if(result){
454 | return false;
455 | }
456 | }
457 |
458 | // same negative time for all chats
459 | // we use negative time for not joined chats
460 | // it will be in the end checking only
461 | return _isSameChatsCheckingTime(chats, true);
462 | }
463 |
464 | function _throwErrorIfNoChats(){
465 | if(_getLibOptions().chats){ return }
466 |
467 | throw new Error("MembershipChecker: please install chats for checking in Admin Panel");
468 | }
469 |
470 | function _getChatsArr(needError){
471 | let options = _getLibOptions();
472 |
473 | if(!options.chats){ return [] }
474 |
475 | let chats = options.chats.split(" ").join(""); // remove spaces
476 | chats = chats.split(",");
477 |
478 | if(!chats[0]){ throw new Error(error) }
479 | return chats
480 | }
481 |
482 | // is member of all chats?
483 | function isMember(chat_id){
484 | if(chat_id){
485 | return _isActualMembership(chat_id);
486 | }
487 |
488 | _throwErrorIfNoChats();
489 |
490 | // for all chats
491 | return ( _getNotJoinedChats().length == 0 )
492 | }
493 |
494 | publish({
495 | setup: setup, // setup admin panel
496 | check: check, // manual checking without time delay
497 | handle: handle, // use on @ command - checking with time delay
498 | isMember: isMember, // is member for all chats?
499 | getChats: getChats, // get all chats for checking
500 | getNotJoinedChats: getNotJoinedChats // get not joined chats for this user
501 | })
502 |
503 | on(LIB_PREFIX + "checkMemberships", checkMemberships);
504 | on(LIB_PREFIX + "checkMembership", checkMembership);
505 | on(LIB_PREFIX + "onCheckMembership", onCheckMembership);
506 | on(LIB_PREFIX + "onError", onError);
507 |
--------------------------------------------------------------------------------