├── .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 | --------------------------------------------------------------------------------