From 6f22cd6455eb481f27a0739865e282313d3235e3 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 9 Jun 2023 20:03:53 +0000 Subject: [PATCH] deploy: fe91bd17a6c24d6e2e517b2f51cac5c7a8c9755f --- .nojekyll | 0 404.html | 853 +++ assets/images/favicon.png | Bin 0 -> 1870 bytes assets/javascripts/bundle.b4d07000.min.js | 29 + assets/javascripts/bundle.b4d07000.min.js.map | 8 + assets/javascripts/lunr/min/lunr.ar.min.js | 1 + assets/javascripts/lunr/min/lunr.da.min.js | 18 + assets/javascripts/lunr/min/lunr.de.min.js | 18 + assets/javascripts/lunr/min/lunr.du.min.js | 18 + assets/javascripts/lunr/min/lunr.es.min.js | 18 + assets/javascripts/lunr/min/lunr.fi.min.js | 18 + assets/javascripts/lunr/min/lunr.fr.min.js | 18 + assets/javascripts/lunr/min/lunr.hi.min.js | 1 + assets/javascripts/lunr/min/lunr.hu.min.js | 18 + assets/javascripts/lunr/min/lunr.hy.min.js | 1 + assets/javascripts/lunr/min/lunr.it.min.js | 18 + assets/javascripts/lunr/min/lunr.ja.min.js | 1 + assets/javascripts/lunr/min/lunr.jp.min.js | 1 + assets/javascripts/lunr/min/lunr.kn.min.js | 1 + assets/javascripts/lunr/min/lunr.ko.min.js | 1 + assets/javascripts/lunr/min/lunr.multi.min.js | 1 + assets/javascripts/lunr/min/lunr.nl.min.js | 18 + assets/javascripts/lunr/min/lunr.no.min.js | 18 + assets/javascripts/lunr/min/lunr.pt.min.js | 18 + assets/javascripts/lunr/min/lunr.ro.min.js | 18 + assets/javascripts/lunr/min/lunr.ru.min.js | 18 + assets/javascripts/lunr/min/lunr.sa.min.js | 1 + .../lunr/min/lunr.stemmer.support.min.js | 1 + assets/javascripts/lunr/min/lunr.sv.min.js | 18 + assets/javascripts/lunr/min/lunr.ta.min.js | 1 + assets/javascripts/lunr/min/lunr.te.min.js | 1 + assets/javascripts/lunr/min/lunr.th.min.js | 1 + assets/javascripts/lunr/min/lunr.tr.min.js | 18 + assets/javascripts/lunr/min/lunr.vi.min.js | 1 + assets/javascripts/lunr/min/lunr.zh.min.js | 1 + assets/javascripts/lunr/tinyseg.js | 206 + assets/javascripts/lunr/wordcut.js | 6708 +++++++++++++++++ .../workers/search.208ed371.min.js | 42 + .../workers/search.208ed371.min.js.map | 8 + assets/stylesheets/main.26e3688c.min.css | 1 + assets/stylesheets/main.26e3688c.min.css.map | 1 + assets/stylesheets/palette.ecc896b0.min.css | 1 + .../stylesheets/palette.ecc896b0.min.css.map | 1 + index.html | 972 +++ search/search_index.json | 1 + sitemap.xml | 108 + sitemap.xml.gz | Bin 0 -> 452 bytes tgbotapi/dsls/keyboards.html | 1011 +++ tgbotapi/dsls/live-location.html | 1085 +++ tgbotapi/dsls/text.html | 942 +++ .../introduction/before-any-bot-project.html | 998 +++ tgbotapi/introduction/first-bot.html | 1014 +++ .../including-in-your-project.html | 1381 ++++ tgbotapi/introduction/proxy-setup.html | 1051 +++ tgbotapi/logic/api-extensions.html | 1056 +++ .../logic/behaviour-builder-with-fsm.html | 1056 +++ tgbotapi/logic/behaviour-builder.html | 1070 +++ tgbotapi/logic/exceptions-handling.html | 1133 +++ tgbotapi/logic/files-handling.html | 1060 +++ tgbotapi/logic/low-level-work-with-bots.html | 958 +++ tgbotapi/logic/media-groups.html | 1030 +++ tgbotapi/logic/types-conversations.html | 1038 +++ tgbotapi/logic/updates-with-flows.html | 1062 +++ tgbotapi/updates/heroku.html | 1097 +++ tgbotapi/updates/long-polling.html | 1105 +++ tgbotapi/updates/updates-filters.html | 1181 +++ tgbotapi/updates/webhooks.html | 1137 +++ 67 files changed, 30689 insertions(+) create mode 100644 .nojekyll create mode 100644 404.html create mode 100644 assets/images/favicon.png create mode 100644 assets/javascripts/bundle.b4d07000.min.js create mode 100644 assets/javascripts/bundle.b4d07000.min.js.map create mode 100644 assets/javascripts/lunr/min/lunr.ar.min.js create mode 100644 assets/javascripts/lunr/min/lunr.da.min.js create mode 100644 assets/javascripts/lunr/min/lunr.de.min.js create mode 100644 assets/javascripts/lunr/min/lunr.du.min.js create mode 100644 assets/javascripts/lunr/min/lunr.es.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.fr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hu.min.js create mode 100644 assets/javascripts/lunr/min/lunr.hy.min.js create mode 100644 assets/javascripts/lunr/min/lunr.it.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ja.min.js create mode 100644 assets/javascripts/lunr/min/lunr.jp.min.js create mode 100644 assets/javascripts/lunr/min/lunr.kn.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ko.min.js create mode 100644 assets/javascripts/lunr/min/lunr.multi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.nl.min.js create mode 100644 assets/javascripts/lunr/min/lunr.no.min.js create mode 100644 assets/javascripts/lunr/min/lunr.pt.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ro.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ru.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sa.min.js create mode 100644 assets/javascripts/lunr/min/lunr.stemmer.support.min.js create mode 100644 assets/javascripts/lunr/min/lunr.sv.min.js create mode 100644 assets/javascripts/lunr/min/lunr.ta.min.js create mode 100644 assets/javascripts/lunr/min/lunr.te.min.js create mode 100644 assets/javascripts/lunr/min/lunr.th.min.js create mode 100644 assets/javascripts/lunr/min/lunr.tr.min.js create mode 100644 assets/javascripts/lunr/min/lunr.vi.min.js create mode 100644 assets/javascripts/lunr/min/lunr.zh.min.js create mode 100644 assets/javascripts/lunr/tinyseg.js create mode 100644 assets/javascripts/lunr/wordcut.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js create mode 100644 assets/javascripts/workers/search.208ed371.min.js.map create mode 100644 assets/stylesheets/main.26e3688c.min.css create mode 100644 assets/stylesheets/main.26e3688c.min.css.map create mode 100644 assets/stylesheets/palette.ecc896b0.min.css create mode 100644 assets/stylesheets/palette.ecc896b0.min.css.map create mode 100644 index.html create mode 100644 search/search_index.json create mode 100644 sitemap.xml create mode 100644 sitemap.xml.gz create mode 100644 tgbotapi/dsls/keyboards.html create mode 100644 tgbotapi/dsls/live-location.html create mode 100644 tgbotapi/dsls/text.html create mode 100644 tgbotapi/introduction/before-any-bot-project.html create mode 100644 tgbotapi/introduction/first-bot.html create mode 100644 tgbotapi/introduction/including-in-your-project.html create mode 100644 tgbotapi/introduction/proxy-setup.html create mode 100644 tgbotapi/logic/api-extensions.html create mode 100644 tgbotapi/logic/behaviour-builder-with-fsm.html create mode 100644 tgbotapi/logic/behaviour-builder.html create mode 100644 tgbotapi/logic/exceptions-handling.html create mode 100644 tgbotapi/logic/files-handling.html create mode 100644 tgbotapi/logic/low-level-work-with-bots.html create mode 100644 tgbotapi/logic/media-groups.html create mode 100644 tgbotapi/logic/types-conversations.html create mode 100644 tgbotapi/logic/updates-with-flows.html create mode 100644 tgbotapi/updates/heroku.html create mode 100644 tgbotapi/updates/long-polling.html create mode 100644 tgbotapi/updates/updates-filters.html create mode 100644 tgbotapi/updates/webhooks.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/404.html b/404.html new file mode 100644 index 0000000..9c1ae21 --- /dev/null +++ b/404.html @@ -0,0 +1,853 @@ + + + + + + + + + + + + + + + + + + + + + + InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ +

404 - Not found

+ +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/assets/images/favicon.png b/assets/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf13b9f9d978896599290a74f77d5dbe7d1655c GIT binary patch literal 1870 zcmV-U2eJ5xP)Gc)JR9QMau)O=X#!i9;T z37kk-upj^(fsR36MHs_+1RCI)NNu9}lD0S{B^g8PN?Ww(5|~L#Ng*g{WsqleV}|#l zz8@ri&cTzw_h33bHI+12+kK6WN$h#n5cD8OQt`5kw6p~9H3()bUQ8OS4Q4HTQ=1Ol z_JAocz`fLbT2^{`8n~UAo=#AUOf=SOq4pYkt;XbC&f#7lb$*7=$na!mWCQ`dBQsO0 zLFBSPj*N?#u5&pf2t4XjEGH|=pPQ8xh7tpx;US5Cx_Ju;!O`ya-yF`)b%TEt5>eP1ZX~}sjjA%FJF?h7cX8=b!DZl<6%Cv z*G0uvvU+vmnpLZ2paivG-(cd*y3$hCIcsZcYOGh{$&)A6*XX&kXZd3G8m)G$Zz-LV z^GF3VAW^Mdv!)4OM8EgqRiz~*Cji;uzl2uC9^=8I84vNp;ltJ|q-*uQwGp2ma6cY7 z;`%`!9UXO@fr&Ebapfs34OmS9^u6$)bJxrucutf>`dKPKT%%*d3XlFVKunp9 zasduxjrjs>f8V=D|J=XNZp;_Zy^WgQ$9WDjgY=z@stwiEBm9u5*|34&1Na8BMjjgf3+SHcr`5~>oz1Y?SW^=K z^bTyO6>Gar#P_W2gEMwq)ot3; zREHn~U&Dp0l6YT0&k-wLwYjb?5zGK`W6S2v+K>AM(95m2C20L|3m~rN8dprPr@t)5lsk9Hu*W z?pS990s;Ez=+Rj{x7p``4>+c0G5^pYnB1^!TL=(?HLHZ+HicG{~4F1d^5Awl_2!1jICM-!9eoLhbbT^;yHcefyTAaqRcY zmuctDopPT!%k+}x%lZRKnzykr2}}XfG_ne?nRQO~?%hkzo;@RN{P6o`&mMUWBYMTe z6i8ChtjX&gXl`nvrU>jah)2iNM%JdjqoaeaU%yVn!^70x-flljp6Q5tK}5}&X8&&G zX3fpb3E(!rH=zVI_9Gjl45w@{(ITqngWFe7@9{mX;tO25Z_8 zQHEpI+FkTU#4xu>RkN>b3Tnc3UpWzPXWm#o55GKF09j^Mh~)K7{QqbO_~(@CVq! zS<8954|P8mXN2MRs86xZ&Q4EfM@JB94b=(YGuk)s&^jiSF=t3*oNK3`rD{H`yQ?d; ztE=laAUoZx5?RC8*WKOj`%LXEkgDd>&^Q4M^z`%u0rg-It=hLCVsq!Z%^6eB-OvOT zFZ28TN&cRmgU}Elrnk43)!>Z1FCPL2K$7}gwzIc48NX}#!A1BpJP?#v5wkNprhV** z?Cpalt1oH&{r!o3eSKc&ap)iz2BTn_VV`4>9M^b3;(YY}4>#ML6{~(4mH+?%07*qo IM6N<$f(jP3KmY&$ literal 0 HcmV?d00001 diff --git a/assets/javascripts/bundle.b4d07000.min.js b/assets/javascripts/bundle.b4d07000.min.js new file mode 100644 index 0000000..3c0bdad --- /dev/null +++ b/assets/javascripts/bundle.b4d07000.min.js @@ -0,0 +1,29 @@ +"use strict";(()=>{var Ci=Object.create;var gr=Object.defineProperty;var Ri=Object.getOwnPropertyDescriptor;var ki=Object.getOwnPropertyNames,Ht=Object.getOwnPropertySymbols,Hi=Object.getPrototypeOf,yr=Object.prototype.hasOwnProperty,nn=Object.prototype.propertyIsEnumerable;var rn=(e,t,r)=>t in e?gr(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r,P=(e,t)=>{for(var r in t||(t={}))yr.call(t,r)&&rn(e,r,t[r]);if(Ht)for(var r of Ht(t))nn.call(t,r)&&rn(e,r,t[r]);return e};var on=(e,t)=>{var r={};for(var n in e)yr.call(e,n)&&t.indexOf(n)<0&&(r[n]=e[n]);if(e!=null&&Ht)for(var n of Ht(e))t.indexOf(n)<0&&nn.call(e,n)&&(r[n]=e[n]);return r};var Pt=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var Pi=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ki(t))!yr.call(e,o)&&o!==r&&gr(e,o,{get:()=>t[o],enumerable:!(n=Ri(t,o))||n.enumerable});return e};var yt=(e,t,r)=>(r=e!=null?Ci(Hi(e)):{},Pi(t||!e||!e.__esModule?gr(r,"default",{value:e,enumerable:!0}):r,e));var sn=Pt((xr,an)=>{(function(e,t){typeof xr=="object"&&typeof an!="undefined"?t():typeof define=="function"&&define.amd?define(t):t()})(xr,function(){"use strict";function e(r){var n=!0,o=!1,i=null,s={text:!0,search:!0,url:!0,tel:!0,email:!0,password:!0,number:!0,date:!0,month:!0,week:!0,time:!0,datetime:!0,"datetime-local":!0};function a(O){return!!(O&&O!==document&&O.nodeName!=="HTML"&&O.nodeName!=="BODY"&&"classList"in O&&"contains"in O.classList)}function f(O){var Qe=O.type,De=O.tagName;return!!(De==="INPUT"&&s[Qe]&&!O.readOnly||De==="TEXTAREA"&&!O.readOnly||O.isContentEditable)}function c(O){O.classList.contains("focus-visible")||(O.classList.add("focus-visible"),O.setAttribute("data-focus-visible-added",""))}function u(O){O.hasAttribute("data-focus-visible-added")&&(O.classList.remove("focus-visible"),O.removeAttribute("data-focus-visible-added"))}function p(O){O.metaKey||O.altKey||O.ctrlKey||(a(r.activeElement)&&c(r.activeElement),n=!0)}function m(O){n=!1}function d(O){a(O.target)&&(n||f(O.target))&&c(O.target)}function h(O){a(O.target)&&(O.target.classList.contains("focus-visible")||O.target.hasAttribute("data-focus-visible-added"))&&(o=!0,window.clearTimeout(i),i=window.setTimeout(function(){o=!1},100),u(O.target))}function v(O){document.visibilityState==="hidden"&&(o&&(n=!0),Y())}function Y(){document.addEventListener("mousemove",N),document.addEventListener("mousedown",N),document.addEventListener("mouseup",N),document.addEventListener("pointermove",N),document.addEventListener("pointerdown",N),document.addEventListener("pointerup",N),document.addEventListener("touchmove",N),document.addEventListener("touchstart",N),document.addEventListener("touchend",N)}function B(){document.removeEventListener("mousemove",N),document.removeEventListener("mousedown",N),document.removeEventListener("mouseup",N),document.removeEventListener("pointermove",N),document.removeEventListener("pointerdown",N),document.removeEventListener("pointerup",N),document.removeEventListener("touchmove",N),document.removeEventListener("touchstart",N),document.removeEventListener("touchend",N)}function N(O){O.target.nodeName&&O.target.nodeName.toLowerCase()==="html"||(n=!1,B())}document.addEventListener("keydown",p,!0),document.addEventListener("mousedown",m,!0),document.addEventListener("pointerdown",m,!0),document.addEventListener("touchstart",m,!0),document.addEventListener("visibilitychange",v,!0),Y(),r.addEventListener("focus",d,!0),r.addEventListener("blur",h,!0),r.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&r.host?r.host.setAttribute("data-js-focus-visible",""):r.nodeType===Node.DOCUMENT_NODE&&(document.documentElement.classList.add("js-focus-visible"),document.documentElement.setAttribute("data-js-focus-visible",""))}if(typeof window!="undefined"&&typeof document!="undefined"){window.applyFocusVisiblePolyfill=e;var t;try{t=new CustomEvent("focus-visible-polyfill-ready")}catch(r){t=document.createEvent("CustomEvent"),t.initCustomEvent("focus-visible-polyfill-ready",!1,!1,{})}window.dispatchEvent(t)}typeof document!="undefined"&&e(document)})});var cn=Pt(Er=>{(function(e){var t=function(){try{return!!Symbol.iterator}catch(c){return!1}},r=t(),n=function(c){var u={next:function(){var p=c.shift();return{done:p===void 0,value:p}}};return r&&(u[Symbol.iterator]=function(){return u}),u},o=function(c){return encodeURIComponent(c).replace(/%20/g,"+")},i=function(c){return decodeURIComponent(String(c).replace(/\+/g," "))},s=function(){var c=function(p){Object.defineProperty(this,"_entries",{writable:!0,value:{}});var m=typeof p;if(m!=="undefined")if(m==="string")p!==""&&this._fromString(p);else if(p instanceof c){var d=this;p.forEach(function(B,N){d.append(N,B)})}else if(p!==null&&m==="object")if(Object.prototype.toString.call(p)==="[object Array]")for(var h=0;hd[0]?1:0}),c._entries&&(c._entries={});for(var p=0;p1?i(d[1]):"")}})})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er);(function(e){var t=function(){try{var o=new e.URL("b","http://a");return o.pathname="c d",o.href==="http://a/c%20d"&&o.searchParams}catch(i){return!1}},r=function(){var o=e.URL,i=function(f,c){typeof f!="string"&&(f=String(f)),c&&typeof c!="string"&&(c=String(c));var u=document,p;if(c&&(e.location===void 0||c!==e.location.href)){c=c.toLowerCase(),u=document.implementation.createHTMLDocument(""),p=u.createElement("base"),p.href=c,u.head.appendChild(p);try{if(p.href.indexOf(c)!==0)throw new Error(p.href)}catch(O){throw new Error("URL unable to set base "+c+" due to "+O)}}var m=u.createElement("a");m.href=f,p&&(u.body.appendChild(m),m.href=m.href);var d=u.createElement("input");if(d.type="url",d.value=f,m.protocol===":"||!/:/.test(m.href)||!d.checkValidity()&&!c)throw new TypeError("Invalid URL");Object.defineProperty(this,"_anchorElement",{value:m});var h=new e.URLSearchParams(this.search),v=!0,Y=!0,B=this;["append","delete","set"].forEach(function(O){var Qe=h[O];h[O]=function(){Qe.apply(h,arguments),v&&(Y=!1,B.search=h.toString(),Y=!0)}}),Object.defineProperty(this,"searchParams",{value:h,enumerable:!0});var N=void 0;Object.defineProperty(this,"_updateSearchParams",{enumerable:!1,configurable:!1,writable:!1,value:function(){this.search!==N&&(N=this.search,Y&&(v=!1,this.searchParams._fromString(this.search),v=!0))}})},s=i.prototype,a=function(f){Object.defineProperty(s,f,{get:function(){return this._anchorElement[f]},set:function(c){this._anchorElement[f]=c},enumerable:!0})};["hash","host","hostname","port","protocol"].forEach(function(f){a(f)}),Object.defineProperty(s,"search",{get:function(){return this._anchorElement.search},set:function(f){this._anchorElement.search=f,this._updateSearchParams()},enumerable:!0}),Object.defineProperties(s,{toString:{get:function(){var f=this;return function(){return f.href}}},href:{get:function(){return this._anchorElement.href.replace(/\?$/,"")},set:function(f){this._anchorElement.href=f,this._updateSearchParams()},enumerable:!0},pathname:{get:function(){return this._anchorElement.pathname.replace(/(^\/?)/,"/")},set:function(f){this._anchorElement.pathname=f},enumerable:!0},origin:{get:function(){var f={"http:":80,"https:":443,"ftp:":21}[this._anchorElement.protocol],c=this._anchorElement.port!=f&&this._anchorElement.port!=="";return this._anchorElement.protocol+"//"+this._anchorElement.hostname+(c?":"+this._anchorElement.port:"")},enumerable:!0},password:{get:function(){return""},set:function(f){},enumerable:!0},username:{get:function(){return""},set:function(f){},enumerable:!0}}),i.createObjectURL=function(f){return o.createObjectURL.apply(o,arguments)},i.revokeObjectURL=function(f){return o.revokeObjectURL.apply(o,arguments)},e.URL=i};if(t()||r(),e.location!==void 0&&!("origin"in e.location)){var n=function(){return e.location.protocol+"//"+e.location.hostname+(e.location.port?":"+e.location.port:"")};try{Object.defineProperty(e.location,"origin",{get:n,enumerable:!0})}catch(o){setInterval(function(){e.location.origin=n()},100)}}})(typeof global!="undefined"?global:typeof window!="undefined"?window:typeof self!="undefined"?self:Er)});var qr=Pt((Mt,Nr)=>{/*! + * clipboard.js v2.0.11 + * https://clipboardjs.com/ + * + * Licensed MIT © Zeno Rocha + */(function(t,r){typeof Mt=="object"&&typeof Nr=="object"?Nr.exports=r():typeof define=="function"&&define.amd?define([],r):typeof Mt=="object"?Mt.ClipboardJS=r():t.ClipboardJS=r()})(Mt,function(){return function(){var e={686:function(n,o,i){"use strict";i.d(o,{default:function(){return Ai}});var s=i(279),a=i.n(s),f=i(370),c=i.n(f),u=i(817),p=i.n(u);function m(j){try{return document.execCommand(j)}catch(T){return!1}}var d=function(T){var E=p()(T);return m("cut"),E},h=d;function v(j){var T=document.documentElement.getAttribute("dir")==="rtl",E=document.createElement("textarea");E.style.fontSize="12pt",E.style.border="0",E.style.padding="0",E.style.margin="0",E.style.position="absolute",E.style[T?"right":"left"]="-9999px";var H=window.pageYOffset||document.documentElement.scrollTop;return E.style.top="".concat(H,"px"),E.setAttribute("readonly",""),E.value=j,E}var Y=function(T,E){var H=v(T);E.container.appendChild(H);var I=p()(H);return m("copy"),H.remove(),I},B=function(T){var E=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body},H="";return typeof T=="string"?H=Y(T,E):T instanceof HTMLInputElement&&!["text","search","url","tel","password"].includes(T==null?void 0:T.type)?H=Y(T.value,E):(H=p()(T),m("copy")),H},N=B;function O(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?O=function(E){return typeof E}:O=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},O(j)}var Qe=function(){var T=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},E=T.action,H=E===void 0?"copy":E,I=T.container,q=T.target,Me=T.text;if(H!=="copy"&&H!=="cut")throw new Error('Invalid "action" value, use either "copy" or "cut"');if(q!==void 0)if(q&&O(q)==="object"&&q.nodeType===1){if(H==="copy"&&q.hasAttribute("disabled"))throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');if(H==="cut"&&(q.hasAttribute("readonly")||q.hasAttribute("disabled")))throw new Error(`Invalid "target" attribute. You can't cut text from elements with "readonly" or "disabled" attributes`)}else throw new Error('Invalid "target" value, use a valid Element');if(Me)return N(Me,{container:I});if(q)return H==="cut"?h(q):N(q,{container:I})},De=Qe;function $e(j){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?$e=function(E){return typeof E}:$e=function(E){return E&&typeof Symbol=="function"&&E.constructor===Symbol&&E!==Symbol.prototype?"symbol":typeof E},$e(j)}function Ei(j,T){if(!(j instanceof T))throw new TypeError("Cannot call a class as a function")}function tn(j,T){for(var E=0;E0&&arguments[0]!==void 0?arguments[0]:{};this.action=typeof I.action=="function"?I.action:this.defaultAction,this.target=typeof I.target=="function"?I.target:this.defaultTarget,this.text=typeof I.text=="function"?I.text:this.defaultText,this.container=$e(I.container)==="object"?I.container:document.body}},{key:"listenClick",value:function(I){var q=this;this.listener=c()(I,"click",function(Me){return q.onClick(Me)})}},{key:"onClick",value:function(I){var q=I.delegateTarget||I.currentTarget,Me=this.action(q)||"copy",kt=De({action:Me,container:this.container,target:this.target(q),text:this.text(q)});this.emit(kt?"success":"error",{action:Me,text:kt,trigger:q,clearSelection:function(){q&&q.focus(),window.getSelection().removeAllRanges()}})}},{key:"defaultAction",value:function(I){return vr("action",I)}},{key:"defaultTarget",value:function(I){var q=vr("target",I);if(q)return document.querySelector(q)}},{key:"defaultText",value:function(I){return vr("text",I)}},{key:"destroy",value:function(){this.listener.destroy()}}],[{key:"copy",value:function(I){var q=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{container:document.body};return N(I,q)}},{key:"cut",value:function(I){return h(I)}},{key:"isSupported",value:function(){var I=arguments.length>0&&arguments[0]!==void 0?arguments[0]:["copy","cut"],q=typeof I=="string"?[I]:I,Me=!!document.queryCommandSupported;return q.forEach(function(kt){Me=Me&&!!document.queryCommandSupported(kt)}),Me}}]),E}(a()),Ai=Li},828:function(n){var o=9;if(typeof Element!="undefined"&&!Element.prototype.matches){var i=Element.prototype;i.matches=i.matchesSelector||i.mozMatchesSelector||i.msMatchesSelector||i.oMatchesSelector||i.webkitMatchesSelector}function s(a,f){for(;a&&a.nodeType!==o;){if(typeof a.matches=="function"&&a.matches(f))return a;a=a.parentNode}}n.exports=s},438:function(n,o,i){var s=i(828);function a(u,p,m,d,h){var v=c.apply(this,arguments);return u.addEventListener(m,v,h),{destroy:function(){u.removeEventListener(m,v,h)}}}function f(u,p,m,d,h){return typeof u.addEventListener=="function"?a.apply(null,arguments):typeof m=="function"?a.bind(null,document).apply(null,arguments):(typeof u=="string"&&(u=document.querySelectorAll(u)),Array.prototype.map.call(u,function(v){return a(v,p,m,d,h)}))}function c(u,p,m,d){return function(h){h.delegateTarget=s(h.target,p),h.delegateTarget&&d.call(u,h)}}n.exports=f},879:function(n,o){o.node=function(i){return i!==void 0&&i instanceof HTMLElement&&i.nodeType===1},o.nodeList=function(i){var s=Object.prototype.toString.call(i);return i!==void 0&&(s==="[object NodeList]"||s==="[object HTMLCollection]")&&"length"in i&&(i.length===0||o.node(i[0]))},o.string=function(i){return typeof i=="string"||i instanceof String},o.fn=function(i){var s=Object.prototype.toString.call(i);return s==="[object Function]"}},370:function(n,o,i){var s=i(879),a=i(438);function f(m,d,h){if(!m&&!d&&!h)throw new Error("Missing required arguments");if(!s.string(d))throw new TypeError("Second argument must be a String");if(!s.fn(h))throw new TypeError("Third argument must be a Function");if(s.node(m))return c(m,d,h);if(s.nodeList(m))return u(m,d,h);if(s.string(m))return p(m,d,h);throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList")}function c(m,d,h){return m.addEventListener(d,h),{destroy:function(){m.removeEventListener(d,h)}}}function u(m,d,h){return Array.prototype.forEach.call(m,function(v){v.addEventListener(d,h)}),{destroy:function(){Array.prototype.forEach.call(m,function(v){v.removeEventListener(d,h)})}}}function p(m,d,h){return a(document.body,m,d,h)}n.exports=f},817:function(n){function o(i){var s;if(i.nodeName==="SELECT")i.focus(),s=i.value;else if(i.nodeName==="INPUT"||i.nodeName==="TEXTAREA"){var a=i.hasAttribute("readonly");a||i.setAttribute("readonly",""),i.select(),i.setSelectionRange(0,i.value.length),a||i.removeAttribute("readonly"),s=i.value}else{i.hasAttribute("contenteditable")&&i.focus();var f=window.getSelection(),c=document.createRange();c.selectNodeContents(i),f.removeAllRanges(),f.addRange(c),s=f.toString()}return s}n.exports=o},279:function(n){function o(){}o.prototype={on:function(i,s,a){var f=this.e||(this.e={});return(f[i]||(f[i]=[])).push({fn:s,ctx:a}),this},once:function(i,s,a){var f=this;function c(){f.off(i,c),s.apply(a,arguments)}return c._=s,this.on(i,c,a)},emit:function(i){var s=[].slice.call(arguments,1),a=((this.e||(this.e={}))[i]||[]).slice(),f=0,c=a.length;for(f;f{"use strict";/*! + * escape-html + * Copyright(c) 2012-2013 TJ Holowaychuk + * Copyright(c) 2015 Andreas Lubbe + * Copyright(c) 2015 Tiancheng "Timothy" Gu + * MIT Licensed + */var rs=/["'&<>]/;Yo.exports=ns;function ns(e){var t=""+e,r=rs.exec(t);if(!r)return t;var n,o="",i=0,s=0;for(i=r.index;i0&&i[i.length-1])&&(c[0]===6||c[0]===2)){r=0;continue}if(c[0]===3&&(!i||c[1]>i[0]&&c[1]=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}function W(e,t){var r=typeof Symbol=="function"&&e[Symbol.iterator];if(!r)return e;var n=r.call(e),o,i=[],s;try{for(;(t===void 0||t-- >0)&&!(o=n.next()).done;)i.push(o.value)}catch(a){s={error:a}}finally{try{o&&!o.done&&(r=n.return)&&r.call(n)}finally{if(s)throw s.error}}return i}function D(e,t,r){if(r||arguments.length===2)for(var n=0,o=t.length,i;n1||a(m,d)})})}function a(m,d){try{f(n[m](d))}catch(h){p(i[0][3],h)}}function f(m){m.value instanceof et?Promise.resolve(m.value.v).then(c,u):p(i[0][2],m)}function c(m){a("next",m)}function u(m){a("throw",m)}function p(m,d){m(d),i.shift(),i.length&&a(i[0][0],i[0][1])}}function pn(e){if(!Symbol.asyncIterator)throw new TypeError("Symbol.asyncIterator is not defined.");var t=e[Symbol.asyncIterator],r;return t?t.call(e):(e=typeof Ee=="function"?Ee(e):e[Symbol.iterator](),r={},n("next"),n("throw"),n("return"),r[Symbol.asyncIterator]=function(){return this},r);function n(i){r[i]=e[i]&&function(s){return new Promise(function(a,f){s=e[i](s),o(a,f,s.done,s.value)})}}function o(i,s,a,f){Promise.resolve(f).then(function(c){i({value:c,done:a})},s)}}function C(e){return typeof e=="function"}function at(e){var t=function(n){Error.call(n),n.stack=new Error().stack},r=e(t);return r.prototype=Object.create(Error.prototype),r.prototype.constructor=r,r}var It=at(function(e){return function(r){e(this),this.message=r?r.length+` errors occurred during unsubscription: +`+r.map(function(n,o){return o+1+") "+n.toString()}).join(` + `):"",this.name="UnsubscriptionError",this.errors=r}});function Ve(e,t){if(e){var r=e.indexOf(t);0<=r&&e.splice(r,1)}}var Ie=function(){function e(t){this.initialTeardown=t,this.closed=!1,this._parentage=null,this._finalizers=null}return e.prototype.unsubscribe=function(){var t,r,n,o,i;if(!this.closed){this.closed=!0;var s=this._parentage;if(s)if(this._parentage=null,Array.isArray(s))try{for(var a=Ee(s),f=a.next();!f.done;f=a.next()){var c=f.value;c.remove(this)}}catch(v){t={error:v}}finally{try{f&&!f.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}else s.remove(this);var u=this.initialTeardown;if(C(u))try{u()}catch(v){i=v instanceof It?v.errors:[v]}var p=this._finalizers;if(p){this._finalizers=null;try{for(var m=Ee(p),d=m.next();!d.done;d=m.next()){var h=d.value;try{ln(h)}catch(v){i=i!=null?i:[],v instanceof It?i=D(D([],W(i)),W(v.errors)):i.push(v)}}}catch(v){n={error:v}}finally{try{d&&!d.done&&(o=m.return)&&o.call(m)}finally{if(n)throw n.error}}}if(i)throw new It(i)}},e.prototype.add=function(t){var r;if(t&&t!==this)if(this.closed)ln(t);else{if(t instanceof e){if(t.closed||t._hasParent(this))return;t._addParent(this)}(this._finalizers=(r=this._finalizers)!==null&&r!==void 0?r:[]).push(t)}},e.prototype._hasParent=function(t){var r=this._parentage;return r===t||Array.isArray(r)&&r.includes(t)},e.prototype._addParent=function(t){var r=this._parentage;this._parentage=Array.isArray(r)?(r.push(t),r):r?[r,t]:t},e.prototype._removeParent=function(t){var r=this._parentage;r===t?this._parentage=null:Array.isArray(r)&&Ve(r,t)},e.prototype.remove=function(t){var r=this._finalizers;r&&Ve(r,t),t instanceof e&&t._removeParent(this)},e.EMPTY=function(){var t=new e;return t.closed=!0,t}(),e}();var Sr=Ie.EMPTY;function jt(e){return e instanceof Ie||e&&"closed"in e&&C(e.remove)&&C(e.add)&&C(e.unsubscribe)}function ln(e){C(e)?e():e.unsubscribe()}var Le={onUnhandledError:null,onStoppedNotification:null,Promise:void 0,useDeprecatedSynchronousErrorHandling:!1,useDeprecatedNextContext:!1};var st={setTimeout:function(e,t){for(var r=[],n=2;n0},enumerable:!1,configurable:!0}),t.prototype._trySubscribe=function(r){return this._throwIfClosed(),e.prototype._trySubscribe.call(this,r)},t.prototype._subscribe=function(r){return this._throwIfClosed(),this._checkFinalizedStatuses(r),this._innerSubscribe(r)},t.prototype._innerSubscribe=function(r){var n=this,o=this,i=o.hasError,s=o.isStopped,a=o.observers;return i||s?Sr:(this.currentObservers=null,a.push(r),new Ie(function(){n.currentObservers=null,Ve(a,r)}))},t.prototype._checkFinalizedStatuses=function(r){var n=this,o=n.hasError,i=n.thrownError,s=n.isStopped;o?r.error(i):s&&r.complete()},t.prototype.asObservable=function(){var r=new F;return r.source=this,r},t.create=function(r,n){return new xn(r,n)},t}(F);var xn=function(e){ie(t,e);function t(r,n){var o=e.call(this)||this;return o.destination=r,o.source=n,o}return t.prototype.next=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.next)===null||o===void 0||o.call(n,r)},t.prototype.error=function(r){var n,o;(o=(n=this.destination)===null||n===void 0?void 0:n.error)===null||o===void 0||o.call(n,r)},t.prototype.complete=function(){var r,n;(n=(r=this.destination)===null||r===void 0?void 0:r.complete)===null||n===void 0||n.call(r)},t.prototype._subscribe=function(r){var n,o;return(o=(n=this.source)===null||n===void 0?void 0:n.subscribe(r))!==null&&o!==void 0?o:Sr},t}(x);var Et={now:function(){return(Et.delegate||Date).now()},delegate:void 0};var wt=function(e){ie(t,e);function t(r,n,o){r===void 0&&(r=1/0),n===void 0&&(n=1/0),o===void 0&&(o=Et);var i=e.call(this)||this;return i._bufferSize=r,i._windowTime=n,i._timestampProvider=o,i._buffer=[],i._infiniteTimeWindow=!0,i._infiniteTimeWindow=n===1/0,i._bufferSize=Math.max(1,r),i._windowTime=Math.max(1,n),i}return t.prototype.next=function(r){var n=this,o=n.isStopped,i=n._buffer,s=n._infiniteTimeWindow,a=n._timestampProvider,f=n._windowTime;o||(i.push(r),!s&&i.push(a.now()+f)),this._trimBuffer(),e.prototype.next.call(this,r)},t.prototype._subscribe=function(r){this._throwIfClosed(),this._trimBuffer();for(var n=this._innerSubscribe(r),o=this,i=o._infiniteTimeWindow,s=o._buffer,a=s.slice(),f=0;f0?e.prototype.requestAsyncId.call(this,r,n,o):(r.actions.push(this),r._scheduled||(r._scheduled=ut.requestAnimationFrame(function(){return r.flush(void 0)})))},t.prototype.recycleAsyncId=function(r,n,o){var i;if(o===void 0&&(o=0),o!=null?o>0:this.delay>0)return e.prototype.recycleAsyncId.call(this,r,n,o);var s=r.actions;n!=null&&((i=s[s.length-1])===null||i===void 0?void 0:i.id)!==n&&(ut.cancelAnimationFrame(n),r._scheduled=void 0)},t}(Wt);var Sn=function(e){ie(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.flush=function(r){this._active=!0;var n=this._scheduled;this._scheduled=void 0;var o=this.actions,i;r=r||o.shift();do if(i=r.execute(r.state,r.delay))break;while((r=o[0])&&r.id===n&&o.shift());if(this._active=!1,i){for(;(r=o[0])&&r.id===n&&o.shift();)r.unsubscribe();throw i}},t}(Dt);var Oe=new Sn(wn);var _=new F(function(e){return e.complete()});function Vt(e){return e&&C(e.schedule)}function Cr(e){return e[e.length-1]}function Ye(e){return C(Cr(e))?e.pop():void 0}function Te(e){return Vt(Cr(e))?e.pop():void 0}function zt(e,t){return typeof Cr(e)=="number"?e.pop():t}var pt=function(e){return e&&typeof e.length=="number"&&typeof e!="function"};function Nt(e){return C(e==null?void 0:e.then)}function qt(e){return C(e[ft])}function Kt(e){return Symbol.asyncIterator&&C(e==null?void 0:e[Symbol.asyncIterator])}function Qt(e){return new TypeError("You provided "+(e!==null&&typeof e=="object"?"an invalid object":"'"+e+"'")+" where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.")}function zi(){return typeof Symbol!="function"||!Symbol.iterator?"@@iterator":Symbol.iterator}var Yt=zi();function Gt(e){return C(e==null?void 0:e[Yt])}function Bt(e){return un(this,arguments,function(){var r,n,o,i;return $t(this,function(s){switch(s.label){case 0:r=e.getReader(),s.label=1;case 1:s.trys.push([1,,9,10]),s.label=2;case 2:return[4,et(r.read())];case 3:return n=s.sent(),o=n.value,i=n.done,i?[4,et(void 0)]:[3,5];case 4:return[2,s.sent()];case 5:return[4,et(o)];case 6:return[4,s.sent()];case 7:return s.sent(),[3,2];case 8:return[3,10];case 9:return r.releaseLock(),[7];case 10:return[2]}})})}function Jt(e){return C(e==null?void 0:e.getReader)}function U(e){if(e instanceof F)return e;if(e!=null){if(qt(e))return Ni(e);if(pt(e))return qi(e);if(Nt(e))return Ki(e);if(Kt(e))return On(e);if(Gt(e))return Qi(e);if(Jt(e))return Yi(e)}throw Qt(e)}function Ni(e){return new F(function(t){var r=e[ft]();if(C(r.subscribe))return r.subscribe(t);throw new TypeError("Provided object does not correctly implement Symbol.observable")})}function qi(e){return new F(function(t){for(var r=0;r=2;return function(n){return n.pipe(e?A(function(o,i){return e(o,i,n)}):de,ge(1),r?He(t):Dn(function(){return new Zt}))}}function Vn(){for(var e=[],t=0;t=2,!0))}function pe(e){e===void 0&&(e={});var t=e.connector,r=t===void 0?function(){return new x}:t,n=e.resetOnError,o=n===void 0?!0:n,i=e.resetOnComplete,s=i===void 0?!0:i,a=e.resetOnRefCountZero,f=a===void 0?!0:a;return function(c){var u,p,m,d=0,h=!1,v=!1,Y=function(){p==null||p.unsubscribe(),p=void 0},B=function(){Y(),u=m=void 0,h=v=!1},N=function(){var O=u;B(),O==null||O.unsubscribe()};return y(function(O,Qe){d++,!v&&!h&&Y();var De=m=m!=null?m:r();Qe.add(function(){d--,d===0&&!v&&!h&&(p=$r(N,f))}),De.subscribe(Qe),!u&&d>0&&(u=new rt({next:function($e){return De.next($e)},error:function($e){v=!0,Y(),p=$r(B,o,$e),De.error($e)},complete:function(){h=!0,Y(),p=$r(B,s),De.complete()}}),U(O).subscribe(u))})(c)}}function $r(e,t){for(var r=[],n=2;ne.next(document)),e}function K(e,t=document){return Array.from(t.querySelectorAll(e))}function z(e,t=document){let r=ce(e,t);if(typeof r=="undefined")throw new ReferenceError(`Missing element: expected "${e}" to be present`);return r}function ce(e,t=document){return t.querySelector(e)||void 0}function _e(){return document.activeElement instanceof HTMLElement&&document.activeElement||void 0}function tr(e){return L(b(document.body,"focusin"),b(document.body,"focusout")).pipe(ke(1),l(()=>{let t=_e();return typeof t!="undefined"?e.contains(t):!1}),V(e===_e()),J())}function Xe(e){return{x:e.offsetLeft,y:e.offsetTop}}function Kn(e){return L(b(window,"load"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>Xe(e)),V(Xe(e)))}function rr(e){return{x:e.scrollLeft,y:e.scrollTop}}function dt(e){return L(b(e,"scroll"),b(window,"resize")).pipe(Ce(0,Oe),l(()=>rr(e)),V(rr(e)))}var Yn=function(){if(typeof Map!="undefined")return Map;function e(t,r){var n=-1;return t.some(function(o,i){return o[0]===r?(n=i,!0):!1}),n}return function(){function t(){this.__entries__=[]}return Object.defineProperty(t.prototype,"size",{get:function(){return this.__entries__.length},enumerable:!0,configurable:!0}),t.prototype.get=function(r){var n=e(this.__entries__,r),o=this.__entries__[n];return o&&o[1]},t.prototype.set=function(r,n){var o=e(this.__entries__,r);~o?this.__entries__[o][1]=n:this.__entries__.push([r,n])},t.prototype.delete=function(r){var n=this.__entries__,o=e(n,r);~o&&n.splice(o,1)},t.prototype.has=function(r){return!!~e(this.__entries__,r)},t.prototype.clear=function(){this.__entries__.splice(0)},t.prototype.forEach=function(r,n){n===void 0&&(n=null);for(var o=0,i=this.__entries__;o0},e.prototype.connect_=function(){!Wr||this.connected_||(document.addEventListener("transitionend",this.onTransitionEnd_),window.addEventListener("resize",this.refresh),va?(this.mutationsObserver_=new MutationObserver(this.refresh),this.mutationsObserver_.observe(document,{attributes:!0,childList:!0,characterData:!0,subtree:!0})):(document.addEventListener("DOMSubtreeModified",this.refresh),this.mutationEventsAdded_=!0),this.connected_=!0)},e.prototype.disconnect_=function(){!Wr||!this.connected_||(document.removeEventListener("transitionend",this.onTransitionEnd_),window.removeEventListener("resize",this.refresh),this.mutationsObserver_&&this.mutationsObserver_.disconnect(),this.mutationEventsAdded_&&document.removeEventListener("DOMSubtreeModified",this.refresh),this.mutationsObserver_=null,this.mutationEventsAdded_=!1,this.connected_=!1)},e.prototype.onTransitionEnd_=function(t){var r=t.propertyName,n=r===void 0?"":r,o=ba.some(function(i){return!!~n.indexOf(i)});o&&this.refresh()},e.getInstance=function(){return this.instance_||(this.instance_=new e),this.instance_},e.instance_=null,e}(),Gn=function(e,t){for(var r=0,n=Object.keys(t);r0},e}(),Jn=typeof WeakMap!="undefined"?new WeakMap:new Yn,Xn=function(){function e(t){if(!(this instanceof e))throw new TypeError("Cannot call a class as a function.");if(!arguments.length)throw new TypeError("1 argument required, but only 0 present.");var r=ga.getInstance(),n=new La(t,r,this);Jn.set(this,n)}return e}();["observe","unobserve","disconnect"].forEach(function(e){Xn.prototype[e]=function(){var t;return(t=Jn.get(this))[e].apply(t,arguments)}});var Aa=function(){return typeof nr.ResizeObserver!="undefined"?nr.ResizeObserver:Xn}(),Zn=Aa;var eo=new x,Ca=$(()=>k(new Zn(e=>{for(let t of e)eo.next(t)}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function he(e){return{width:e.offsetWidth,height:e.offsetHeight}}function ye(e){return Ca.pipe(S(t=>t.observe(e)),g(t=>eo.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(()=>he(e)))),V(he(e)))}function bt(e){return{width:e.scrollWidth,height:e.scrollHeight}}function ar(e){let t=e.parentElement;for(;t&&(e.scrollWidth<=t.scrollWidth&&e.scrollHeight<=t.scrollHeight);)t=(e=t).parentElement;return t?e:void 0}var to=new x,Ra=$(()=>k(new IntersectionObserver(e=>{for(let t of e)to.next(t)},{threshold:0}))).pipe(g(e=>L(ze,k(e)).pipe(R(()=>e.disconnect()))),X(1));function sr(e){return Ra.pipe(S(t=>t.observe(e)),g(t=>to.pipe(A(({target:r})=>r===e),R(()=>t.unobserve(e)),l(({isIntersecting:r})=>r))))}function ro(e,t=16){return dt(e).pipe(l(({y:r})=>{let n=he(e),o=bt(e);return r>=o.height-n.height-t}),J())}var cr={drawer:z("[data-md-toggle=drawer]"),search:z("[data-md-toggle=search]")};function no(e){return cr[e].checked}function Ke(e,t){cr[e].checked!==t&&cr[e].click()}function Ue(e){let t=cr[e];return b(t,"change").pipe(l(()=>t.checked),V(t.checked))}function ka(e,t){switch(e.constructor){case HTMLInputElement:return e.type==="radio"?/^Arrow/.test(t):!0;case HTMLSelectElement:case HTMLTextAreaElement:return!0;default:return e.isContentEditable}}function Ha(){return L(b(window,"compositionstart").pipe(l(()=>!0)),b(window,"compositionend").pipe(l(()=>!1))).pipe(V(!1))}function oo(){let e=b(window,"keydown").pipe(A(t=>!(t.metaKey||t.ctrlKey)),l(t=>({mode:no("search")?"search":"global",type:t.key,claim(){t.preventDefault(),t.stopPropagation()}})),A(({mode:t,type:r})=>{if(t==="global"){let n=_e();if(typeof n!="undefined")return!ka(n,r)}return!0}),pe());return Ha().pipe(g(t=>t?_:e))}function le(){return new URL(location.href)}function ot(e){location.href=e.href}function io(){return new x}function ao(e,t){if(typeof t=="string"||typeof t=="number")e.innerHTML+=t.toString();else if(t instanceof Node)e.appendChild(t);else if(Array.isArray(t))for(let r of t)ao(e,r)}function M(e,t,...r){let n=document.createElement(e);if(t)for(let o of Object.keys(t))typeof t[o]!="undefined"&&(typeof t[o]!="boolean"?n.setAttribute(o,t[o]):n.setAttribute(o,""));for(let o of r)ao(n,o);return n}function fr(e){if(e>999){let t=+((e-950)%1e3>99);return`${((e+1e-6)/1e3).toFixed(t)}k`}else return e.toString()}function so(){return location.hash.substring(1)}function Dr(e){let t=M("a",{href:e});t.addEventListener("click",r=>r.stopPropagation()),t.click()}function Pa(e){return L(b(window,"hashchange"),e).pipe(l(so),V(so()),A(t=>t.length>0),X(1))}function co(e){return Pa(e).pipe(l(t=>ce(`[id="${t}"]`)),A(t=>typeof t!="undefined"))}function Vr(e){let t=matchMedia(e);return er(r=>t.addListener(()=>r(t.matches))).pipe(V(t.matches))}function fo(){let e=matchMedia("print");return L(b(window,"beforeprint").pipe(l(()=>!0)),b(window,"afterprint").pipe(l(()=>!1))).pipe(V(e.matches))}function zr(e,t){return e.pipe(g(r=>r?t():_))}function ur(e,t={credentials:"same-origin"}){return ue(fetch(`${e}`,t)).pipe(fe(()=>_),g(r=>r.status!==200?Ot(()=>new Error(r.statusText)):k(r)))}function We(e,t){return ur(e,t).pipe(g(r=>r.json()),X(1))}function uo(e,t){let r=new DOMParser;return ur(e,t).pipe(g(n=>n.text()),l(n=>r.parseFromString(n,"text/xml")),X(1))}function pr(e){let t=M("script",{src:e});return $(()=>(document.head.appendChild(t),L(b(t,"load"),b(t,"error").pipe(g(()=>Ot(()=>new ReferenceError(`Invalid script: ${e}`))))).pipe(l(()=>{}),R(()=>document.head.removeChild(t)),ge(1))))}function po(){return{x:Math.max(0,scrollX),y:Math.max(0,scrollY)}}function lo(){return L(b(window,"scroll",{passive:!0}),b(window,"resize",{passive:!0})).pipe(l(po),V(po()))}function mo(){return{width:innerWidth,height:innerHeight}}function ho(){return b(window,"resize",{passive:!0}).pipe(l(mo),V(mo()))}function bo(){return G([lo(),ho()]).pipe(l(([e,t])=>({offset:e,size:t})),X(1))}function lr(e,{viewport$:t,header$:r}){let n=t.pipe(ee("size")),o=G([n,r]).pipe(l(()=>Xe(e)));return G([r,t,o]).pipe(l(([{height:i},{offset:s,size:a},{x:f,y:c}])=>({offset:{x:s.x-f,y:s.y-c+i},size:a})))}(()=>{function e(n,o){parent.postMessage(n,o||"*")}function t(...n){return n.reduce((o,i)=>o.then(()=>new Promise(s=>{let a=document.createElement("script");a.src=i,a.onload=s,document.body.appendChild(a)})),Promise.resolve())}var r=class extends EventTarget{constructor(n){super(),this.url=n,this.m=i=>{i.source===this.w&&(this.dispatchEvent(new MessageEvent("message",{data:i.data})),this.onmessage&&this.onmessage(i))},this.e=(i,s,a,f,c)=>{if(s===`${this.url}`){let u=new ErrorEvent("error",{message:i,filename:s,lineno:a,colno:f,error:c});this.dispatchEvent(u),this.onerror&&this.onerror(u)}};let o=document.createElement("iframe");o.hidden=!0,document.body.appendChild(this.iframe=o),this.w.document.open(),this.w.document.write(` + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Insanus Mokrassar libraries home

+

Hello :) It is my libraries docs place and I glad to welcome you here. I hope, this documentation place will help you.

+

Projects

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/search/search_index.json b/search/search_index.json new file mode 100644 index 0000000..cd50732 --- /dev/null +++ b/search/search_index.json @@ -0,0 +1 @@ +{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"index.html","title":"Insanus Mokrassar libraries home","text":"

Hello :) It is my libraries docs place and I glad to welcome you here. I hope, this documentation place will help you.

"},{"location":"index.html#projects","title":"Projects","text":"
  • TelegramBotAPI
"},{"location":"tgbotapi/dsls/keyboards.html","title":"Keyboards","text":"

In the telegram system there are two types of keyboards:

Reply Inline Keyboard for each user in the chat Keyboard linked to the certain message

Low-level way to create keyboard looks like in the next snippet:

ReplyKeyboardMarkup(\n    matrix {\n        row {\n            add(SimpleKeyboardButton(\"Simple text\"))\n            // ...\n        }\n        // ...\n    }\n)\n

In case you wish to create inline keyboard, it will look like the same as for reply keyboard. But there is another way. The next snippet will create the same keyboard as on the screenshots above:

// reply keyboard\nreplyKeyboard {\n    row {\n        simpleButton(\"7\")\n        simpleButton(\"8\")\n        simpleButton(\"9\")\n        simpleButton(\"*\")\n    }\n    row {\n        simpleButton(\"4\")\n        simpleButton(\"5\")\n        simpleButton(\"6\")\n        simpleButton(\"/\")\n    }\n    row {\n        simpleButton(\"1\")\n        simpleButton(\"2\")\n        simpleButton(\"3\")\n        simpleButton(\"-\")\n    }\n    row {\n        simpleButton(\"0\")\n        simpleButton(\".\")\n        simpleButton(\"=\")\n        simpleButton(\"+\")\n    }\n}\n\n// inline keyboard\ninlineKeyboard {\n    row {\n        dataButton(\"Get random music\", \"random\")\n    }\n    row {\n        urlButton(\"Send music to friends\", \"https://some.link\")\n    }\n}\n
"},{"location":"tgbotapi/dsls/live-location.html","title":"Live Location","text":"

Bot API allows you to send live locations and update them during their lifetime. In this library there are several ways to use this API:

  • Directly via API calls (sendLiveLocation and editLiveLocation)
  • startLiveLocation
  • handleLiveLocation
"},{"location":"tgbotapi/dsls/live-location.html#sendlivelocation","title":"sendLiveLocation","text":"

In the Bot API there is no independent sendLiveLocation method, instead it is suggested to use sendLocation with setting up live_period. In this library in difference with original Bot API live location is special request. It was required because of in fact live locations and static locations are different types of location info and you as bot developer may interact with them differently.

Anyway, in common case the logic looks like:

  • Send sendLiveLocation
  • Use editLiveLocation to change it during its lifetime
  • Use stopLiveLocation to abort it before lifetime end
"},{"location":"tgbotapi/dsls/live-location.html#startlivelocation","title":"startLiveLocation","text":"

In difference with sendLiveLocation, startLiveLocation using LiveLocationProvider. With this provider you need not to handle chat and message ids and keep some other data for location changes. Instead, you workflow with provider will be next:

  • startLiveLocation
  • Use LiveLocationProvider#updateLocation to update location and optionally add inline keyboard
  • Use LiveLocationProvider#close to abort live location before its end

Besides, LiveLocationProvider contains different useful parameters about live location

"},{"location":"tgbotapi/dsls/live-location.html#handlelivelocation","title":"handleLiveLocation","text":"

This way of live locations handling is based on coroutines Flow and allow you to pass some external Flow with EditLiveLocationInfo. So, workflow:

  • Create your own flow of locations. For example:
flow {\n  var i = 0\n  while (isActive) {\n    val newInfo = EditLiveLocationInfo(\n      latitude = i.toDouble(),\n      longitude = i.toDouble(),\n      replyMarkup = flatInlineKeyboard {\n        dataButton(\"Cancel\", \"cancel\")\n      }\n    )\n    emit(newInfo)\n    i++\n    delay(10000L) // 10 seconds\n  }\n}\n
  • In case you needed, create your collector to store the message with live location:
val currentMessageState = MutableStateFlow<ContentMessage<LocationContent>?>(null)\n
  • Start handle live location. handleLiveLocation works synchronosly (in current coroutine) and will ends only when your flow will ends. Thats why there are two ways to call it:
handleLiveLocation(\n  it.chat.id,\n  locationsFlow,\n  sentMessageFlow = FlowCollector { currentMessageState.emit(it) }\n)\n// this code will be called after `locationsFlow` will ends\n

OR

scope.launch {\n  handleLiveLocation(\n    it.chat.id,\n    locationsFlow,\n    sentMessageFlow = FlowCollector { currentMessageState.emit(it) }\n  )\n}\n// this code will be called right after launch will be completed\n

See our example to get more detailed sample

"},{"location":"tgbotapi/dsls/text.html","title":"Text","text":"

For the text creating there are several tools. The most simple one is to concatenate several text sources to make list of text sources as a result:

val sources = \"Regular start of text \" + bold(\"with bold part\") + italic(\"and italic ending\")\n

But there is a little bit more useful way: entities builder:

val items = (0 until 10).map { it.toString() }\nbuildEntities(\" \") {// optional \" \" auto separator which will be pasted between text sources\n    +\"It is regular start too\" + bold(\"it is bold as well\")\n    items.forEachIndexed { i, item ->\n        if (i % 2) {\n            italic(item)\n        } else {\n            strikethrough(item)\n        }\n    }\n}\n

In the code above we are creating an items list just for demonstrating that inside of buildEntities body we may use any operations for cunstructing our result list of TextSources. As a result, will be created the list which will looks like in telegram as \u201cIt is regular start too it is bold as well 0 ~~1~~ 2 ~~3~~ 4 ~~5~~ 6 ~~7~~ 8 ~~9~~\u201d.

"},{"location":"tgbotapi/introduction/before-any-bot-project.html","title":"Before any bot project","text":"

There are several places you need to visit for starting work with any Telegram Bot framework on any language:

  • Bots info introduction
  • Telegram Bot API reference (you can skip it, but it could be useful to know some specific cases in Telegram Bot API)

Anyway, the most important link is How do I create a bot? inside of Telegram Bot API

"},{"location":"tgbotapi/introduction/before-any-bot-project.html#next-steps","title":"Next steps","text":"
  • Including in your project
"},{"location":"tgbotapi/introduction/first-bot.html","title":"First bot","text":"

Examples info

A lot of examples with using of Telegram Bot API you can find in this github repository

"},{"location":"tgbotapi/introduction/first-bot.html#the-most-simple-bot","title":"The most simple bot","text":"

The most simple bot will just print information about itself. All source code you can find in this repository. Our interest here will be concentrated on the next example part:

suspend fun main(vararg args: String) {\n  val botToken = args.first()\n  val bot = telegramBot(botToken)\n  println(bot.getMe())\n}\n

So, let\u2019s get understanding, about what is going on:

  1. suspend fun main(vararg args: String):
    • suspend required for making of requests inside of this function. For more info you can open official documentation for coroutins. In fact, suspend fun main is the same that fun main() = runBlocking {} from examples
  2. val botToken = args.first(): here we are just getting the bot token from first arguments of command line
  3. val bot = telegramBot(botToken) : inside of bot will be RequestsExecutor object which will be used for all requests in any project with this library
  4. println(bot.getMe()): here happens calling of getMe extension

As a result, we will see in the command line something like

ExtendedBot(id=ChatId(chatId=123456789), username=Username(username=@first_test_ee17e8_bot), firstName=Your bot name, lastName=, canJoinGroups=false, canReadAllGroupMessages=false, supportsInlineQueries=false)\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html","title":"Including in your project","text":"

There are three projects:

  • TelegramBotAPI Core - project with base for all working with Telegram Bot API
  • TelegramBotAPI API Extensions - extension of TelegramBotAPI with functions for more comfortable work with Telegram Bot API
  • TelegramBotAPI Utils Extensions - extension of TelegramBotAPI with functions for extending of different things like retrieving of updates

TelegramBotAPI

Also, there is an aggregator-version tgbotapi, which will automatically include all projects above. It is most recommended version due to the fact that it is including all necessary tools around TelegramBotAPI Core, but it is optionally due to the possible restrictions on the result methods count (for android) or bundle size

Examples

You can find full examples info in this repository. In this repository there full codes which are working in normal situation. Currently, there is only one exception when these examples could work incorrectly: you are living in the location where Telegram Bot API is unavailable. For solving this problem you can read Proxy setup part

"},{"location":"tgbotapi/introduction/including-in-your-project.html#notice-about-repository","title":"Notice about repository","text":"

To use this library, you will need to include Maven Central repository in your project

"},{"location":"tgbotapi/introduction/including-in-your-project.html#buildgradle","title":"build.gradle","text":"
mavenCentral()\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml","title":"pom.xml","text":"
<repository>\n  <id>central</id>\n  <name>mavenCentral</name>\n  <url>https://repo1.maven.org/maven2</url>\n</repository>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#dev-channel","title":"Dev channel","text":"

Besides, there is developer versions repo. To use it in your project, add the repo in repositories section:

Gradle
maven {\n    url \"https://git.inmo.dev/api/packages/InsanusMokrassar/maven\"\n}\n
Maven
<repository>\n  <id>dev.inmo</id>\n  <name>InmoDev</name>\n  <url>https://git.inmo.dev/api/packages/InsanusMokrassar/maven</url>\n</repository>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi","title":"TelegramBotAPI","text":"

As tgbotapi_version variable in next snippets will be used variable with next last published version:

"},{"location":"tgbotapi/introduction/including-in-your-project.html#buildgradle_1","title":"build.gradle","text":"
implementation \"dev.inmo:tgbotapi:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_1","title":"pom.xml","text":"
<dependency>\n    <groupId>dev.inmo</groupId>\n    <artifactId>tgbotapi</artifactId>\n    <version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi-core","title":"TelegramBotAPI Core","text":"

As tgbotapi_version variable in next snippets will be used variable with next last published version:

"},{"location":"tgbotapi/introduction/including-in-your-project.html#buildgradle_2","title":"build.gradle","text":"
implementation \"dev.inmo:tgbotapi.core:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_2","title":"pom.xml","text":"
<dependency>\n    <groupId>dev.inmo</groupId>\n    <artifactId>tgbotapi.core</artifactId>\n    <version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi-api-extensions","title":"TelegramBotAPI API Extensions","text":"

As tgbotapi_version variable in next snippets will be used variable with next last published version:

"},{"location":"tgbotapi/introduction/including-in-your-project.html#buildgradle_3","title":"build.gradle","text":"
implementation \"dev.inmo:tgbotapi.api:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_3","title":"pom.xml","text":"
<dependency>\n    <groupId>dev.inmo</groupId>\n    <artifactId>tgbotapi.api</artifactId>\n    <version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#telegrambotapi-utils-extensions","title":"TelegramBotAPI Utils Extensions","text":"

As tgbotapi_version variable in next snippets will be used variable with next last published version:

"},{"location":"tgbotapi/introduction/including-in-your-project.html#buildgradle_4","title":"build.gradle","text":"
implementation \"dev.inmo:tgbotapi.utils:$tgbotapi_version\"\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#pomxml_4","title":"pom.xml","text":"
<dependency>\n    <groupId>dev.inmo</groupId>\n    <artifactId>tgbotapi.utils</artifactId>\n    <version>${tgbotapi_version}</version>\n</dependency>\n
"},{"location":"tgbotapi/introduction/including-in-your-project.html#next-steps","title":"Next steps","text":"
  • Proxy setup
  • First bot
"},{"location":"tgbotapi/introduction/proxy-setup.html","title":"Proxy setup","text":"

In some locations Telegram Bots API urls will be unavailable. In this case all examples will just throw exception like:

Exception in thread \"main\" java.net.ConnectException: Connection refused\n    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)\n    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)\n    at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:36)\n    at io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)\n    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)\n    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)\n    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)\n    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)\n    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)\n    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)\n\nProcess finished with exit code 1\n

There are several ways to solve this problem:

  • Built-in proxy config (will require some socks or http proxy server)
  • System-configured VPN or proxy
  • Your own Bot API Server
"},{"location":"tgbotapi/introduction/proxy-setup.html#using-ktor-client-built-in-proxy","title":"Using Ktor Client built-in proxy","text":"

First of all, you will need to use one more library:

build.gradle:

implementation \"io.ktor:ktor-client-okhttp:2.0.1\"\n

Dependency note

In the snippet above was used version 2.0.1 which is actual for TelegramBotAPI at the moment of filling this documentation (May 22 2022, TelegramBotAPI version 2.0.0) and you can update version of this dependency in case if it is outdated.

For configuring proxy for your bot inside your program, you can use next snippet:

val botToken = \"HERE MUST BE YOUR TOKEN\"\nval bot = telegramBot(botToken) {\n  ktorClientEngineFactory = OkHttp\n  proxy = ProxyBuilder.socks(\"127.0.0.1\", 1080)\n}\n

Explanation line by line:

  1. val botToken = \"HERE MUST BE YOUR TOKEN\" - here we are just creating variable botToken
  2. val bot = telegramBot(botToken) { - start creating bot
  3. ktorClientEngineFactory = OkHttp - setting up engine factory of our bot. On the time of documentation filling, OkHttp is one of the engines in Ktor system which supports socks proxy. More you can read on Ktor site in subparts about engines and proxy
  4. proxy = ProxyBuilder.socks(\"127.0.0.1\", 1080) - here we are setting up our proxy. Here was used local server which (as assumed) will connect to server like shadowsocks
"},{"location":"tgbotapi/introduction/proxy-setup.html#next-steps","title":"Next steps","text":"
  • First bot
"},{"location":"tgbotapi/logic/api-extensions.html","title":"API Extensions","text":"

API extensions is a module which you may include in your project in addition to core part. In most cases this module will allow just use syntax like bot.getUpdates() instead of bot.execute(GetUpdates()), but there are several other things you will achieve with that syntax.

"},{"location":"tgbotapi/logic/api-extensions.html#bot-builder","title":"Bot builder","text":"

This functionality allow you to build bot in more unified and comfortable way than standard creating with telegramBot function

buildBot(\n    \"TOKEN\"\n) {\n  proxy = ProxyBuilder.socks(host = \"127.0.0.1\", port = 4001) // just an example, more info on https://ktor.io/docs/proxy.html\n  ktorClientConfig = {\n    // configuring of ktor client\n  }\n  ktorClientEngineFactory = {\n   // configuring of ktor client engine \n  }\n}\n
"},{"location":"tgbotapi/logic/api-extensions.html#downloading-of-files","title":"Downloading of files","text":"

In standard library requests there are no way to download some file retrieved in updates or after requests. You may use syntax like bot.downloadFile(file) where file is TelegramMediaFile from telegram, FileId or even PathedFile from GetFile request (sources).

"},{"location":"tgbotapi/logic/api-extensions.html#live-location","title":"Live location","text":"

By default, you should handle updates of Live location by your code. But with extension bot#startLiveLocation you may provide all necessary startup parameters and handle updates with just calling updateLocation for retrieved LiveLocationProvider.

"},{"location":"tgbotapi/logic/api-extensions.html#what-is-next","title":"What is next?","text":"

There are several things you may read next:

  • Updates retrieving
  • Read about second level of working with library
  • Read about BehaviourBuilder
"},{"location":"tgbotapi/logic/behaviour-builder-with-fsm.html","title":"Behaviour Builder with FSM","text":"

Behaviour builder with FSM is based on the MicroUtils FSM. There are several important things in FSM:

  • State - any object which implements State interface
  • StateHandler (or CheckableHandlerHolder) - the handler of states
  • StatesMachine - some machine which work with states and handlers
  • StatesManager - simple manager that will solve which states to save and notify about states changes via its flows

StatesMachine have two methods:

  • start which will start work of machine
  • startChain which will add new state for handling

The most based way to create StatesMachine and register StateHandlers looks like in the next snippet:

buildFSM<TrafficLightState> {\n    strictlyOn<SomeState> {\n        // state handling\n    }\n}.start(CoroutineScope(...)).join()\n

Full example

You may find full example of FSM usage in the tests of FSM in MicroUtils

So, you must do next steps before you will launch your bot with FSM:

  • Create your states. Remember that you may plan to save them, so it is likely you will need to serialize it there
  • Create your handlers for your states. In most cases it is useful to use CheckableHandlerHolder if you want to use standard states machine
  • Solve which states managers to use (the most simple one is the DefaultStatesManager with InMemoryDefaultStatesManager)
"},{"location":"tgbotapi/logic/behaviour-builder-with-fsm.html#bot-with-fsm","title":"Bot with FSM","text":"

There are several extensions for TelegramBot to create your bot with FSM:

  • buildBehaviourWithFSM
    • buildBehaviourWithFSMAndStartLongPolling
  • telegramBotWithBehaviourAndFSM
    • telegramBotWithBehaviourAndFSMAndStartLongPolling

All of them will take as an callback some object with type CustomBehaviourContextReceiver and will looks like in the next snippet:

telegramBotWithBehaviourAndFSMAndStartLongPolling<YourStateType>(\"BOT_TOKEN\") {\n    // here you may use any operations from BehaviourBuilder\n    // here you may use any operations from BehaviourContextWithFSMBuilder like strictlyOn and others\n}\n
"},{"location":"tgbotapi/logic/behaviour-builder-with-fsm.html#examples","title":"Examples","text":"
  • TelegramBotAPI-examples/FSMBot
  • MicroUtils simple example in the tests
"},{"location":"tgbotapi/logic/behaviour-builder.html","title":"Behaviour Builder","text":"

In the previous pages about updates handling and was mentioned that currently in the most cases you should use Flows. So, there is an improvement for that system which hide direct work with flows and allow you to create more declarative logic of your bot.

"},{"location":"tgbotapi/logic/behaviour-builder.html#main-parts-of-behaviour-builder","title":"Main parts of Behaviour Builder","text":"

There are several things you should know for better understanding of behaviour builder:

  • BehaviourContext - it is the thing which contains all necessary tools for working with bots
  • Triggers - on* extensions for BehaviourContext which allow you to create reaction on some update
  • Expectations (or waiters) - wait* extensions which you may use in buildBehaviour function, but it is recommended to use it in bodies of triggers
"},{"location":"tgbotapi/logic/behaviour-builder.html#initialization","title":"Initialization","text":"

As was said above, there is buildBehaviour function which allow you set up your bot logic. Let\u2019s see an example:

val bot = telegramBot(\"TOKEN\")\n\nbot.buildBehaviour {\n  onCommand(\"start\") { // creating of trigger\n    val message = it\n    val content = message.content\n\n    reply(message, \"Ok, send me one photo\") // send text message with replying on incoming message\n\n    val photoContent = waitPhoto().first() // waitPhoto will return List, so, get first element\n\n    val photo = downloadFile(photoContent) // ByteArray of photo\n\n    // some logic with saving of photos\n  }\n}\n
"},{"location":"tgbotapi/logic/behaviour-builder.html#filters","title":"Filters","text":"

In most cases there are opportunity to filter some of messages before starting of main logic. Let\u2019s look at this using the example above:

val bot = telegramBot(\"TOKEN\")\n\nbot.buildBehaviour {\n  onCommand(\n    \"start\",\n    initialFilter = {\n      it.content.textSources.size == 1 // make sure that user has sent /start without any additions\n    }\n  ) {\n    // ...\n  }\n}\n

OR

val bot = telegramBot(\"TOKEN\")\n\nbot.buildBehaviour {\n  onCommand(\n    \"start\",\n    requireOnlyCommandInMessage = true // it is default, but you can overwrite it with `requireOnlyCommandInMessage = false`\n  ) {\n    // ...\n  }\n}\n
"},{"location":"tgbotapi/logic/exceptions-handling.html","title":"Exceptions handling","text":"

Unfortunatelly, exceptions handling in this library is a bit difficult in some places, but that have at least two reasons: flexibility and usability.

"},{"location":"tgbotapi/logic/exceptions-handling.html#in-place-handling","title":"\u201cIn place\u201d handling","text":"

In case you know, where exceptions are happening, you may use several tools for exceptions catching:

  • Catching with result
  • Catching with callback
"},{"location":"tgbotapi/logic/exceptions-handling.html#catching-with-result","title":"Catching with result","text":"

If you prefer to receive Result objects instead of some weird callbacks, you may use the next syntax:

safelyWithResult {\n    // do something\n}.onSuccess { // will be called if everything is right\n    // handle success\n}.onFailure { // will be called if something went wrong\n    // handle error\n    it.printStackTrace()\n}.getOrThrow() // will return value or throw exception\n
"},{"location":"tgbotapi/logic/exceptions-handling.html#catching-with-callback","title":"Catching with callback","text":"

Also there is more simple (in some cases) way to handle exceptions with callbacks:

safely(\n  {\n      // handle error\n      it.printStackTrace()\n      null // return value\n  }\n) {\n    // do something\n}\n
"},{"location":"tgbotapi/logic/exceptions-handling.html#bonus-different-types-of-handling","title":"Bonus: different types of handling","text":"

There are two types of handling:

  • Just safely - when you are using something to obviously retrieve value or throw exception. When handling callback has been skipped, it will throw exception by default. For example:
safely(\n    {\n        it.printStackTrace()\n        \"error\"\n    }\n) {\n    error(\"Hi :)\") // emulate exception throwing\n    \"ok\"\n} // result will be with type String\n
  • Safely without exceptions - almost the same as safely, but this type by default allow to return nullable value (when exception was thrown) instead of just throwing (as with safely):
safelyWithouExceptions {\n    // do something\n} // will returns nullable result type\n
"},{"location":"tgbotapi/logic/exceptions-handling.html#global-exceptions-handling","title":"Global exceptions handling","text":"

The most simple way to configure exceptions handling is to change CoroutineContext when you are creating your CoroutineScope for bot processing:

val bot = telegramBot(\"TOKEN\")\n\nbot.buildBehaviour (\n    scope = scope,\n    defaultExceptionsHandler = {\n        it.printStackTrace()\n    }\n) {\n    // ...\n}\n

OR

val bot = telegramBotWithBehaviour (\n    \"TOKEN\",\n    scope = scope,\n    defaultExceptionsHandler = {\n        it.printStackTrace()\n    }\n) {\n    // ...\n}\n

Here we have used ContextSafelyExceptionHandler class. It will pass default handling of exceptions and will call the block in most cases when something inside of your bot logic has thrown exception.

"},{"location":"tgbotapi/logic/files-handling.html","title":"Files handling","text":"

According to the documentation there are several ways to work with files:

  • By FileId
  • By FileUrl (typealias for the FileId)
  • By some MultipartFile (in Telegram Bot API it is multipart requests)
"},{"location":"tgbotapi/logic/files-handling.html#files-receiving","title":"Files receiving","text":"

There are several cases you may need in your app to work with files:

  • Save FileId (for sending in future)
  • Download some file into memory/file in filesystem
"},{"location":"tgbotapi/logic/files-handling.html#where-to-get-file-id-or-url","title":"Where to get File id or url?","text":"

The most simple way to send some file is to get file id and send it. You may get file id from any message with media. For example, if you have received some Message, you may use asCommonMessage conversation to be able to get its content and then convert it to some content with media. Full code here:

val message: Message;\n\nval fileId = message.asCommonMessage() ?.withContent<MediaContent>() ?.content ?.media ?.fileId;\n

WAT? O.o

In the code above we get some message, safely converted it to CommonMessage with asCommonMessage, then safely took its content via withContent<MediaContent>() ?.content and then just get its media file id.

"},{"location":"tgbotapi/logic/files-handling.html#download-files","title":"Download files","text":"

There are three ways to download files:

  • Download it in memory as ByteArray
  • Take ByteReadChannelAllocator which allow to retrieve ByteReadChannel and do whatever you want with it
  • [JVM Only] Download it directly to file or temporal file
"},{"location":"tgbotapi/logic/files-handling.html#downloading-with-api-extensions","title":"Downloading with API extensions","text":""},{"location":"tgbotapi/logic/files-handling.html#files-jvmandroid","title":"Files (JVM/Android)","text":"
val bot: TelegramBot;\nval fileId: FileId;\nval outputFile: File;\n\nbot.downloadFile(fileId, outputFile)\n

See downloadFile extension docs in the JVM tab to get more available options

There is also way with saving of data into temporal file. That will allow you to do with data whatever you want without high requirements to memory or network connection:

val bot: TelegramBot;\nval fileId: FileId;\n\nval tempFile: File = bot.downloadFileToTemp(fileId)\n

See downloadFileToTemp extension docs to get more available options

"},{"location":"tgbotapi/logic/files-handling.html#byte-read-channel","title":"Byte read channel","text":"
val bot: TelegramBot;\nval fileId: FileId;\n\nval bytes: ByteReadChannelAllocator = bot.downloadFileStream(fileId)\n

See downloadFileStream extension docs to get more available options

"},{"location":"tgbotapi/logic/files-handling.html#byte-read-channel-allocator","title":"Byte read channel allocator","text":"
val bot: TelegramBot;\nval fileId: FileId;\n\nval bytes: ByteReadChannelAllocator = bot.downloadFileStreamAllocator(fileId)\n

See downloadFileStreamAllocator extension docs to get more available options

"},{"location":"tgbotapi/logic/files-handling.html#byte-arrays","title":"Byte arrays","text":"
val bot: TelegramBot;\nval fileId: FileId;\n\nval bytes: ByteArray = bot.downloadFile(fileId)\n

See downloadFile extension docs to get more available options

"},{"location":"tgbotapi/logic/files-handling.html#low-level-or-how-does-it-work","title":"Low level or how does it work?","text":"

You may download file with streams or with downloading into the memory first. On low level you should do several things. They are presented in next snippet:

val bot: TelegramBot;\nval fileId: FileId;\n\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\n\nval downloadedBytes: ByteArray = bot.execute(DownloadFile(pathedFile.filePath))\n

In the snippet above we are getting file PathedFile by its FileId and use it to download file bytes into memory using DownloadFile request.

You may use almost the same way but with byte read channel allocator:

val bot: TelegramBot;\nval fileId: FileId;\n\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\n\nval channelAllocator: ByteReadChannelAllocator = bot.execute(DownloadFileStream(pathedFile.filePath))\n\nval byteReadChannel: ByteReadChannel = channelAllocator()\n

And then you may look into ByteReadChannel docs to get more info about what you can do with that.

Several useful links

  • GetFile
  • PathedFile
  • DownloadFile
  • DownloadFileStream
"},{"location":"tgbotapi/logic/files-handling.html#files-sending","title":"Files sending","text":"

Of course, in most cases you must be sure that file have correct type.

"},{"location":"tgbotapi/logic/files-handling.html#fileid-and-fileurl","title":"FileId and FileUrl","text":"

It is the most simple way to send any media in Telegram, but this way have several restrictions:

  • The FileId which has retrieved for file should not (and probably will not too) equal to the FileId retrieved by some other bot
  • There is a chance that the file id you are using will be expired with time
"},{"location":"tgbotapi/logic/files-handling.html#sending-via-file","title":"Sending via file","text":"

JS Restrictions

Sending via file is accessible from all supported platforms, but there is small note about JS - due to restrictions of work with streams and stream-like data (JS have no native support of files streaming) on this platform all the files will be loaded inside of RAM before the sending to the telegram services.

Sending via file is available throw the MultipartFile. There are several wayt to get it:

  • Simple creating via its constructor: MultipartFile(\"filename.jpg\") { /* here Input allocation */ }
  • Via asMultiparFile extension applicable to any ByteArray, ByteReadChannel, ByteReadChannelAllocator or File (on any platform)

In most cases, sending via files looks like in the next snippet:

val file: File;\n\nbot.sendDocument(chatId, file.asMultipartFile())\n
"},{"location":"tgbotapi/logic/low-level-work-with-bots.html","title":"Low-level work with bots","text":"

The base version of library was done a lot of time ago and just got several additions related to improvements, updates in Telegram Bot API or some requests from our community.

"},{"location":"tgbotapi/logic/low-level-work-with-bots.html#base-things","title":"Base things","text":"

There are several important things in context of this library:

  • RequestsExecutor (also \u201cknown\u201d as TelegramBot)
  • Types
  • Requests

So, in most cases all your request calls with simplified api of this library (like bot.getMe()) will looks like bot.execute(GetMe). Result of these calls is defined in type of any request (for example, for GetMe request the result type is ExtendedBot). As a result, you can avoid any extension api (like special API extensions) and use low level request with full controlling of the whole logic flow.

"},{"location":"tgbotapi/logic/low-level-work-with-bots.html#how-to-handle-updates","title":"How to handle updates","text":"

As was written above, it will require some request:

val updates = bot.execute(GetUpdates())\n

Result type of GetUpdates request is Update. You may find inheritors of this interface in Update kdocs.

"},{"location":"tgbotapi/logic/low-level-work-with-bots.html#what-is-next","title":"What is next?","text":"

As was said above, you may look into our API extensions in case you wish to use more high-level functions instead of bot.execute(SomeRequest()). Besides, it will be very useful to know more about updates retrieving.

"},{"location":"tgbotapi/logic/media-groups.html","title":"Media Groups","text":"

As you know, Telegram have the feature named Media Groups. Media groups have several differences with the common messages:

  • Each media group message contains special media group id
  • Media group may have special caption which will be visible if only the first message of media group contains caption
  • In most cases media groups came with long polling/webhooks in one pack
  • Media groups can be one of three types:
    • Visual (image/video)
    • Documents
    • Playlists (audio)
"},{"location":"tgbotapi/logic/media-groups.html#specific-of-media-groups-in-libraries","title":"Specific of media groups in libraries","text":"

Row updates

In tgbotapi there is no any additional handling of media groups by default and in case you will use simple bot.getUpdates, you will get the list of row updates and media groups will be included in this list as separated messages with MediaGroupPartContent. In that case you may use convertWithMediaGroupUpdates to be able to work with media groups as will be described below

In case you are using standard long polling (one of alternatives is telegramBotWithBehaviourAndLongPolling) or webhooks updates will be converted uner the hood and as a result, you will take media groups as a content in one message:

telegramBotWithBehaviourAndLongPolling(\n  \"token\"\n) {\n  onVisualGallery { // it: CommonMessage<MediaGroupContent<VisualMediaGroupPartContent>>\n    it.content // MediaGroupContent<VisualMediaGroupPartContent>\n    it.content.group // List<MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>>\n    it.content.group.forEach { // it: MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>\n      it.messageId // source message id for current media group part\n      it.sourceMessage // source message for current media group part\n      it.content // VisualMediaGroupPartContent\n      println(it.content) // will print current content part info\n    }\n  }\n}\n

KDocs:

  • onVisualGallery
  • MediaGroupContent
  • VisualMediaGroupPartContent
  • MediaGroupCollectionContent.PartWrapper

In two words, in difference with row Telegram Bot API, you will take media groups in one message instead of messages list.

"},{"location":"tgbotapi/logic/types-conversations.html","title":"Types conversations","text":"

One of the most important topics in context of tgbotapi is types conversations. This library is very strong-typed and a lot of things are based on types hierarchy. Lets look into the hierarchy of classes for the Message in 0.35.8:

As you may see, it is a little bit complex and require several tools for types conversation.

"},{"location":"tgbotapi/logic/types-conversations.html#as","title":"As","text":"

as conversations will return new type in case if it is possible. For example, when you got Message, you may use asContentMessage conversation to get message with content:

val message: Message;\nprintln(message.asContentMessage() ?.content)\n

This code will print null in case when message is not ContentMessage, and content when is.

"},{"location":"tgbotapi/logic/types-conversations.html#require","title":"Require","text":"

require works like as, but instead of returning nullable type, it will always return object with required type OR throw ClassCastException:

val message: Message;\nprintln(message.requireContentMessage().content)\n

This code will throw exception when message is not ContentMessage and print content when is.

"},{"location":"tgbotapi/logic/types-conversations.html#when","title":"When","text":"

when extensions will call passed block when type is correct. For example:

val message: Message;\nmessage.whenContentMessage {\n    println(it.content)\n}\n

Code placed above will print content when message is ContentMessage and do nothing when not

"},{"location":"tgbotapi/logic/updates-with-flows.html","title":"Updates with flows","text":"

Of course, in most cases here we will look up the way of using utils extnsions, but you may read deeper about updates retrieving here.

"},{"location":"tgbotapi/logic/updates-with-flows.html#phylosophy-of-flow-updates-retrieving","title":"Phylosophy of Flow updates retrieving","text":"

In most updates retrieving processes there are two components: UpdatesFiler and its inheritor FlowsUpdatesFilter. It is assumed, that you will do several things in your app to handle updates:

  • Create your UpdatesFilter (for example, with flowsUpdatesFilter factory)
  • Set it up (in case of flowsUpdatesFilter you will set up updates handling in the lambda passed to this factory)
  • Provide updates to this filter with filter#asUpdateReceiver object

Let\u2019s look how it works with the factory above:

// Step 1 - create filter\nval filter = flowsUpdatesFilter {\n  // Step 2 - set up handling. In this case we will print any message from group or user in console\n  messageFlow.onEach {\n    println(it)\n  }.launchIn(someCoroutineScope)\n}\n\n// Step 3 - passing updates to filter\nbot.getUpdates().forEach {\n  filter.asUpdatesReceiver(it)\n}\n
"},{"location":"tgbotapi/logic/updates-with-flows.html#long-polling","title":"Long polling","text":"

Some example with long polling has been described above. But it is more useful to use some factories for it. In this page we will look for simple variant with TelegramBot#longPolling. So, with this function, your handling of updates will looks like:

val bot = telegramBot(\"TOKEN\")\n\nbot.longPolling {\n  messageFlow.onEach {\n    println(it)\n  }.launchIn(someCoroutineScope)\n}.join()\n

This example looks like the example above with three steps, but there are several important things here:

  • You do not manage retrieving of updates by hands
  • .join() will suspend your function \ud83d\ude0a longPolling function returns Job and you may use it to:
  • cancel working of long polling (just call job.cancel())
  • join and wait while the work of longPolling will not be completed (it will works infinity if you will not cancel it anywhere)
  • FlowsUpdatesFilter has been created under the hood of longPolling function
"},{"location":"tgbotapi/logic/updates-with-flows.html#results-and-what-is-next","title":"Results and What is next?","text":"

As a result you can start listen updates and react on it. Next recommended articles:

  • Behaviour Builder as a variant of asynchronous handling of your bot logic
  • FSM variant of Behaviour Builder
"},{"location":"tgbotapi/updates/heroku.html","title":"Heroku","text":"

Preview reading

It is recommended to visit our pages about UpdatesFilters and Webhooks to have more clear understanding about what is happening in this examples page

Heroku is a popular place for bots hosting. In common case you will need to configure webhooks for your server to include getting updates without problems. There are several things related to heroku you should know:

  • Heroku apps by default accessible via https://<app name>.herokuapp.com/
  • Heroku provide one port to be proxied for the link above. You can retrieve number of this port by calling System.getenv(\"PORT\").toInt()
  • Currently (Sat Aug 15 5:04:21 +00 2020) there is only one official server engine for ktor which is correctly working with Heroku: Tomcat server engine

Server configuration alternatives

Here will be presented variants of configuration of webhooks and starting server. You always able to set webhook manualy, create your own ktor server and include webhooks handling in it or create and start server with only webhooks handling. More info you can get on page Webhooks

"},{"location":"tgbotapi/updates/heroku.html#short-example-with-behaviour-builder","title":"Short example with Behaviour Builder","text":"
suspend fun main {\n    // This subroute will be used as random webhook subroute to improve security according to the recommendations of Telegram\n    val subroute = uuid4().toString()\n    // Input/Output coroutines scope more info here: https://kotlinlang.org/docs/coroutines-guide.html\n    val scope = CoroutineScope(Dispatchers.IO)\n    // Here will be automatically created bot and available inside of lambda where you will setup your bot behaviour\n    telegramBotWithBehaviour(\n        // Pass TOKEN inside of your application environment variables\n        System.getenv(\"TOKEN\"),\n        scope = scope\n    ) {\n        // Set up webhooks and start to listen them\n        setWebhookInfoAndStartListenWebhooks(\n            // Automatic env which will be passed by heroku to the app\n            System.getenv(\"PORT\").toInt(),\n            // Server engine. More info here: https://ktor.io/docs/engines.html\n            Tomcat,\n            // Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\n            SetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n            // Just callback which will be called when exceptions will happen inside of webhooks\n            {\n                it.printStackTrace()\n            },\n            // Set up listen requests from outside\n            \"0.0.0.0\",\n            // Set up subroute to listen webhooks to\n            subroute,\n            // BehaviourContext is the CoroutineScope and it is recommended to pass it inside of webhooks server\n            scope = this,\n            // BehaviourContext is the FlowsUpdatesFilter and it is recommended to pass its asUpdateReceiver as a block to retrieve all the updates\n            block = asUpdateReceiver\n        )\n        // Test reaction on each command with reply and text `Got it`\n        onUnhandledCommand {\n            reply(it, \"Got it\")\n        }\n    }\n    // Just potentially infinite await of bot completion\n    scope.coroutineContext.job.join()\n}\n
"},{"location":"tgbotapi/updates/heroku.html#configuration-example-without-behaviour-builder","title":"Configuration example without Behaviour Builder","text":"
// This subroute will be used as random webhook subroute to improve security according to the recommendations of Telegram\nval subroute = uuid4().toString()\nval bot = telegramBot(TOKEN)\nval scope = CoroutineScope(Dispatchers.Default)\n\nval filter = flowsUpdatesFilter {\n  messageFlow.onEach {\n    println(it) // will be printed \n  }.launchIn(scope)\n}\n\nval subroute = UUID.randomUUID().toString() // It will be used as subpath for security target as recommended by https://core.telegram.org/bots/api#setwebhook\n\nval server = bot.setWebhookInfoAndStartListenWebhooks(\n  // Automatic env which will be passed by heroku to the app\n  System.getenv(\"PORT\").toInt(),\n  // Server engine. More info here: https://ktor.io/docs/engines.html\n  Tomcat,\n  // Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\n  SetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n  // Just callback which will be called when exceptions will happen inside of webhooks\n  {\n    it.printStackTrace()\n  },\n  // Set up listen requests from outside\n  \"0.0.0.0\",\n  // Set up subroute to listen webhooks to\n  subroute,\n  scope = scope,\n  block = filter.asUpdateReceiver\n)\n\nserver.environment.connectors.forEach {\n  println(it)\n}\nserver.start(false)\n
"},{"location":"tgbotapi/updates/long-polling.html","title":"Long polling","text":"

Long polling is a technology of getting updates for cases you do not have some dedicated server or you have no opportunity to receive updates via webhooks. More about this you can read in wiki.

"},{"location":"tgbotapi/updates/long-polling.html#related-topics","title":"Related topics","text":"
  • Updates filters
"},{"location":"tgbotapi/updates/long-polling.html#long-polling-in-this-library","title":"Long polling in this library","text":"

There are a lot of ways to include work with long polling:

  • RequestsExecutor#longPollingFlow Is the base way to get all updates cold Flow. Remember, that this flow will not be launched automatically
    • RequestsExecutor#startGettingOfUpdatesByLongPolling Old and almost deprecated way
    • RequestsExecutor#longPolling Works like startGettingOfUpdatesByLongPolling but shorted in a name :)
  • RequestsExecutor#createAccumulatedUpdatesRetrieverFlow Works like longPollingFlow, but flow inside will return only the updates accumulated at the moment of calls (all new updates will not be passed throw this flow)
    • RequestsExecutor#retrieveAccumulatedUpdates Use createAccumulatedUpdatesRetrieverFlow to perform all accumulated updates
    • RequestsExecutor#flushAccumulatedUpdates Works like retrieveAccumulatedUpdates but perform all updates directly in a place of calling
  • By yourself with GetUpdates request or RequestsExecutor#getUpdates extension
"},{"location":"tgbotapi/updates/long-polling.html#longpolling","title":"longPolling","text":"

longPolling is a simple way to start getting updates and work with bot:

val bot = telegramBot(token)\nbot.longPolling(\n  textMessages().subscribe(scope) { // here \"scope\" is a CoroutineScope\n    println(it) // will be printed each update from chats with messages\n  }\n)\n
"},{"location":"tgbotapi/updates/long-polling.html#startgettingofupdatesbylongpolling","title":"startGettingOfUpdatesByLongPolling","text":"

The main aim of startGettingOfUpdatesByLongPolling extension was to provide more simple way to get updates in automatic mode:

val bot = telegramBot(token)\nbot.startGettingOfUpdatesByLongPolling(\n  {\n    println(it) // will be printed each update from chats with messages\n  }\n)\n

The other way is to use the most basic startGettingOfUpdatesByLongPolling extension:

val bot = telegramBot(token)\nbot.startGettingOfUpdatesByLongPolling {\n  println(it) // will be printed each update\n}\n
"},{"location":"tgbotapi/updates/long-polling.html#see-also","title":"See also","text":"
  • Webhooks
  • Updates filters
"},{"location":"tgbotapi/updates/updates-filters.html","title":"Updates filters","text":"

Due to the fact, that anyway you will get updates in one format (Update objects), some time ago was solved to create one point of updates filters for more usefull way of updates handling

"},{"location":"tgbotapi/updates/updates-filters.html#updatesfilter","title":"UpdatesFilter","text":"

UpdatesFilter currently have two properties:

  • asUpdateReceiver - required to represent this filter as common updates receiver which able to get any Update
  • allowedUpdates - required to determine, which updates are usefull for this filter

Anyway, this filter can\u2019t work with updates by itself. For retrieving updates you should pass this filter to some of getting updates functions (long polling or webhooks).

"},{"location":"tgbotapi/updates/updates-filters.html#simpleupdatesfilter","title":"SimpleUpdatesFilter","text":"

SimpleUpdatesFilter is a simple variant of filters. It have a lot of UpdateReceiver properties which can be set up on creating of this object. For example, if you wish to get messages from chats (but not from channels), you can use next snippet:

SimpleUpdatesFilter {\n  println(it)\n}\n
"},{"location":"tgbotapi/updates/updates-filters.html#flowsupdatesfilter","title":"FlowsUpdatesFilter","text":"

A little bit more modern way is to use FlowsUpdatesFilter. It is very powerfull API of Kotlin Coroutines Flows, built-in support of additional extensions for FlowsUpdatesFilter and Flow<...> receivers and opportunity to split one filter for as much receivers as you want. Filter creating example:

val scope = CoroutineScope(Dispatchers.Default)\nflowsUpdatesFilter {\n  messageFlow.onEach {\n    println(it)\n  }.launchIn(scope)\n}\n
"},{"location":"tgbotapi/updates/updates-filters.html#combining-of-flows","title":"Combining of flows","text":"

In cases you need not separate logic for handling of messages from channels and chats there are three ways to combine different flows into one:

  • Standard plus operation and handling of different flows:
flowsUpdatesFilter {\n  (messageFlow + channelPostFlow).onEach {\n    println(it) // will be printed each message update from channels and chats both\n  }.launchIn(scope)\n}\n
  • TelegramBotAPI library support function aggregateFlows:
flowsUpdatesFilter {\n  aggregateFlows(\n    scope,\n    messageFlow,\n    channelPostFlow\n  ).onEach {\n    println(it) // will be printed each message update from channels and chats both\n  }.launchIn(scope)\n}\n
  • FlowsUpdatesFilter extensions:
flowsUpdatesFilter {\n  allSentMessagesFlow.onEach {\n    println(it) // will be printed each message update from channels and chats both\n  }.launchIn(scope)\n}\n
"},{"location":"tgbotapi/updates/updates-filters.html#types-filtering","title":"Types filtering","text":"

FlowsUpdatesFilter have a lot of extensions for messages types filtering:

flowsUpdatesFilter {\n  textMessages(scope).onEach {\n    println(it) // will be printed each message from channels and chats both with content only `TextContent`\n  }.launchIn(scope)\n}\n

The same things were created for media groups:

flowsUpdatesFilter {\n  mediaGroupMessages(scope).onEach {\n    println(it) // will be printed each media group messages list from both channels and chats without filtering of content\n  }.launchIn(scope)\n\n  mediaGroupPhotosMessages(scope).onEach {\n    println(it) // will be printed each media group messages list from both channels and chats with PhotoContent only\n  }.launchIn(scope)\n\n  mediaGroupVideosMessages(scope).onEach {\n    println(it) // will be printed each media group messages list from both channels and chats with VideoContent only\n  }.launchIn(scope)\n}\n

Besides, there is an opportunity to avoid separation on media groups and common messages and receive photos and videos content in one flow:

flowsUpdatesFilter {\n  sentMessagesWithMediaGroups(scope).onEach {\n    println(it) // will be printed each message including each separated media group message from both channels and chats without filtering of content\n  }.launchIn(scope)\n\n  photoMessagesWithMediaGroups(scope).onEach {\n    println(it) // will be printed each message including each separated media group message from both channels and chats with PhotoContent only\n  }.launchIn(scope)\n\n  videoMessagesWithMediaGroups(scope).onEach {\n    println(it) // will be printed each message including each separated media group message from both channels and chats with VideoContent only\n  }.launchIn(scope)\n}\n
"},{"location":"tgbotapi/updates/updates-filters.html#see-also","title":"See also","text":"
  • Long polling
  • Webhooks
"},{"location":"tgbotapi/updates/webhooks.html","title":"Webhooks","text":"

In telegram bot API there is an opportunity to get updates via webhooks. In this case you will be able to retrieve updates without making additional requests. Most of currently available methods for webhooks are working on ktor server for JVM. Currently, next ways are available for using for webhooks:

  • Route#includeWebhookHandlingInRoute for ktor server
  • Route#includeWebhookHandlingInRouteWithFlows
  • startListenWebhooks
  • RequestsExecutor#setWebhookInfoAndStartListenWebhooks
"},{"location":"tgbotapi/updates/webhooks.html#setwebhookinfoandstartlistenwebhooks","title":"setWebhookInfoAndStartListenWebhooks","text":"

It is the most common way to set updates webhooks and start listening of them. Example:

val bot = telegramBot(TOKEN)\n\nval filter = flowsUpdatesFilter {\n  // ...\n}\n\nbot.setWebhookInfoAndStartListenWebhooks(\n  8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\n  CIO, // default ktor server engine. It is recommended to replace it with something like `Netty`. More info about engines here: https://ktor.io/servers/configuration.html\n  SetWebhook(\n    \"address.com/webhook_route\",\n    File(\"/path/to/certificate\").toInputFile(), // certificate file. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n    40, // max allowed updates, by default is null\n    filter.allowedUpdates\n  ),\n  {\n    it.printStackTrace() // optional handling of exceptions\n  },\n  \"0.0.0.0\", // listening host which will be used to bind by server\n  \"subroute\", // Optional subroute, if null - will listen root of address\n  WebhookPrivateKeyConfig( // optional config of private key. It will be installed in server to use TLS with custom certificate. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n    \"/path/to/keystore.jks\",\n    \"KeystorePassword\",\n    \"Keystore key alias name\",\n    \"KeystoreAliasPassword\"\n  ),\n  scope, // Kotlin coroutine scope for internal transforming of media groups\n  filter.asUpdateReceiver\n)\n

If you will use previous example, ktor server will bind and listen url 0.0.0.0:8080/subroute and telegram will send requests to address address.com/webhook_route with custom certificate. Alternative variant will use the other SetWebhook request variant:

SetWebhook(\n  \"address.com/webhook_route\",\n  \"some_file_bot_id\".toInputFile(),\n  40, // max allowed updates, by default is null\n  filter.allowedUpdates\n)\n

As a result, request SetWebhook will be executed and after this server will start its working and handling of updates.

"},{"location":"tgbotapi/updates/webhooks.html#startlistenwebhooks","title":"startListenWebhooks","text":"

This function is working almost exactly like previous example, but this one will not set up webhook info in telegram:

val filter = flowsUpdatesFilter {\n  // ...\n}\n\nstartListenWebhooks(\n  8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\n  CIO, // default ktor server engine. It is recommended to replace it with something like `Netty`. More info about engines here: https://ktor.io/servers/configuration.html\n  {\n    it.printStackTrace() // optional handling of exceptions\n  },\n  \"0.0.0.0\", // listening host which will be used to bind by server\n  \"subroute\", // Optional subroute, if null - will listen root of address\n  WebhookPrivateKeyConfig( // optional config of private key. It will be installed in server to use TLS with custom certificate. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n    \"/path/to/keystore.jks\",\n    \"KeystorePassword\",\n    \"Keystore key alias name\",\n    \"KeystoreAliasPassword\"\n  ),\n  scope, // Kotlin coroutine scope for internal transforming of media groups\n  filter.asUpdateReceiver\n)\n

The result will be the same as in previous example: server will start its working and handling of updates on 0.0.0.0:8080/subroute. The difference here is that in case if this bot must not answer or send some requiests - it will not be necessary to create bot for receiving of updates.

"},{"location":"tgbotapi/updates/webhooks.html#extensions-includewebhookhandlinginroute-and-includewebhookhandlinginroutewithflows","title":"Extensions includeWebhookHandlingInRoute and includeWebhookHandlingInRouteWithFlows","text":"

For these extensions you will need to start your server manualy. In common case it will look like:

val scope = CoroutineScope(Dispatchers.Default)\n\nval filter = flowsUpdatesFilter {\n  // ...\n}\n\nval environment = applicationEngineEnvironment {\n  module {\n    routing {\n      includeWebhookHandlingInRoute(\n        scope,\n        {\n          it.printStackTrace()\n        },\n        filter.asUpdateReceiver\n      )\n    }\n  }\n  connector {\n    host = \"0.0.0.0\"\n    port = 8080\n  }\n}\n\nembeddedServer(CIO, environment).start(true) // will start server and wait its stoping\n

In the example above server will started and binded for listening on 0.0.0.0:8080.

"},{"location":"tgbotapi/updates/webhooks.html#see-also","title":"See also","text":"
  • Updates filters
  • Long polling
"}]} \ No newline at end of file diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000..0030f35 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,108 @@ + + + + https://docs.inmo.dev/index.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/dsls/keyboards.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/dsls/live-location.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/dsls/text.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/introduction/before-any-bot-project.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/introduction/first-bot.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/introduction/including-in-your-project.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/introduction/proxy-setup.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/api-extensions.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/behaviour-builder-with-fsm.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/behaviour-builder.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/exceptions-handling.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/files-handling.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/low-level-work-with-bots.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/media-groups.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/types-conversations.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/logic/updates-with-flows.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/updates/heroku.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/updates/long-polling.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/updates/updates-filters.html + 2023-06-09 + daily + + + https://docs.inmo.dev/tgbotapi/updates/webhooks.html + 2023-06-09 + daily + + \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz new file mode 100644 index 0000000000000000000000000000000000000000..271d97c59f7d93a03dc3d204cf711af49e4b716f GIT binary patch literal 452 zcmV;#0XzO5iwFp)g@a@Q|8r?{Wo=<_E_iKh0M(XDa-1*_hW9?j6ni1!RJrUFgLgSW zasVMUpo~x}dhoctTQg(VEVD`>Z4k{**H_~2rrY;zHm3!A&}h%qx4NlLU?nPT$8+`j z*H89Zy?0M-@+Lq$k=TZFHO1(DaK5f<9dv|QxKN`Xd5{y`1bz?lrhcosrxUH5;I_N2 z)}ZWimOyC~%1~=NW36D}+A7%UDbA+lS@rmm2ysT$JvYzaS@Xi0HCCkJHp`M zZ>NMdd&?gpHyZwh^Y37B>@f$?CU9>mpgX2<8>~3GwouV6PV%#kvpvj!TdX4*&q;%HGug literal 0 HcmV?d00001 diff --git a/tgbotapi/dsls/keyboards.html b/tgbotapi/dsls/keyboards.html new file mode 100644 index 0000000..6511f43 --- /dev/null +++ b/tgbotapi/dsls/keyboards.html @@ -0,0 +1,1011 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Keyboards - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Keyboards

+

In the telegram system there are two types of keyboards:

+ + + + + + + + + + + + + + + + + +
ReplyInline
Reply keyboardInline keyboard
Keyboard for each user in the chatKeyboard linked to the certain message
+

Low-level way to create keyboard looks like in the next snippet:

+
ReplyKeyboardMarkup(
+    matrix {
+        row {
+            add(SimpleKeyboardButton("Simple text"))
+            // ...
+        }
+        // ...
+    }
+)
+
+

In case you wish to create inline keyboard, it will look like the same as for reply keyboard. But there is another way. The next snippet will create the same keyboard as on the screenshots above:

+
// reply keyboard
+replyKeyboard {
+    row {
+        simpleButton("7")
+        simpleButton("8")
+        simpleButton("9")
+        simpleButton("*")
+    }
+    row {
+        simpleButton("4")
+        simpleButton("5")
+        simpleButton("6")
+        simpleButton("/")
+    }
+    row {
+        simpleButton("1")
+        simpleButton("2")
+        simpleButton("3")
+        simpleButton("-")
+    }
+    row {
+        simpleButton("0")
+        simpleButton(".")
+        simpleButton("=")
+        simpleButton("+")
+    }
+}
+
+// inline keyboard
+inlineKeyboard {
+    row {
+        dataButton("Get random music", "random")
+    }
+    row {
+        urlButton("Send music to friends", "https://some.link")
+    }
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/dsls/live-location.html b/tgbotapi/dsls/live-location.html new file mode 100644 index 0000000..5a1ce6f --- /dev/null +++ b/tgbotapi/dsls/live-location.html @@ -0,0 +1,1085 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Live Location - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Live Location

+

Bot API allows you to send live locations and update them during their lifetime. In this library there are several ways to use this API:

+ +

sendLiveLocation

+

In the Bot API there is no independent sendLiveLocation method, instead it is suggested to use sendLocation with setting up live_period. In this library in difference with original Bot API live location is special request. It was required because of in fact live locations and static locations are different types of location info and you as bot developer may interact with them differently.

+

Anyway, in common case the logic looks like:

+ +

startLiveLocation

+

In difference with sendLiveLocation, startLiveLocation using LiveLocationProvider. With this provider you need not to handle chat and message ids and keep some other data for location changes. Instead, you workflow with provider will be next:

+ +

Besides, LiveLocationProvider contains different useful parameters about live location

+

handleLiveLocation

+

This way of live locations handling is based on coroutines Flow and allow you to pass some external Flow with EditLiveLocationInfo. So, workflow:

+
    +
  • Create your own flow of locations. For example:
  • +
+
flow {
+  var i = 0
+  while (isActive) {
+    val newInfo = EditLiveLocationInfo(
+      latitude = i.toDouble(),
+      longitude = i.toDouble(),
+      replyMarkup = flatInlineKeyboard {
+        dataButton("Cancel", "cancel")
+      }
+    )
+    emit(newInfo)
+    i++
+    delay(10000L) // 10 seconds
+  }
+}
+
+
    +
  • In case you needed, create your collector to store the message with live location:
  • +
+
val currentMessageState = MutableStateFlow<ContentMessage<LocationContent>?>(null)
+
+
    +
  • Start handle live location. handleLiveLocation works synchronosly (in current coroutine) and will ends only when your flow will ends. Thats why there are two ways to call it:
  • +
+
handleLiveLocation(
+  it.chat.id,
+  locationsFlow,
+  sentMessageFlow = FlowCollector { currentMessageState.emit(it) }
+)
+// this code will be called after `locationsFlow` will ends
+
+

OR

+
scope.launch {
+  handleLiveLocation(
+    it.chat.id,
+    locationsFlow,
+    sentMessageFlow = FlowCollector { currentMessageState.emit(it) }
+  )
+}
+// this code will be called right after launch will be completed
+
+

See our example to get more detailed sample

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/dsls/text.html b/tgbotapi/dsls/text.html new file mode 100644 index 0000000..b05ab9d --- /dev/null +++ b/tgbotapi/dsls/text.html @@ -0,0 +1,942 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + Text - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Text

+

For the text creating there are several tools. The most simple one is to concatenate several text sources to make list of text sources as a result:

+
val sources = "Regular start of text " + bold("with bold part") + italic("and italic ending")
+
+

But there is a little bit more useful way: entities builder:

+
val items = (0 until 10).map { it.toString() }
+buildEntities(" ") {// optional " " auto separator which will be pasted between text sources
+    +"It is regular start too" + bold("it is bold as well")
+    items.forEachIndexed { i, item ->
+        if (i % 2) {
+            italic(item)
+        } else {
+            strikethrough(item)
+        }
+    }
+}
+
+

In the code above we are creating an items list just for demonstrating that inside of buildEntities body we may use any operations for cunstructing our result list of TextSources. As a result, will be created the list which will looks like in telegram as “It is regular start too it is bold as well 0 ~~1~~ 2 ~~3~~ 4 ~~5~~ 6 ~~7~~ 8 ~~9~~”.

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/introduction/before-any-bot-project.html b/tgbotapi/introduction/before-any-bot-project.html new file mode 100644 index 0000000..bd5e5d3 --- /dev/null +++ b/tgbotapi/introduction/before-any-bot-project.html @@ -0,0 +1,998 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Before any bot project - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+ +
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/introduction/first-bot.html b/tgbotapi/introduction/first-bot.html new file mode 100644 index 0000000..ac89406 --- /dev/null +++ b/tgbotapi/introduction/first-bot.html @@ -0,0 +1,1014 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + First bot - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

First bot

+
+

Examples info

+

+A lot of examples with using of Telegram Bot API you can find in this github repository

+
+

The most simple bot

+

The most simple bot will just print information about itself. All source code you can find in this repository. Our interest here will be concentrated on the next example part:

+
suspend fun main(vararg args: String) {
+  val botToken = args.first()
+  val bot = telegramBot(botToken)
+  println(bot.getMe())
+}
+
+

So, let’s get understanding, about what is going on:

+
    +
  1. suspend fun main(vararg args: String):
      +
    • suspend required for making of requests inside of this function. For more info you can open official documentation for coroutins. In fact, suspend fun main is the same that fun main() = runBlocking {} from examples
    • +
    +
  2. +
  3. val botToken = args.first(): here we are just getting the bot token from first arguments of command line
  4. +
  5. val bot = telegramBot(botToken) : inside of bot will be RequestsExecutor object which will be used for all requests in any project with this library
  6. +
  7. println(bot.getMe()): here happens calling of getMe extension
  8. +
+

As a result, we will see in the command line something like

+
ExtendedBot(id=ChatId(chatId=123456789), username=Username(username=@first_test_ee17e8_bot), firstName=Your bot name, lastName=, canJoinGroups=false, canReadAllGroupMessages=false, supportsInlineQueries=false)
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/introduction/including-in-your-project.html b/tgbotapi/introduction/including-in-your-project.html new file mode 100644 index 0000000..25a8ceb --- /dev/null +++ b/tgbotapi/introduction/including-in-your-project.html @@ -0,0 +1,1381 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Including in your project - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Including in your project

+

There are three projects:

+
    +
  • TelegramBotAPI Core - project with base for all working with Telegram Bot API
  • +
  • TelegramBotAPI API Extensions - extension of TelegramBotAPI with functions for more comfortable work with Telegram Bot API
  • +
  • TelegramBotAPI Utils Extensions - extension of TelegramBotAPI with functions for extending of different things like retrieving of updates
  • +
+
+

TelegramBotAPI

+

Also, there is an aggregator-version tgbotapi, which will automatically include all projects above. It is most recommended version due to the fact that it is including all necessary tools around TelegramBotAPI Core, but it is optionally due to the possible restrictions on the result methods count (for android) or bundle size

+
+
+

Examples

+

You can find full examples info in this repository. In this repository there full codes which are working in normal situation. Currently, there is only one exception when these examples could work incorrectly: you are living in the location where Telegram Bot API is unavailable. For solving this problem you can read Proxy setup part

+
+

Notice about repository

+

To use this library, you will need to include Maven Central repository in your project

+
build.gradle
+
mavenCentral()
+
+
pom.xml
+
<repository>
+  <id>central</id>
+  <name>mavenCentral</name>
+  <url>https://repo1.maven.org/maven2</url>
+</repository>
+
+
Dev channel
+

Besides, there is developer versions repo. To use it in your project, add the repo in repositories section:

+
Gradle + + +
maven {
+    url "https://git.inmo.dev/api/packages/InsanusMokrassar/maven"
+}
+
+ + +
+ +
Maven + + +
<repository>
+  <id>dev.inmo</id>
+  <name>InmoDev</name>
+  <url>https://git.inmo.dev/api/packages/InsanusMokrassar/maven</url>
+</repository>
+
+ + +
+ +

TelegramBotAPI

+

As tgbotapi_version variable in next snippets will be used variable with next last published version:

+

Maven Central

+
build.gradle
+
implementation "dev.inmo:tgbotapi:$tgbotapi_version"
+
+
pom.xml
+
<dependency>
+    <groupId>dev.inmo</groupId>
+    <artifactId>tgbotapi</artifactId>
+    <version>${tgbotapi_version}</version>
+</dependency>
+
+

TelegramBotAPI Core

+

As tgbotapi_version variable in next snippets will be used variable with next last published version:

+

Maven Central

+
build.gradle
+
implementation "dev.inmo:tgbotapi.core:$tgbotapi_version"
+
+
pom.xml
+
<dependency>
+    <groupId>dev.inmo</groupId>
+    <artifactId>tgbotapi.core</artifactId>
+    <version>${tgbotapi_version}</version>
+</dependency>
+
+

TelegramBotAPI API Extensions

+

As tgbotapi_version variable in next snippets will be used variable with next last published version:

+

Maven Central

+
build.gradle
+
implementation "dev.inmo:tgbotapi.api:$tgbotapi_version"
+
+
pom.xml
+
<dependency>
+    <groupId>dev.inmo</groupId>
+    <artifactId>tgbotapi.api</artifactId>
+    <version>${tgbotapi_version}</version>
+</dependency>
+
+

TelegramBotAPI Utils Extensions

+

As tgbotapi_version variable in next snippets will be used variable with next last published version:

+

Maven Central

+
build.gradle
+
implementation "dev.inmo:tgbotapi.utils:$tgbotapi_version"
+
+
pom.xml
+
<dependency>
+    <groupId>dev.inmo</groupId>
+    <artifactId>tgbotapi.utils</artifactId>
+    <version>${tgbotapi_version}</version>
+</dependency>
+
+

Next steps

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/introduction/proxy-setup.html b/tgbotapi/introduction/proxy-setup.html new file mode 100644 index 0000000..be46849 --- /dev/null +++ b/tgbotapi/introduction/proxy-setup.html @@ -0,0 +1,1051 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Proxy setup - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Proxy setup

+

In some locations Telegram Bots API urls will be unavailable. In this case all examples will just throw exception like:

+
Exception in thread "main" java.net.ConnectException: Connection refused
+    at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
+    at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
+    at io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:36)
+    at io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)
+    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
+    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
+    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
+    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
+    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
+    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
+
+Process finished with exit code 1
+
+

There are several ways to solve this problem:

+
    +
  • Built-in proxy config (will require some socks or http proxy server)
  • +
  • System-configured VPN or proxy
  • +
  • Your own Bot API Server
  • +
+

Using Ktor Client built-in proxy

+

First of all, you will need to use one more library:

+

build.gradle:

+
implementation "io.ktor:ktor-client-okhttp:2.0.1"
+
+
+

Dependency note

+

+In the snippet above was used version 2.0.1 which is actual for TelegramBotAPI at the moment of filling this documentation (May 22 2022, TelegramBotAPI version 2.0.0) and you can update version of this dependency in case if it is outdated.

+
+

For configuring proxy for your bot inside your program, you can use next snippet:

+
val botToken = "HERE MUST BE YOUR TOKEN"
+val bot = telegramBot(botToken) {
+  ktorClientEngineFactory = OkHttp
+  proxy = ProxyBuilder.socks("127.0.0.1", 1080)
+}
+
+

Explanation line by line:

+
    +
  1. val botToken = "HERE MUST BE YOUR TOKEN" - here we are just creating variable botToken
  2. +
  3. val bot = telegramBot(botToken) { - start creating bot
  4. +
  5. ktorClientEngineFactory = OkHttp - setting up engine factory of our bot. On the time of documentation filling, OkHttp is one of the engines in Ktor system which supports socks proxy. More you can read on Ktor site in subparts about engines and proxy
  6. +
  7. proxy = ProxyBuilder.socks("127.0.0.1", 1080) - here we are setting up our proxy. Here was used local server which (as assumed) will connect to server like shadowsocks
  8. +
+

Next steps

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/api-extensions.html b/tgbotapi/logic/api-extensions.html new file mode 100644 index 0000000..375d087 --- /dev/null +++ b/tgbotapi/logic/api-extensions.html @@ -0,0 +1,1056 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + API Extensions - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

API Extensions

+

API extensions is a module which you may include in your project in addition to core part. In most cases this module will allow just use syntax like bot.getUpdates() instead of bot.execute(GetUpdates()), but there are several other things you will achieve with that syntax.

+

Bot builder

+

This functionality allow you to build bot in more unified and comfortable way than standard creating with telegramBot function

+
buildBot(
+    "TOKEN"
+) {
+  proxy = ProxyBuilder.socks(host = "127.0.0.1", port = 4001) // just an example, more info on https://ktor.io/docs/proxy.html
+  ktorClientConfig = {
+    // configuring of ktor client
+  }
+  ktorClientEngineFactory = {
+   // configuring of ktor client engine 
+  }
+}
+
+

Downloading of files

+

In standard library requests there are no way to download some file retrieved in updates or after requests. You may use syntax like bot.downloadFile(file) where file is TelegramMediaFile from telegram, FileId or even PathedFile from GetFile request (sources).

+

Live location

+

By default, you should handle updates of Live location by your code. But with extension bot#startLiveLocation you may provide all necessary startup parameters and handle updates with just calling updateLocation for retrieved LiveLocationProvider.

+

What is next?

+

There are several things you may read next:

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/behaviour-builder-with-fsm.html b/tgbotapi/logic/behaviour-builder-with-fsm.html new file mode 100644 index 0000000..e96ce69 --- /dev/null +++ b/tgbotapi/logic/behaviour-builder-with-fsm.html @@ -0,0 +1,1056 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Behaviour Builder with FSM - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Behaviour Builder with FSM

+

Behaviour builder with FSM is based on the MicroUtils FSM. There are several important things in FSM:

+
    +
  • State - any object which implements State interface
  • +
  • StateHandler (or CheckableHandlerHolder) - the handler of states
  • +
  • StatesMachine - some machine which work with states and handlers
  • +
  • StatesManager - simple manager that will solve which states to save and notify about states changes via its flows
  • +
+

StatesMachine have two methods:

+
    +
  • start which will start work of machine
  • +
  • startChain which will add new state for handling
  • +
+

The most based way to create StatesMachine and register StateHandlers looks like in the next snippet:

+
buildFSM<TrafficLightState> {
+    strictlyOn<SomeState> {
+        // state handling
+    }
+}.start(CoroutineScope(...)).join()
+
+
+

Full example

+

+You may find full example of FSM usage in the tests of FSM in MicroUtils

+
+

So, you must do next steps before you will launch your bot with FSM:

+
    +
  • Create your states. Remember that you may plan to save them, so it is likely you will need to serialize it there
  • +
  • Create your handlers for your states. In most cases it is useful to use CheckableHandlerHolder if you want to use standard states machine
  • +
  • Solve which states managers to use (the most simple one is the DefaultStatesManager with InMemoryDefaultStatesManager)
  • +
+

Bot with FSM

+

There are several extensions for TelegramBot to create your bot with FSM:

+ +

All of them will take as an callback some object with type CustomBehaviourContextReceiver and will looks like in the next snippet:

+
telegramBotWithBehaviourAndFSMAndStartLongPolling<YourStateType>("BOT_TOKEN") {
+    // here you may use any operations from BehaviourBuilder
+    // here you may use any operations from BehaviourContextWithFSMBuilder like strictlyOn and others
+}
+
+

Examples

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/behaviour-builder.html b/tgbotapi/logic/behaviour-builder.html new file mode 100644 index 0000000..1c5c135 --- /dev/null +++ b/tgbotapi/logic/behaviour-builder.html @@ -0,0 +1,1070 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Behaviour Builder - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Behaviour Builder

+

In the previous pages about updates handling and was mentioned that currently in the most cases you should use Flows. So, there is an improvement for that system which hide direct work with flows and allow you to create more declarative logic of your bot.

+

Main parts of Behaviour Builder

+

There are several things you should know for better understanding of behaviour builder:

+
    +
  • BehaviourContext - it is the thing which contains all necessary tools for working with bots
  • +
  • Triggers - on* extensions for BehaviourContext which allow you to create reaction on some update
  • +
  • Expectations (or waiters) - wait* extensions which you may use in buildBehaviour function, but it is recommended to use it in bodies of triggers
  • +
+

Initialization

+

As was said above, there is buildBehaviour function which allow you set up your bot logic. Let’s see an example:

+
val bot = telegramBot("TOKEN")
+
+bot.buildBehaviour {
+  onCommand("start") { // creating of trigger
+    val message = it
+    val content = message.content
+
+    reply(message, "Ok, send me one photo") // send text message with replying on incoming message
+
+    val photoContent = waitPhoto().first() // waitPhoto will return List, so, get first element
+
+    val photo = downloadFile(photoContent) // ByteArray of photo
+
+    // some logic with saving of photos
+  }
+}
+
+

Filters

+

In most cases there are opportunity to filter some of messages before starting of main logic. Let’s look at this using the example above:

+
val bot = telegramBot("TOKEN")
+
+bot.buildBehaviour {
+  onCommand(
+    "start",
+    initialFilter = {
+      it.content.textSources.size == 1 // make sure that user has sent /start without any additions
+    }
+  ) {
+    // ...
+  }
+}
+
+

OR

+
val bot = telegramBot("TOKEN")
+
+bot.buildBehaviour {
+  onCommand(
+    "start",
+    requireOnlyCommandInMessage = true // it is default, but you can overwrite it with `requireOnlyCommandInMessage = false`
+  ) {
+    // ...
+  }
+}
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/exceptions-handling.html b/tgbotapi/logic/exceptions-handling.html new file mode 100644 index 0000000..a33c535 --- /dev/null +++ b/tgbotapi/logic/exceptions-handling.html @@ -0,0 +1,1133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Exceptions handling - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Exceptions handling

+

Unfortunatelly, exceptions handling in this library is a bit difficult in some places, but that have at least two reasons: flexibility and usability.

+

“In place” handling

+

In case you know, where exceptions are happening, you may use several tools for exceptions catching:

+
    +
  • Catching with result
  • +
  • Catching with callback
  • +
+

Catching with result

+

If you prefer to receive Result objects instead of some weird callbacks, you may use the next syntax:

+
safelyWithResult {
+    // do something
+}.onSuccess { // will be called if everything is right
+    // handle success
+}.onFailure { // will be called if something went wrong
+    // handle error
+    it.printStackTrace()
+}.getOrThrow() // will return value or throw exception
+
+

Catching with callback

+

Also there is more simple (in some cases) way to handle exceptions with callbacks:

+
safely(
+  {
+      // handle error
+      it.printStackTrace()
+      null // return value
+  }
+) {
+    // do something
+}
+
+

Bonus: different types of handling

+

There are two types of handling:

+
    +
  • Just safely - when you are using something to obviously retrieve value or throw exception. When handling callback has been skipped, it will throw exception by default. For example:
  • +
+
safely(
+    {
+        it.printStackTrace()
+        "error"
+    }
+) {
+    error("Hi :)") // emulate exception throwing
+    "ok"
+} // result will be with type String
+
+
    +
  • Safely without exceptions - almost the same as safely, but this type by default allow to return nullable value (when exception was thrown) instead of just throwing (as with safely):
  • +
+
safelyWithouExceptions {
+    // do something
+} // will returns nullable result type
+
+

Global exceptions handling

+

The most simple way to configure exceptions handling is to change CoroutineContext when you are creating your CoroutineScope for bot processing:

+
val bot = telegramBot("TOKEN")
+
+bot.buildBehaviour (
+    scope = scope,
+    defaultExceptionsHandler = {
+        it.printStackTrace()
+    }
+) {
+    // ...
+}
+
+

OR

+
val bot = telegramBotWithBehaviour (
+    "TOKEN",
+    scope = scope,
+    defaultExceptionsHandler = {
+        it.printStackTrace()
+    }
+) {
+    // ...
+}
+
+

Here we have used ContextSafelyExceptionHandler class. It will pass default handling of exceptions and will call the block in most cases when something inside of your bot logic has thrown exception.

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/files-handling.html b/tgbotapi/logic/files-handling.html new file mode 100644 index 0000000..32a7394 --- /dev/null +++ b/tgbotapi/logic/files-handling.html @@ -0,0 +1,1060 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Files handling - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Files handling

+

According to the documentation there are several ways to work with files:

+ +

Files receiving

+

There are several cases you may need in your app to work with files:

+
    +
  • Save FileId (for sending in future)
  • +
  • Download some file into memory/file in filesystem
  • +
+

Where to get File id or url?

+

The most simple way to send some file is to get file id and send it. You may get file id from any message with media. For example, if you have received some Message, you may use asCommonMessage conversation to be able to get its content and then convert it to some content with media. Full code here:

+
val message: Message;
+
+val fileId = message.asCommonMessage() ?.withContent<MediaContent>() ?.content ?.media ?.fileId;
+
+

WAT? O.o

+

In the code above we get some message, safely converted it to CommonMessage with asCommonMessage, then safely took its content via withContent<MediaContent>() ?.content and then just get its media file id.

+

Download files

+

There are three ways to download files:

+
    +
  • Download it in memory as ByteArray
  • +
  • Take ByteReadChannelAllocator which allow to retrieve ByteReadChannel and do whatever you want with it
  • +
  • [JVM Only] Download it directly to file or temporal file
  • +
+

Downloading with API extensions

+

Files (JVM/Android)

+
val bot: TelegramBot;
+val fileId: FileId;
+val outputFile: File;
+
+bot.downloadFile(fileId, outputFile)
+
+

See downloadFile extension docs in the JVM tab to get more available options

+

There is also way with saving of data into temporal file. That will allow you to do with data whatever you want without high requirements to memory or network connection:

+
val bot: TelegramBot;
+val fileId: FileId;
+
+val tempFile: File = bot.downloadFileToTemp(fileId)
+
+

See downloadFileToTemp extension docs to get more available options

+

Byte read channel

+
val bot: TelegramBot;
+val fileId: FileId;
+
+val bytes: ByteReadChannelAllocator = bot.downloadFileStream(fileId)
+
+

See downloadFileStream extension docs to get more available options

+

Byte read channel allocator

+
val bot: TelegramBot;
+val fileId: FileId;
+
+val bytes: ByteReadChannelAllocator = bot.downloadFileStreamAllocator(fileId)
+
+

See downloadFileStreamAllocator extension docs to get more available options

+

Byte arrays

+
val bot: TelegramBot;
+val fileId: FileId;
+
+val bytes: ByteArray = bot.downloadFile(fileId)
+
+

See downloadFile extension docs to get more available options

+

Low level or how does it work?

+

You may download file with streams or with downloading into the memory first. On low level you should do several things. They are presented in next snippet:

+
val bot: TelegramBot;
+val fileId: FileId;
+
+val pathedFile: PathedFile = bot.execute(GetFile(fileId))
+
+val downloadedBytes: ByteArray = bot.execute(DownloadFile(pathedFile.filePath))
+
+

In the snippet above we are getting file PathedFile by its FileId and use it to download file bytes into memory using DownloadFile request.

+

You may use almost the same way but with byte read channel allocator:

+
val bot: TelegramBot;
+val fileId: FileId;
+
+val pathedFile: PathedFile = bot.execute(GetFile(fileId))
+
+val channelAllocator: ByteReadChannelAllocator = bot.execute(DownloadFileStream(pathedFile.filePath))
+
+val byteReadChannel: ByteReadChannel = channelAllocator()
+
+

And then you may look into ByteReadChannel docs to get more info about what you can do with that.

+
+

Several useful links

+ +
+

Files sending

+

Of course, in most cases you must be sure that file have correct type.

+

FileId and FileUrl

+

It is the most simple way to send any media in Telegram, but this way have several restrictions:

+
    +
  • The FileId which has retrieved for file should not (and probably will not too) equal to the FileId retrieved by some other bot
  • +
  • There is a chance that the file id you are using will be expired with time
  • +
+

Sending via file

+
+

JS Restrictions

+

+Sending via file is accessible from all supported platforms, but there is small note about JS - due to restrictions of work with streams and stream-like data (JS have no native support of files streaming) on this platform all the files will be loaded inside of RAM before the sending to the telegram services.

+
+

Sending via file is available throw the MultipartFile. There are several wayt to get it:

+
    +
  • Simple creating via its constructor: MultipartFile("filename.jpg") { /* here Input allocation */ }
  • +
  • Via asMultiparFile extension applicable to any ByteArray, ByteReadChannel, ByteReadChannelAllocator or File (on any platform)
  • +
+

In most cases, sending via files looks like in the next snippet:

+
val file: File;
+
+bot.sendDocument(chatId, file.asMultipartFile())
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/low-level-work-with-bots.html b/tgbotapi/logic/low-level-work-with-bots.html new file mode 100644 index 0000000..61223eb --- /dev/null +++ b/tgbotapi/logic/low-level-work-with-bots.html @@ -0,0 +1,958 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Low-level work with bots - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Low-level work with bots

+

The base version of library was done a lot of time ago and just got several additions related to improvements, updates in Telegram Bot API or some requests from our community.

+

Base things

+

There are several important things in context of this library:

+ +

So, in most cases all your request calls with simplified api of this library (like bot.getMe()) will looks like bot.execute(GetMe). Result of these calls is defined in type of any request (for example, for GetMe request the result type is ExtendedBot). As a result, you can avoid any extension api (like special API extensions) and use low level request with full controlling of the whole logic flow.

+

How to handle updates

+

As was written above, it will require some request:

+
val updates = bot.execute(GetUpdates())
+
+

Result type of GetUpdates request is Update. You may find inheritors of this interface in Update kdocs.

+

What is next?

+

As was said above, you may look into our API extensions in case you wish to use more high-level functions instead of bot.execute(SomeRequest()). Besides, it will be very useful to know more about updates retrieving.

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/media-groups.html b/tgbotapi/logic/media-groups.html new file mode 100644 index 0000000..5d49475 --- /dev/null +++ b/tgbotapi/logic/media-groups.html @@ -0,0 +1,1030 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Media Groups - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Media Groups

+

As you know, Telegram have the feature named Media Groups. Media groups have several differences with the common messages:

+
    +
  • Each media group message contains special media group id
  • +
  • Media group may have special caption which will be visible if only the first message of media group contains caption
  • +
  • In most cases media groups came with long polling/webhooks in one pack
  • +
  • Media groups can be one of three types:
      +
    • Visual (image/video)
    • +
    • Documents
    • +
    • Playlists (audio)
    • +
    +
  • +
+

Specific of media groups in libraries

+
+

Row updates

+

+In tgbotapi there is no any additional handling of media groups by default and in case you will use simple bot.getUpdates, you will get the list of row updates and media groups will be included in this list as separated messages with MediaGroupPartContent. In that case you may use convertWithMediaGroupUpdates to be able to work with media groups as will be described below

+
+

In case you are using standard long polling (one of alternatives is telegramBotWithBehaviourAndLongPolling) or webhooks updates will be converted uner the hood and as a result, you will take media groups as a content in one message:

+
telegramBotWithBehaviourAndLongPolling(
+  "token"
+) {
+  onVisualGallery { // it: CommonMessage<MediaGroupContent<VisualMediaGroupPartContent>>
+    it.content // MediaGroupContent<VisualMediaGroupPartContent>
+    it.content.group // List<MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>>
+    it.content.group.forEach { // it: MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>
+      it.messageId // source message id for current media group part
+      it.sourceMessage // source message for current media group part
+      it.content // VisualMediaGroupPartContent
+      println(it.content) // will print current content part info
+    }
+  }
+}
+
+

KDocs:

+ +

In two words, in difference with row Telegram Bot API, you will take media groups in one message instead of messages list.

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/types-conversations.html b/tgbotapi/logic/types-conversations.html new file mode 100644 index 0000000..10b50f7 --- /dev/null +++ b/tgbotapi/logic/types-conversations.html @@ -0,0 +1,1038 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Types conversations - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Types conversations

+

One of the most important topics in context of tgbotapi is types conversations. This library is very strong-typed and a lot of things are based on types hierarchy. Lets look into the hierarchy of classes for the Message in 0.35.8: Message Diagram.png

+

As you may see, it is a little bit complex and require several tools for types conversation.

+

As

+

as conversations will return new type in case if it is possible. For example, when you got Message, you may use asContentMessage conversation to get message with content:

+
val message: Message;
+println(message.asContentMessage() ?.content)
+
+

This code will print null in case when message is not ContentMessage, and content when is.

+

Require

+

require works like as, but instead of returning nullable type, it will always return object with required type OR throw ClassCastException:

+
val message: Message;
+println(message.requireContentMessage().content)
+
+

This code will throw exception when message is not ContentMessage and print content when is.

+

When

+

when extensions will call passed block when type is correct. For example:

+
val message: Message;
+message.whenContentMessage {
+    println(it.content)
+}
+
+

Code placed above will print content when message is ContentMessage and do nothing when not

+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/updates-with-flows.html b/tgbotapi/logic/updates-with-flows.html new file mode 100644 index 0000000..f3b8200 --- /dev/null +++ b/tgbotapi/logic/updates-with-flows.html @@ -0,0 +1,1062 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Updates with flows - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Updates with flows

+

Of course, in most cases here we will look up the way of using utils extnsions, but you may read deeper about updates retrieving here.

+

Phylosophy of Flow updates retrieving

+

In most updates retrieving processes there are two components: UpdatesFiler and its inheritor FlowsUpdatesFilter. It is assumed, that you will do several things in your app to handle updates:

+
    +
  • Create your UpdatesFilter (for example, with flowsUpdatesFilter factory)
  • +
  • Set it up (in case of flowsUpdatesFilter you will set up updates handling in the lambda passed to this factory)
  • +
  • Provide updates to this filter with filter#asUpdateReceiver object
  • +
+

Let’s look how it works with the factory above:

+
// Step 1 - create filter
+val filter = flowsUpdatesFilter {
+  // Step 2 - set up handling. In this case we will print any message from group or user in console
+  messageFlow.onEach {
+    println(it)
+  }.launchIn(someCoroutineScope)
+}
+
+// Step 3 - passing updates to filter
+bot.getUpdates().forEach {
+  filter.asUpdatesReceiver(it)
+}
+
+

Long polling

+

Some example with long polling has been described above. But it is more useful to use some factories for it. In this page we will look for simple variant with TelegramBot#longPolling. So, with this function, your handling of updates will looks like:

+
val bot = telegramBot("TOKEN")
+
+bot.longPolling {
+  messageFlow.onEach {
+    println(it)
+  }.launchIn(someCoroutineScope)
+}.join()
+
+

This example looks like the example above with three steps, but there are several important things here:

+
    +
  • You do not manage retrieving of updates by hands
  • +
  • .join() will suspend your function 😊 longPolling function returns Job and you may use it to:
  • +
  • cancel working of long polling (just call job.cancel())
  • +
  • join and wait while the work of longPolling will not be completed (it will works infinity if you will not cancel it anywhere)
  • +
  • FlowsUpdatesFilter has been created under the hood of longPolling function
  • +
+

Results and What is next?

+

As a result you can start listen updates and react on it. Next recommended articles:

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/updates/heroku.html b/tgbotapi/updates/heroku.html new file mode 100644 index 0000000..7b8183e --- /dev/null +++ b/tgbotapi/updates/heroku.html @@ -0,0 +1,1097 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Heroku - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Heroku

+
+

Preview reading

+

It is recommended to visit our pages about UpdatesFilters and Webhooks to have more clear understanding about what is happening in this examples page

+
+

Heroku is a popular place for bots hosting. In common case you will need to configure webhooks for your server to include getting updates without problems. There are several things related to heroku you should know:

+
    +
  • Heroku apps by default accessible via https://<app name>.herokuapp.com/
  • +
  • Heroku provide one port to be proxied for the link above. You can retrieve number of this port by calling System.getenv("PORT").toInt()
  • +
  • Currently (Sat Aug 15 5:04:21 +00 2020) there is only one official server engine for ktor which is correctly working with Heroku: Tomcat server engine
  • +
+
+

Server configuration alternatives

+

+Here will be presented variants of configuration of webhooks and starting server. You always able to set webhook manualy, create your own ktor server and include webhooks handling in it or create and start server with only webhooks handling. More info you can get on page Webhooks

+
+

Short example with Behaviour Builder

+
suspend fun main {
+    // This subroute will be used as random webhook subroute to improve security according to the recommendations of Telegram
+    val subroute = uuid4().toString()
+    // Input/Output coroutines scope more info here: https://kotlinlang.org/docs/coroutines-guide.html
+    val scope = CoroutineScope(Dispatchers.IO)
+    // Here will be automatically created bot and available inside of lambda where you will setup your bot behaviour
+    telegramBotWithBehaviour(
+        // Pass TOKEN inside of your application environment variables
+        System.getenv("TOKEN"),
+        scope = scope
+    ) {
+        // Set up webhooks and start to listen them
+        setWebhookInfoAndStartListenWebhooks(
+            // Automatic env which will be passed by heroku to the app
+            System.getenv("PORT").toInt(),
+            // Server engine. More info here: https://ktor.io/docs/engines.html
+            Tomcat,
+            // Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com
+            SetWebhook("${System.getenv("URL").removeSuffix("/")}/$subroute"),
+            // Just callback which will be called when exceptions will happen inside of webhooks
+            {
+                it.printStackTrace()
+            },
+            // Set up listen requests from outside
+            "0.0.0.0",
+            // Set up subroute to listen webhooks to
+            subroute,
+            // BehaviourContext is the CoroutineScope and it is recommended to pass it inside of webhooks server
+            scope = this,
+            // BehaviourContext is the FlowsUpdatesFilter and it is recommended to pass its asUpdateReceiver as a block to retrieve all the updates
+            block = asUpdateReceiver
+        )
+        // Test reaction on each command with reply and text `Got it`
+        onUnhandledCommand {
+            reply(it, "Got it")
+        }
+    }
+    // Just potentially infinite await of bot completion
+    scope.coroutineContext.job.join()
+}
+
+

Configuration example without Behaviour Builder

+
// This subroute will be used as random webhook subroute to improve security according to the recommendations of Telegram
+val subroute = uuid4().toString()
+val bot = telegramBot(TOKEN)
+val scope = CoroutineScope(Dispatchers.Default)
+
+val filter = flowsUpdatesFilter {
+  messageFlow.onEach {
+    println(it) // will be printed 
+  }.launchIn(scope)
+}
+
+val subroute = UUID.randomUUID().toString() // It will be used as subpath for security target as recommended by https://core.telegram.org/bots/api#setwebhook
+
+val server = bot.setWebhookInfoAndStartListenWebhooks(
+  // Automatic env which will be passed by heroku to the app
+  System.getenv("PORT").toInt(),
+  // Server engine. More info here: https://ktor.io/docs/engines.html
+  Tomcat,
+  // Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com
+  SetWebhook("${System.getenv("URL").removeSuffix("/")}/$subroute"),
+  // Just callback which will be called when exceptions will happen inside of webhooks
+  {
+    it.printStackTrace()
+  },
+  // Set up listen requests from outside
+  "0.0.0.0",
+  // Set up subroute to listen webhooks to
+  subroute,
+  scope = scope,
+  block = filter.asUpdateReceiver
+)
+
+server.environment.connectors.forEach {
+  println(it)
+}
+server.start(false)
+
+ + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/updates/long-polling.html b/tgbotapi/updates/long-polling.html new file mode 100644 index 0000000..f68f24c --- /dev/null +++ b/tgbotapi/updates/long-polling.html @@ -0,0 +1,1105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Long polling - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Long polling

+

Long polling is a technology of getting updates for cases you do not have some dedicated server or you have no opportunity to receive updates via webhooks. More about this you can read in wiki.

+ + +

Long polling in this library

+

There are a lot of ways to include work with long polling:

+
    +
  • RequestsExecutor#longPollingFlow Is the base way to get all updates cold Flow. Remember, that this flow will not be launched automatically
      +
    • RequestsExecutor#startGettingOfUpdatesByLongPolling Old and almost deprecated way
    • +
    • RequestsExecutor#longPolling Works like startGettingOfUpdatesByLongPolling but shorted in a name :)
    • +
    +
  • +
  • RequestsExecutor#createAccumulatedUpdatesRetrieverFlow Works like longPollingFlow, but flow inside will return only the updates accumulated at the moment of calls (all new updates will not be passed throw this flow)
      +
    • RequestsExecutor#retrieveAccumulatedUpdates Use createAccumulatedUpdatesRetrieverFlow to perform all accumulated updates
    • +
    • RequestsExecutor#flushAccumulatedUpdates Works like retrieveAccumulatedUpdates but perform all updates directly in a place of calling
    • +
    +
  • +
  • By yourself with GetUpdates request or RequestsExecutor#getUpdates extension
  • +
+

longPolling

+

longPolling is a simple way to start getting updates and work with bot:

+
val bot = telegramBot(token)
+bot.longPolling(
+  textMessages().subscribe(scope) { // here "scope" is a CoroutineScope
+    println(it) // will be printed each update from chats with messages
+  }
+)
+
+

startGettingOfUpdatesByLongPolling

+

The main aim of startGettingOfUpdatesByLongPolling extension was to provide more simple way to get updates in automatic mode:

+
val bot = telegramBot(token)
+bot.startGettingOfUpdatesByLongPolling(
+  {
+    println(it) // will be printed each update from chats with messages
+  }
+)
+
+

The other way is to use the most basic startGettingOfUpdatesByLongPolling extension:

+
val bot = telegramBot(token)
+bot.startGettingOfUpdatesByLongPolling {
+  println(it) // will be printed each update
+}
+
+

See also

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/updates/updates-filters.html b/tgbotapi/updates/updates-filters.html new file mode 100644 index 0000000..4979e22 --- /dev/null +++ b/tgbotapi/updates/updates-filters.html @@ -0,0 +1,1181 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Updates filters - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Updates filters

+

Due to the fact, that anyway you will get updates in one format (Update objects), some time ago was solved to create one point of updates filters for more usefull way of updates handling

+

UpdatesFilter

+

UpdatesFilter currently have two properties:

+
    +
  • asUpdateReceiver - required to represent this filter as common updates receiver which able to get any Update
  • +
  • allowedUpdates - required to determine, which updates are usefull for this filter
  • +
+

Anyway, this filter can’t work with updates by itself. For retrieving updates you should pass this filter to some of getting updates functions (long polling or webhooks).

+

SimpleUpdatesFilter

+

SimpleUpdatesFilter is a simple variant of filters. It have a lot of UpdateReceiver properties which can be set up on creating of this object. For example, if you wish to get messages from chats (but not from channels), you can use next snippet:

+
SimpleUpdatesFilter {
+  println(it)
+}
+
+

FlowsUpdatesFilter

+

A little bit more modern way is to use FlowsUpdatesFilter. It is very powerfull API of Kotlin Coroutines Flows, built-in support of additional extensions for FlowsUpdatesFilter and Flow<...> receivers and opportunity to split one filter for as much receivers as you want. Filter creating example:

+
val scope = CoroutineScope(Dispatchers.Default)
+flowsUpdatesFilter {
+  messageFlow.onEach {
+    println(it)
+  }.launchIn(scope)
+}
+
+

Combining of flows

+

In cases you need not separate logic for handling of messages from channels and chats there are three ways to combine different flows into one:

+
    +
  • Standard plus operation and handling of different flows:
  • +
+
flowsUpdatesFilter {
+  (messageFlow + channelPostFlow).onEach {
+    println(it) // will be printed each message update from channels and chats both
+  }.launchIn(scope)
+}
+
+
    +
  • TelegramBotAPI library support function aggregateFlows:
  • +
+
flowsUpdatesFilter {
+  aggregateFlows(
+    scope,
+    messageFlow,
+    channelPostFlow
+  ).onEach {
+    println(it) // will be printed each message update from channels and chats both
+  }.launchIn(scope)
+}
+
+
    +
  • FlowsUpdatesFilter extensions:
  • +
+
flowsUpdatesFilter {
+  allSentMessagesFlow.onEach {
+    println(it) // will be printed each message update from channels and chats both
+  }.launchIn(scope)
+}
+
+

Types filtering

+

FlowsUpdatesFilter have a lot of extensions for messages types filtering:

+
flowsUpdatesFilter {
+  textMessages(scope).onEach {
+    println(it) // will be printed each message from channels and chats both with content only `TextContent`
+  }.launchIn(scope)
+}
+
+

The same things were created for media groups:

+
flowsUpdatesFilter {
+  mediaGroupMessages(scope).onEach {
+    println(it) // will be printed each media group messages list from both channels and chats without filtering of content
+  }.launchIn(scope)
+
+  mediaGroupPhotosMessages(scope).onEach {
+    println(it) // will be printed each media group messages list from both channels and chats with PhotoContent only
+  }.launchIn(scope)
+
+  mediaGroupVideosMessages(scope).onEach {
+    println(it) // will be printed each media group messages list from both channels and chats with VideoContent only
+  }.launchIn(scope)
+}
+
+

Besides, there is an opportunity to avoid separation on media groups and common messages and receive photos and videos content in one flow:

+
flowsUpdatesFilter {
+  sentMessagesWithMediaGroups(scope).onEach {
+    println(it) // will be printed each message including each separated media group message from both channels and chats without filtering of content
+  }.launchIn(scope)
+
+  photoMessagesWithMediaGroups(scope).onEach {
+    println(it) // will be printed each message including each separated media group message from both channels and chats with PhotoContent only
+  }.launchIn(scope)
+
+  videoMessagesWithMediaGroups(scope).onEach {
+    println(it) // will be printed each message including each separated media group message from both channels and chats with VideoContent only
+  }.launchIn(scope)
+}
+
+

See also

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/tgbotapi/updates/webhooks.html b/tgbotapi/updates/webhooks.html new file mode 100644 index 0000000..275fb46 --- /dev/null +++ b/tgbotapi/updates/webhooks.html @@ -0,0 +1,1137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + Webhooks - InMo Docs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + +
+ +
+ + + + + + + + + +
+
+ + + +
+
+
+ + + + + + +
+
+
+ + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +

Webhooks

+

In telegram bot API there is an opportunity to get updates via webhooks. In this case you will be able to retrieve updates without making additional requests. Most of currently available methods for webhooks are working on ktor server for JVM. Currently, next ways are available for using for webhooks:

+
    +
  • Route#includeWebhookHandlingInRoute for ktor server
  • +
  • Route#includeWebhookHandlingInRouteWithFlows
  • +
  • startListenWebhooks
  • +
  • RequestsExecutor#setWebhookInfoAndStartListenWebhooks
  • +
+

setWebhookInfoAndStartListenWebhooks

+

It is the most common way to set updates webhooks and start listening of them. Example:

+
val bot = telegramBot(TOKEN)
+
+val filter = flowsUpdatesFilter {
+  // ...
+}
+
+bot.setWebhookInfoAndStartListenWebhooks(
+  8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku
+  CIO, // default ktor server engine. It is recommended to replace it with something like `Netty`. More info about engines here: https://ktor.io/servers/configuration.html
+  SetWebhook(
+    "address.com/webhook_route",
+    File("/path/to/certificate").toInputFile(), // certificate file. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how
+    40, // max allowed updates, by default is null
+    filter.allowedUpdates
+  ),
+  {
+    it.printStackTrace() // optional handling of exceptions
+  },
+  "0.0.0.0", // listening host which will be used to bind by server
+  "subroute", // Optional subroute, if null - will listen root of address
+  WebhookPrivateKeyConfig( // optional config of private key. It will be installed in server to use TLS with custom certificate. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how
+    "/path/to/keystore.jks",
+    "KeystorePassword",
+    "Keystore key alias name",
+    "KeystoreAliasPassword"
+  ),
+  scope, // Kotlin coroutine scope for internal transforming of media groups
+  filter.asUpdateReceiver
+)
+
+

If you will use previous example, ktor server will bind and listen url 0.0.0.0:8080/subroute and telegram will send requests to address address.com/webhook_route with custom certificate. Alternative variant will use the other SetWebhook request variant:

+
SetWebhook(
+  "address.com/webhook_route",
+  "some_file_bot_id".toInputFile(),
+  40, // max allowed updates, by default is null
+  filter.allowedUpdates
+)
+
+

As a result, request SetWebhook will be executed and after this server will start its working and handling of updates.

+

startListenWebhooks

+

This function is working almost exactly like previous example, but this one will not set up webhook info in telegram:

+
val filter = flowsUpdatesFilter {
+  // ...
+}
+
+startListenWebhooks(
+  8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku
+  CIO, // default ktor server engine. It is recommended to replace it with something like `Netty`. More info about engines here: https://ktor.io/servers/configuration.html
+  {
+    it.printStackTrace() // optional handling of exceptions
+  },
+  "0.0.0.0", // listening host which will be used to bind by server
+  "subroute", // Optional subroute, if null - will listen root of address
+  WebhookPrivateKeyConfig( // optional config of private key. It will be installed in server to use TLS with custom certificate. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how
+    "/path/to/keystore.jks",
+    "KeystorePassword",
+    "Keystore key alias name",
+    "KeystoreAliasPassword"
+  ),
+  scope, // Kotlin coroutine scope for internal transforming of media groups
+  filter.asUpdateReceiver
+)
+
+

The result will be the same as in previous example: server will start its working and handling of updates on 0.0.0.0:8080/subroute. The difference here is that in case if this bot must not answer or send some requiests - it will not be necessary to create bot for receiving of updates.

+

Extensions includeWebhookHandlingInRoute and includeWebhookHandlingInRouteWithFlows

+

For these extensions you will need to start your server manualy. In common case it will look like:

+
val scope = CoroutineScope(Dispatchers.Default)
+
+val filter = flowsUpdatesFilter {
+  // ...
+}
+
+val environment = applicationEngineEnvironment {
+  module {
+    routing {
+      includeWebhookHandlingInRoute(
+        scope,
+        {
+          it.printStackTrace()
+        },
+        filter.asUpdateReceiver
+      )
+    }
+  }
+  connector {
+    host = "0.0.0.0"
+    port = 8080
+  }
+}
+
+embeddedServer(CIO, environment).start(true) // will start server and wait its stoping
+
+

In the example above server will started and binded for listening on 0.0.0.0:8080.

+

See also

+ + + + + + + +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + + + + + + + + \ No newline at end of file