From 834604a3cb8519302d0ccc6afc51ff90655a9276 Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Fri, 21 Jul 2023 13:56:14 +0000 Subject: [PATCH] deploy: 11a83139dbe0097ebe855885952a34e462ce0122 --- 404.html | 16 +- ...b4d07000.min.js => bundle.220ee61c.min.js} | 4 +- ....min.js.map => bundle.220ee61c.min.js.map} | 8 +- ...208ed371.min.js => search.74e28a9f.min.js} | 2 +- ....min.js.map => search.74e28a9f.min.js.map} | 2 +- assets/stylesheets/main.26e3688c.min.css | 1 - assets/stylesheets/main.eebd395e.min.css | 1 + ....min.css.map => main.eebd395e.min.css.map} | 2 +- index.html | 2014 ++++--------- krontab/describing/krontabscheduler.html | 2062 ++++--------- krontab/describing/string-format.html | 2102 ++++--------- krontab/index.html | 2151 ++++---------- krontab/introduction/faq.html | 1990 +++---------- krontab/introduction/how-to-use.html | 2052 ++++--------- .../introduction/including-in-project.html | 2076 ++++--------- kslog/index.html | 2045 ++++--------- kslog/logging.html | 2060 ++++--------- kslog/setup.html | 2244 ++++---------- micro_utils/index.html | 1897 +++--------- navigation/index.html | 1961 ++++-------- resources/js/mermaid_dark_light_switcher.js | 10 + resources/stylesheets/navigation.css | 6 +- search/search_index.json | 2 +- sitemap.xml | 70 +- sitemap.xml.gz | Bin 564 -> 564 bytes tgbotapi/dsls/keyboards.html | 2006 ++++--------- tgbotapi/dsls/live-location.html | 2048 ++++--------- tgbotapi/dsls/text.html | 1936 +++--------- tgbotapi/faq.html | 2028 ++++--------- tgbotapi/guides/keyboards.html | 2634 +++++------------ tgbotapi/index.html | 2086 ++++--------- .../introduction/before-any-bot-project.html | 1960 +++--------- tgbotapi/introduction/first-bot.html | 1974 +++--------- .../including-in-your-project.html | 2374 ++++----------- tgbotapi/introduction/proxy-setup.html | 2012 ++++--------- tgbotapi/logic/api-extensions.html | 2024 ++++--------- .../logic/behaviour-builder-with-fsm.html | 1992 +++---------- tgbotapi/logic/behaviour-builder.html | 2068 ++++--------- tgbotapi/logic/exceptions-handling.html | 2132 ++++--------- tgbotapi/logic/files-handling.html | 2016 ++++--------- tgbotapi/logic/low-level-work-with-bots.html | 1920 +++--------- tgbotapi/logic/media-groups.html | 1988 +++---------- tgbotapi/logic/types-conversations.html | 2006 ++++--------- tgbotapi/logic/updates-with-flows.html | 2028 ++++--------- tgbotapi/updates/heroku.html | 2126 ++++--------- tgbotapi/updates/long-polling.html | 2068 ++++--------- tgbotapi/updates/updates-filters.html | 2190 ++++---------- tgbotapi/updates/webhooks.html | 2164 ++++---------- 48 files changed, 18620 insertions(+), 53938 deletions(-) rename assets/javascripts/{bundle.b4d07000.min.js => bundle.220ee61c.min.js} (68%) rename assets/javascripts/{bundle.b4d07000.min.js.map => bundle.220ee61c.min.js.map} (68%) rename assets/javascripts/workers/{search.208ed371.min.js => search.74e28a9f.min.js} (99%) rename assets/javascripts/workers/{search.208ed371.min.js.map => search.74e28a9f.min.js.map} (77%) delete mode 100644 assets/stylesheets/main.26e3688c.min.css create mode 100644 assets/stylesheets/main.eebd395e.min.css rename assets/stylesheets/{main.26e3688c.min.css.map => main.eebd395e.min.css.map} (51%) create mode 100644 resources/js/mermaid_dark_light_switcher.js diff --git a/404.html b/404.html index ca1763a..793e9d0 100644 --- a/404.html +++ b/404.html @@ -15,7 +15,7 @@ - + @@ -23,7 +23,7 @@ - + @@ -1508,10 +1508,18 @@
- + - + + + + + + + + + diff --git a/assets/javascripts/bundle.b4d07000.min.js b/assets/javascripts/bundle.220ee61c.min.js similarity index 68% rename from assets/javascripts/bundle.b4d07000.min.js rename to assets/javascripts/bundle.220ee61c.min.js index 3c0bdad..116072a 100644 --- a/assets/javascripts/bundle.b4d07000.min.js +++ b/assets/javascripts/bundle.220ee61c.min.js @@ -24,6 +24,6 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */var wr=function(e,t){return wr=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(r,n){r.__proto__=n}||function(r,n){for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(r[o]=n[o])},wr(e,t)};function ie(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");wr(e,t);function r(){this.constructor=e}e.prototype=t===null?Object.create(t):(r.prototype=t.prototype,new r)}function fn(e,t,r,n){function o(i){return i instanceof r?i:new r(function(s){s(i)})}return new(r||(r=Promise))(function(i,s){function a(u){try{c(n.next(u))}catch(p){s(p)}}function f(u){try{c(n.throw(u))}catch(p){s(p)}}function c(u){u.done?i(u.value):o(u.value).then(a,f)}c((n=n.apply(e,t||[])).next())})}function $t(e,t){var r={label:0,sent:function(){if(i[0]&1)throw i[1];return i[1]},trys:[],ops:[]},n,o,i,s;return s={next:a(0),throw:a(1),return:a(2)},typeof Symbol=="function"&&(s[Symbol.iterator]=function(){return this}),s;function a(c){return function(u){return f([c,u])}}function f(c){if(n)throw new TypeError("Generator is already executing.");for(;r;)try{if(n=1,o&&(i=c[0]&2?o.return:c[0]?o.throw||((i=o.return)&&i.call(o),0):o.next)&&!(i=i.call(o,c[1])).done)return i;switch(o=0,i&&(c=[c[0]&2,i.value]),c[0]){case 0:case 1:i=c;break;case 4:return r.label++,{value:c[1],done:!1};case 5:r.label++,o=c[1],c=[0];continue;case 7:c=r.ops.pop(),r.trys.pop();continue;default:if(i=r.trys,!(i=i.length>0&&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(` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - + + + + + + + + + + +InMo Docs + + + + + + + + + + + + + + + + -
- -
- - - - +
+
+
-
- -
- - - - - - - - - -
-
- - - -
-
-
- - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    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

    +

    Projects

    @@ -1535,60 +596,60 @@ - - - + + + - - - + + + - + - + - + - +
    Maven CentralMaven CentralMaven CentralMaven CentralMaven CentralMaven Central
    Maven CentralMaven CentralMaven CentralMaven CentralMaven CentralMaven Central
    Maven CentralMaven Central
    Maven CentralMaven Central
    Maven CentralMaven Central
    Maven CentralMaven Central
    -

    Dependencies graph:

    -
    flowchart RL
    -    KSLog[<a href='https://github.com/InsanusMokrassar/kslog'>KSLog</a>]
    -    MicroUtils[<a href='https://github.com/InsanusMokrassar/MicroUtils'>MicroUtils</a>]
    -    TelegramBotAPI[<a href='https://github.com/InsanusMokrassar/ktgbotapi'>TelegramBotAPI</a>]
    -    TelegramBotAPI-examples[<a href='https://github.com/InsanusMokrassar/TelegramBotAPI-examples'>TelegramBotAPI-examples </a>]
    -    PlaguBot[<a href='https://github.com/InsanusMokrassar/PlaguBot'>PlaguBot</a>]
    -    TelegramBotAPILibraries[<a href='https://github.com/InsanusMokrassar/TelegramBotAPILibraries'>TelegramBotAPILibraries</a>]
    -    PlaguBotPlugins[<a href='https://github.com/InsanusMokrassar/PlaguBotPlugins'>PlaguBotPlugins</a>]
    -    PlaguBotExample[<a href='https://github.com/InsanusMokrassar/PlaguBotExample'>PlaguBotExample</a>]
    -    BooruGrabberTelegramBot[<a href='https://github.com/InsanusMokrassar/BooruGrabberTelegramBot'>BooruGrabberTelegramBot</a>]
    -    SauceNaoTelegramBot[<a href='https://github.com/InsanusMokrassar/SauceNaoTelegramBot'>SauceNaoTelegramBot</a>]
    -    PlaguPoster[<a href='https://github.com/InsanusMokrassar/PlaguPoster'>PlaguPoster</a>]
    -    PlaguBotSuggestionsBot[<a href='https://github.com/InsanusMokrassar/PlaguBotSuggestionsBot'>PlaguBotSuggestionsBot</a>]
    -    TelegramBotTutorial[<a href='https://github.com/InsanusMokrassar/TelegramBotTutorial'>TelegramBotTutorial</a>]
    -    Krontab[<a href='https://github.com/InsanusMokrassar/krontab'>Krontab</a>]
    -    KJSUiKit[<a href='https://github.com/InsanusMokrassar/JSUiKitKBindings'>KJSUiKit</a>]
    -    SauceNaoAPI[<a href='https://github.com/InsanusMokrassar/SauceNaoAPI'>SauceNaoAPI</a>]
    -    Navigation[<a href='https://github.com/InsanusMokrassar/navigation'>Navigation</a>]
    +

    Dependencies graph:

    +
    flowchart BT + KSLog[KSLog] + MicroUtils[MicroUtils] + TelegramBotAPI[TelegramBotAPI] + TelegramBotAPI-examples[TelegramBotAPI-examples ] + PlaguBot[PlaguBot] + TelegramBotAPILibraries[TelegramBotAPILibraries] + PlaguBotPlugins[PlaguBotPlugins] + PlaguBotExample[PlaguBotExample] + BooruGrabberTelegramBot[BooruGrabberTelegramBot] + SauceNaoTelegramBot[SauceNaoTelegramBot] + PlaguPoster[PlaguPoster] + PlaguBotSuggestionsBot[PlaguBotSuggestionsBot] + TelegramBotTutorial[TelegramBotTutorial] + Krontab[Krontab] + KJSUiKit[KJSUiKit] + SauceNaoAPI[SauceNaoAPI] + Navigation[Navigation] - TelegramBotAPI-bot_template[<a href='https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template'>TelegramBotAPI-bot_template</a>] - PlaguBotPluginTemplate[<a href='https://github.com/InsanusMokrassar/PlaguBotPluginTemplate'>PlaguBotPluginTemplate</a>] - PlaguBotBotTemplate[<a href='https://github.com/InsanusMokrassar/PlaguBotBotTemplate'>PlaguBotBotTemplate</a>] + TelegramBotAPI-bot_template[TelegramBotAPI-bot_template] + PlaguBotPluginTemplate[PlaguBotPluginTemplate] + PlaguBotBotTemplate[PlaguBotBotTemplate] MicroUtils --> KSLog TelegramBotAPI --> MicroUtils @@ -1610,127 +671,68 @@ TelegramBotAPI-bot_template -.- TelegramBotAPI PlaguBotPluginTemplate -.- PlaguBot - PlaguBotBotTemplate -.- PlaguBot
    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/krontab/describing/krontabscheduler.html b/krontab/describing/krontabscheduler.html index db66651..f09fdc4 100644 --- a/krontab/describing/krontabscheduler.html +++ b/krontab/describing/krontabscheduler.html @@ -1,1752 +1,732 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - KrontabScheduler - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +KrontabScheduler - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    KrontabScheduler

    KronScheduler is the simple interface with only one function next. This function optionally get as a parameter DateTime which will be used as start point for the calculation of next trigger time. This function will return the next DateTime when something must happen.

    -

    Default realisation

    +

    Default realisation

    Default realisation (CronDateTimeScheduler) can be created using several ways:

    • Via buildSchedule (or createSimpleScheduler) functions with crontab-like syntax parameter
    • Via buildSchedule (or SchedulerBuilder object), which using lambda to configure scheduler

    In the examples below the result of created scheduler will be the same.

    -

    Crontab-like way

    +

    Crontab-like way

    Crontab-like syntax

    See String format for more info about the crontab-line syntax

    This way will be very useful for cases when you need to configure something via external configuration (from file on startup or via some parameter from requests, for example):

    -
    val schedule = "5 * * * *"
    -val scheduler = buildSchedule(schedule)
    -
    -scheduler.asFlow().onEach {
    -  // this block will be called every minute at 5 seconds
    -}.launchIn(someCoroutineScope)
    +
    val schedule = "5 * * * *"
    +val scheduler = buildSchedule(schedule)
    +
    +scheduler.asFlow().onEach {
    +  // this block will be called every minute at 5 seconds
    +}.launchIn(someCoroutineScope)
     
    -

    Lambda way

    +

    Lambda way

    In case of usage builder (lets call it lambda way), you will be able to configure scheduler in more type-safe way:

    -
    val scheduler = buildSchedule {
    -  seconds {
    -    at(5)
    -  }
    -}
    -
    -scheduler.asFlow().onEach {
    -  // this block will be called every minute at 5 seconds
    -}.launchIn(someCoroutineScope)
    +
    val scheduler = buildSchedule {
    +  seconds {
    +    at(5)
    +  }
    +}
    +
    +scheduler.asFlow().onEach {
    +  // this block will be called every minute at 5 seconds
    +}.launchIn(someCoroutineScope)
     
    -

    Custom scheduler

    +

    Custom scheduler

    You are always able to use your own realisation of scheduler. For example:

    -
    class RandomScheduler : KronScheduler {
    -  override suspend fun next(relatively: DateTime): DateTime {
    -    return relatively + DateTimeSpan(seconds = Random.nextInt() % 60)
    -  }
    -}
    +
    class RandomScheduler : KronScheduler {
    +  override suspend fun next(relatively: DateTime): DateTime {
    +    return relatively + DateTimeSpan(seconds = Random.nextInt() % 60)
    +  }
    +}
     

    In the example above we have created RandomScheduler, which will return random next time in range 0-60 seconds since relatively argument.

    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/krontab/describing/string-format.html b/krontab/describing/string-format.html index bab6fd8..e8d82a7 100644 --- a/krontab/describing/string-format.html +++ b/krontab/describing/string-format.html @@ -1,1616 +1,648 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - String format - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +String format - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    String format

    As in crontab util, this library have almost the same format of string:

    @@ -1703,23 +735,23 @@

    Example with almost same description:

    -
    /-------------------- (0-59) ············ Seconds
    -| /------------------ (0-59) ············ Minutes
    -| | /---------------- (0-23) ············ Hours
    -| | | /-------------- (0-30) ············ Days of months
    -| | | | /------------ (0-11) ············ Months
    -| | | | | /---------- (optional, any int) Year
    -| | | | | | /-------- (optional) ········ Timezone offset
    -| | | | | | |  /----- (optional, 0-6) ··· Week days
    -| | | | | | |  |  /-- (optional, 0-999) · Milliseconds (0 by default)
    -* * * * * * 0o *w 0ms
    +
    /-------------------- (0-59) ············ Seconds
    +| /------------------ (0-59) ············ Minutes
    +| | /---------------- (0-23) ············ Hours
    +| | | /-------------- (0-30) ············ Days of months
    +| | | | /------------ (0-11) ············ Months
    +| | | | | /---------- (optional, any int) Year
    +| | | | | | /-------- (optional) ········ Timezone offset
    +| | | | | | |  /----- (optional, 0-6) ··· Week days
    +| | | | | | |  |  /-- (optional, 0-999) · Milliseconds (0 by default)
    +* * * * * * 0o *w 0ms
     

    Years, timezone, week days and milliseconds are optional settings. Next snippets are equal:

    -
    */15 * * * *
    -*/15 * * * * * // with year
    -*/15 * * * * * 0ms // with year and milliseconds
    +
    */15 * * * *
    +*/15 * * * * * // with year
    +*/15 * * * * * 0ms // with year and milliseconds
     
    -

    Supported syntax

    +

    Supported syntax

    Currently the library support next syntax for date/time elements:

    • {int}-{int} - ranges
    • @@ -1730,35 +762,35 @@
    • F or f - first possible value
    • L or l - last possible value (last day of month, for example)
    -

    Ranges

    +

    Ranges

    Ranges are working like common rangeTo (or ..) in kotlin:

    -
    0-5 * * * *
    +
    0-5 * * * *
     

    In the example above scheduler will trigger every second from the beginning of the minute up to fifth second of minute.

    -

    Start/Step

    +

    Start/Step

    Start/step is a little bit more complicated syntax. It means start from the first element, repeat triggering every second element. Examples:

    -
    5/15 * * * *
    +
    5/15 * * * *
     

    Means that each minute starting from fifth second it will repeat triggering every fifteenth second: 5, 20, 35, 50.

    -

    Every

    +

    Every

    Every is more simple syntax and could be explained as a shortcut for 0/{int}. Example:

    -
    */15 * * * *
    +
    */15 * * * *
     

    Means that each minute it will repeat triggering every fifteenth second: 0, 15, 30, 45.

    -

    Just at the time

    +

    Just at the time

    The most simple syntax. It means, that scheduler will call triggering every time when element was reached:

    -
    15 * * * *
    +
    15 * * * *
     

    Means that each minute scheduler will call triggering at the fifteenth second.

    -

    Listing

    +

    Listing

    All the previous elements can be combined with listing. Lets just see several examples:

    -
    0,10 * * * *
    +
    0,10 * * * *
     

    Will trigger every minute at the 0 and 10 seconds (see Just at the time)

    -
    0-5,10 * * * *
    +
    0-5,10 * * * *
     

    Will trigger every minute from 0 to 5 seconds and at the 10 seconds (see Ranges)

    -

    Examples

    +

    Examples

    • 0/5 * * * * for every five seconds triggering
    • 0/5,L * * * * for every five seconds triggering and on 59 second
    • @@ -1772,141 +804,77 @@
    • 1 2 3 F,4,L 5 2021 60o 0-2w for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00
    • 1 2 3 F,4,L 5 2021 60o 0-2w 500ms for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 when milliseconds will be equal to 500
    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/krontab/index.html b/krontab/index.html index 744f57c..9dda3aa 100644 --- a/krontab/index.html +++ b/krontab/index.html @@ -1,1651 +1,716 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - krontab - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +krontab - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    + - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    krontab

    +

    Maven Central

    +

    Telegram Chat

    +

    JVM +Android +Js +Linux x64

    +

    KDocs

    Library was created to give oppotunity to launch some things from time to time according to some schedule in runtime of applications.

    -

    How to use

    +

    How to use

    There are several ways to configure and use this library:

    • From some string
    • From builder

    Anyway, to start some action from time to time you will need to use one of extensions/functions:

    -
    val kronScheduler = /* creating of KronScheduler instance */;
    -
    -kronScheduler.doWhile {
    -    // some action
    -    true // true - repeat on next time
    -}
    +
    val kronScheduler = /* creating of KronScheduler instance */;
    +
    +kronScheduler.doWhile {
    +    // some action
    +    true // true - repeat on next time
    +}
     
    -

    Including in project

    +

    Including in project

    If you want to include krontab in your project, just add next line to your dependencies part:

    -
    implementation "dev.inmo:krontab:$krontab_version"
    +
    implementation "dev.inmo:krontab:$krontab_version"
     

    Next version is the latest currently for the library:

    -

    Maven Central

    +

    Maven Central

    For old version of Gradle, instead of implementation word developers must use compile.

    -

    Config from string

    +

    Config from string

    Developers can use more simple way to configure repeat times is string. String configuring like a crontab, but with a little bit different meanings:

    -
    /--------------- Seconds
    -| /------------- Minutes
    -| | /----------- Hours
    -| | | /--------- Days of months
    -| | | | /------- Months
    -| | | | | /----- (optional) Year
    -| | | | | | /--- (optional) Timezone offset
    -| | | | | | |  / (optional) Week days
    -* * * * * * 0o *w
    +
    /--------------- Seconds
    +| /------------- Minutes
    +| | /----------- Hours
    +| | | /--------- Days of months
    +| | | | /------- Months
    +| | | | | /----- (optional) Year
    +| | | | | | /--- (optional) Timezone offset
    +| | | | | | |  / (optional) Week days
    +* * * * * * 0o *w
     

    It is different with original crontab syntax for the reason, that expected that in practice developers will use seconds and minutes with more probability than months (for example) or even years. In fact, developers will use something like:

    -
    doWhile("/5 * * * *") {
    -    println("Called")
    -    true // true - repeat on next time
    -}
    +
    doWhile("/5 * * * *") {
    +    println("Called")
    +    true // true - repeat on next time
    +}
     

    An other version:

    -
    doInfinity("/5 * * * *") {
    -    println("Called")
    -}
    +
    doInfinity("/5 * * * *") {
    +    println("Called")
    +}
     

    Both of examples will print Called message every five seconds.

    -

    Config via builder

    +

    Config via builder

    Also, this library currently supports DSL for creating the same goals:

    -
    val kronScheduler = buildSchedule {
    -    seconds {
    -        from (0) every 5
    -    }
    -}
    -kronScheduler.doWhile {
    -    println("Called")
    -    true // true - repeat on next time
    -}
    +
    val kronScheduler = buildSchedule {
    +    seconds {
    +        from (0) every 5
    +    }
    +}
    +kronScheduler.doWhile {
    +    println("Called")
    +    true // true - repeat on next time
    +}
     

    Or

    -
    val kronScheduler = buildSchedule {
    -    seconds {
    -        0 every 5
    -    }
    -}
    -kronScheduler.doWhile {
    -    println("Called")
    -    true // true - repeat on next time
    -}
    +
    val kronScheduler = buildSchedule {
    +    seconds {
    +        0 every 5
    +    }
    +}
    +kronScheduler.doWhile {
    +    println("Called")
    +    true // true - repeat on next time
    +}
     

    Or

    -
    val kronScheduler = buildSchedule {
    -    seconds {
    -        0 every 5
    -    }
    -}
    -kronScheduler.doInfinity {
    -    println("Called")
    -}
    +
    val kronScheduler = buildSchedule {
    +    seconds {
    +        0 every 5
    +    }
    +}
    +kronScheduler.doInfinity {
    +    println("Called")
    +}
     

    All of these examples will do the same things: print Called message every five seconds.

    -

    do* functions

    +

    do* functions

    With regular doOnce/doWhile/doInfinity there are two types of their variations: local and timezoned. Local variations (doOnceLocal/doWhileLocal/doInfinityLocal) will pass DateTime as an argument into the block:

    -
    doInfinityLocal("/5 * * * *") {
    -    println(it) // will print current date time
    -}
    +
    doInfinityLocal("/5 * * * *") {
    +    println(it) // will print current date time
    +}
     

    Timezoned variations (doOnceTz/doWhileTz/doInfinityTz) will do the same thing but pass as an argument DateTimeTz:

    -
    doInfinityTz("/5 * * * * 0o") {
    -    println(it) // will print current date time in UTC
    -}
    +
    doInfinityTz("/5 * * * * 0o") {
    +    println(it) // will print current date time in UTC
    +}
     

    It is useful in cases when you need to get the time of calling and avoid extra calls to system time.

    -

    Helpful table for

    +

    Helpful table for

    @@ -1678,25 +743,25 @@ variations (doOnceLocal/doWhileLocal/doInfinityL

    *Here there is an important notice, that Work infinity is not exactly infinity. Actually, that means that do while coroutine is alive and in fact executing will be stopped when coroutine became cancelled.

    -

    KronScheduler as a Flow

    +

    KronScheduler as a Flow

    Any KronSchedulercan e converted to a Flow<DateTime using extension asFlow:

    -
    val kronScheduler = buildSchedule {
    -    seconds {
    -        0 every 1
    -    }
    -}
    -
    -val flow = kronScheduler.asFlow()
    +
    val kronScheduler = buildSchedule {
    +    seconds {
    +        0 every 1
    +    }
    +}
    +
    +val flow = kronScheduler.asFlow()
     

    So, in this case any operations related to flow are available and it is expected that they will work correctly. For example, it is possible to use this flow with takeWhile:

    -
    flow.takeWhile {
    -    condition()
    -}.collect {
    -    action()
    -}
    +
    flow.takeWhile {
    +    condition()
    +}.collect {
    +    action()
    +}
     
    -

    Offsets

    +

    Offsets

    Offsets in this library works via passing parameter ending with o in any place after month config. Currently there is only one format supported for offsets: minutes of offsets. To use time zones you will need to call next method with DateTimeTz argument or nextTimeZoned method with any KronScheduler instance, but in case if this @@ -1709,7 +774,7 @@ scheduler is not instance of KronSchedulerTz it will work like you methods with casting to KronSchedulerTz in case you are pretty sure that it is timezoned KronScheduler

  • Creating your own implementation of KronSchedulerTz
  • -

    Note about week days

    +

    Note about week days

    Unlike original CRON, here week days:

    • Works as AND: cron date time will search first day which will pass requirement according all parameters including @@ -1717,141 +782,77 @@ scheduler is not instance of KronSchedulerTz it will work like you
    • You may use any related to numbers syntax with week days: 0-3w, 0,1,2,3w, etc.
    • Week days (like years and offsets) are optional and can be placed anywhere after month
    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/krontab/introduction/faq.html b/krontab/introduction/faq.html index 7bf7f78..27d14ff 100644 --- a/krontab/introduction/faq.html +++ b/krontab/introduction/faq.html @@ -1,1692 +1,680 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - FAQ - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +FAQ - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    + - - - -
    -
    - - - - - - - - - - - - - - - - - - - - -

    FAQ

    -

    How oftern new versions are releasing?

    +
    +
    +
    +
    +
    + + + + + + +

    FAQ

    +

    How oftern new versions are releasing?

    Not very often. It depends on libraries (coroutines, korlibs/klock) updates and on some new awesome, but lightweight, features coming.

    -

    Where this library could be useful?

    +

    Where this library could be useful?

    First of all, this library will be useful for long uptime applications which have some tasks to do from time to time.

    -

    How to use crontab-like syntax?

    +

    How to use crontab-like syntax?

    In two words, you should call buildSchedule or createSimpleScheduler:

    -
    buildSchedule("5 * * * *").asFlow().collect { /* do something */ }
    +
    buildSchedule("5 * * * *").asFlow().collect { /* do something */ }
     

    You can read more about syntax in String format section.

    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/krontab/introduction/how-to-use.html b/krontab/introduction/how-to-use.html index 6b3b6cf..6daabad 100644 --- a/krontab/introduction/how-to-use.html +++ b/krontab/introduction/how-to-use.html @@ -1,1606 +1,646 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - How to use - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +How to use - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    How to use

    +

    Previous pages

    -

    buildSchedule

    +

    buildSchedule

    Custom KronScheduler

    You are always may create your own scheduler. In this section will be presented different ways and examples around standard CronDateTimeScheduler builders buildSchedule. You can read about schedulers in KrontabScheduler

    Currently, buildSchedule is the recommended start point for every scheduler. Usually, it is look like:

    -
    val scheduler = buildSchedule("5 * * * *")
    +
    val scheduler = buildSchedule("5 * * * *")
     

    Or:

    -
    val scheduler = buildSchedule {
    -  seconds {
    -    at(5)
    -  }
    -}
    +
    val scheduler = buildSchedule {
    +  seconds {
    +    at(5)
    +  }
    +}
     

    On the top of any KronScheduler currently there are several groups of extensions:

      @@ -1608,14 +648,14 @@
    • Shortcuts
    • Flows
    -

    Executes

    +

    Executes

    All executes are look like do.... All executes are described below:

    • doOnce - will get the next time for executing, delay until that time and call block with returning of the block result
    • doWhile - will call doOnce while it will return true (that means that block must return true if it expects that next call must happen). In two words: it will run while block returning true
    • doInfinity - will call the block using doWhile with predefined returning true. In two words: it will call block while it do not throw error
    -

    Shortcuts

    +

    Shortcuts

    Shortcuts are the constants that are initializing in a lazy way to provide preset KronSchedulers. For more info about KrontabScheduler you can read its own page.

    • AnyTimeScheduler - will always return incoming DateTime as next
    • @@ -1628,143 +668,79 @@
    • EveryMonthScheduler / KronScheduler.monthly
    • EveryYearScheduler / KronScheduler.annually
    -

    Flows

    +

    Flows

    Here currently there is only one extension for KronScheduler: KronScheduler#asFlow. As a result you will get Flow<DateTime> (in fact SchedulerFlow) which will trigger next emit on each not null next DateTime

    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/krontab/introduction/including-in-project.html b/krontab/introduction/including-in-project.html index b261e9d..8903e95 100644 --- a/krontab/introduction/including-in-project.html +++ b/krontab/introduction/including-in-project.html @@ -1,1754 +1,726 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Including in project - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +Including in project - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    Including in project

    In two words, you must add dependency dev.inmo:krontab:$krontab_version to your project. The latest version presented by next badge:

    -

    Maven Central

    -

    Notice about repository

    +

    Maven Central

    +

    Notice about repository

    To use this library, you will need to include MavenCentral repository in you project

    -
    build.gradle
    -
    mavenCentral()
    +
    build.gradle
    +
    mavenCentral()
     
    -

    Dependencies

    +

    Dependencies

    Next snippets must be placed into your dependencies part of build.gradle (for gradle) or pom.xml (for maven).

    -

    Gradle

    -
    implementation "dev.inmo:krontab:$krontab_version"
    +

    Gradle

    +
    implementation "dev.inmo:krontab:$krontab_version"
     
    -

    Maven

    -
    <dependency>
    -    <groupId>dev.inmo</groupId>
    -    <artifactId>krontab</artifactId>
    -    <version>${krontab_version}</version>
    -</dependency>
    +

    Maven

    +
    <dependency>
    +    <groupId>dev.inmo</groupId>
    +    <artifactId>krontab</artifactId>
    +    <version>${krontab_version}</version>
    +</dependency>
     
    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/kslog/index.html b/kslog/index.html index ef2336a..b3fd204 100644 --- a/kslog/index.html +++ b/kslog/index.html @@ -1,1532 +1,603 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - KSLog - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +KSLog - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    KSLog

    It is simple and easy-to-use tool for logging on the most popular platforms in Kotlin Multiplatform:

    -

    JVM -Android -Js -ARM x64 -ARM x32 -Linux x64

    -

    KDocs

    +

    JVM +Android +Js +ARM x64 +ARM x32 +Linux x64

    +

    KDocs

    By default, KSLog is using built-in tools for logging on each supported platform:

    • java.util.logging.Logger for JVM
    • @@ -1534,30 +605,30 @@
    • Console for JS

    But you always may create your logger and customize as you wish:

    -
    KSLog.default = KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
    -    // do your logging
    -}
    +
    KSLog.default = KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->
    +    // do your logging
    +}
     

    This library also supports native targets in experimental mode. By default, all native targets will use simple printing in the console

    -

    How to use

    -

    Fast-travel

    +

    How to use

    +

    Fast-travel

    Just use some boring extensions like:

    -
    KSLog.i("Some message")
    -// OR
    -KSLog.i("Some tag", "Some message")
    -// OR
    -KSLog.i("Some tag", "Some message", IllegalArgumentException("So, that is exception :)"))
    -// OR
    -KSLog.i("Some optional tag", Exception("Optional")) { "Lazy inited message" }
    -// OR
    -KSLog.iS("Some optional tag", Exception("Optional")) { "Lazy inited message for suspendable calculation of text" }
    -// OR EVEN
    -KSLog.l(LogLevel.INFO, "Some tag", "Some message", IllegalArgumentException("So, that is exception :)"))
    -// OR
    -KSLog.l(LogLevel.INFO, "Some optional tag", IllegalArgumentException("So, that is exception :)")) { "And lazily inited message" }
    +
    KSLog.i("Some message")
    +// OR
    +KSLog.i("Some tag", "Some message")
    +// OR
    +KSLog.i("Some tag", "Some message", IllegalArgumentException("So, that is exception :)"))
    +// OR
    +KSLog.i("Some optional tag", Exception("Optional")) { "Lazy inited message" }
    +// OR
    +KSLog.iS("Some optional tag", Exception("Optional")) { "Lazy inited message for suspendable calculation of text" }
    +// OR EVEN
    +KSLog.l(LogLevel.INFO, "Some tag", "Some message", IllegalArgumentException("So, that is exception :)"))
    +// OR
    +KSLog.l(LogLevel.INFO, "Some optional tag", IllegalArgumentException("So, that is exception :)")) { "And lazily inited message" }
     
    -

    A little bit deeper

    -

    There are several important “terms” in context of this library:

    +

    A little bit deeper

    +

    There are several important “terms” in context of this library:

    • Default logger (available via KSLog.default or simply KSLog)
    • Local logger (can be created via KSLog functions and passed anywhere as KSLog)
    • @@ -1569,158 +640,94 @@

    Every logging extension (like KSLog.i) have its analog with lazy inited message text and the same one with suffix S (like KSLog.iS) for the suspendable message calculation.

    Default logger can be created by passing defaultTag and one of variants log level filters: set or minimal loggable level. In JVM you also may setup any logger as base logger for default realizations of KSLog. Besides, you may use your own callback (on any target platform) as output of logging:

    -
    val logger = KSLog { logLevel, optionalTag, message, optionalThrowable ->
    -    println("[$logLevel] $optionalTag - $message: $optionalThrowable.stackTraceToString()")
    -}
    +
    val logger = KSLog { logLevel, optionalTag, message, optionalThrowable ->
    +    println("[$logLevel] $optionalTag - $message: $optionalThrowable.stackTraceToString()")
    +}
     

    In the example above we will take the logger which will just print incoming data as common output.

    -

    Installation

    -

    Maven Central

    -

    Gradle

    -
    implementation "dev.inmo:kslog:$kslog_version"
    +

    Installation

    +

    Maven Central

    +

    Gradle

    +
    implementation "dev.inmo:kslog:$kslog_version"
     
    -

    Maven

    -
    <dependency>
    -  <groupId>dev.inmo</groupId>
    -  <artifactId>kslog</artifactId>
    -  <version>${kslog_version}</version>
    -</dependency>
    +

    Maven

    +
    <dependency>
    +  <groupId>dev.inmo</groupId>
    +  <artifactId>kslog</artifactId>
    +  <version>${kslog_version}</version>
    +</dependency>
     
    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/kslog/logging.html b/kslog/logging.html index b00c9f0..0972743 100644 --- a/kslog/logging.html +++ b/kslog/logging.html @@ -1,1472 +1,552 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Logging - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +Logging - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + +

    Logging

    Message type notice

    @@ -1544,224 +624,160 @@ On this page all the messages will be just simple String, but you m

  • Tag, Message, Throwable (Optional)
  • So, when you want to log some expected exception, there are three common ways to do it:

    -
    val logger = KSLog.default
    -
    -// with callback
    -logger.info(tag, throwable) {
    -  "Some your message for this event"
    -}
    -
    -// with suspendable callback
    -logger.infoS(tag, throwable) {
    -  withContext(Dispatchers.Default) {
    -    "Some your message for this event"
    -  }
    -}
    -
    -// Just with message
    -logger.info("Some your message for this event", throwable)
    -
    -// With message and tag as strings
    -logger.info(tag, "Some your message for this event", throwable)
    +
    val logger = KSLog.default
    +
    +// with callback
    +logger.info(tag, throwable) {
    +  "Some your message for this event"
    +}
    +
    +// with suspendable callback
    +logger.infoS(tag, throwable) {
    +  withContext(Dispatchers.Default) {
    +    "Some your message for this event"
    +  }
    +}
    +
    +// Just with message
    +logger.info("Some your message for this event", throwable)
    +
    +// With message and tag as strings
    +logger.info(tag, "Some your message for this event", throwable)
     

    Of course, any of this calls can be shortenned:

    -
    val logger = KSLog.default
    -
    -// with callback
    -logger.i(tag, throwable) {
    -  "Some your message for this event"
    -}
    -
    -// with suspendable callback
    -logger.iS(tag, throwable) {
    -  withContext(Dispatchers.Default) {
    -    "Some your message for this event"
    -  }
    -}
    -
    -// Just with message
    -logger.i("Some your message for this event", throwable)
    -
    -// With message and tag as strings
    -logger.i(tag, "Some your message for this event", throwable)
    +
    val logger = KSLog.default
    +
    +// with callback
    +logger.i(tag, throwable) {
    +  "Some your message for this event"
    +}
    +
    +// with suspendable callback
    +logger.iS(tag, throwable) {
    +  withContext(Dispatchers.Default) {
    +    "Some your message for this event"
    +  }
    +}
    +
    +// Just with message
    +logger.i("Some your message for this event", throwable)
    +
    +// With message and tag as strings
    +logger.i(tag, "Some your message for this event", throwable)
     

    There is special shortcat - for base performLog. In that case the only change is that you will require to pass the LogLevel more obviously:

    -
    val logger = KSLog.default
    -
    -// with callback
    -logger.log(LogLevel.INFO, tag, throwable) {
    -  "Some your message for this event"
    -}
    -
    -// with suspendable callback
    -logger.logS(LogLevel.INFO, tag, throwable) {
    -  withContext(Dispatchers.Default) {
    -    "Some your message for this event"
    -  }
    -}
    -
    -// Just with message
    -logger.log(LogLevel.INFO, "Some your message for this event", throwable)
    -
    -// With message and tag as strings
    -logger.log(LogLevel.INFO, tag, "Some your message for this event", throwable)
    +
    val logger = KSLog.default
    +
    +// with callback
    +logger.log(LogLevel.INFO, tag, throwable) {
    +  "Some your message for this event"
    +}
    +
    +// with suspendable callback
    +logger.logS(LogLevel.INFO, tag, throwable) {
    +  withContext(Dispatchers.Default) {
    +    "Some your message for this event"
    +  }
    +}
    +
    +// Just with message
    +logger.log(LogLevel.INFO, "Some your message for this event", throwable)
    +
    +// With message and tag as strings
    +logger.log(LogLevel.INFO, tag, "Some your message for this event", throwable)
     

    OR

    -
    val logger = KSLog.default
    -
    -// with callback
    -logger.l(LogLevel.INFO, tag, throwable) {
    -  "Some your message for this event"
    -}
    -
    -// with suspendable callback
    -logger.lS(LogLevel.INFO, tag, throwable) {
    -  withContext(Dispatchers.Default) {
    -    "Some your message for this event"
    -  }
    -}
    -
    -// Just with message
    -logger.l(LogLevel.INFO, "Some your message for this event", throwable)
    -
    -// With message and tag as strings
    -logger.l(LogLevel.INFO, tag, "Some your message for this event", throwable)
    +
    val logger = KSLog.default
    +
    +// with callback
    +logger.l(LogLevel.INFO, tag, throwable) {
    +  "Some your message for this event"
    +}
    +
    +// with suspendable callback
    +logger.lS(LogLevel.INFO, tag, throwable) {
    +  withContext(Dispatchers.Default) {
    +    "Some your message for this event"
    +  }
    +}
    +
    +// Just with message
    +logger.l(LogLevel.INFO, "Some your message for this event", throwable)
    +
    +// With message and tag as strings
    +logger.l(LogLevel.INFO, tag, "Some your message for this event", throwable)
     
    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/kslog/setup.html b/kslog/setup.html index b133c62..faf3c20 100644 --- a/kslog/setup.html +++ b/kslog/setup.html @@ -1,1722 +1,732 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Setup - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +Setup - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    + - - - -
    -
    - - - - - - - - - - - - - - - - - - - - -

    Setup

    -

    Dependency installation

    -

    Maven Central

    -

    Gradle (Groovy)

    -
    implementation "dev.inmo:kslog:$kslog_version"
    +
    +
    +
    +
    +
    + + + + + + +

    Setup

    +

    Dependency installation

    +

    Maven Central

    +

    Gradle (Groovy)

    +
    implementation "dev.inmo:kslog:$kslog_version"
     
    -

    Gradle (Kotlin Script)

    -
    implementation("dev.inmo:kslog:$kslog_version")
    +

    Gradle (Kotlin Script)

    +
    implementation("dev.inmo:kslog:$kslog_version")
     
    -

    Maven (pom)

    -
    <dependency>
    -  <groupId>dev.inmo</groupId>
    -  <artifactId>kslog</artifactId>
    -  <version>${kslog_version}</version>
    -</dependency>
    +

    Maven (pom)

    +
    <dependency>
    +  <groupId>dev.inmo</groupId>
    +  <artifactId>kslog</artifactId>
    +  <version>${kslog_version}</version>
    +</dependency>
     
    -

    Setup in code

    +

    Setup in code

    The main point in setup in your code is to setup default logger:

    -
    KSLog.default = KSLog("defaultTag")
    +
    KSLog.default = KSLog("defaultTag")
     

    You may use custom messageFormatter in any of KSLog factory to customize output of KSLog logging. For example:

    -
    KSLog(
    -  "loggingWithCustomFormat",
    -  messageFormatter = { level, tag, message, throwable ->
    -    println("[$level] $tag - $message: $throwable")
    -  }
    -)
    +
    KSLog(
    +  "loggingWithCustomFormat",
    +  messageFormatter = { level, tag, message, throwable ->
    +    println("[$level] $tag - $message: $throwable")
    +  }
    +)
     

    Additionally you may use one of several different settings:

      @@ -1731,10 +741,10 @@
    • firstLevel,secondLevel,otherLevels - as levels, but vararg :)

    In case you are passing minLoggingLevel, the level and more important levels will be passed to logs. For example, when you are settings up your logger as in next snippet:

    -
    val logger = KSLog(
    -    "exampleTag",
    -    minLoggingLevel = LogLevel.INFO
    -)
    +
    val logger = KSLog(
    +    "exampleTag",
    +    minLoggingLevel = LogLevel.INFO
    +)
     

    The next levels will be logged with logger:

      @@ -1743,193 +753,129 @@
    • ERROR
    • ASSERT
    -

    Special loggers

    -

    CallbackKSLog

    +

    Special loggers

    +

    CallbackKSLog

    It is logger which will call incoming performLogCallback on each logging. This logger can be create simply with one callback:

    -
    KSLog { level, tag, message, throwable ->
    -  println("[$level] $tag - $message: $throwable")
    -}
    +
    KSLog { level, tag, message, throwable ->
    +  println("[$level] $tag - $message: $throwable")
    +}
     
    -

    TagLogger

    +

    TagLogger

    It is simple value class which can be used for zero-cost usage of some tag and calling for KSLog.default. For example, if you will create tag logger with next code:

    -
    val logger = TagLogger("tagLoggerTag")
    +
    val logger = TagLogger("tagLoggerTag")
     

    The logger will call KSLog.default with the tag tagLoggerTag on each calling of logging.

    -

    FilterKSLog

    +

    FilterKSLog

    This pretty simple logger will call its fallbackLogger only in cases when incoming messageFilter will return true for logging:

    -
    val baseLogger = KSLog("base") // log everything with the tag `base` if not set other
    -val filtered = baseLogger.filtered { _, t, _ ->
    -    t == "base"
    -}
    +
    val baseLogger = KSLog("base") // log everything with the tag `base` if not set other
    +val filtered = baseLogger.filtered { _, t, _ ->
    +    t == "base"
    +}
     

    In the example above baseLogger will perform logs in two ways: when it has been called directly or when we call log performing with the tag "base" or null. Besides, you can see there extension filtered which allow to create FilterKSLog logger with simple lambda.

    -

    TypedKSLog

    +

    TypedKSLog

    This logger accepts map of types with the target loggers. You may build this logger with the special simple DSL:

    -
    val baseLogger = KSLog("base") // log everything with the tag `base` if not set other
    -val typed = buildTypedLogger {
    -  on<Int>(baseLogger) // log all ints to the baseLogger
    -  on<Float> { _, _, message, _ ->// log all floats to the passed logger
    -    println(message.toString()) // just print all floats
    -  }
    -  default { level, tag, message, throwable ->
    -    KSLog.performLog(level, tag, message, throwable)
    -  }
    -}
    +
    val baseLogger = KSLog("base") // log everything with the tag `base` if not set other
    +val typed = buildTypedLogger {
    +  on<Int>(baseLogger) // log all ints to the baseLogger
    +  on<Float> { _, _, message, _ ->// log all floats to the passed logger
    +    println(message.toString()) // just print all floats
    +  }
    +  default { level, tag, message, throwable ->
    +    KSLog.performLog(level, tag, message, throwable)
    +  }
    +}
     
    -

    Automatical loggers

    +

    Automatical loggers

    There are two things which can be useful in your code: logger and logTag extensions. logTag is the autocalculated by your object classname tag. logger extension can be used with applying to any object like in the next snippet:

    -
    class SomeClass {
    -  init {
    -    logger.i("inited")
    -  }
    -}
    +
    class SomeClass {
    +  init {
    +    logger.i("inited")
    +  }
    +}
     

    The code above will trigger calling of logging in KSLog.default with level LogLevel.INFO using tag SomeClass and message "inited". As you could have guessed, logger is using TagLogger with logTag underhood and the most expensive operation here is automatical calculation of logTag.

    • Extension logger
    -

    JVM specific setup

    +

    JVM specific setup

    For JVM you may setup additionally use java loggers as the second parameter of KSLog factory. For example:

    -
    KSLog(
    -  "yourTag"
    -  Logger.getLogger("YourJavaLoggerName")
    -)
    +
    KSLog(
    +  "yourTag"
    +  Logger.getLogger("YourJavaLoggerName")
    +)
     
    - - - - - - - - -
    -
    - - -
    - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/micro_utils/index.html b/micro_utils/index.html index 30dbadc..a174dce 100644 --- a/micro_utils/index.html +++ b/micro_utils/index.html @@ -1,1462 +1,551 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - MicroUtils - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +MicroUtils - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + +

    MicroUtils

    MicroUtils is a set of libraries to help me (and, I hope, you too) in some routine doings of coding.

    First of all, this library collection is oriented to use next technologies:

      @@ -1475,141 +564,77 @@ Me and the users of this library will try hard to keep its docs as actual as pos you will find some inconsistency of docs and library work (signatures, behaviour, API) you may write me directly in my telegram

    - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/navigation/index.html b/navigation/index.html index b157d36..f675fd9 100644 --- a/navigation/index.html +++ b/navigation/index.html @@ -1,1484 +1,570 @@ - - - - - - - - - - - - - - - - - - - - - - - - - Navigation - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + +Navigation - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    Navigation

    Navigation is a library for simple management for your app views (or some other logics). In this library there are several important terms:

    • Node - is a core thing. Node itself contains current config and its state
    • Chain - some sequence of nodes. In one chain only the last one node can be active
    -

    Nodes tree

    -

    Let’s see the next sample:

    -
    flowchart BT
    +

    Nodes tree

    +

    Let’s see the next sample:

    +
    flowchart TB NodeN1(N1) NodeN2(N2) @@ -1492,132 +578,133 @@ important terms:

    class NodeN3 navigation-part; subgraph RootChain + direction TB NodeN1 --> NodeN2 NodeN2 --> NodeN3 end + class RootChain navigation-resumed; - class RootChain navigation-part;
    + class RootChain navigation-part; + + NodeN4(N4) + NodeN5(N5) + NodeN6(N6) + + class NodeN4 navigation-paused; + class NodeN4 navigation-part; + class NodeN5 navigation-paused; + class NodeN5 navigation-part; + class NodeN6 navigation-paused; + class NodeN6 navigation-part; + + subgraph N2Subchain + direction TB + NodeN4 --> NodeN5 + NodeN5 --> NodeN6 + end + + class N2Subchain navigation-paused; + class N2Subchain navigation-part; + + NodeN2 --> N2Subchain + + NodeN7(N7) + NodeN8(N8) + + class NodeN7 navigation-paused; + class NodeN7 navigation-part; + class NodeN8 navigation-resumed; + class NodeN8 navigation-part; + + subgraph N3Subchain + direction TB + NodeN7 --> NodeN8 + end + + class N3Subchain navigation-resumed; + class N3Subchain navigation-part; + + NodeN3 --> N3Subchain + + NodeN9(N9) + NodeN10(N10) + + class NodeN9 navigation-paused; + class NodeN9 navigation-part; + class NodeN10 navigation-resumed; + class NodeN10 navigation-part; + + subgraph N3Subchain2 + direction TB + NodeN9 --> NodeN10 + end + + class N3Subchain2 navigation-resumed; + class N3Subchain2 navigation-part; + + NodeN3 --> N3Subchain2 +

    Any hierarchy starts with some root chain.

    - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/resources/js/mermaid_dark_light_switcher.js b/resources/js/mermaid_dark_light_switcher.js new file mode 100644 index 0000000..48e9b5a --- /dev/null +++ b/resources/js/mermaid_dark_light_switcher.js @@ -0,0 +1,10 @@ +var paletteSwitcher1 = document.getElementById("__palette_1"); +var paletteSwitcher2 = document.getElementById("__palette_2"); + +paletteSwitcher1.addEventListener("change", function () { + location.reload(); +}); + +paletteSwitcher2.addEventListener("change", function () { + location.reload(); +}); diff --git a/resources/stylesheets/navigation.css b/resources/stylesheets/navigation.css index 7468782..12f61b2 100644 --- a/resources/stylesheets/navigation.css +++ b/resources/stylesheets/navigation.css @@ -3,11 +3,11 @@ } .navigation-paused > rect { - fill: #FFFF00AA !important; - color: black !important; + fill: #b2b200 !important; + color: white !important; } .navigation-resumed > rect { - fill: #00FF0022 !important; + fill: seagreen !important; color: white !important; } diff --git a/search/search_index.json b/search/search_index.json index 58cc07a..9ce0f15 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +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":"Common and independent TelegramBotAPI Plagubot"},{"location":"index.html#dependencies-graph","title":"Dependencies graph:","text":"
    flowchart RL\n    KSLog[<a href='https://github.com/InsanusMokrassar/kslog'>KSLog</a>]\n    MicroUtils[<a href='https://github.com/InsanusMokrassar/MicroUtils'>MicroUtils</a>]\n    TelegramBotAPI[<a href='https://github.com/InsanusMokrassar/ktgbotapi'>TelegramBotAPI</a>]\n    TelegramBotAPI-examples[<a href='https://github.com/InsanusMokrassar/TelegramBotAPI-examples'>TelegramBotAPI-examples </a>]\n    PlaguBot[<a href='https://github.com/InsanusMokrassar/PlaguBot'>PlaguBot</a>]\n    TelegramBotAPILibraries[<a href='https://github.com/InsanusMokrassar/TelegramBotAPILibraries'>TelegramBotAPILibraries</a>]\n    PlaguBotPlugins[<a href='https://github.com/InsanusMokrassar/PlaguBotPlugins'>PlaguBotPlugins</a>]\n    PlaguBotExample[<a href='https://github.com/InsanusMokrassar/PlaguBotExample'>PlaguBotExample</a>]\n    BooruGrabberTelegramBot[<a href='https://github.com/InsanusMokrassar/BooruGrabberTelegramBot'>BooruGrabberTelegramBot</a>]\n    SauceNaoTelegramBot[<a href='https://github.com/InsanusMokrassar/SauceNaoTelegramBot'>SauceNaoTelegramBot</a>]\n    PlaguPoster[<a href='https://github.com/InsanusMokrassar/PlaguPoster'>PlaguPoster</a>]\n    PlaguBotSuggestionsBot[<a href='https://github.com/InsanusMokrassar/PlaguBotSuggestionsBot'>PlaguBotSuggestionsBot</a>]\n    TelegramBotTutorial[<a href='https://github.com/InsanusMokrassar/TelegramBotTutorial'>TelegramBotTutorial</a>]\n    Krontab[<a href='https://github.com/InsanusMokrassar/krontab'>Krontab</a>]\n    KJSUiKit[<a href='https://github.com/InsanusMokrassar/JSUiKitKBindings'>KJSUiKit</a>]\n    SauceNaoAPI[<a href='https://github.com/InsanusMokrassar/SauceNaoAPI'>SauceNaoAPI</a>]\n    Navigation[<a href='https://github.com/InsanusMokrassar/navigation'>Navigation</a>]\n\n    TelegramBotAPI-bot_template[<a href='https://github.com/InsanusMokrassar/TelegramBotAPI-bot_template'>TelegramBotAPI-bot_template</a>]\n    PlaguBotPluginTemplate[<a href='https://github.com/InsanusMokrassar/PlaguBotPluginTemplate'>PlaguBotPluginTemplate</a>]\n    PlaguBotBotTemplate[<a href='https://github.com/InsanusMokrassar/PlaguBotBotTemplate'>PlaguBotBotTemplate</a>]\n\n    MicroUtils --> KSLog\n    TelegramBotAPI --> MicroUtils\n    TelegramBotAPI-examples --> TelegramBotAPI\n    PlaguBot --> TelegramBotAPI\n    TelegramBotAPILibraries --> PlaguBot\n    PlaguBotPlugins --> TelegramBotAPILibraries\n    PlaguBotExample --> PlaguBotPlugins\n    BooruGrabberTelegramBot --> TelegramBotAPI\n    BooruGrabberTelegramBot --> Krontab\n    SauceNaoTelegramBot --> TelegramBotAPI\n    SauceNaoTelegramBot --> SauceNaoAPI\n    TelegramBotTutorial --> PlaguBotPlugins\n    PlaguBotSuggestionsBot --> PlaguBotPlugins\n    PlaguPoster --> PlaguBotPlugins\n    PlaguPoster --> Krontab\n    SauceNaoAPI --> MicroUtils\n    Navigation --> MicroUtils\n\n    TelegramBotAPI-bot_template -.- TelegramBotAPI\n    PlaguBotPluginTemplate -.- PlaguBot\n    PlaguBotBotTemplate -.- PlaguBot
    "},{"location":"krontab/index.html","title":"krontab","text":"

    Library was created to give oppotunity to launch some things from time to time according to some schedule in runtime of applications.

    "},{"location":"krontab/index.html#how-to-use","title":"How to use","text":"

    There are several ways to configure and use this library:

    • From some string
    • From builder

    Anyway, to start some action from time to time you will need to use one of extensions/functions:

    val kronScheduler = /* creating of KronScheduler instance */;\nkronScheduler.doWhile {\n// some action\ntrue // true - repeat on next time\n}\n
    "},{"location":"krontab/index.html#including-in-project","title":"Including in project","text":"

    If you want to include krontab in your project, just add next line to your dependencies part:

    implementation \"dev.inmo:krontab:$krontab_version\"\n

    Next version is the latest currently for the library:

    For old version of Gradle, instead of implementation word developers must use compile.

    "},{"location":"krontab/index.html#config-from-string","title":"Config from string","text":"

    Developers can use more simple way to configure repeat times is string. String configuring like a crontab, but with a little bit different meanings:

    /--------------- Seconds\n| /------------- Minutes\n| | /----------- Hours\n| | | /--------- Days of months\n| | | | /------- Months\n| | | | | /----- (optional) Year\n| | | | | | /--- (optional) Timezone offset\n| | | | | | |  / (optional) Week days\n* * * * * * 0o *w\n

    It is different with original crontab syntax for the reason, that expected that in practice developers will use seconds and minutes with more probability than months (for example) or even years. In fact, developers will use something like:

    doWhile(\"/5 * * * *\") {\nprintln(\"Called\")\ntrue // true - repeat on next time\n}\n

    An other version:

    doInfinity(\"/5 * * * *\") {\nprintln(\"Called\")\n}\n

    Both of examples will print Called message every five seconds.

    "},{"location":"krontab/index.html#config-via-builder","title":"Config via builder","text":"

    Also, this library currently supports DSL for creating the same goals:

    val kronScheduler = buildSchedule {\nseconds {\nfrom (0) every 5\n}\n}\nkronScheduler.doWhile {\nprintln(\"Called\")\ntrue // true - repeat on next time\n}\n

    Or

    val kronScheduler = buildSchedule {\nseconds {\n0 every 5\n}\n}\nkronScheduler.doWhile {\nprintln(\"Called\")\ntrue // true - repeat on next time\n}\n

    Or

    val kronScheduler = buildSchedule {\nseconds {\n0 every 5\n}\n}\nkronScheduler.doInfinity {\nprintln(\"Called\")\n}\n

    All of these examples will do the same things: print Called message every five seconds.

    "},{"location":"krontab/index.html#do-functions","title":"do* functions","text":"

    With regular doOnce/doWhile/doInfinity there are two types of their variations: local and timezoned. Local variations (doOnceLocal/doWhileLocal/doInfinityLocal) will pass DateTime as an argument into the block:

    doInfinityLocal(\"/5 * * * *\") {\nprintln(it) // will print current date time\n}\n

    Timezoned variations (doOnceTz/doWhileTz/doInfinityTz) will do the same thing but pass as an argument DateTimeTz:

    doInfinityTz(\"/5 * * * * 0o\") {\nprintln(it) // will print current date time in UTC\n}\n

    It is useful in cases when you need to get the time of calling and avoid extra calls to system time.

    "},{"location":"krontab/index.html#helpful-table-for","title":"Helpful table for","text":"No args Local DateTime Local DateTimeTz with offset of KronScheduler Call only near time doOnce doOnceLocal doOnceTz Call while condition is true doWhile doWhileLocal doWhileTz Work infinity* doInfinity doInfinityLocal doInfinityTz

    *Here there is an important notice, that Work infinity is not exactly infinity. Actually, that means that do while coroutine is alive and in fact executing will be stopped when coroutine became cancelled.

    "},{"location":"krontab/index.html#kronscheduler-as-a-flow","title":"KronScheduler as a Flow","text":"

    Any KronSchedulercan e converted to a Flow<DateTime using extension asFlow:

    val kronScheduler = buildSchedule {\nseconds {\n0 every 1\n}\n}\nval flow = kronScheduler.asFlow()\n

    So, in this case any operations related to flow are available and it is expected that they will work correctly. For example, it is possible to use this flow with takeWhile:

    flow.takeWhile {\ncondition()\n}.collect {\naction()\n}\n
    "},{"location":"krontab/index.html#offsets","title":"Offsets","text":"

    Offsets in this library works via passing parameter ending with o in any place after month config. Currently there is only one format supported for offsets: minutes of offsets. To use time zones you will need to call next method with DateTimeTz argument or nextTimeZoned method with any KronScheduler instance, but in case if this scheduler is not instance of KronSchedulerTz it will work like you passed just DateTime.

    Besides, in case you wish to use time zones explicitly, you will need to get KronSchedulerTz. It is possible by:

    • Using createSimpleScheduler/buildSchedule/KrontabTemplate#toSchedule/KrontabTemplate#toKronScheduler methods with passing defaultOffset parameter
    • Using SchedulerBuilder#build/createSimpleScheduler/buildSchedule/KrontabTemplate#toSchedule/KrontabTemplate#toKronScheduler methods with casting to KronSchedulerTz in case you are pretty sure that it is timezoned KronScheduler
    • Creating your own implementation of KronSchedulerTz
    "},{"location":"krontab/index.html#note-about-week-days","title":"Note about week days","text":"

    Unlike original CRON, here week days:

    • Works as AND: cron date time will search first day which will pass requirement according all parameters including week days
    • You may use any related to numbers syntax with week days: 0-3w, 0,1,2,3w, etc.
    • Week days (like years and offsets) are optional and can be placed anywhere after month
    "},{"location":"krontab/describing/krontabscheduler.html","title":"KrontabScheduler","text":"

    KronScheduler is the simple interface with only one function next. This function optionally get as a parameter DateTime which will be used as start point for the calculation of next trigger time. This function will return the next DateTime when something must happen.

    "},{"location":"krontab/describing/krontabscheduler.html#default-realisation","title":"Default realisation","text":"

    Default realisation (CronDateTimeScheduler) can be created using several ways:

    • Via buildSchedule (or createSimpleScheduler) functions with crontab-like syntax parameter
    • Via buildSchedule (or SchedulerBuilder object), which using lambda to configure scheduler

    In the examples below the result of created scheduler will be the same.

    "},{"location":"krontab/describing/krontabscheduler.html#crontab-like-way","title":"Crontab-like way","text":"

    Crontab-like syntax

    See String format for more info about the crontab-line syntax

    This way will be very useful for cases when you need to configure something via external configuration (from file on startup or via some parameter from requests, for example):

    val schedule = \"5 * * * *\"\nval scheduler = buildSchedule(schedule)\nscheduler.asFlow().onEach {\n// this block will be called every minute at 5 seconds\n}.launchIn(someCoroutineScope)\n
    "},{"location":"krontab/describing/krontabscheduler.html#lambda-way","title":"Lambda way","text":"

    In case of usage builder (lets call it lambda way), you will be able to configure scheduler in more type-safe way:

    val scheduler = buildSchedule {\nseconds {\nat(5)\n}\n}\nscheduler.asFlow().onEach {\n// this block will be called every minute at 5 seconds\n}.launchIn(someCoroutineScope)\n
    "},{"location":"krontab/describing/krontabscheduler.html#custom-scheduler","title":"Custom scheduler","text":"

    You are always able to use your own realisation of scheduler. For example:

    class RandomScheduler : KronScheduler {\noverride suspend fun next(relatively: DateTime): DateTime {\nreturn relatively + DateTimeSpan(seconds = Random.nextInt() % 60)\n}\n}\n

    In the example above we have created RandomScheduler, which will return random next time in range 0-60 seconds since relatively argument.

    "},{"location":"krontab/describing/string-format.html","title":"String format","text":"

    As in crontab util, this library have almost the same format of string:

    Seconds Minutes Hours Days of months Months Years Timezone Offset Week days Milliseconds Range 0..59 0..59 0..23 0..30 0..11 Any Int Any Int 0..6 0..999 Suffix - - - - - - o w ms Optional \u274c \u274c \u274c \u274c \u274c \u2705 \u2705 \u2705 \u2705 Full syntax support \u2705 \u2705 \u2705 \u2705 \u2705 \u2705 \u274c \u2705 \u2705 Position 0 1 2 3 4 Any after months Any after months Any after months Any after months Examples 0, */15, 30 0, */15, 30 0, */15, 22 0, */15, 30 0, */5, 11 0, */15, 30 60o (UTC+1) 0w, */2w, 4w 0ms, */150ms, 300ms

    Example with almost same description:

    /-------------------- (0-59) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Seconds\n| /------------------ (0-59) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Minutes\n| | /---------------- (0-23) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Hours\n| | | /-------------- (0-30) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Days of months\n| | | | /------------ (0-11) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Months\n| | | | | /---------- (optional, any int) Year\n| | | | | | /-------- (optional) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Timezone offset\n| | | | | | |  /----- (optional, 0-6) \u00b7\u00b7\u00b7 Week days\n| | | | | | |  |  /-- (optional, 0-999) \u00b7 Milliseconds (0 by default)\n* * * * * * 0o *w 0ms\n

    Years, timezone, week days and milliseconds are optional settings. Next snippets are equal:

    */15 * * * *\n*/15 * * * * * // with year\n*/15 * * * * * 0ms // with year and milliseconds\n
    "},{"location":"krontab/describing/string-format.html#supported-syntax","title":"Supported syntax","text":"

    Currently the library support next syntax for date/time elements:

    • {int}-{int} - ranges
    • {int}/{int} - start/step
    • */{int} - every {int}
    • {int} - just at the time
    • {other_element},{other_element} - listing
    • F or f - first possible value
    • L or l - last possible value (last day of month, for example)
    "},{"location":"krontab/describing/string-format.html#ranges","title":"Ranges","text":"

    Ranges are working like common rangeTo (or ..) in kotlin:

    0-5 * * * *\n

    In the example above scheduler will trigger every second from the beginning of the minute up to fifth second of minute.

    "},{"location":"krontab/describing/string-format.html#startstep","title":"Start/Step","text":"

    Start/step is a little bit more complicated syntax. It means start from the first element, repeat triggering every second element. Examples:

    5/15 * * * *\n

    Means that each minute starting from fifth second it will repeat triggering every fifteenth second: 5, 20, 35, 50.

    "},{"location":"krontab/describing/string-format.html#every","title":"Every","text":"

    Every is more simple syntax and could be explained as a shortcut for 0/{int}. Example:

    */15 * * * *\n

    Means that each minute it will repeat triggering every fifteenth second: 0, 15, 30, 45.

    "},{"location":"krontab/describing/string-format.html#just-at-the-time","title":"Just at the time","text":"

    The most simple syntax. It means, that scheduler will call triggering every time when element was reached:

    15 * * * *\n

    Means that each minute scheduler will call triggering at the fifteenth second.

    "},{"location":"krontab/describing/string-format.html#listing","title":"Listing","text":"

    All the previous elements can be combined with listing. Lets just see several examples:

    0,10 * * * *\n

    Will trigger every minute at the 0 and 10 seconds (see Just at the time)

    0-5,10 * * * *\n

    Will trigger every minute from 0 to 5 seconds and at the 10 seconds (see Ranges)

    "},{"location":"krontab/describing/string-format.html#examples","title":"Examples","text":"
    • 0/5 * * * * for every five seconds triggering
    • 0/5,L * * * * for every five seconds triggering and on 59 second
    • 0/15 30 * * * for every 15th seconds in a half of each hour
    • 0/15 30 * * * 500ms for every 15th seconds in a half of each hour when milliseconds equal to 500
    • 1 2 3 F,4,L 5 for triggering in near first second of second minute of third hour of first, fifth and last days of may
    • 1 2 3 F,4,L 5 60o for triggering in near first second of second minute of third hour of first, fifth and last days of may with timezone UTC+01:00
    • 1 2 3 F,4,L 5 60o 0-2w for triggering in near first second of second minute of third hour of first, fifth and last days of may in case if it will be in Sunday-Tuesday week days with timezone UTC+01:00
    • 1 2 3 F,4,L 5 2021 for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year
    • 1 2 3 F,4,L 5 2021 60o for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year with timezone UTC+01:00
    • 1 2 3 F,4,L 5 2021 60o 0-2w for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00
    • 1 2 3 F,4,L 5 2021 60o 0-2w 500ms for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 when milliseconds will be equal to 500
    "},{"location":"krontab/introduction/faq.html","title":"FAQ","text":""},{"location":"krontab/introduction/faq.html#how-oftern-new-versions-are-releasing","title":"How oftern new versions are releasing?","text":"

    Not very often. It depends on libraries (coroutines, korlibs/klock) updates and on some new awesome, but lightweight, features coming.

    "},{"location":"krontab/introduction/faq.html#where-this-library-could-be-useful","title":"Where this library could be useful?","text":"

    First of all, this library will be useful for long uptime applications which have some tasks to do from time to time.

    "},{"location":"krontab/introduction/faq.html#how-to-use-crontab-like-syntax","title":"How to use crontab-like syntax?","text":"

    In two words, you should call buildSchedule or createSimpleScheduler:

    buildSchedule(\"5 * * * *\").asFlow().collect { /* do something */ }\n

    You can read more about syntax in String format section.

    "},{"location":"krontab/introduction/how-to-use.html","title":"How to use","text":""},{"location":"krontab/introduction/how-to-use.html#previous-pages","title":"Previous pages","text":"
    • Including in project
    "},{"location":"krontab/introduction/how-to-use.html#buildschedule","title":"buildSchedule","text":"

    Custom KronScheduler

    You are always may create your own scheduler. In this section will be presented different ways and examples around standard CronDateTimeScheduler builders buildSchedule. You can read about schedulers in KrontabScheduler

    Currently, buildSchedule is the recommended start point for every scheduler. Usually, it is look like:

    val scheduler = buildSchedule(\"5 * * * *\")\n

    Or:

    val scheduler = buildSchedule {\nseconds {\nat(5)\n}\n}\n

    On the top of any KronScheduler currently there are several groups of extensions:

    • Executes
    • Shortcuts
    • Flows
    "},{"location":"krontab/introduction/how-to-use.html#executes","title":"Executes","text":"

    All executes are look like do.... All executes are described below:

    • doOnce - will get the next time for executing, delay until that time and call block with returning of the block result
    • doWhile - will call doOnce while it will return true (that means that block must return true if it expects that next call must happen). In two words: it will run while block returning true
    • doInfinity - will call the block using doWhile with predefined returning true. In two words: it will call block while it do not throw error
    "},{"location":"krontab/introduction/how-to-use.html#shortcuts","title":"Shortcuts","text":"

    Shortcuts are the constants that are initializing in a lazy way to provide preset KronSchedulers. For more info about KrontabScheduler you can read its own page.

    • AnyTimeScheduler - will always return incoming DateTime as next
    • Every*Scheduler - return near * since the passed relatively:
    • EverySecondScheduler / KronScheduler.everyMillisecond
    • EverySecondScheduler / KronScheduler.everySecond
    • EveryMinuteScheduler / KronScheduler.everyMinute
    • EveryHourScheduler / KronScheduler.hourly
    • EveryDayOfMonthScheduler / KronScheduler.daily
    • EveryMonthScheduler / KronScheduler.monthly
    • EveryYearScheduler / KronScheduler.annually
    "},{"location":"krontab/introduction/how-to-use.html#flows","title":"Flows","text":"

    Here currently there is only one extension for KronScheduler: KronScheduler#asFlow. As a result you will get Flow<DateTime> (in fact SchedulerFlow) which will trigger next emit on each not null next DateTime

    "},{"location":"krontab/introduction/including-in-project.html","title":"Including in project","text":"

    In two words, you must add dependency dev.inmo:krontab:$krontab_version to your project. The latest version presented by next badge:

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

    To use this library, you will need to include MavenCentral repository in you project

    "},{"location":"krontab/introduction/including-in-project.html#buildgradle","title":"build.gradle","text":"
    mavenCentral()\n
    "},{"location":"krontab/introduction/including-in-project.html#dependencies","title":"Dependencies","text":"

    Next snippets must be placed into your dependencies part of build.gradle (for gradle) or pom.xml (for maven).

    "},{"location":"krontab/introduction/including-in-project.html#gradle","title":"Gradle","text":"
    implementation \"dev.inmo:krontab:$krontab_version\"\n
    "},{"location":"krontab/introduction/including-in-project.html#maven","title":"Maven","text":"
    <dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>krontab</artifactId>\n<version>${krontab_version}</version>\n</dependency>\n
    "},{"location":"kslog/index.html","title":"KSLog","text":"

    It is simple and easy-to-use tool for logging on the most popular platforms in Kotlin Multiplatform:

    By default, KSLog is using built-in tools for logging on each supported platform:

    • java.util.logging.Logger for JVM
    • android.util.Log for Android
    • Console for JS

    But you always may create your logger and customize as you wish:

    KSLog.default = KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->\n// do your logging\n}\n

    This library also supports native targets in experimental mode. By default, all native targets will use simple printing in the console

    "},{"location":"kslog/index.html#how-to-use","title":"How to use","text":""},{"location":"kslog/index.html#fast-travel","title":"Fast-travel","text":"

    Just use some boring extensions like:

    KSLog.i(\"Some message\")\n// OR\nKSLog.i(\"Some tag\", \"Some message\")\n// OR\nKSLog.i(\"Some tag\", \"Some message\", IllegalArgumentException(\"So, that is exception :)\"))\n// OR\nKSLog.i(\"Some optional tag\", Exception(\"Optional\")) { \"Lazy inited message\" }\n// OR\nKSLog.iS(\"Some optional tag\", Exception(\"Optional\")) { \"Lazy inited message for suspendable calculation of text\" }\n// OR EVEN\nKSLog.l(LogLevel.INFO, \"Some tag\", \"Some message\", IllegalArgumentException(\"So, that is exception :)\"))\n// OR\nKSLog.l(LogLevel.INFO, \"Some optional tag\", IllegalArgumentException(\"So, that is exception :)\")) { \"And lazily inited message\" }\n
    "},{"location":"kslog/index.html#a-little-bit-deeper","title":"A little bit deeper","text":"

    There are several important \u201cterms\u201d in context of this library:

    • Default logger (available via KSLog.default or simply KSLog)
    • Local logger (can be created via KSLog functions and passed anywhere as KSLog)
    • Logging shortcuts like KSLog.i/KSLog.info
    • Built-in extension Any.logger which allow you to create logger binded to the default with the tag based on the class of receiver
      • Be careful with the receivers: if you will use some extension like apply, the receiver will be different with your class inside of that apply

    Every logging extension (like KSLog.i) have its analog with lazy inited message text and the same one with suffix S (like KSLog.iS) for the suspendable message calculation.

    Default logger can be created by passing defaultTag and one of variants log level filters: set or minimal loggable level. In JVM you also may setup any logger as base logger for default realizations of KSLog. Besides, you may use your own callback (on any target platform) as output of logging:

    val logger = KSLog { logLevel, optionalTag, message, optionalThrowable ->\nprintln(\"[$logLevel] $optionalTag - $message: $optionalThrowable.stackTraceToString()\")\n}\n

    In the example above we will take the logger which will just print incoming data as common output.

    "},{"location":"kslog/index.html#installation","title":"Installation","text":""},{"location":"kslog/index.html#gradle","title":"Gradle","text":"
    implementation \"dev.inmo:kslog:$kslog_version\"\n
    "},{"location":"kslog/index.html#maven","title":"Maven","text":"
    <dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>kslog</artifactId>\n<version>${kslog_version}</version>\n</dependency>\n
    "},{"location":"kslog/logging.html","title":"Logging","text":"

    Message type notice

    On this page all the messages will be just simple String, but you may pass any object as the message

    As has been said in the setup section, this library contains next levels of logging with their default representations on each platform:

    Weight (by order) LogLevel name JS JVM Loggers Android 0 DEBUG console.log Level.FINEST Log.d 1 VERBOSE console.info Level.FINE Log.v 2 INFO console.info Level.INFO Log.i 3 WARNING console.warn Level.WARNING Log.w 4 ERROR console.error Level.SEVERE Log.e 5 ASSERT console.error Level.SEVERE Log.wtf

    Each of these levels have fullname and shortname shortcat extensions:

    • KSLog.debug/KSLog.d/KSLog.dS
    • KSLog.verbose/KSLog.v/KSLog.vS
    • KSLog.info/KSLog.i/KSLog.iS
    • KSLog.warning/KSLog.w/KSLog.wS
    • KSLog.error/KSLog.e/KSLog.eS
    • KSLog.assert/KSLog.wtf/KSLog.wtfS

    And any of these shortcuts may accept one of several arguments combinations:

    • Tag (Optional), Throwable (Optional), Message Builder (simple inline callback for lazy creating of log message). This type of arguments is duplicated with S suffix for suspendable messages creating, for example
    • Message, Throwable (Optional)
    • Tag, Message, Throwable (Optional)

    So, when you want to log some expected exception, there are three common ways to do it:

    val logger = KSLog.default\n// with callback\nlogger.info(tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.infoS(tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.info(\"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.info(tag, \"Some your message for this event\", throwable)\n

    Of course, any of this calls can be shortenned:

    val logger = KSLog.default\n// with callback\nlogger.i(tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.iS(tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.i(\"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.i(tag, \"Some your message for this event\", throwable)\n

    There is special shortcat - for base performLog. In that case the only change is that you will require to pass the LogLevel more obviously:

    val logger = KSLog.default\n// with callback\nlogger.log(LogLevel.INFO, tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.logS(LogLevel.INFO, tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.log(LogLevel.INFO, \"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.log(LogLevel.INFO, tag, \"Some your message for this event\", throwable)\n

    OR

    val logger = KSLog.default\n// with callback\nlogger.l(LogLevel.INFO, tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.lS(LogLevel.INFO, tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.l(LogLevel.INFO, \"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.l(LogLevel.INFO, tag, \"Some your message for this event\", throwable)\n
    "},{"location":"kslog/setup.html","title":"Setup","text":""},{"location":"kslog/setup.html#dependency-installation","title":"Dependency installation","text":""},{"location":"kslog/setup.html#gradle-groovy","title":"Gradle (Groovy)","text":"
    implementation \"dev.inmo:kslog:$kslog_version\"\n
    "},{"location":"kslog/setup.html#gradle-kotlin-script","title":"Gradle (Kotlin Script)","text":"
    implementation(\"dev.inmo:kslog:$kslog_version\")\n
    "},{"location":"kslog/setup.html#maven-pom","title":"Maven (pom)","text":"
    <dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>kslog</artifactId>\n<version>${kslog_version}</version>\n</dependency>\n
    "},{"location":"kslog/setup.html#setup-in-code","title":"Setup in code","text":"

    The main point in setup in your code is to setup default logger:

    KSLog.default = KSLog(\"defaultTag\")\n

    You may use custom messageFormatter in any of KSLog factory to customize output of KSLog logging. For example:

    KSLog(\n\"loggingWithCustomFormat\",\nmessageFormatter = { level, tag, message, throwable ->\nprintln(\"[$level] $tag - $message: $throwable\")\n}\n)\n

    Additionally you may use one of several different settings:

    • minLoggingLevel - minimal logging level for the log which will be logged. The order of log level is next:
    • DEBUG
    • VERBOSE
    • INFO
    • WARNING
    • ERROR
    • ASSERT
    • levels - and iterable with the levels which should be logged
    • firstLevel,secondLevel,otherLevels - as levels, but vararg :)

    In case you are passing minLoggingLevel, the level and more important levels will be passed to logs. For example, when you are settings up your logger as in next snippet:

    val logger = KSLog(\n\"exampleTag\",\nminLoggingLevel = LogLevel.INFO\n)\n

    The next levels will be logged with logger:

    • INFO
    • WARNING
    • ERROR
    • ASSERT
    "},{"location":"kslog/setup.html#special-loggers","title":"Special loggers","text":""},{"location":"kslog/setup.html#callbackkslog","title":"CallbackKSLog","text":"

    It is logger which will call incoming performLogCallback on each logging. This logger can be create simply with one callback:

    KSLog { level, tag, message, throwable ->\nprintln(\"[$level] $tag - $message: $throwable\")\n}\n
    "},{"location":"kslog/setup.html#taglogger","title":"TagLogger","text":"

    It is simple value class which can be used for zero-cost usage of some tag and calling for KSLog.default. For example, if you will create tag logger with next code:

    val logger = TagLogger(\"tagLoggerTag\")\n

    The logger will call KSLog.default with the tag tagLoggerTag on each calling of logging.

    "},{"location":"kslog/setup.html#filterkslog","title":"FilterKSLog","text":"

    This pretty simple logger will call its fallbackLogger only in cases when incoming messageFilter will return true for logging:

    val baseLogger = KSLog(\"base\") // log everything with the tag `base` if not set other\nval filtered = baseLogger.filtered { _, t, _ ->\nt == \"base\"\n}\n

    In the example above baseLogger will perform logs in two ways: when it has been called directly or when we call log performing with the tag \"base\" or null. Besides, you can see there extension filtered which allow to create FilterKSLog logger with simple lambda.

    "},{"location":"kslog/setup.html#typedkslog","title":"TypedKSLog","text":"

    This logger accepts map of types with the target loggers. You may build this logger with the special simple DSL:

    val baseLogger = KSLog(\"base\") // log everything with the tag `base` if not set other\nval typed = buildTypedLogger {\non<Int>(baseLogger) // log all ints to the baseLogger\non<Float> { _, _, message, _ ->// log all floats to the passed logger\nprintln(message.toString()) // just print all floats\n}\ndefault { level, tag, message, throwable ->\nKSLog.performLog(level, tag, message, throwable)\n}\n}\n
    "},{"location":"kslog/setup.html#automatical-loggers","title":"Automatical loggers","text":"

    There are two things which can be useful in your code: logger and logTag extensions. logTag is the autocalculated by your object classname tag. logger extension can be used with applying to any object like in the next snippet:

    class SomeClass {\ninit {\nlogger.i(\"inited\")\n}\n}\n

    The code above will trigger calling of logging in KSLog.default with level LogLevel.INFO using tag SomeClass and message \"inited\". As you could have guessed, logger is using TagLogger with logTag underhood and the most expensive operation here is automatical calculation of logTag.

    • Extension logger
    "},{"location":"kslog/setup.html#jvm-specific-setup","title":"JVM specific setup","text":"

    For JVM you may setup additionally use java loggers as the second parameter of KSLog factory. For example:

    KSLog(\n\"yourTag\"\nLogger.getLogger(\"YourJavaLoggerName\")\n)\n
    "},{"location":"micro_utils/index.html","title":"MicroUtils","text":"

    MicroUtils is a set of libraries to help me (and, I hope, you too) in some routine doings of coding.

    First of all, this library collection is oriented to use next technologies:

    • Kotlin Coroutines
    • Kotlin Serialization
    • Kotlin Exposed
    • Ktor
    • Koin
    • Korlibs

    Warning

    Due to complexity of this library, it is possible that some things will be missed or inactual. Me and the users of this library will try hard to keep its docs as actual as possible, but in case you will find some inconsistency of docs and library work (signatures, behaviour, API) you may write me directly in my telegram

    "},{"location":"navigation/index.html","title":"Navigation","text":"

    Navigation is a library for simple management for your app views (or some other logics). In this library there are several important terms:

    • Node - is a core thing. Node itself contains current config and its state
    • Chain - some sequence of nodes. In one chain only the last one node can be active
    "},{"location":"navigation/index.html#nodes-tree","title":"Nodes tree","text":"

    Let\u2019s see the next sample:

    flowchart BT\n\n    NodeN1(N1)\n    NodeN2(N2)\n    NodeN3(N3)\n\n    class NodeN1 navigation-paused;\n    class NodeN1 navigation-part;\n    class NodeN2 navigation-paused;\n    class NodeN2 navigation-part;\n    class NodeN3 navigation-resumed;\n    class NodeN3 navigation-part;\n\n    subgraph RootChain\n        NodeN1 --> NodeN2\n        NodeN2 --> NodeN3\n    end\n    class RootChain navigation-resumed;\n    class RootChain navigation-part;

    Any hierarchy starts with some root chain.

    "},{"location":"tgbotapi/index.html","title":"TelegramBotAPI","text":"

    Hello! This is a set of libraries for working with Telegram Bot API.

    "},{"location":"tgbotapi/index.html#examples","title":"Examples","text":"

    There are several things you need to do to launch examples below:

    • Add mavenCentral() to your project repositories
      • Maven variant
    • Add dependency implementation \"dev.inmo:tgbotapi:$tgbotapi_version\"
      • Replace tgbotapi_version with exact version (see last one in the table above) or put variable with this name in project
      • Alternative variant for maven here

    More including instructions available here. Other configuration examples:

    • For multiplatform
    • For JVM
    "},{"location":"tgbotapi/index.html#most-common-example","title":"Most common example","text":"
    suspend fun main() {\nval bot = telegramBot(TOKEN)\nbot.buildBehaviourWithLongPolling {\nprintln(getMe())\nonCommand(\"start\") {\nreply(it, \"Hi:)\")\n}\n}.join()\n}\n

    In this example you will see information about this bot at the moment of starting and answer with Hi:) every time it gets message /start

    "},{"location":"tgbotapi/index.html#handling-only-last-messages","title":"Handling only last messages","text":"
    suspend fun main() {\nval bot = telegramBot(TOKEN)\nval flowsUpdatesFilter = FlowsUpdatesFilter()\nbot.buildBehaviour(flowUpdatesFilter = flowsUpdatesFilter) {\nprintln(getMe())\nonCommand(\"start\") {\nreply(it, \"Hi:)\")\n}\nretrieveAccumulatedUpdates(this).join()\n}\n}\n

    The main difference with the previous example is that bot will get only last updates (accumulated before bot launch and maybe some updates it got after launch)

    "},{"location":"tgbotapi/index.html#build-a-little-bit-more-complex-behaviour","title":"Build a little bit more complex behaviour","text":"
    suspend fun main() {\nval bot = telegramBot(TOKEN)\nbot.buildBehaviourWithLongPolling {\nprintln(getMe())\nval nameReplyMarkup = ReplyKeyboardMarkup(\nmatrix {\nrow {\n+SimpleKeyboardButton(\"nope\")\n}\n}\n)\nonCommand(\"start\") {\nval photo = waitPhoto(\nSendTextMessage(it.chat.id, \"Send me your photo please\")\n).first()\nval name = waitText(\nSendTextMessage(\nit.chat.id,\n\"Send me your name or choose \\\"nope\\\"\",\nreplyMarkup = nameReplyMarkup\n)\n).first().text.takeIf { it != \"nope\" }\nsendPhoto(\nit.chat,\nphoto.mediaCollection,\nentities = buildEntities {\nif (name != null) regular(name) // may be collapsed up to name ?.let(::regular)\n}\n)\n}\n}.join()\n}\n
    "},{"location":"tgbotapi/index.html#more-examples","title":"More examples","text":"

    You may find examples in this project. Besides, you are always welcome in our chat.

    "},{"location":"tgbotapi/faq.html","title":"FAQ","text":""},{"location":"tgbotapi/faq.html#how-to-filter-updates-in-some-part-of-behaviourbuilder","title":"How to filter updates in some part of BehaviourBuilder?","text":"

    You may create subcontext with BehaviourBuilder.createSubContextAndDoWithUpdatesFilter and pass there updatesUpstreamFlow parameter with any operations over parent behaviour builder:

    buildBehaviourWithLongPolling {\ncreateSubContextAndDoWithUpdatesFilter(\nupdatesUpstreamFlow = filter { /* some condition */ },\nstopOnCompletion = false // disable stopping of sub context after setup\n) {\nonCommand() //...\n}\n}\n
    "},{"location":"tgbotapi/faq.html#additional-info","title":"Additional info","text":"
    • Flows docs
    • BehaviourBuilder
    "},{"location":"tgbotapi/faq.html#cases","title":"Cases","text":"
    • Filtering of chats and users:
          updatesUpstreamFlow = filter { it.sourceChat() ?.id == requiredChatId || it.sourceUser() ?.id == requiredUserId }\n
      • See:
        • Update.sourceChat
        • Update.sourceUser
    "},{"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(\nmatrix {\nrow {\nadd(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 {\nrow {\nsimpleButton(\"7\")\nsimpleButton(\"8\")\nsimpleButton(\"9\")\nsimpleButton(\"*\")\n}\nrow {\nsimpleButton(\"4\")\nsimpleButton(\"5\")\nsimpleButton(\"6\")\nsimpleButton(\"/\")\n}\nrow {\nsimpleButton(\"1\")\nsimpleButton(\"2\")\nsimpleButton(\"3\")\nsimpleButton(\"-\")\n}\nrow {\nsimpleButton(\"0\")\nsimpleButton(\".\")\nsimpleButton(\"=\")\nsimpleButton(\"+\")\n}\n}\n// inline keyboard\ninlineKeyboard {\nrow {\ndataButton(\"Get random music\", \"random\")\n}\nrow {\nurlButton(\"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 {\nvar i = 0\nwhile (isActive) {\nval newInfo = EditLiveLocationInfo(\nlatitude = i.toDouble(),\nlongitude = i.toDouble(),\nreplyMarkup = flatInlineKeyboard {\ndataButton(\"Cancel\", \"cancel\")\n}\n)\nemit(newInfo)\ni++\ndelay(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(\nit.chat.id,\nlocationsFlow,\nsentMessageFlow = FlowCollector { currentMessageState.emit(it) }\n)\n// this code will be called after `locationsFlow` will ends\n
      OR
      scope.launch {\nhandleLiveLocation(\nit.chat.id,\nlocationsFlow,\nsentMessageFlow = 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\")\nitems.forEachIndexed { i, item ->\nif (i % 2) {\nitalic(item)\n} else {\nstrikethrough(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/guides/keyboards.html","title":"Keyboards Guide","text":"

    This guide will help you choose the right keyboard for your needs and show you various API facilities available in the library to support your choice.

    "},{"location":"tgbotapi/guides/keyboards.html#introduction","title":"Introduction","text":""},{"location":"tgbotapi/guides/keyboards.html#keyboard-types","title":"Keyboard Types","text":"

    The first thing you need to know is that there are two types of keyboards available in the Telegram Bot API: reply and inline keyboards.

    Resize option

    In the screenshots above (and in the most others) you may see usage of reply keyboards without resize_keyboard. In case you will use resize_keyboard = true the keyboard will be smaller.

    Note the differences in the way these keyboards are shown to a user.

    A reply keyboard is shown under the message input field. It replaces the device\u2019s native input method on a mobile device.

    An inline keyboard is shown as a part of the message in the chat.

    "},{"location":"tgbotapi/guides/keyboards.html#simple-keyboard-interactions","title":"Simple Keyboard Interactions","text":"

    When a user clicks on a simple reply keyboard button, its text is just sent in the chat.

    When a user clicks on a simple inline keyboard button, nothing is sent to the chat. Instead, a callback query (a fancy way to say \u201ca request\u201d) is sent directly to the bot and the button is highlighted. It will stay highlighted until the bot acks the callback.

    It\u2019s a common mistake to forget to handle callback queries

    It leads to the buttons being highlighted for long periods of time, which leads to a bad user experience. Don\u2019t forget to handle these callbacks!

    As new messages arrive, a reply keyboard will stay there, while the inline keyboard will stick to the message and move with it.

    Ups\u2026 The reply keyboard is now far away from the message it was sent with.

    Actually, they are two different unrelated entities now: the original message and the reply keyboard. A reply keyboard persists until you explicitly remove it or replace it with a different one.

    It\u2019s a common mistake to forget to remove or replace reply keyboards

    It leads to the keyboards being shown forever. Don\u2019t forget to remove reply keyboards when you don\u2019t need them anymore!

    You also may use option one_time_keyboard and the keyboard will be automatically removed after user interaction

    An inline keyboard could also be removed or changed by editing the original message it was attached to.

    "},{"location":"tgbotapi/guides/keyboards.html#extended-keyboard-interactions","title":"Extended Keyboard Interactions","text":"

    Keyboards are not limited to text only. They could be used to ask users for different things, like payments, locations, phone numbers, etc. They could be used to open arbitrary URLs or web apps. Telegram clients process these buttons and interact with the users in the appropriate ways.

    For the full list of options, see the official documentation on reply and inline keyboards.

    "},{"location":"tgbotapi/guides/keyboards.html#basic-api-classes","title":"Basic API & Classes","text":"

    Now, that you know the basics, let\u2019s see how to use the library.

    "},{"location":"tgbotapi/guides/keyboards.html#keyboards","title":"Keyboards","text":"

    In Telegram Bot API keyboards are sent to the user as a part of an interaction via the reply_markup parameter. More specifically, this parameter is available:

    • in the sendXXX methods, like sendMessage, sendPhoto, sendSticker, etc.
    • in the copyMessage method
    • in the editMessageXXX methods, like editMessageText, editMessageCaption, editMessageReplyMarkup, etc. This also includes stopXXX methods like the stopMessageLiveLocation method.

    Tip

    editMessageReplyMarkup is specifically designed to edit a message\u2019s inline keyboard.

    Sending inline keyboards is also supported in inline mode through the reply_markup parameter of the InlineQueryResult type and its inheritors. However, this inline mode is unrelated to the inline keyboards.

    The reply_markup parameter accepts four different types. Two of them \u2014 ReplyKeyboardMarkup and InlineKeyboardMarkup \u2014 correspond to the reply and inline keyboards respectively. The ReplyKeyboardRemove type is used to remove reply keyboards, but it\u2019s not a keyboard itself. The last one, ForceReply, is used to force users to reply to the bot. It is not a keyboard either, but yet another dirty hack employed by the Telegram Bot API.

    Now, in the library, the WithReplyMarkup is a marker interface for all the interactions which could have a replyMarkup (represents reply_markup) parameter. It is extended by the ReplyingMarkupSendMessageRequest, and then, finally, by classes like SendTextMessage. This, basically, corresponds to the Telegram Bot API.

    Note

    You may see all the inheritors of WithReplyMarkup interfaces in the corresponding KDoc

    The other way to send a keyboard is through the replyMarkup parameter of the numerous extension methods, like sendMessage. Those are just convenient wrappers around general interaction classes, like the aforementioned SendTextMessage.

    "},{"location":"tgbotapi/guides/keyboards.html#buttons","title":"Buttons","text":"

    As we already know, keyboards consist of buttons. Button classes reside in the dev.inmo.tgbotapi.types.buttons package.

    The base class for the reply keyboard buttons is the KeyboardButton. The base class for the inline keyboard buttons is the InlineKeyboardButton.

    See their inheritors for the full list of the available buttons. The names are pretty self-explanatory and correspond to the Telegram Bot API.

    For example, to send a simple reply keyboard button, use the SimpleKeyboardButton class. To request a contact from the user through the reply, use the RequestContactKeyboardButton class. To attach a URL button to the message, use the URLInlineKeyboardButton. And to attach a callback button, use the CallbackDataInlineKeyboardButton.

    You get the idea.

    So, to send a reply keyboard use the following code:

    bot.sendMessage(\nchatId = chat,\ntext = \"What is the best Kotlin Telegram Bot API library?\",\nreplyMarkup = ReplyKeyboardMarkup(\nkeyboard = listOf(\nlistOf(\nSimpleKeyboardButton(\"ktgbotapi\"),\n),\n)\n)\n)\n

    And here is how you send a basic inline keyboard:

    bot.sendMessage(\nchatId = chat,\ntext = \"ktgbotapi is the best Kotlin Telegram Bot API library\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = listOf(\nlistOf(\nCallbackDataInlineKeyboardButton(\"I know\", \"know\"),\nURLInlineKeyboardButton(\"Learn more\", \"https://github.com/InsanusMokrassar/ktgbotapi\")\n),\n)\n),\n)\n

    When we\u2019re done with this simple quiz, we can remove the keyboard with the following code:

    bot.sendMessage(\nchatId = chat,\ntext = \"You're goddamn right!\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n

    Note

    Don\u2019t forget to remove the reply keyboards!

    "},{"location":"tgbotapi/guides/keyboards.html#matrices","title":"Matrices","text":"

    Buttons in keyboards are arranged in matrices, i.e. two-dimensional arrays, or, to say in layperson\u2019s terms, rows and columns. In contrast to the matrices you\u2019ve learned in school, keyboards are not always necessarily square. Try it:

    bot.sendMessage(\nchatId = chat,\ntext = \"In contrast to the matrices you've learned in school, keyboards are not always necessary square.\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = listOf(\nlistOf(\nCallbackDataInlineKeyboardButton(\"1\", \"1\"),\nCallbackDataInlineKeyboardButton(\"2\", \"2\"),\nCallbackDataInlineKeyboardButton(\"3\", \"3\"),\n),\nlistOf(\nCallbackDataInlineKeyboardButton(\"4\", \"4\"),\nCallbackDataInlineKeyboardButton(\"5\", \"5\"),\n),\nlistOf(\nCallbackDataInlineKeyboardButton(\"6\", \"6\"),\n)\n)\n)\n)\n

    This way of building matrices is not very convenient, so the library provides a few eloquent DSLs to simplify that.

    First, there are matrix and row, so the keyboard above can be built like this:

    bot.sendMessage(\nchatId = chat,\ntext = \"DSLs are sweet!\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = matrix {\nrow {\n+CallbackDataInlineKeyboardButton(\"1\", \"1\")\n+CallbackDataInlineKeyboardButton(\"2\", \"2\")\n+CallbackDataInlineKeyboardButton(\"3\", \"3\")\n}\nrow(\nCallbackDataInlineKeyboardButton(\"4\", \"4\"),\nCallbackDataInlineKeyboardButton(\"5\", \"5\"),\n)\nrow {\n+CallbackDataInlineKeyboardButton(\"6\", \"6\")\n}\n},\n)\n)\n

    Note

    Those plus signs are mandatory.

    Note

    There are two different row functions here. Can you spot the difference?

    A single-row matrix can be built with a flatMatrix:

    flatMatrix {\n+CallbackDataInlineKeyboardButton(\"1\", \"1\")\n+CallbackDataInlineKeyboardButton(\"2\", \"2\")\n+CallbackDataInlineKeyboardButton(\"3\", \"3\")\n+CallbackDataInlineKeyboardButton(\"4\", \"4\")\n+CallbackDataInlineKeyboardButton(\"5\", \"5\")\n}\n

    But the most convenient way to build a simple keyboard is to use the constructor-like methods: InlineKeyboardMarkup and ReplyKeyboardMarkup. Note, that they are named just like the corresponding constructor, but take a vararg of buttons. They create flat matrices, i.e. single rows.

    "},{"location":"tgbotapi/guides/keyboards.html#keyboards-dsl","title":"Keyboards DSL","text":"

    Finally, there are inlineKeyboard and replyKeyboard

    DSL methods above rely on Kotlin\u2019s feature of receivers and extensions. So, the magic is done by MatrixBuilder and RowBuilder. That\u2019s why you must use the plus sign to add buttons to the matrix: it\u2019s just an overloaded operator call, another cool Kotlin feature widely used to create sweet DSLs.

    Another bonus of using these DSLs is button builders, like payButton, dataButton, and urlButton:

    bot.sendMessage(\nchatId = chat,\ntext = \"All in one!\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = matrix {\nrow {\npayButton(\"Send money\")\ndataButton(\"Ok\", \"ok\")\nurlButton(\"Google\", \"https://google.com\")\n}\n},\n)\n)\n

    Reply keyboard builders provide similar extensions, e.g. requestLocationButton.

    So, choose the style you like \u2014 from plain Kotlin lists to sweet DSLs \u2014 and use it!

    "},{"location":"tgbotapi/guides/keyboards.html#working-with-keyboards","title":"Working with keyboards","text":"

    Working with keyboards is not something special in Telegram Bot API. As you have already seen, keyboards are just message parameters. Similarly, keyboard interactions are represented by regular Updates. I.e. when a user interacts with a keyboard, the bot receives an update.

    On the other hand, the library is heavily typed, so the actual type of update you would receive varies.

    "},{"location":"tgbotapi/guides/keyboards.html#reply-keyboards","title":"Reply keyboards","text":"

    As it was said, reply keyboards cause Telegram clients to send regular messages back to the bot. Peruse this example:

    bot.buildBehaviourWithLongPolling {\nbot.sendMessage(\nchatId = chat,\ntext = \"\ud83d\udc6e Turn in your accomplices or be prepared for a lengthy \ud83c\udf46 incarceration \u26d3 \ud83d\udc4a \u203c\",\nreplyMarkup = replyKeyboard {\n+SimpleKeyboardButton(\n\"I ain't no rat! \ud83d\udeab\ud83d\udc00\ud83e\udd10\ud83d\ude45\"\n)\n+RequestUserKeyboardButton(\n\"Rat out \ud83d\udc00 a friend \ud83d\udc64\",\nKeyboardButtonRequestUser.Common(RequestId.random())\n)\n+RequestChatKeyboardButton(\n\"Rat out \ud83d\udc00 a group of friends \ud83d\udc65\",\nKeyboardButtonRequestChat.Group(RequestId.random())\n)\n}\n)\nonText { message: CommonMessage<TextContent> ->\nassert(message.text == \"I ain't no rat! \ud83d\udeab\ud83d\udc00\ud83e\udd10\ud83d\ude45\")\nbot.reply(\nto = message,\ntext = \"Good, you're going to jail alone! \u26d3\ud83e\uddd1\u26d3\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n}\nonUserShared { message: PrivateEventMessage<UserShared> ->\nbot.reply(\nto = message,\ntext = \"Haha, you and you friend are both going to jail! \u26d3\ud83d\udc6c\u26d3\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n}\nonChatShared { message: PrivateEventMessage<ChatShared> ->\nbot.reply(\nto = message,\ntext = \"Haha, now you're all going to jail! \u26d3\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66\u26d3\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n}\n}.join()\n

    Note

    Read more about buildBehaviourWithLongPolling here

    I hope you get the idea: the bot acts like a cop and asks the user to rat out his friends via a reply keyboard (it\u2019s an imaginary situation, of course). The user may refuse to cooperate, rat out a single friend or the whole imaginary group. The bot receives the user\u2019s choices as regular updates, the code above has explicit types (generally optional in Kotlin) and an assert to demonstrate this.

    And here is how it works (the user selects the options in the order):

    Note how you handle reply keyboards: you process regular messages. For instance, a simple text button sends a regular text message indistinguishable from a case when a user simply types the same text manually.

    And don\u2019t be a rat in real life: remove the keyboards with the ReplyKeyboardRemove after you\u2019ve received the input! Otherwise, a keyboard will stay there indefinitely.

    "},{"location":"tgbotapi/guides/keyboards.html#inline-keyboards","title":"Inline keyboards","text":"

    Finally, to master the keyboards, you need to know how to handle the inline ones.

    Again, let\u2019s explore the example. Imagine you\u2019re making a quiz where users are given a question and a set of answers. Additionally, users are given a link to the wiki page to help with the question and a Google button.

    The quiz could be implemented this way:

    // A simple data class to represent a question\nval question = Question(\nimage = \"https://upload.wikimedia.org/wikipedia/commons/a/a5/Tsunami_by_hokusai_19th_century.jpg\",\nquestion = \"Who painted this?\",\nanswers = listOf(\nAnswer(\"Hokusai\", correct = true),\nAnswer(\"Sukenobu\"),\nAnswer(\"Ch\u014dshun\"),\nAnswer(\"Kiyonobu I\"),\n),\nwiki = \"https://en.wikipedia.org/wiki/Ukiyo-e\",\n)\nbot.buildBehaviourWithLongPolling {\nbot.sendPhoto(\nchatId = chat,\nfileId = InputFile.fromUrl(question.image),\ntext = question.question,\nreplyMarkup = inlineKeyboard {\n// First row: answers\nrow {\nfor (answer in question.answers.shuffled()) {\ndataButton(\ntext = answer.answer,\ndata = \"${answer.answer}:${answer.correct}\",\n)\n}\n}\n// Second row: help buttons\nrow {\nurlButton(\"Wiki \ud83d\udc81\", question.wiki)\nwebAppButton(\"Google \ud83d\udd0d\", \"https://google.com\")\n}\n}\n)\nonDataCallbackQuery { callback: DataCallbackQuery ->\nval (answer, correct) = callback.data.split(\":\")\nif (correct.toBoolean()) {\nbot.answerCallbackQuery(\ncallback,\ntext = \"$answer is a \u2705 correct answer!\",\nshowAlert = true\n)\n} else {\nbot.answerCallbackQuery(\ncallback,\ntext = \"\u274c Try again, $answer is not a correct answer\u2026\",\nshowAlert = true\n)\n}\n}\n}.join()\n

    A few important things to note here.

    First, the data buttons (they have the CallbackDataInlineKeyboardButton type, but in the code we used a neat DSL) must have unique data. If the data is not unique, Telegram clients will highlight all the buttons with the same data when a user clicks on one of them. Guess how I know that? Well, it\u2019s not in the docs, so trial and error is the only way to learn it (and many other things about the Telegram Bot API).

    Second, the way you handle inline keyboards is different from the way you handle reply keyboards. Bot API will send updates with a callback_query field populated. This field, of a CallbackQuery type, represents incoming callbacks from callback buttons in inline keyboards. The library turns them into multiple callback types, like the DataCallbackQuery we used in the example. Finally, to handle these callbacks you could use onDataCallbackQuery. Alternatively, if you\u2019re not using any DSLs, you have to handle the CallbackQueryUpdate update type.

    Third, the buttons got highlighted when a user clicks on them. When you\u2019re done with the callback, you need to answer it, by using the answerCallbackQuery function. Otherwise, the button will remain highlighted. Telegram clients will eventually remove the highlight, but it\u2019s still frustrating.

    Finally, you could choose between two styles of acknowledgment: a simple toast-like message or a modal alert. The showAlert flag controls this behavior.

    And here is the demo of the quiz:

    "},{"location":"tgbotapi/guides/keyboards.html#conclusion","title":"Conclusion","text":"

    Today we\u2019ve learned how to use keyboards in Telegram bots. There are two types of keyboards: reply and inline. Reply keyboards replace the device\u2019s keyboard and make clients send a message with the predefined content. Inline keyboards are buttons attached to messages. Clicking on them causes the client to send a callback to the bot. In both scenarios the bot receives an update of a corresponding type and has to acknowledge the keayboard interaction for the client to work properly.

    "},{"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) {\nval botToken = args.first()\nval bot = telegramBot(botToken)\nprintln(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 {\nurl \"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)\nat sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)\nat io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:36)\nat io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)\nat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)\nat kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)\nat kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)\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) {\nktorClientEngineFactory = OkHttp\nproxy = 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) {\nproxy = ProxyBuilder.socks(host = \"127.0.0.1\", port = 4001) // just an example, more info on https://ktor.io/docs/proxy.html\nktorClientConfig = {\n// configuring of ktor client\n}\nktorClientEngineFactory = {\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> {\nstrictlyOn<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\")\nbot.buildBehaviour {\nonCommand(\"start\") { // creating of trigger\nval message = it\nval content = message.content\nreply(message, \"Ok, send me one photo\") // send text message with replying on incoming message\nval photoContent = waitPhoto().first() // waitPhoto will return List, so, get first element\nval photo = downloadFile(photoContent) // ByteArray of photo\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\")\nbot.buildBehaviour {\nonCommand(\n\"start\",\ninitialFilter = {\nit.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\")\nbot.buildBehaviour {\nonCommand(\n\"start\",\nrequireOnlyCommandInMessage = 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\nit.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\nit.printStackTrace()\nnull // 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{\nit.printStackTrace()\n\"error\"\n}\n) {\nerror(\"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\")\nbot.buildBehaviour (\nscope = scope,\ndefaultExceptionsHandler = {\nit.printStackTrace()\n}\n) {\n// ...\n}\n

    OR

    val bot = telegramBotWithBehaviour (\n\"TOKEN\",\nscope = scope,\ndefaultExceptionsHandler = {\nit.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;\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;\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;\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;\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;\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;\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;\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\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;\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\nval channelAllocator: ByteReadChannelAllocator = bot.execute(DownloadFileStream(pathedFile.filePath))\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;\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) {\nonVisualGallery { // it: CommonMessage<MediaGroupContent<VisualMediaGroupPartContent>>\nit.content // MediaGroupContent<VisualMediaGroupPartContent>\nit.content.group // List<MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>>\nit.content.group.forEach { // it: MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>\nit.messageId // source message id for current media group part\nit.sourceMessage // source message for current media group part\nit.content // VisualMediaGroupPartContent\nprintln(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 {\nprintln(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\nmessageFlow.onEach {\nprintln(it)\n}.launchIn(someCoroutineScope)\n}\n// Step 3 - passing updates to filter\nbot.getUpdates().forEach {\nfilter.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\")\nbot.longPolling {\nmessageFlow.onEach {\nprintln(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\nval subroute = uuid4().toString()\n// Input/Output coroutines scope more info here: https://kotlinlang.org/docs/coroutines-guide.html\nval scope = CoroutineScope(Dispatchers.IO)\n// Here will be automatically created bot and available inside of lambda where you will setup your bot behaviour\ntelegramBotWithBehaviour(\n// Pass TOKEN inside of your application environment variables\nSystem.getenv(\"TOKEN\"),\nscope = scope\n) {\n// Set up webhooks and start to listen them\nsetWebhookInfoAndStartListenWebhooks(\n// Automatic env which will be passed by heroku to the app\nSystem.getenv(\"PORT\").toInt(),\n// Server engine. More info here: https://ktor.io/docs/engines.html\nTomcat,\n// Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\nSetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n// Just callback which will be called when exceptions will happen inside of webhooks\n{\nit.printStackTrace()\n},\n// Set up listen requests from outside\n\"0.0.0.0\",\n// Set up subroute to listen webhooks to\nsubroute,\n// BehaviourContext is the CoroutineScope and it is recommended to pass it inside of webhooks server\nscope = this,\n// BehaviourContext is the FlowsUpdatesFilter and it is recommended to pass its asUpdateReceiver as a block to retrieve all the updates\nblock = asUpdateReceiver\n)\n// Test reaction on each command with reply and text `Got it`\nonUnhandledCommand {\nreply(it, \"Got it\")\n}\n}\n// Just potentially infinite await of bot completion\nscope.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)\nval filter = flowsUpdatesFilter {\nmessageFlow.onEach {\nprintln(it) // will be printed \n}.launchIn(scope)\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\nval server = bot.setWebhookInfoAndStartListenWebhooks(\n// Automatic env which will be passed by heroku to the app\nSystem.getenv(\"PORT\").toInt(),\n// Server engine. More info here: https://ktor.io/docs/engines.html\nTomcat,\n// Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\nSetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n// Just callback which will be called when exceptions will happen inside of webhooks\n{\nit.printStackTrace()\n},\n// Set up listen requests from outside\n\"0.0.0.0\",\n// Set up subroute to listen webhooks to\nsubroute,\nscope = scope,\nblock = filter.asUpdateReceiver\n)\nserver.environment.connectors.forEach {\nprintln(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(\ntextMessages().subscribe(scope) { // here \"scope\" is a CoroutineScope\nprintln(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{\nprintln(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 {\nprintln(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 {\nprintln(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 {\nmessageFlow.onEach {\nprintln(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 {\nprintln(it) // will be printed each message update from channels and chats both\n}.launchIn(scope)\n}\n
    • TelegramBotAPI library support function aggregateFlows:
      flowsUpdatesFilter {\naggregateFlows(\nscope,\nmessageFlow,\nchannelPostFlow\n).onEach {\nprintln(it) // will be printed each message update from channels and chats both\n}.launchIn(scope)\n}\n
    • FlowsUpdatesFilter extensions:
      flowsUpdatesFilter {\nallSentMessagesFlow.onEach {\nprintln(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 {\ntextMessages(scope).onEach {\nprintln(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 {\nmediaGroupMessages(scope).onEach {\nprintln(it) // will be printed each media group messages list from both channels and chats without filtering of content\n}.launchIn(scope)\nmediaGroupPhotosMessages(scope).onEach {\nprintln(it) // will be printed each media group messages list from both channels and chats with PhotoContent only\n}.launchIn(scope)\nmediaGroupVideosMessages(scope).onEach {\nprintln(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 {\nsentMessagesWithMediaGroups(scope).onEach {\nprintln(it) // will be printed each message including each separated media group message from both channels and chats without filtering of content\n}.launchIn(scope)\nphotoMessagesWithMediaGroups(scope).onEach {\nprintln(it) // will be printed each message including each separated media group message from both channels and chats with PhotoContent only\n}.launchIn(scope)\nvideoMessagesWithMediaGroups(scope).onEach {\nprintln(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)\nval filter = flowsUpdatesFilter {\n// ...\n}\nbot.setWebhookInfoAndStartListenWebhooks(\n8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\nCIO, // 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\nSetWebhook(\n\"address.com/webhook_route\",\nFile(\"/path/to/certificate\").toInputFile(), // certificate file. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n40, // max allowed updates, by default is null\nfilter.allowedUpdates\n),\n{\nit.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\nWebhookPrivateKeyConfig( // 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),\nscope, // Kotlin coroutine scope for internal transforming of media groups\nfilter.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(),\n40, // max allowed updates, by default is null\nfilter.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}\nstartListenWebhooks(\n8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\nCIO, // 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{\nit.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\nWebhookPrivateKeyConfig( // 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),\nscope, // Kotlin coroutine scope for internal transforming of media groups\nfilter.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)\nval filter = flowsUpdatesFilter {\n// ...\n}\nval environment = applicationEngineEnvironment {\nmodule {\nrouting {\nincludeWebhookHandlingInRoute(\nscope,\n{\nit.printStackTrace()\n},\nfilter.asUpdateReceiver\n)\n}\n}\nconnector {\nhost = \"0.0.0.0\"\nport = 8080\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 +{"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":"Common and independent TelegramBotAPI Plagubot"},{"location":"index.html#dependencies-graph","title":"Dependencies graph:","text":"flowchart BT KSLog[KSLog] MicroUtils[MicroUtils] TelegramBotAPI[TelegramBotAPI] TelegramBotAPI-examples[TelegramBotAPI-examples ] PlaguBot[PlaguBot] TelegramBotAPILibraries[TelegramBotAPILibraries] PlaguBotPlugins[PlaguBotPlugins] PlaguBotExample[PlaguBotExample] BooruGrabberTelegramBot[BooruGrabberTelegramBot] SauceNaoTelegramBot[SauceNaoTelegramBot] PlaguPoster[PlaguPoster] PlaguBotSuggestionsBot[PlaguBotSuggestionsBot] TelegramBotTutorial[TelegramBotTutorial] Krontab[Krontab] KJSUiKit[KJSUiKit] SauceNaoAPI[SauceNaoAPI] Navigation[Navigation] TelegramBotAPI-bot_template[TelegramBotAPI-bot_template] PlaguBotPluginTemplate[PlaguBotPluginTemplate] PlaguBotBotTemplate[PlaguBotBotTemplate] MicroUtils --> KSLog TelegramBotAPI --> MicroUtils TelegramBotAPI-examples --> TelegramBotAPI PlaguBot --> TelegramBotAPI TelegramBotAPILibraries --> PlaguBot PlaguBotPlugins --> TelegramBotAPILibraries PlaguBotExample --> PlaguBotPlugins BooruGrabberTelegramBot --> TelegramBotAPI BooruGrabberTelegramBot --> Krontab SauceNaoTelegramBot --> TelegramBotAPI SauceNaoTelegramBot --> SauceNaoAPI TelegramBotTutorial --> PlaguBotPlugins PlaguBotSuggestionsBot --> PlaguBotPlugins PlaguPoster --> PlaguBotPlugins PlaguPoster --> Krontab SauceNaoAPI --> MicroUtils Navigation --> MicroUtils TelegramBotAPI-bot_template -.- TelegramBotAPI PlaguBotPluginTemplate -.- PlaguBot PlaguBotBotTemplate -.- PlaguBot"},{"location":"krontab/index.html","title":"krontab","text":"

    Library was created to give oppotunity to launch some things from time to time according to some schedule in runtime of applications.

    "},{"location":"krontab/index.html#how-to-use","title":"How to use","text":"

    There are several ways to configure and use this library:

    • From some string
    • From builder

    Anyway, to start some action from time to time you will need to use one of extensions/functions:

    val kronScheduler = /* creating of KronScheduler instance */;\nkronScheduler.doWhile {\n// some action\ntrue // true - repeat on next time\n}\n
    "},{"location":"krontab/index.html#including-in-project","title":"Including in project","text":"

    If you want to include krontab in your project, just add next line to your dependencies part:

    implementation \"dev.inmo:krontab:$krontab_version\"\n

    Next version is the latest currently for the library:

    For old version of Gradle, instead of implementation word developers must use compile.

    "},{"location":"krontab/index.html#config-from-string","title":"Config from string","text":"

    Developers can use more simple way to configure repeat times is string. String configuring like a crontab, but with a little bit different meanings:

    /--------------- Seconds\n| /------------- Minutes\n| | /----------- Hours\n| | | /--------- Days of months\n| | | | /------- Months\n| | | | | /----- (optional) Year\n| | | | | | /--- (optional) Timezone offset\n| | | | | | |  / (optional) Week days\n* * * * * * 0o *w\n

    It is different with original crontab syntax for the reason, that expected that in practice developers will use seconds and minutes with more probability than months (for example) or even years. In fact, developers will use something like:

    doWhile(\"/5 * * * *\") {\nprintln(\"Called\")\ntrue // true - repeat on next time\n}\n

    An other version:

    doInfinity(\"/5 * * * *\") {\nprintln(\"Called\")\n}\n

    Both of examples will print Called message every five seconds.

    "},{"location":"krontab/index.html#config-via-builder","title":"Config via builder","text":"

    Also, this library currently supports DSL for creating the same goals:

    val kronScheduler = buildSchedule {\nseconds {\nfrom (0) every 5\n}\n}\nkronScheduler.doWhile {\nprintln(\"Called\")\ntrue // true - repeat on next time\n}\n

    Or

    val kronScheduler = buildSchedule {\nseconds {\n0 every 5\n}\n}\nkronScheduler.doWhile {\nprintln(\"Called\")\ntrue // true - repeat on next time\n}\n

    Or

    val kronScheduler = buildSchedule {\nseconds {\n0 every 5\n}\n}\nkronScheduler.doInfinity {\nprintln(\"Called\")\n}\n

    All of these examples will do the same things: print Called message every five seconds.

    "},{"location":"krontab/index.html#do-functions","title":"do* functions","text":"

    With regular doOnce/doWhile/doInfinity there are two types of their variations: local and timezoned. Local variations (doOnceLocal/doWhileLocal/doInfinityLocal) will pass DateTime as an argument into the block:

    doInfinityLocal(\"/5 * * * *\") {\nprintln(it) // will print current date time\n}\n

    Timezoned variations (doOnceTz/doWhileTz/doInfinityTz) will do the same thing but pass as an argument DateTimeTz:

    doInfinityTz(\"/5 * * * * 0o\") {\nprintln(it) // will print current date time in UTC\n}\n

    It is useful in cases when you need to get the time of calling and avoid extra calls to system time.

    "},{"location":"krontab/index.html#helpful-table-for","title":"Helpful table for","text":"No args Local DateTime Local DateTimeTz with offset of KronScheduler Call only near time doOnce doOnceLocal doOnceTz Call while condition is true doWhile doWhileLocal doWhileTz Work infinity* doInfinity doInfinityLocal doInfinityTz

    *Here there is an important notice, that Work infinity is not exactly infinity. Actually, that means that do while coroutine is alive and in fact executing will be stopped when coroutine became cancelled.

    "},{"location":"krontab/index.html#kronscheduler-as-a-flow","title":"KronScheduler as a Flow","text":"

    Any KronSchedulercan e converted to a Flow<DateTime using extension asFlow:

    val kronScheduler = buildSchedule {\nseconds {\n0 every 1\n}\n}\nval flow = kronScheduler.asFlow()\n

    So, in this case any operations related to flow are available and it is expected that they will work correctly. For example, it is possible to use this flow with takeWhile:

    flow.takeWhile {\ncondition()\n}.collect {\naction()\n}\n
    "},{"location":"krontab/index.html#offsets","title":"Offsets","text":"

    Offsets in this library works via passing parameter ending with o in any place after month config. Currently there is only one format supported for offsets: minutes of offsets. To use time zones you will need to call next method with DateTimeTz argument or nextTimeZoned method with any KronScheduler instance, but in case if this scheduler is not instance of KronSchedulerTz it will work like you passed just DateTime.

    Besides, in case you wish to use time zones explicitly, you will need to get KronSchedulerTz. It is possible by:

    • Using createSimpleScheduler/buildSchedule/KrontabTemplate#toSchedule/KrontabTemplate#toKronScheduler methods with passing defaultOffset parameter
    • Using SchedulerBuilder#build/createSimpleScheduler/buildSchedule/KrontabTemplate#toSchedule/KrontabTemplate#toKronScheduler methods with casting to KronSchedulerTz in case you are pretty sure that it is timezoned KronScheduler
    • Creating your own implementation of KronSchedulerTz
    "},{"location":"krontab/index.html#note-about-week-days","title":"Note about week days","text":"

    Unlike original CRON, here week days:

    • Works as AND: cron date time will search first day which will pass requirement according all parameters including week days
    • You may use any related to numbers syntax with week days: 0-3w, 0,1,2,3w, etc.
    • Week days (like years and offsets) are optional and can be placed anywhere after month
    "},{"location":"krontab/describing/krontabscheduler.html","title":"KrontabScheduler","text":"

    KronScheduler is the simple interface with only one function next. This function optionally get as a parameter DateTime which will be used as start point for the calculation of next trigger time. This function will return the next DateTime when something must happen.

    "},{"location":"krontab/describing/krontabscheduler.html#default-realisation","title":"Default realisation","text":"

    Default realisation (CronDateTimeScheduler) can be created using several ways:

    • Via buildSchedule (or createSimpleScheduler) functions with crontab-like syntax parameter
    • Via buildSchedule (or SchedulerBuilder object), which using lambda to configure scheduler

    In the examples below the result of created scheduler will be the same.

    "},{"location":"krontab/describing/krontabscheduler.html#crontab-like-way","title":"Crontab-like way","text":"

    Crontab-like syntax

    See String format for more info about the crontab-line syntax

    This way will be very useful for cases when you need to configure something via external configuration (from file on startup or via some parameter from requests, for example):

    val schedule = \"5 * * * *\"\nval scheduler = buildSchedule(schedule)\nscheduler.asFlow().onEach {\n// this block will be called every minute at 5 seconds\n}.launchIn(someCoroutineScope)\n
    "},{"location":"krontab/describing/krontabscheduler.html#lambda-way","title":"Lambda way","text":"

    In case of usage builder (lets call it lambda way), you will be able to configure scheduler in more type-safe way:

    val scheduler = buildSchedule {\nseconds {\nat(5)\n}\n}\nscheduler.asFlow().onEach {\n// this block will be called every minute at 5 seconds\n}.launchIn(someCoroutineScope)\n
    "},{"location":"krontab/describing/krontabscheduler.html#custom-scheduler","title":"Custom scheduler","text":"

    You are always able to use your own realisation of scheduler. For example:

    class RandomScheduler : KronScheduler {\noverride suspend fun next(relatively: DateTime): DateTime {\nreturn relatively + DateTimeSpan(seconds = Random.nextInt() % 60)\n}\n}\n

    In the example above we have created RandomScheduler, which will return random next time in range 0-60 seconds since relatively argument.

    "},{"location":"krontab/describing/string-format.html","title":"String format","text":"

    As in crontab util, this library have almost the same format of string:

    Seconds Minutes Hours Days of months Months Years Timezone Offset Week days Milliseconds Range 0..59 0..59 0..23 0..30 0..11 Any Int Any Int 0..6 0..999 Suffix - - - - - - o w ms Optional \u274c \u274c \u274c \u274c \u274c \u2705 \u2705 \u2705 \u2705 Full syntax support \u2705 \u2705 \u2705 \u2705 \u2705 \u2705 \u274c \u2705 \u2705 Position 0 1 2 3 4 Any after months Any after months Any after months Any after months Examples 0, */15, 30 0, */15, 30 0, */15, 22 0, */15, 30 0, */5, 11 0, */15, 30 60o (UTC+1) 0w, */2w, 4w 0ms, */150ms, 300ms

    Example with almost same description:

    /-------------------- (0-59) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Seconds\n| /------------------ (0-59) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Minutes\n| | /---------------- (0-23) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Hours\n| | | /-------------- (0-30) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Days of months\n| | | | /------------ (0-11) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Months\n| | | | | /---------- (optional, any int) Year\n| | | | | | /-------- (optional) \u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7\u00b7 Timezone offset\n| | | | | | |  /----- (optional, 0-6) \u00b7\u00b7\u00b7 Week days\n| | | | | | |  |  /-- (optional, 0-999) \u00b7 Milliseconds (0 by default)\n* * * * * * 0o *w 0ms\n

    Years, timezone, week days and milliseconds are optional settings. Next snippets are equal:

    */15 * * * *\n*/15 * * * * * // with year\n*/15 * * * * * 0ms // with year and milliseconds\n
    "},{"location":"krontab/describing/string-format.html#supported-syntax","title":"Supported syntax","text":"

    Currently the library support next syntax for date/time elements:

    • {int}-{int} - ranges
    • {int}/{int} - start/step
    • */{int} - every {int}
    • {int} - just at the time
    • {other_element},{other_element} - listing
    • F or f - first possible value
    • L or l - last possible value (last day of month, for example)
    "},{"location":"krontab/describing/string-format.html#ranges","title":"Ranges","text":"

    Ranges are working like common rangeTo (or ..) in kotlin:

    0-5 * * * *\n

    In the example above scheduler will trigger every second from the beginning of the minute up to fifth second of minute.

    "},{"location":"krontab/describing/string-format.html#startstep","title":"Start/Step","text":"

    Start/step is a little bit more complicated syntax. It means start from the first element, repeat triggering every second element. Examples:

    5/15 * * * *\n

    Means that each minute starting from fifth second it will repeat triggering every fifteenth second: 5, 20, 35, 50.

    "},{"location":"krontab/describing/string-format.html#every","title":"Every","text":"

    Every is more simple syntax and could be explained as a shortcut for 0/{int}. Example:

    */15 * * * *\n

    Means that each minute it will repeat triggering every fifteenth second: 0, 15, 30, 45.

    "},{"location":"krontab/describing/string-format.html#just-at-the-time","title":"Just at the time","text":"

    The most simple syntax. It means, that scheduler will call triggering every time when element was reached:

    15 * * * *\n

    Means that each minute scheduler will call triggering at the fifteenth second.

    "},{"location":"krontab/describing/string-format.html#listing","title":"Listing","text":"

    All the previous elements can be combined with listing. Lets just see several examples:

    0,10 * * * *\n

    Will trigger every minute at the 0 and 10 seconds (see Just at the time)

    0-5,10 * * * *\n

    Will trigger every minute from 0 to 5 seconds and at the 10 seconds (see Ranges)

    "},{"location":"krontab/describing/string-format.html#examples","title":"Examples","text":"
    • 0/5 * * * * for every five seconds triggering
    • 0/5,L * * * * for every five seconds triggering and on 59 second
    • 0/15 30 * * * for every 15th seconds in a half of each hour
    • 0/15 30 * * * 500ms for every 15th seconds in a half of each hour when milliseconds equal to 500
    • 1 2 3 F,4,L 5 for triggering in near first second of second minute of third hour of first, fifth and last days of may
    • 1 2 3 F,4,L 5 60o for triggering in near first second of second minute of third hour of first, fifth and last days of may with timezone UTC+01:00
    • 1 2 3 F,4,L 5 60o 0-2w for triggering in near first second of second minute of third hour of first, fifth and last days of may in case if it will be in Sunday-Tuesday week days with timezone UTC+01:00
    • 1 2 3 F,4,L 5 2021 for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year
    • 1 2 3 F,4,L 5 2021 60o for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year with timezone UTC+01:00
    • 1 2 3 F,4,L 5 2021 60o 0-2w for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00
    • 1 2 3 F,4,L 5 2021 60o 0-2w 500ms for triggering in near first second of second minute of third hour of first, fifth and last days of may of 2021st year if it will be in Sunday-Tuesday week days with timezone UTC+01:00 when milliseconds will be equal to 500
    "},{"location":"krontab/introduction/faq.html","title":"FAQ","text":""},{"location":"krontab/introduction/faq.html#how-oftern-new-versions-are-releasing","title":"How oftern new versions are releasing?","text":"

    Not very often. It depends on libraries (coroutines, korlibs/klock) updates and on some new awesome, but lightweight, features coming.

    "},{"location":"krontab/introduction/faq.html#where-this-library-could-be-useful","title":"Where this library could be useful?","text":"

    First of all, this library will be useful for long uptime applications which have some tasks to do from time to time.

    "},{"location":"krontab/introduction/faq.html#how-to-use-crontab-like-syntax","title":"How to use crontab-like syntax?","text":"

    In two words, you should call buildSchedule or createSimpleScheduler:

    buildSchedule(\"5 * * * *\").asFlow().collect { /* do something */ }\n

    You can read more about syntax in String format section.

    "},{"location":"krontab/introduction/how-to-use.html","title":"How to use","text":""},{"location":"krontab/introduction/how-to-use.html#previous-pages","title":"Previous pages","text":"
    • Including in project
    "},{"location":"krontab/introduction/how-to-use.html#buildschedule","title":"buildSchedule","text":"

    Custom KronScheduler

    You are always may create your own scheduler. In this section will be presented different ways and examples around standard CronDateTimeScheduler builders buildSchedule. You can read about schedulers in KrontabScheduler

    Currently, buildSchedule is the recommended start point for every scheduler. Usually, it is look like:

    val scheduler = buildSchedule(\"5 * * * *\")\n

    Or:

    val scheduler = buildSchedule {\nseconds {\nat(5)\n}\n}\n

    On the top of any KronScheduler currently there are several groups of extensions:

    • Executes
    • Shortcuts
    • Flows
    "},{"location":"krontab/introduction/how-to-use.html#executes","title":"Executes","text":"

    All executes are look like do.... All executes are described below:

    • doOnce - will get the next time for executing, delay until that time and call block with returning of the block result
    • doWhile - will call doOnce while it will return true (that means that block must return true if it expects that next call must happen). In two words: it will run while block returning true
    • doInfinity - will call the block using doWhile with predefined returning true. In two words: it will call block while it do not throw error
    "},{"location":"krontab/introduction/how-to-use.html#shortcuts","title":"Shortcuts","text":"

    Shortcuts are the constants that are initializing in a lazy way to provide preset KronSchedulers. For more info about KrontabScheduler you can read its own page.

    • AnyTimeScheduler - will always return incoming DateTime as next
    • Every*Scheduler - return near * since the passed relatively:
    • EverySecondScheduler / KronScheduler.everyMillisecond
    • EverySecondScheduler / KronScheduler.everySecond
    • EveryMinuteScheduler / KronScheduler.everyMinute
    • EveryHourScheduler / KronScheduler.hourly
    • EveryDayOfMonthScheduler / KronScheduler.daily
    • EveryMonthScheduler / KronScheduler.monthly
    • EveryYearScheduler / KronScheduler.annually
    "},{"location":"krontab/introduction/how-to-use.html#flows","title":"Flows","text":"

    Here currently there is only one extension for KronScheduler: KronScheduler#asFlow. As a result you will get Flow<DateTime> (in fact SchedulerFlow) which will trigger next emit on each not null next DateTime

    "},{"location":"krontab/introduction/including-in-project.html","title":"Including in project","text":"

    In two words, you must add dependency dev.inmo:krontab:$krontab_version to your project. The latest version presented by next badge:

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

    To use this library, you will need to include MavenCentral repository in you project

    "},{"location":"krontab/introduction/including-in-project.html#buildgradle","title":"build.gradle","text":"
    mavenCentral()\n
    "},{"location":"krontab/introduction/including-in-project.html#dependencies","title":"Dependencies","text":"

    Next snippets must be placed into your dependencies part of build.gradle (for gradle) or pom.xml (for maven).

    "},{"location":"krontab/introduction/including-in-project.html#gradle","title":"Gradle","text":"
    implementation \"dev.inmo:krontab:$krontab_version\"\n
    "},{"location":"krontab/introduction/including-in-project.html#maven","title":"Maven","text":"
    <dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>krontab</artifactId>\n<version>${krontab_version}</version>\n</dependency>\n
    "},{"location":"kslog/index.html","title":"KSLog","text":"

    It is simple and easy-to-use tool for logging on the most popular platforms in Kotlin Multiplatform:

    By default, KSLog is using built-in tools for logging on each supported platform:

    • java.util.logging.Logger for JVM
    • android.util.Log for Android
    • Console for JS

    But you always may create your logger and customize as you wish:

    KSLog.default = KSLog { level: LogLevel, tag: String?, message: Any, throwable: Throwable? ->\n// do your logging\n}\n

    This library also supports native targets in experimental mode. By default, all native targets will use simple printing in the console

    "},{"location":"kslog/index.html#how-to-use","title":"How to use","text":""},{"location":"kslog/index.html#fast-travel","title":"Fast-travel","text":"

    Just use some boring extensions like:

    KSLog.i(\"Some message\")\n// OR\nKSLog.i(\"Some tag\", \"Some message\")\n// OR\nKSLog.i(\"Some tag\", \"Some message\", IllegalArgumentException(\"So, that is exception :)\"))\n// OR\nKSLog.i(\"Some optional tag\", Exception(\"Optional\")) { \"Lazy inited message\" }\n// OR\nKSLog.iS(\"Some optional tag\", Exception(\"Optional\")) { \"Lazy inited message for suspendable calculation of text\" }\n// OR EVEN\nKSLog.l(LogLevel.INFO, \"Some tag\", \"Some message\", IllegalArgumentException(\"So, that is exception :)\"))\n// OR\nKSLog.l(LogLevel.INFO, \"Some optional tag\", IllegalArgumentException(\"So, that is exception :)\")) { \"And lazily inited message\" }\n
    "},{"location":"kslog/index.html#a-little-bit-deeper","title":"A little bit deeper","text":"

    There are several important \u201cterms\u201d in context of this library:

    • Default logger (available via KSLog.default or simply KSLog)
    • Local logger (can be created via KSLog functions and passed anywhere as KSLog)
    • Logging shortcuts like KSLog.i/KSLog.info
    • Built-in extension Any.logger which allow you to create logger binded to the default with the tag based on the class of receiver
      • Be careful with the receivers: if you will use some extension like apply, the receiver will be different with your class inside of that apply

    Every logging extension (like KSLog.i) have its analog with lazy inited message text and the same one with suffix S (like KSLog.iS) for the suspendable message calculation.

    Default logger can be created by passing defaultTag and one of variants log level filters: set or minimal loggable level. In JVM you also may setup any logger as base logger for default realizations of KSLog. Besides, you may use your own callback (on any target platform) as output of logging:

    val logger = KSLog { logLevel, optionalTag, message, optionalThrowable ->\nprintln(\"[$logLevel] $optionalTag - $message: $optionalThrowable.stackTraceToString()\")\n}\n

    In the example above we will take the logger which will just print incoming data as common output.

    "},{"location":"kslog/index.html#installation","title":"Installation","text":""},{"location":"kslog/index.html#gradle","title":"Gradle","text":"
    implementation \"dev.inmo:kslog:$kslog_version\"\n
    "},{"location":"kslog/index.html#maven","title":"Maven","text":"
    <dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>kslog</artifactId>\n<version>${kslog_version}</version>\n</dependency>\n
    "},{"location":"kslog/logging.html","title":"Logging","text":"

    Message type notice

    On this page all the messages will be just simple String, but you may pass any object as the message

    As has been said in the setup section, this library contains next levels of logging with their default representations on each platform:

    Weight (by order) LogLevel name JS JVM Loggers Android 0 DEBUG console.log Level.FINEST Log.d 1 VERBOSE console.info Level.FINE Log.v 2 INFO console.info Level.INFO Log.i 3 WARNING console.warn Level.WARNING Log.w 4 ERROR console.error Level.SEVERE Log.e 5 ASSERT console.error Level.SEVERE Log.wtf

    Each of these levels have fullname and shortname shortcat extensions:

    • KSLog.debug/KSLog.d/KSLog.dS
    • KSLog.verbose/KSLog.v/KSLog.vS
    • KSLog.info/KSLog.i/KSLog.iS
    • KSLog.warning/KSLog.w/KSLog.wS
    • KSLog.error/KSLog.e/KSLog.eS
    • KSLog.assert/KSLog.wtf/KSLog.wtfS

    And any of these shortcuts may accept one of several arguments combinations:

    • Tag (Optional), Throwable (Optional), Message Builder (simple inline callback for lazy creating of log message). This type of arguments is duplicated with S suffix for suspendable messages creating, for example
    • Message, Throwable (Optional)
    • Tag, Message, Throwable (Optional)

    So, when you want to log some expected exception, there are three common ways to do it:

    val logger = KSLog.default\n// with callback\nlogger.info(tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.infoS(tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.info(\"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.info(tag, \"Some your message for this event\", throwable)\n

    Of course, any of this calls can be shortenned:

    val logger = KSLog.default\n// with callback\nlogger.i(tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.iS(tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.i(\"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.i(tag, \"Some your message for this event\", throwable)\n

    There is special shortcat - for base performLog. In that case the only change is that you will require to pass the LogLevel more obviously:

    val logger = KSLog.default\n// with callback\nlogger.log(LogLevel.INFO, tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.logS(LogLevel.INFO, tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.log(LogLevel.INFO, \"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.log(LogLevel.INFO, tag, \"Some your message for this event\", throwable)\n

    OR

    val logger = KSLog.default\n// with callback\nlogger.l(LogLevel.INFO, tag, throwable) {\n\"Some your message for this event\"\n}\n// with suspendable callback\nlogger.lS(LogLevel.INFO, tag, throwable) {\nwithContext(Dispatchers.Default) {\n\"Some your message for this event\"\n}\n}\n// Just with message\nlogger.l(LogLevel.INFO, \"Some your message for this event\", throwable)\n// With message and tag as strings\nlogger.l(LogLevel.INFO, tag, \"Some your message for this event\", throwable)\n
    "},{"location":"kslog/setup.html","title":"Setup","text":""},{"location":"kslog/setup.html#dependency-installation","title":"Dependency installation","text":""},{"location":"kslog/setup.html#gradle-groovy","title":"Gradle (Groovy)","text":"
    implementation \"dev.inmo:kslog:$kslog_version\"\n
    "},{"location":"kslog/setup.html#gradle-kotlin-script","title":"Gradle (Kotlin Script)","text":"
    implementation(\"dev.inmo:kslog:$kslog_version\")\n
    "},{"location":"kslog/setup.html#maven-pom","title":"Maven (pom)","text":"
    <dependency>\n<groupId>dev.inmo</groupId>\n<artifactId>kslog</artifactId>\n<version>${kslog_version}</version>\n</dependency>\n
    "},{"location":"kslog/setup.html#setup-in-code","title":"Setup in code","text":"

    The main point in setup in your code is to setup default logger:

    KSLog.default = KSLog(\"defaultTag\")\n

    You may use custom messageFormatter in any of KSLog factory to customize output of KSLog logging. For example:

    KSLog(\n\"loggingWithCustomFormat\",\nmessageFormatter = { level, tag, message, throwable ->\nprintln(\"[$level] $tag - $message: $throwable\")\n}\n)\n

    Additionally you may use one of several different settings:

    • minLoggingLevel - minimal logging level for the log which will be logged. The order of log level is next:
    • DEBUG
    • VERBOSE
    • INFO
    • WARNING
    • ERROR
    • ASSERT
    • levels - and iterable with the levels which should be logged
    • firstLevel,secondLevel,otherLevels - as levels, but vararg :)

    In case you are passing minLoggingLevel, the level and more important levels will be passed to logs. For example, when you are settings up your logger as in next snippet:

    val logger = KSLog(\n\"exampleTag\",\nminLoggingLevel = LogLevel.INFO\n)\n

    The next levels will be logged with logger:

    • INFO
    • WARNING
    • ERROR
    • ASSERT
    "},{"location":"kslog/setup.html#special-loggers","title":"Special loggers","text":""},{"location":"kslog/setup.html#callbackkslog","title":"CallbackKSLog","text":"

    It is logger which will call incoming performLogCallback on each logging. This logger can be create simply with one callback:

    KSLog { level, tag, message, throwable ->\nprintln(\"[$level] $tag - $message: $throwable\")\n}\n
    "},{"location":"kslog/setup.html#taglogger","title":"TagLogger","text":"

    It is simple value class which can be used for zero-cost usage of some tag and calling for KSLog.default. For example, if you will create tag logger with next code:

    val logger = TagLogger(\"tagLoggerTag\")\n

    The logger will call KSLog.default with the tag tagLoggerTag on each calling of logging.

    "},{"location":"kslog/setup.html#filterkslog","title":"FilterKSLog","text":"

    This pretty simple logger will call its fallbackLogger only in cases when incoming messageFilter will return true for logging:

    val baseLogger = KSLog(\"base\") // log everything with the tag `base` if not set other\nval filtered = baseLogger.filtered { _, t, _ ->\nt == \"base\"\n}\n

    In the example above baseLogger will perform logs in two ways: when it has been called directly or when we call log performing with the tag \"base\" or null. Besides, you can see there extension filtered which allow to create FilterKSLog logger with simple lambda.

    "},{"location":"kslog/setup.html#typedkslog","title":"TypedKSLog","text":"

    This logger accepts map of types with the target loggers. You may build this logger with the special simple DSL:

    val baseLogger = KSLog(\"base\") // log everything with the tag `base` if not set other\nval typed = buildTypedLogger {\non<Int>(baseLogger) // log all ints to the baseLogger\non<Float> { _, _, message, _ ->// log all floats to the passed logger\nprintln(message.toString()) // just print all floats\n}\ndefault { level, tag, message, throwable ->\nKSLog.performLog(level, tag, message, throwable)\n}\n}\n
    "},{"location":"kslog/setup.html#automatical-loggers","title":"Automatical loggers","text":"

    There are two things which can be useful in your code: logger and logTag extensions. logTag is the autocalculated by your object classname tag. logger extension can be used with applying to any object like in the next snippet:

    class SomeClass {\ninit {\nlogger.i(\"inited\")\n}\n}\n

    The code above will trigger calling of logging in KSLog.default with level LogLevel.INFO using tag SomeClass and message \"inited\". As you could have guessed, logger is using TagLogger with logTag underhood and the most expensive operation here is automatical calculation of logTag.

    • Extension logger
    "},{"location":"kslog/setup.html#jvm-specific-setup","title":"JVM specific setup","text":"

    For JVM you may setup additionally use java loggers as the second parameter of KSLog factory. For example:

    KSLog(\n\"yourTag\"\nLogger.getLogger(\"YourJavaLoggerName\")\n)\n
    "},{"location":"micro_utils/index.html","title":"MicroUtils","text":"

    MicroUtils is a set of libraries to help me (and, I hope, you too) in some routine doings of coding.

    First of all, this library collection is oriented to use next technologies:

    • Kotlin Coroutines
    • Kotlin Serialization
    • Kotlin Exposed
    • Ktor
    • Koin
    • Korlibs

    Warning

    Due to complexity of this library, it is possible that some things will be missed or inactual. Me and the users of this library will try hard to keep its docs as actual as possible, but in case you will find some inconsistency of docs and library work (signatures, behaviour, API) you may write me directly in my telegram

    "},{"location":"navigation/index.html","title":"Navigation","text":"

    Navigation is a library for simple management for your app views (or some other logics). In this library there are several important terms:

    • Node - is a core thing. Node itself contains current config and its state
    • Chain - some sequence of nodes. In one chain only the last one node can be active
    "},{"location":"navigation/index.html#nodes-tree","title":"Nodes tree","text":"

    Let\u2019s see the next sample:

    flowchart TB NodeN1(N1) NodeN2(N2) NodeN3(N3) class NodeN1 navigation-paused; class NodeN1 navigation-part; class NodeN2 navigation-paused; class NodeN2 navigation-part; class NodeN3 navigation-resumed; class NodeN3 navigation-part; subgraph RootChain direction TB NodeN1 --> NodeN2 NodeN2 --> NodeN3 end class RootChain navigation-resumed; class RootChain navigation-part; NodeN4(N4) NodeN5(N5) NodeN6(N6) class NodeN4 navigation-paused; class NodeN4 navigation-part; class NodeN5 navigation-paused; class NodeN5 navigation-part; class NodeN6 navigation-paused; class NodeN6 navigation-part; subgraph N2Subchain direction TB NodeN4 --> NodeN5 NodeN5 --> NodeN6 end class N2Subchain navigation-paused; class N2Subchain navigation-part; NodeN2 --> N2Subchain NodeN7(N7) NodeN8(N8) class NodeN7 navigation-paused; class NodeN7 navigation-part; class NodeN8 navigation-resumed; class NodeN8 navigation-part; subgraph N3Subchain direction TB NodeN7 --> NodeN8 end class N3Subchain navigation-resumed; class N3Subchain navigation-part; NodeN3 --> N3Subchain NodeN9(N9) NodeN10(N10) class NodeN9 navigation-paused; class NodeN9 navigation-part; class NodeN10 navigation-resumed; class NodeN10 navigation-part; subgraph N3Subchain2 direction TB NodeN9 --> NodeN10 end class N3Subchain2 navigation-resumed; class N3Subchain2 navigation-part; NodeN3 --> N3Subchain2

    Any hierarchy starts with some root chain.

    "},{"location":"tgbotapi/index.html","title":"TelegramBotAPI","text":"

    Hello! This is a set of libraries for working with Telegram Bot API.

    "},{"location":"tgbotapi/index.html#examples","title":"Examples","text":"

    There are several things you need to do to launch examples below:

    • Add mavenCentral() to your project repositories
      • Maven variant
    • Add dependency implementation \"dev.inmo:tgbotapi:$tgbotapi_version\"
      • Replace tgbotapi_version with exact version (see last one in the table above) or put variable with this name in project
      • Alternative variant for maven here

    More including instructions available here. Other configuration examples:

    • For multiplatform
    • For JVM
    "},{"location":"tgbotapi/index.html#most-common-example","title":"Most common example","text":"
    suspend fun main() {\nval bot = telegramBot(TOKEN)\nbot.buildBehaviourWithLongPolling {\nprintln(getMe())\nonCommand(\"start\") {\nreply(it, \"Hi:)\")\n}\n}.join()\n}\n

    In this example you will see information about this bot at the moment of starting and answer with Hi:) every time it gets message /start

    "},{"location":"tgbotapi/index.html#handling-only-last-messages","title":"Handling only last messages","text":"
    suspend fun main() {\nval bot = telegramBot(TOKEN)\nval flowsUpdatesFilter = FlowsUpdatesFilter()\nbot.buildBehaviour(flowUpdatesFilter = flowsUpdatesFilter) {\nprintln(getMe())\nonCommand(\"start\") {\nreply(it, \"Hi:)\")\n}\nretrieveAccumulatedUpdates(this).join()\n}\n}\n

    The main difference with the previous example is that bot will get only last updates (accumulated before bot launch and maybe some updates it got after launch)

    "},{"location":"tgbotapi/index.html#build-a-little-bit-more-complex-behaviour","title":"Build a little bit more complex behaviour","text":"
    suspend fun main() {\nval bot = telegramBot(TOKEN)\nbot.buildBehaviourWithLongPolling {\nprintln(getMe())\nval nameReplyMarkup = ReplyKeyboardMarkup(\nmatrix {\nrow {\n+SimpleKeyboardButton(\"nope\")\n}\n}\n)\nonCommand(\"start\") {\nval photo = waitPhoto(\nSendTextMessage(it.chat.id, \"Send me your photo please\")\n).first()\nval name = waitText(\nSendTextMessage(\nit.chat.id,\n\"Send me your name or choose \\\"nope\\\"\",\nreplyMarkup = nameReplyMarkup\n)\n).first().text.takeIf { it != \"nope\" }\nsendPhoto(\nit.chat,\nphoto.mediaCollection,\nentities = buildEntities {\nif (name != null) regular(name) // may be collapsed up to name ?.let(::regular)\n}\n)\n}\n}.join()\n}\n
    "},{"location":"tgbotapi/index.html#more-examples","title":"More examples","text":"

    You may find examples in this project. Besides, you are always welcome in our chat.

    "},{"location":"tgbotapi/faq.html","title":"FAQ","text":""},{"location":"tgbotapi/faq.html#how-to-filter-updates-in-some-part-of-behaviourbuilder","title":"How to filter updates in some part of BehaviourBuilder?","text":"

    You may create subcontext with BehaviourBuilder.createSubContextAndDoWithUpdatesFilter and pass there updatesUpstreamFlow parameter with any operations over parent behaviour builder:

    buildBehaviourWithLongPolling {\ncreateSubContextAndDoWithUpdatesFilter(\nupdatesUpstreamFlow = filter { /* some condition */ },\nstopOnCompletion = false // disable stopping of sub context after setup\n) {\nonCommand() //...\n}\n}\n
    "},{"location":"tgbotapi/faq.html#additional-info","title":"Additional info","text":"
    • Flows docs
    • BehaviourBuilder
    "},{"location":"tgbotapi/faq.html#cases","title":"Cases","text":"
    • Filtering of chats and users:
          updatesUpstreamFlow = filter { it.sourceChat() ?.id == requiredChatId || it.sourceUser() ?.id == requiredUserId }\n
      • See:
        • Update.sourceChat
        • Update.sourceUser
    "},{"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(\nmatrix {\nrow {\nadd(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 {\nrow {\nsimpleButton(\"7\")\nsimpleButton(\"8\")\nsimpleButton(\"9\")\nsimpleButton(\"*\")\n}\nrow {\nsimpleButton(\"4\")\nsimpleButton(\"5\")\nsimpleButton(\"6\")\nsimpleButton(\"/\")\n}\nrow {\nsimpleButton(\"1\")\nsimpleButton(\"2\")\nsimpleButton(\"3\")\nsimpleButton(\"-\")\n}\nrow {\nsimpleButton(\"0\")\nsimpleButton(\".\")\nsimpleButton(\"=\")\nsimpleButton(\"+\")\n}\n}\n// inline keyboard\ninlineKeyboard {\nrow {\ndataButton(\"Get random music\", \"random\")\n}\nrow {\nurlButton(\"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 {\nvar i = 0\nwhile (isActive) {\nval newInfo = EditLiveLocationInfo(\nlatitude = i.toDouble(),\nlongitude = i.toDouble(),\nreplyMarkup = flatInlineKeyboard {\ndataButton(\"Cancel\", \"cancel\")\n}\n)\nemit(newInfo)\ni++\ndelay(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(\nit.chat.id,\nlocationsFlow,\nsentMessageFlow = FlowCollector { currentMessageState.emit(it) }\n)\n// this code will be called after `locationsFlow` will ends\n
      OR
      scope.launch {\nhandleLiveLocation(\nit.chat.id,\nlocationsFlow,\nsentMessageFlow = 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\")\nitems.forEachIndexed { i, item ->\nif (i % 2) {\nitalic(item)\n} else {\nstrikethrough(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/guides/keyboards.html","title":"Keyboards Guide","text":"

    This guide will help you choose the right keyboard for your needs and show you various API facilities available in the library to support your choice.

    "},{"location":"tgbotapi/guides/keyboards.html#introduction","title":"Introduction","text":""},{"location":"tgbotapi/guides/keyboards.html#keyboard-types","title":"Keyboard Types","text":"

    The first thing you need to know is that there are two types of keyboards available in the Telegram Bot API: reply and inline keyboards.

    Resize option

    In the screenshots above (and in the most others) you may see usage of reply keyboards without resize_keyboard. In case you will use resize_keyboard = true the keyboard will be smaller.

    Note the differences in the way these keyboards are shown to a user.

    A reply keyboard is shown under the message input field. It replaces the device\u2019s native input method on a mobile device.

    An inline keyboard is shown as a part of the message in the chat.

    "},{"location":"tgbotapi/guides/keyboards.html#simple-keyboard-interactions","title":"Simple Keyboard Interactions","text":"

    When a user clicks on a simple reply keyboard button, its text is just sent in the chat.

    When a user clicks on a simple inline keyboard button, nothing is sent to the chat. Instead, a callback query (a fancy way to say \u201ca request\u201d) is sent directly to the bot and the button is highlighted. It will stay highlighted until the bot acks the callback.

    It\u2019s a common mistake to forget to handle callback queries

    It leads to the buttons being highlighted for long periods of time, which leads to a bad user experience. Don\u2019t forget to handle these callbacks!

    As new messages arrive, a reply keyboard will stay there, while the inline keyboard will stick to the message and move with it.

    Ups\u2026 The reply keyboard is now far away from the message it was sent with.

    Actually, they are two different unrelated entities now: the original message and the reply keyboard. A reply keyboard persists until you explicitly remove it or replace it with a different one.

    It\u2019s a common mistake to forget to remove or replace reply keyboards

    It leads to the keyboards being shown forever. Don\u2019t forget to remove reply keyboards when you don\u2019t need them anymore!

    You also may use option one_time_keyboard and the keyboard will be automatically removed after user interaction

    An inline keyboard could also be removed or changed by editing the original message it was attached to.

    "},{"location":"tgbotapi/guides/keyboards.html#extended-keyboard-interactions","title":"Extended Keyboard Interactions","text":"

    Keyboards are not limited to text only. They could be used to ask users for different things, like payments, locations, phone numbers, etc. They could be used to open arbitrary URLs or web apps. Telegram clients process these buttons and interact with the users in the appropriate ways.

    For the full list of options, see the official documentation on reply and inline keyboards.

    "},{"location":"tgbotapi/guides/keyboards.html#basic-api-classes","title":"Basic API & Classes","text":"

    Now, that you know the basics, let\u2019s see how to use the library.

    "},{"location":"tgbotapi/guides/keyboards.html#keyboards","title":"Keyboards","text":"

    In Telegram Bot API keyboards are sent to the user as a part of an interaction via the reply_markup parameter. More specifically, this parameter is available:

    • in the sendXXX methods, like sendMessage, sendPhoto, sendSticker, etc.
    • in the copyMessage method
    • in the editMessageXXX methods, like editMessageText, editMessageCaption, editMessageReplyMarkup, etc. This also includes stopXXX methods like the stopMessageLiveLocation method.

    Tip

    editMessageReplyMarkup is specifically designed to edit a message\u2019s inline keyboard.

    Sending inline keyboards is also supported in inline mode through the reply_markup parameter of the InlineQueryResult type and its inheritors. However, this inline mode is unrelated to the inline keyboards.

    The reply_markup parameter accepts four different types. Two of them \u2014 ReplyKeyboardMarkup and InlineKeyboardMarkup \u2014 correspond to the reply and inline keyboards respectively. The ReplyKeyboardRemove type is used to remove reply keyboards, but it\u2019s not a keyboard itself. The last one, ForceReply, is used to force users to reply to the bot. It is not a keyboard either, but yet another dirty hack employed by the Telegram Bot API.

    Now, in the library, the WithReplyMarkup is a marker interface for all the interactions which could have a replyMarkup (represents reply_markup) parameter. It is extended by the ReplyingMarkupSendMessageRequest, and then, finally, by classes like SendTextMessage. This, basically, corresponds to the Telegram Bot API.

    Note

    You may see all the inheritors of WithReplyMarkup interfaces in the corresponding KDoc

    The other way to send a keyboard is through the replyMarkup parameter of the numerous extension methods, like sendMessage. Those are just convenient wrappers around general interaction classes, like the aforementioned SendTextMessage.

    "},{"location":"tgbotapi/guides/keyboards.html#buttons","title":"Buttons","text":"

    As we already know, keyboards consist of buttons. Button classes reside in the dev.inmo.tgbotapi.types.buttons package.

    The base class for the reply keyboard buttons is the KeyboardButton. The base class for the inline keyboard buttons is the InlineKeyboardButton.

    See their inheritors for the full list of the available buttons. The names are pretty self-explanatory and correspond to the Telegram Bot API.

    For example, to send a simple reply keyboard button, use the SimpleKeyboardButton class. To request a contact from the user through the reply, use the RequestContactKeyboardButton class. To attach a URL button to the message, use the URLInlineKeyboardButton. And to attach a callback button, use the CallbackDataInlineKeyboardButton.

    You get the idea.

    So, to send a reply keyboard use the following code:

    bot.sendMessage(\nchatId = chat,\ntext = \"What is the best Kotlin Telegram Bot API library?\",\nreplyMarkup = ReplyKeyboardMarkup(\nkeyboard = listOf(\nlistOf(\nSimpleKeyboardButton(\"ktgbotapi\"),\n),\n)\n)\n)\n

    And here is how you send a basic inline keyboard:

    bot.sendMessage(\nchatId = chat,\ntext = \"ktgbotapi is the best Kotlin Telegram Bot API library\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = listOf(\nlistOf(\nCallbackDataInlineKeyboardButton(\"I know\", \"know\"),\nURLInlineKeyboardButton(\"Learn more\", \"https://github.com/InsanusMokrassar/ktgbotapi\")\n),\n)\n),\n)\n

    When we\u2019re done with this simple quiz, we can remove the keyboard with the following code:

    bot.sendMessage(\nchatId = chat,\ntext = \"You're goddamn right!\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n

    Note

    Don\u2019t forget to remove the reply keyboards!

    "},{"location":"tgbotapi/guides/keyboards.html#matrices","title":"Matrices","text":"

    Buttons in keyboards are arranged in matrices, i.e. two-dimensional arrays, or, to say in layperson\u2019s terms, rows and columns. In contrast to the matrices you\u2019ve learned in school, keyboards are not always necessarily square. Try it:

    bot.sendMessage(\nchatId = chat,\ntext = \"In contrast to the matrices you've learned in school, keyboards are not always necessary square.\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = listOf(\nlistOf(\nCallbackDataInlineKeyboardButton(\"1\", \"1\"),\nCallbackDataInlineKeyboardButton(\"2\", \"2\"),\nCallbackDataInlineKeyboardButton(\"3\", \"3\"),\n),\nlistOf(\nCallbackDataInlineKeyboardButton(\"4\", \"4\"),\nCallbackDataInlineKeyboardButton(\"5\", \"5\"),\n),\nlistOf(\nCallbackDataInlineKeyboardButton(\"6\", \"6\"),\n)\n)\n)\n)\n

    This way of building matrices is not very convenient, so the library provides a few eloquent DSLs to simplify that.

    First, there are matrix and row, so the keyboard above can be built like this:

    bot.sendMessage(\nchatId = chat,\ntext = \"DSLs are sweet!\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = matrix {\nrow {\n+CallbackDataInlineKeyboardButton(\"1\", \"1\")\n+CallbackDataInlineKeyboardButton(\"2\", \"2\")\n+CallbackDataInlineKeyboardButton(\"3\", \"3\")\n}\nrow(\nCallbackDataInlineKeyboardButton(\"4\", \"4\"),\nCallbackDataInlineKeyboardButton(\"5\", \"5\"),\n)\nrow {\n+CallbackDataInlineKeyboardButton(\"6\", \"6\")\n}\n},\n)\n)\n

    Note

    Those plus signs are mandatory.

    Note

    There are two different row functions here. Can you spot the difference?

    A single-row matrix can be built with a flatMatrix:

    flatMatrix {\n+CallbackDataInlineKeyboardButton(\"1\", \"1\")\n+CallbackDataInlineKeyboardButton(\"2\", \"2\")\n+CallbackDataInlineKeyboardButton(\"3\", \"3\")\n+CallbackDataInlineKeyboardButton(\"4\", \"4\")\n+CallbackDataInlineKeyboardButton(\"5\", \"5\")\n}\n

    But the most convenient way to build a simple keyboard is to use the constructor-like methods: InlineKeyboardMarkup and ReplyKeyboardMarkup. Note, that they are named just like the corresponding constructor, but take a vararg of buttons. They create flat matrices, i.e. single rows.

    "},{"location":"tgbotapi/guides/keyboards.html#keyboards-dsl","title":"Keyboards DSL","text":"

    Finally, there are inlineKeyboard and replyKeyboard

    DSL methods above rely on Kotlin\u2019s feature of receivers and extensions. So, the magic is done by MatrixBuilder and RowBuilder. That\u2019s why you must use the plus sign to add buttons to the matrix: it\u2019s just an overloaded operator call, another cool Kotlin feature widely used to create sweet DSLs.

    Another bonus of using these DSLs is button builders, like payButton, dataButton, and urlButton:

    bot.sendMessage(\nchatId = chat,\ntext = \"All in one!\",\nreplyMarkup = InlineKeyboardMarkup(\nkeyboard = matrix {\nrow {\npayButton(\"Send money\")\ndataButton(\"Ok\", \"ok\")\nurlButton(\"Google\", \"https://google.com\")\n}\n},\n)\n)\n

    Reply keyboard builders provide similar extensions, e.g. requestLocationButton.

    So, choose the style you like \u2014 from plain Kotlin lists to sweet DSLs \u2014 and use it!

    "},{"location":"tgbotapi/guides/keyboards.html#working-with-keyboards","title":"Working with keyboards","text":"

    Working with keyboards is not something special in Telegram Bot API. As you have already seen, keyboards are just message parameters. Similarly, keyboard interactions are represented by regular Updates. I.e. when a user interacts with a keyboard, the bot receives an update.

    On the other hand, the library is heavily typed, so the actual type of update you would receive varies.

    "},{"location":"tgbotapi/guides/keyboards.html#reply-keyboards","title":"Reply keyboards","text":"

    As it was said, reply keyboards cause Telegram clients to send regular messages back to the bot. Peruse this example:

    bot.buildBehaviourWithLongPolling {\nbot.sendMessage(\nchatId = chat,\ntext = \"\ud83d\udc6e Turn in your accomplices or be prepared for a lengthy \ud83c\udf46 incarceration \u26d3 \ud83d\udc4a \u203c\",\nreplyMarkup = replyKeyboard {\n+SimpleKeyboardButton(\n\"I ain't no rat! \ud83d\udeab\ud83d\udc00\ud83e\udd10\ud83d\ude45\"\n)\n+RequestUserKeyboardButton(\n\"Rat out \ud83d\udc00 a friend \ud83d\udc64\",\nKeyboardButtonRequestUser.Common(RequestId.random())\n)\n+RequestChatKeyboardButton(\n\"Rat out \ud83d\udc00 a group of friends \ud83d\udc65\",\nKeyboardButtonRequestChat.Group(RequestId.random())\n)\n}\n)\nonText { message: CommonMessage<TextContent> ->\nassert(message.text == \"I ain't no rat! \ud83d\udeab\ud83d\udc00\ud83e\udd10\ud83d\ude45\")\nbot.reply(\nto = message,\ntext = \"Good, you're going to jail alone! \u26d3\ud83e\uddd1\u26d3\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n}\nonUserShared { message: PrivateEventMessage<UserShared> ->\nbot.reply(\nto = message,\ntext = \"Haha, you and you friend are both going to jail! \u26d3\ud83d\udc6c\u26d3\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n}\nonChatShared { message: PrivateEventMessage<ChatShared> ->\nbot.reply(\nto = message,\ntext = \"Haha, now you're all going to jail! \u26d3\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66\u26d3\",\nreplyMarkup = ReplyKeyboardRemove()\n)\n}\n}.join()\n

    Note

    Read more about buildBehaviourWithLongPolling here

    I hope you get the idea: the bot acts like a cop and asks the user to rat out his friends via a reply keyboard (it\u2019s an imaginary situation, of course). The user may refuse to cooperate, rat out a single friend or the whole imaginary group. The bot receives the user\u2019s choices as regular updates, the code above has explicit types (generally optional in Kotlin) and an assert to demonstrate this.

    And here is how it works (the user selects the options in the order):

    Note how you handle reply keyboards: you process regular messages. For instance, a simple text button sends a regular text message indistinguishable from a case when a user simply types the same text manually.

    And don\u2019t be a rat in real life: remove the keyboards with the ReplyKeyboardRemove after you\u2019ve received the input! Otherwise, a keyboard will stay there indefinitely.

    "},{"location":"tgbotapi/guides/keyboards.html#inline-keyboards","title":"Inline keyboards","text":"

    Finally, to master the keyboards, you need to know how to handle the inline ones.

    Again, let\u2019s explore the example. Imagine you\u2019re making a quiz where users are given a question and a set of answers. Additionally, users are given a link to the wiki page to help with the question and a Google button.

    The quiz could be implemented this way:

    // A simple data class to represent a question\nval question = Question(\nimage = \"https://upload.wikimedia.org/wikipedia/commons/a/a5/Tsunami_by_hokusai_19th_century.jpg\",\nquestion = \"Who painted this?\",\nanswers = listOf(\nAnswer(\"Hokusai\", correct = true),\nAnswer(\"Sukenobu\"),\nAnswer(\"Ch\u014dshun\"),\nAnswer(\"Kiyonobu I\"),\n),\nwiki = \"https://en.wikipedia.org/wiki/Ukiyo-e\",\n)\nbot.buildBehaviourWithLongPolling {\nbot.sendPhoto(\nchatId = chat,\nfileId = InputFile.fromUrl(question.image),\ntext = question.question,\nreplyMarkup = inlineKeyboard {\n// First row: answers\nrow {\nfor (answer in question.answers.shuffled()) {\ndataButton(\ntext = answer.answer,\ndata = \"${answer.answer}:${answer.correct}\",\n)\n}\n}\n// Second row: help buttons\nrow {\nurlButton(\"Wiki \ud83d\udc81\", question.wiki)\nwebAppButton(\"Google \ud83d\udd0d\", \"https://google.com\")\n}\n}\n)\nonDataCallbackQuery { callback: DataCallbackQuery ->\nval (answer, correct) = callback.data.split(\":\")\nif (correct.toBoolean()) {\nbot.answerCallbackQuery(\ncallback,\ntext = \"$answer is a \u2705 correct answer!\",\nshowAlert = true\n)\n} else {\nbot.answerCallbackQuery(\ncallback,\ntext = \"\u274c Try again, $answer is not a correct answer\u2026\",\nshowAlert = true\n)\n}\n}\n}.join()\n

    A few important things to note here.

    First, the data buttons (they have the CallbackDataInlineKeyboardButton type, but in the code we used a neat DSL) must have unique data. If the data is not unique, Telegram clients will highlight all the buttons with the same data when a user clicks on one of them. Guess how I know that? Well, it\u2019s not in the docs, so trial and error is the only way to learn it (and many other things about the Telegram Bot API).

    Second, the way you handle inline keyboards is different from the way you handle reply keyboards. Bot API will send updates with a callback_query field populated. This field, of a CallbackQuery type, represents incoming callbacks from callback buttons in inline keyboards. The library turns them into multiple callback types, like the DataCallbackQuery we used in the example. Finally, to handle these callbacks you could use onDataCallbackQuery. Alternatively, if you\u2019re not using any DSLs, you have to handle the CallbackQueryUpdate update type.

    Third, the buttons got highlighted when a user clicks on them. When you\u2019re done with the callback, you need to answer it, by using the answerCallbackQuery function. Otherwise, the button will remain highlighted. Telegram clients will eventually remove the highlight, but it\u2019s still frustrating.

    Finally, you could choose between two styles of acknowledgment: a simple toast-like message or a modal alert. The showAlert flag controls this behavior.

    And here is the demo of the quiz:

    "},{"location":"tgbotapi/guides/keyboards.html#conclusion","title":"Conclusion","text":"

    Today we\u2019ve learned how to use keyboards in Telegram bots. There are two types of keyboards: reply and inline. Reply keyboards replace the device\u2019s keyboard and make clients send a message with the predefined content. Inline keyboards are buttons attached to messages. Clicking on them causes the client to send a callback to the bot. In both scenarios the bot receives an update of a corresponding type and has to acknowledge the keayboard interaction for the client to work properly.

    "},{"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) {\nval botToken = args.first()\nval bot = telegramBot(botToken)\nprintln(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 {\nurl \"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)\nat sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)\nat io.ktor.network.sockets.SocketImpl.connect$ktor_network(SocketImpl.kt:36)\nat io.ktor.network.sockets.SocketImpl$connect$1.invokeSuspend(SocketImpl.kt)\nat kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)\nat kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)\nat kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)\nat kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)\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) {\nktorClientEngineFactory = OkHttp\nproxy = 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) {\nproxy = ProxyBuilder.socks(host = \"127.0.0.1\", port = 4001) // just an example, more info on https://ktor.io/docs/proxy.html\nktorClientConfig = {\n// configuring of ktor client\n}\nktorClientEngineFactory = {\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> {\nstrictlyOn<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\")\nbot.buildBehaviour {\nonCommand(\"start\") { // creating of trigger\nval message = it\nval content = message.content\nreply(message, \"Ok, send me one photo\") // send text message with replying on incoming message\nval photoContent = waitPhoto().first() // waitPhoto will return List, so, get first element\nval photo = downloadFile(photoContent) // ByteArray of photo\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\")\nbot.buildBehaviour {\nonCommand(\n\"start\",\ninitialFilter = {\nit.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\")\nbot.buildBehaviour {\nonCommand(\n\"start\",\nrequireOnlyCommandInMessage = 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\nit.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\nit.printStackTrace()\nnull // 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{\nit.printStackTrace()\n\"error\"\n}\n) {\nerror(\"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\")\nbot.buildBehaviour (\nscope = scope,\ndefaultExceptionsHandler = {\nit.printStackTrace()\n}\n) {\n// ...\n}\n

    OR

    val bot = telegramBotWithBehaviour (\n\"TOKEN\",\nscope = scope,\ndefaultExceptionsHandler = {\nit.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;\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;\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;\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;\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;\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;\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;\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\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;\nval pathedFile: PathedFile = bot.execute(GetFile(fileId))\nval channelAllocator: ByteReadChannelAllocator = bot.execute(DownloadFileStream(pathedFile.filePath))\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;\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) {\nonVisualGallery { // it: CommonMessage<MediaGroupContent<VisualMediaGroupPartContent>>\nit.content // MediaGroupContent<VisualMediaGroupPartContent>\nit.content.group // List<MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>>\nit.content.group.forEach { // it: MediaGroupCollectionContent.PartWrapper<VisualMediaGroupPartContent>\nit.messageId // source message id for current media group part\nit.sourceMessage // source message for current media group part\nit.content // VisualMediaGroupPartContent\nprintln(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 {\nprintln(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\nmessageFlow.onEach {\nprintln(it)\n}.launchIn(someCoroutineScope)\n}\n// Step 3 - passing updates to filter\nbot.getUpdates().forEach {\nfilter.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\")\nbot.longPolling {\nmessageFlow.onEach {\nprintln(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\nval subroute = uuid4().toString()\n// Input/Output coroutines scope more info here: https://kotlinlang.org/docs/coroutines-guide.html\nval scope = CoroutineScope(Dispatchers.IO)\n// Here will be automatically created bot and available inside of lambda where you will setup your bot behaviour\ntelegramBotWithBehaviour(\n// Pass TOKEN inside of your application environment variables\nSystem.getenv(\"TOKEN\"),\nscope = scope\n) {\n// Set up webhooks and start to listen them\nsetWebhookInfoAndStartListenWebhooks(\n// Automatic env which will be passed by heroku to the app\nSystem.getenv(\"PORT\").toInt(),\n// Server engine. More info here: https://ktor.io/docs/engines.html\nTomcat,\n// Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\nSetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n// Just callback which will be called when exceptions will happen inside of webhooks\n{\nit.printStackTrace()\n},\n// Set up listen requests from outside\n\"0.0.0.0\",\n// Set up subroute to listen webhooks to\nsubroute,\n// BehaviourContext is the CoroutineScope and it is recommended to pass it inside of webhooks server\nscope = this,\n// BehaviourContext is the FlowsUpdatesFilter and it is recommended to pass its asUpdateReceiver as a block to retrieve all the updates\nblock = asUpdateReceiver\n)\n// Test reaction on each command with reply and text `Got it`\nonUnhandledCommand {\nreply(it, \"Got it\")\n}\n}\n// Just potentially infinite await of bot completion\nscope.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)\nval filter = flowsUpdatesFilter {\nmessageFlow.onEach {\nprintln(it) // will be printed \n}.launchIn(scope)\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\nval server = bot.setWebhookInfoAndStartListenWebhooks(\n// Automatic env which will be passed by heroku to the app\nSystem.getenv(\"PORT\").toInt(),\n// Server engine. More info here: https://ktor.io/docs/engines.html\nTomcat,\n// Pass URL environment variable via settings of application. It must looks like https://<app name>.herokuapp.com\nSetWebhook(\"${System.getenv(\"URL\").removeSuffix(\"/\")}/$subroute\"),\n// Just callback which will be called when exceptions will happen inside of webhooks\n{\nit.printStackTrace()\n},\n// Set up listen requests from outside\n\"0.0.0.0\",\n// Set up subroute to listen webhooks to\nsubroute,\nscope = scope,\nblock = filter.asUpdateReceiver\n)\nserver.environment.connectors.forEach {\nprintln(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(\ntextMessages().subscribe(scope) { // here \"scope\" is a CoroutineScope\nprintln(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{\nprintln(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 {\nprintln(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 {\nprintln(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 {\nmessageFlow.onEach {\nprintln(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 {\nprintln(it) // will be printed each message update from channels and chats both\n}.launchIn(scope)\n}\n
    • TelegramBotAPI library support function aggregateFlows:
      flowsUpdatesFilter {\naggregateFlows(\nscope,\nmessageFlow,\nchannelPostFlow\n).onEach {\nprintln(it) // will be printed each message update from channels and chats both\n}.launchIn(scope)\n}\n
    • FlowsUpdatesFilter extensions:
      flowsUpdatesFilter {\nallSentMessagesFlow.onEach {\nprintln(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 {\ntextMessages(scope).onEach {\nprintln(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 {\nmediaGroupMessages(scope).onEach {\nprintln(it) // will be printed each media group messages list from both channels and chats without filtering of content\n}.launchIn(scope)\nmediaGroupPhotosMessages(scope).onEach {\nprintln(it) // will be printed each media group messages list from both channels and chats with PhotoContent only\n}.launchIn(scope)\nmediaGroupVideosMessages(scope).onEach {\nprintln(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 {\nsentMessagesWithMediaGroups(scope).onEach {\nprintln(it) // will be printed each message including each separated media group message from both channels and chats without filtering of content\n}.launchIn(scope)\nphotoMessagesWithMediaGroups(scope).onEach {\nprintln(it) // will be printed each message including each separated media group message from both channels and chats with PhotoContent only\n}.launchIn(scope)\nvideoMessagesWithMediaGroups(scope).onEach {\nprintln(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)\nval filter = flowsUpdatesFilter {\n// ...\n}\nbot.setWebhookInfoAndStartListenWebhooks(\n8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\nCIO, // 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\nSetWebhook(\n\"address.com/webhook_route\",\nFile(\"/path/to/certificate\").toInputFile(), // certificate file. More info here: https://core.telegram.org/bots/webhooks#a-certificate-where-do-i-get-one-and-how\n40, // max allowed updates, by default is null\nfilter.allowedUpdates\n),\n{\nit.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\nWebhookPrivateKeyConfig( // 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),\nscope, // Kotlin coroutine scope for internal transforming of media groups\nfilter.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(),\n40, // max allowed updates, by default is null\nfilter.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}\nstartListenWebhooks(\n8080, // listening port. It is required for cases when your server hidden by some proxy or other system like Heroku\nCIO, // 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{\nit.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\nWebhookPrivateKeyConfig( // 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),\nscope, // Kotlin coroutine scope for internal transforming of media groups\nfilter.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)\nval filter = flowsUpdatesFilter {\n// ...\n}\nval environment = applicationEngineEnvironment {\nmodule {\nrouting {\nincludeWebhookHandlingInRoute(\nscope,\n{\nit.printStackTrace()\n},\nfilter.asUpdateReceiver\n)\n}\n}\nconnector {\nhost = \"0.0.0.0\"\nport = 8080\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 index f2565a0..64cc3e4 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,177 +2,177 @@ https://docs.inmo.dev/index.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/krontab/index.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/krontab/describing/krontabscheduler.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/krontab/describing/string-format.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/krontab/introduction/faq.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/krontab/introduction/how-to-use.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/krontab/introduction/including-in-project.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/kslog/index.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/kslog/logging.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/kslog/setup.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/micro_utils/index.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/navigation/index.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/index.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/faq.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/dsls/keyboards.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/dsls/live-location.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/dsls/text.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/guides/keyboards.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/introduction/before-any-bot-project.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/introduction/first-bot.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/introduction/including-in-your-project.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/introduction/proxy-setup.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/api-extensions.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/behaviour-builder-with-fsm.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/behaviour-builder.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/exceptions-handling.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/files-handling.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/low-level-work-with-bots.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/media-groups.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/types-conversations.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/logic/updates-with-flows.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/updates/heroku.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/updates/long-polling.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/updates/updates-filters.html - 2023-07-12 + 2023-07-21 daily https://docs.inmo.dev/tgbotapi/updates/webhooks.html - 2023-07-12 + 2023-07-21 daily \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index 7d4c618904dcfc22cf17ae717914b2b32c0abb6d..fdb0cc24a37164b63dce47189b552b9ae7db6f49 100644 GIT binary patch delta 32 ocmdnOvW10RzMF%is&Cgs_MOac=2>n$oXN;xaG539TY`ZB0KBIPz5oCK delta 32 ocmdnOvW10RzMF$XV8*(M>^qrl-dk)uoXN;>N85b0w*&(N0I(DaaR2}S diff --git a/tgbotapi/dsls/keyboards.html b/tgbotapi/dsls/keyboards.html index 491a8b4..5f224c6 100644 --- a/tgbotapi/dsls/keyboards.html +++ b/tgbotapi/dsls/keyboards.html @@ -1,1474 +1,552 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Keyboards - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +Keyboards - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + +

    Keyboards

    In the telegram system there are two types of keyboards:

    @@ -1479,8 +557,8 @@ - - + + @@ -1489,190 +567,126 @@
    Reply keyboardInline keyboardReply keyboardInline keyboard
    Keyboard for each user in the chat

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

    -
    ReplyKeyboardMarkup(
    -    matrix {
    -        row {
    -            add(SimpleKeyboardButton("Simple text"))
    -            // ...
    -        }
    -        // ...
    -    }
    -)
    +
    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")
    -    }
    -}
    +
    // 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 index 6117829..f6a3156 100644 --- a/tgbotapi/dsls/live-location.html +++ b/tgbotapi/dsls/live-location.html @@ -1,1555 +1,607 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Live Location - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    +

    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

    +

    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

    +

    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
      -  }
      -}
      +
      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)
      +
      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
      +
      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
      +
      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 index 579f6ad..77e990e 100644 --- a/tgbotapi/dsls/text.html +++ b/tgbotapi/dsls/text.html @@ -1,1626 +1,640 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Text - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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")
    +
    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)
    -        }
    -    }
    -}
    +
    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/faq.html b/tgbotapi/faq.html index c64416d..eb57116 100644 --- a/tgbotapi/faq.html +++ b/tgbotapi/faq.html @@ -1,1580 +1,630 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - FAQ - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +FAQ - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    +
    +
    +
    + -
    -
    -
    - - - -
    -
    - - - - - - - - - - - - - - - - - - - - -

    FAQ

    -

    How to filter updates in some part of BehaviourBuilder?

    + + + +
    +
    +
    +
    +
    + + + + + + +

    FAQ

    +

    How to filter updates in some part of BehaviourBuilder?

    You may create subcontext with BehaviourBuilder.createSubContextAndDoWithUpdatesFilter and pass there updatesUpstreamFlow parameter with any operations over parent behaviour builder:

    -
    buildBehaviourWithLongPolling {
    -    createSubContextAndDoWithUpdatesFilter(
    -        updatesUpstreamFlow = filter { /* some condition */ },
    -        stopOnCompletion = false // disable stopping of sub context after setup
    -    ) {
    -        onCommand() //...
    -    }
    -}
    +
    buildBehaviourWithLongPolling {
    +    createSubContextAndDoWithUpdatesFilter(
    +        updatesUpstreamFlow = filter { /* some condition */ },
    +        stopOnCompletion = false // disable stopping of sub context after setup
    +    ) {
    +        onCommand() //...
    +    }
    +}
     
    -

    Additional info

    +

    Additional info

    -

    Cases

    +

    Cases

    • Filtering of chats and users: -
          updatesUpstreamFlow = filter { it.sourceChat() ?.id == requiredChatId || it.sourceUser() ?.id == requiredUserId }
      +    
          updatesUpstreamFlow = filter { it.sourceChat() ?.id == requiredChatId || it.sourceUser() ?.id == requiredUserId }
       
      • See:
        • Update.sourceChat
        • @@ -1584,141 +634,77 @@ and pass there updatesUpstreamFlow parameter with any operations ov
      - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/guides/keyboards.html b/tgbotapi/guides/keyboards.html index d674c53..8e80f62 100644 --- a/tgbotapi/guides/keyboards.html +++ b/tgbotapi/guides/keyboards.html @@ -1,1729 +1,729 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Keyboards Guide - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +Keyboards Guide - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    + - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    Keyboards Guide

    This guide will help you choose the right keyboard for your needs and show you various API facilities available in the library to support your choice.

    -

    Introduction

    -

    Keyboard Types

    +

    Introduction

    +

    Keyboard Types

    The first thing you need to know is that there are two types of keyboards available in the Telegram Bot API: reply and inline keyboards.

    -

    Keyboards comparison

    +

    Keyboards comparison

    Resize option

    @@ -1731,43 +731,43 @@ In the screenshots above (and in the most others) you may see usage of reply key

    Note the differences in the way these keyboards are shown to a user.

    A reply keyboard is shown under the message input field. -It replaces the device’s native input method on a mobile device.

    +It replaces the device’s native input method on a mobile device.

    An inline keyboard is shown as a part of the message in the chat.

    -

    Simple Keyboard Interactions

    +

    Simple Keyboard Interactions

    When a user clicks on a simple reply keyboard button, its text is just sent in the chat.

    When a user clicks on a simple inline keyboard button, nothing is sent to the chat. -Instead, a callback query (a fancy way to say “a request”) is sent directly to the bot and the button is highlighted. +Instead, a callback query (a fancy way to say “a request”) is sent directly to the bot and the button is highlighted. It will stay highlighted until the bot acks the callback.

    -

    Simple keyboard interactions

    +

    Simple keyboard interactions

    -

    It’s a common mistake to forget to handle callback queries

    +

    It’s a common mistake to forget to handle callback queries

    It leads to the buttons being highlighted for long periods of time, which leads to a bad user experience. -Don’t forget to handle these callbacks!

    +Don’t forget to handle these callbacks!

    As new messages arrive, a reply keyboard will stay there, while the inline keyboard will stick to the message and move with it.

    -

    Keyboards position with multiple messages

    +

    Keyboards position with multiple messages

    Ups… The reply keyboard is now far away from the message it was sent with.

    Actually, they are two different unrelated entities now: the original message and the reply keyboard. A reply keyboard persists until you explicitly remove it or replace it with a different one.

    -

    It’s a common mistake to forget to remove or replace reply keyboards

    +

    It’s a common mistake to forget to remove or replace reply keyboards

    -It leads to the keyboards being shown forever. Don’t forget to remove reply keyboards when you don’t need them anymore!

    +It leads to the keyboards being shown forever. Don’t forget to remove reply keyboards when you don’t need them anymore!

    You also may use option one_time_keyboard and the keyboard will be automatically removed after user interaction

    An inline keyboard could also be removed or changed by editing the original message it was attached to.

    -

    Extended Keyboard Interactions

    +

    Extended Keyboard Interactions

    Keyboards are not limited to text only. They could be used to ask users for different things, like payments, locations, phone numbers, etc. They could be used to open arbitrary URLs or web apps. Telegram clients process these buttons and interact with the users in the appropriate ways.

    For the full list of options, see the official documentation on reply and inline keyboards.

    -

    Extended keyboard interactions

    -

    Basic API & Classes

    -

    Now, that you know the basics, let’s see how to use the library.

    -

    Keyboards

    +

    Extended keyboard interactions

    +

    Basic API & Classes

    +

    Now, that you know the basics, let’s see how to use the library.

    +

    Keyboards

    In Telegram Bot API keyboards are sent to the user as a part of an interaction via the reply_markup parameter. More specifically, this parameter is available:

    Sending inline keyboards is also supported in inline mode through the reply_markup parameter of the InlineQueryResult type and its inheritors. However, this inline mode is unrelated to the inline keyboards.

    The reply_markup parameter accepts four different types. Two of them — ReplyKeyboardMarkup and InlineKeyboardMarkup — correspond to the reply and inline keyboards respectively. -The ReplyKeyboardRemove type is used to remove reply keyboards, but it’s not a keyboard itself. +The ReplyKeyboardRemove type is used to remove reply keyboards, but it’s not a keyboard itself. The last one, ForceReply, is used to force users to reply to the bot. It is not a keyboard either, but yet another dirty hack employed by the Telegram Bot API.

    -

    A force reply

    +

    A force reply

    Now, in the library, the WithReplyMarkup is a marker interface for all the interactions which could have a replyMarkup (represents reply_markup) parameter. It is extended by the ReplyingMarkupSendMessageRequest, and then, finally, by classes like SendTextMessage. This, basically, corresponds to the Telegram Bot API.

    @@ -1797,7 +797,7 @@ This, basically, corresponds to the Telegram Bot API.

    The other way to send a keyboard is through the replyMarkup parameter of the numerous extension methods, like sendMessage. Those are just convenient wrappers around general interaction classes, like the aforementioned SendTextMessage.

    -

    Buttons

    +

    Buttons

    As we already know, keyboards consist of buttons. Button classes reside in the dev.inmo.tgbotapi.types.buttons package.

    The base class for the reply keyboard buttons is the KeyboardButton. @@ -1810,92 +810,92 @@ To attach a URL button to the message, use the CallbackDataInlineKeyboardButton.

    You get the idea.

    So, to send a reply keyboard use the following code:

    -
    bot.sendMessage(
    -    chatId = chat,
    -    text = "What is the best Kotlin Telegram Bot API library?",
    -    replyMarkup = ReplyKeyboardMarkup(
    -        keyboard = listOf(
    -            listOf(
    -                SimpleKeyboardButton("ktgbotapi"),
    -            ),
    -        )
    -    )
    -)
    +
    bot.sendMessage(
    +    chatId = chat,
    +    text = "What is the best Kotlin Telegram Bot API library?",
    +    replyMarkup = ReplyKeyboardMarkup(
    +        keyboard = listOf(
    +            listOf(
    +                SimpleKeyboardButton("ktgbotapi"),
    +            ),
    +        )
    +    )
    +)
     

    And here is how you send a basic inline keyboard:

    -
    bot.sendMessage(
    -    chatId = chat,
    -    text = "ktgbotapi is the best Kotlin Telegram Bot API library",
    -    replyMarkup = InlineKeyboardMarkup(
    -        keyboard = listOf(
    -            listOf(
    -                CallbackDataInlineKeyboardButton("I know", "know"),
    -                URLInlineKeyboardButton("Learn more", "https://github.com/InsanusMokrassar/ktgbotapi")
    -            ),
    -        )
    -    ),
    -)
    +
    bot.sendMessage(
    +    chatId = chat,
    +    text = "ktgbotapi is the best Kotlin Telegram Bot API library",
    +    replyMarkup = InlineKeyboardMarkup(
    +        keyboard = listOf(
    +            listOf(
    +                CallbackDataInlineKeyboardButton("I know", "know"),
    +                URLInlineKeyboardButton("Learn more", "https://github.com/InsanusMokrassar/ktgbotapi")
    +            ),
    +        )
    +    ),
    +)
     
    -

    Keyboards with ktgbotapi

    -

    When we’re done with this simple quiz, we can remove the keyboard with the following code:

    -
    bot.sendMessage(
    -    chatId = chat,
    -    text = "You're goddamn right!",
    -    replyMarkup = ReplyKeyboardRemove()
    -)
    +

    Keyboards with ktgbotapi

    +

    When we’re done with this simple quiz, we can remove the keyboard with the following code:

    +
    bot.sendMessage(
    +    chatId = chat,
    +    text = "You're goddamn right!",
    +    replyMarkup = ReplyKeyboardRemove()
    +)
     

    Note

    -

    Don’t forget to remove the reply keyboards!

    +

    Don’t forget to remove the reply keyboards!

    -

    Matrices

    -

    Buttons in keyboards are arranged in matrices, i.e. two-dimensional arrays, or, to say in layperson’s terms, rows and columns. -In contrast to the matrices you’ve learned in school, keyboards are not always necessarily square. +

    Matrices

    +

    Buttons in keyboards are arranged in matrices, i.e. two-dimensional arrays, or, to say in layperson’s terms, rows and columns. +In contrast to the matrices you’ve learned in school, keyboards are not always necessarily square. Try it:

    -
    bot.sendMessage(
    -    chatId = chat,
    -    text = "In contrast to the matrices you've learned in school, keyboards are not always necessary square.",
    -    replyMarkup = InlineKeyboardMarkup(
    -        keyboard = listOf(
    -            listOf(
    -                CallbackDataInlineKeyboardButton("1", "1"),
    -                CallbackDataInlineKeyboardButton("2", "2"),
    -                CallbackDataInlineKeyboardButton("3", "3"),
    -            ),
    -            listOf(
    -                CallbackDataInlineKeyboardButton("4", "4"),
    -                CallbackDataInlineKeyboardButton("5", "5"),
    -            ),
    -            listOf(
    -                CallbackDataInlineKeyboardButton("6", "6"),
    -            )
    -        )
    -    )
    -)
    +
    bot.sendMessage(
    +    chatId = chat,
    +    text = "In contrast to the matrices you've learned in school, keyboards are not always necessary square.",
    +    replyMarkup = InlineKeyboardMarkup(
    +        keyboard = listOf(
    +            listOf(
    +                CallbackDataInlineKeyboardButton("1", "1"),
    +                CallbackDataInlineKeyboardButton("2", "2"),
    +                CallbackDataInlineKeyboardButton("3", "3"),
    +            ),
    +            listOf(
    +                CallbackDataInlineKeyboardButton("4", "4"),
    +                CallbackDataInlineKeyboardButton("5", "5"),
    +            ),
    +            listOf(
    +                CallbackDataInlineKeyboardButton("6", "6"),
    +            )
    +        )
    +    )
    +)
     
    -

    A triangular keyboard

    +

    A triangular keyboard

    This way of building matrices is not very convenient, so the library provides a few eloquent DSLs to simplify that.

    First, there are matrix and row, so the keyboard above can be built like this:

    -

    bot.sendMessage(
    -    chatId = chat,
    -    text = "DSLs are sweet!",
    -    replyMarkup = InlineKeyboardMarkup(
    -        keyboard = matrix {
    -            row {
    -                +CallbackDataInlineKeyboardButton("1", "1")
    -                +CallbackDataInlineKeyboardButton("2", "2")
    -                +CallbackDataInlineKeyboardButton("3", "3")
    -            }
    -            row(
    -                CallbackDataInlineKeyboardButton("4", "4"),
    -                CallbackDataInlineKeyboardButton("5", "5"),
    -            )
    -            row {
    -                +CallbackDataInlineKeyboardButton("6", "6")
    -            }
    -        },
    -    )
    -)
    +

    bot.sendMessage(
    +    chatId = chat,
    +    text = "DSLs are sweet!",
    +    replyMarkup = InlineKeyboardMarkup(
    +        keyboard = matrix {
    +            row {
    +                +CallbackDataInlineKeyboardButton("1", "1")
    +                +CallbackDataInlineKeyboardButton("2", "2")
    +                +CallbackDataInlineKeyboardButton("3", "3")
    +            }
    +            row(
    +                CallbackDataInlineKeyboardButton("4", "4"),
    +                CallbackDataInlineKeyboardButton("5", "5"),
    +            )
    +            row {
    +                +CallbackDataInlineKeyboardButton("6", "6")
    +            }
    +        },
    +    )
    +)
     

    @@ -1907,334 +907,268 @@ Try it:

    There are two different row functions here. Can you spot the difference?

    A single-row matrix can be built with a flatMatrix:

    -
    flatMatrix {
    -    +CallbackDataInlineKeyboardButton("1", "1")
    -    +CallbackDataInlineKeyboardButton("2", "2")
    -    +CallbackDataInlineKeyboardButton("3", "3")
    -    +CallbackDataInlineKeyboardButton("4", "4")
    -    +CallbackDataInlineKeyboardButton("5", "5")
    -}
    +
    flatMatrix {
    +    +CallbackDataInlineKeyboardButton("1", "1")
    +    +CallbackDataInlineKeyboardButton("2", "2")
    +    +CallbackDataInlineKeyboardButton("3", "3")
    +    +CallbackDataInlineKeyboardButton("4", "4")
    +    +CallbackDataInlineKeyboardButton("5", "5")
    +}
     

    But the most convenient way to build a simple keyboard is to use the constructor-like methods: InlineKeyboardMarkup and ReplyKeyboardMarkup. Note, that they are named just like the corresponding constructor, but take a vararg of buttons. They create flat matrices, i.e. single rows.

    -

    Keyboards DSL

    +

    Keyboards DSL

    Finally, there are inlineKeyboard and replyKeyboard

    -

    DSL methods above rely on Kotlin’s feature of receivers and extensions. +

    DSL methods above rely on Kotlin’s feature of receivers and extensions. So, the magic is done by MatrixBuilder and RowBuilder. -That’s why you must use the plus sign to add buttons to the matrix: it’s just an overloaded operator call, another cool Kotlin feature widely used to create sweet DSLs.

    +That’s why you must use the plus sign to add buttons to the matrix: it’s just an overloaded operator call, another cool Kotlin feature widely used to create sweet DSLs.

    Another bonus of using these DSLs is button builders, like payButton, dataButton, and urlButton:

    -
    bot.sendMessage(
    -    chatId = chat,
    -    text = "All in one!",
    -    replyMarkup = InlineKeyboardMarkup(
    -        keyboard = matrix {
    -            row {
    -                payButton("Send money")
    -                dataButton("Ok", "ok")
    -                urlButton("Google", "https://google.com")
    -            }
    -        },
    -    )
    -)
    +
    bot.sendMessage(
    +    chatId = chat,
    +    text = "All in one!",
    +    replyMarkup = InlineKeyboardMarkup(
    +        keyboard = matrix {
    +            row {
    +                payButton("Send money")
    +                dataButton("Ok", "ok")
    +                urlButton("Google", "https://google.com")
    +            }
    +        },
    +    )
    +)
     

    Reply keyboard builders provide similar extensions, e.g. requestLocationButton.

    So, choose the style you like — from plain Kotlin lists to sweet DSLs — and use it!

    -

    Working with keyboards

    +

    Working with keyboards

    Working with keyboards is not something special in Telegram Bot API. As you have already seen, keyboards are just message parameters. Similarly, keyboard interactions are represented by regular Updates. I.e. when a user interacts with a keyboard, the bot receives an update.

    On the other hand, the library is heavily typed, so the actual type of update you would receive varies.

    -

    Reply keyboards

    +

    Reply keyboards

    As it was said, reply keyboards cause Telegram clients to send regular messages back to the bot. Peruse this example:

    -
    bot.buildBehaviourWithLongPolling {
    -    bot.sendMessage(
    -        chatId = chat,
    -        text = "👮 Turn in your accomplices or be prepared for a lengthy 🍆 incarceration ⛓ 👊 ‼",
    -        replyMarkup = replyKeyboard {
    -            +SimpleKeyboardButton(
    -                "I ain't no rat! 🚫🐀🤐🙅"
    -            )
    -            +RequestUserKeyboardButton(
    -                "Rat out 🐀 a friend 👤",
    -                KeyboardButtonRequestUser.Common(RequestId.random())
    -            )
    -            +RequestChatKeyboardButton(
    -                "Rat out 🐀 a group of friends 👥",
    -                KeyboardButtonRequestChat.Group(RequestId.random())
    -            )
    -        }
    -    )
    -
    -    onText { message: CommonMessage<TextContent> ->
    -        assert(message.text == "I ain't no rat! 🚫🐀🤐🙅")
    -        bot.reply(
    -            to = message,
    -            text = "Good, you're going to jail alone! ⛓🧑⛓",
    -            replyMarkup = ReplyKeyboardRemove()
    -        )
    -    }
    -
    -    onUserShared { message: PrivateEventMessage<UserShared> ->
    -        bot.reply(
    -            to = message,
    -            text = "Haha, you and you friend are both going to jail! ⛓👬⛓",
    -            replyMarkup = ReplyKeyboardRemove()
    -        )
    -    }
    -
    -    onChatShared { message: PrivateEventMessage<ChatShared> ->
    -        bot.reply(
    -            to = message,
    -            text = "Haha, now you're all going to jail! ⛓👨‍👦‍👦⛓",
    -            replyMarkup = ReplyKeyboardRemove()
    -        )
    -    }
    -}.join()
    +
    bot.buildBehaviourWithLongPolling {
    +    bot.sendMessage(
    +        chatId = chat,
    +        text = "👮 Turn in your accomplices or be prepared for a lengthy 🍆 incarceration ⛓ 👊 ‼",
    +        replyMarkup = replyKeyboard {
    +            +SimpleKeyboardButton(
    +                "I ain't no rat! 🚫🐀🤐🙅"
    +            )
    +            +RequestUserKeyboardButton(
    +                "Rat out 🐀 a friend 👤",
    +                KeyboardButtonRequestUser.Common(RequestId.random())
    +            )
    +            +RequestChatKeyboardButton(
    +                "Rat out 🐀 a group of friends 👥",
    +                KeyboardButtonRequestChat.Group(RequestId.random())
    +            )
    +        }
    +    )
    +
    +    onText { message: CommonMessage<TextContent> ->
    +        assert(message.text == "I ain't no rat! 🚫🐀🤐🙅")
    +        bot.reply(
    +            to = message,
    +            text = "Good, you're going to jail alone! ⛓🧑⛓",
    +            replyMarkup = ReplyKeyboardRemove()
    +        )
    +    }
    +
    +    onUserShared { message: PrivateEventMessage<UserShared> ->
    +        bot.reply(
    +            to = message,
    +            text = "Haha, you and you friend are both going to jail! ⛓👬⛓",
    +            replyMarkup = ReplyKeyboardRemove()
    +        )
    +    }
    +
    +    onChatShared { message: PrivateEventMessage<ChatShared> ->
    +        bot.reply(
    +            to = message,
    +            text = "Haha, now you're all going to jail! ⛓👨‍👦‍👦⛓",
    +            replyMarkup = ReplyKeyboardRemove()
    +        )
    +    }
    +}.join()
     

    Note

    Read more about buildBehaviourWithLongPolling here

    -

    I hope you get the idea: the bot acts like a cop and asks the user to rat out his friends via a reply keyboard (it’s an imaginary situation, of course). +

    I hope you get the idea: the bot acts like a cop and asks the user to rat out his friends via a reply keyboard (it’s an imaginary situation, of course). The user may refuse to cooperate, rat out a single friend or the whole imaginary group. -The bot receives the user’s choices as regular updates, the code above has explicit types (generally optional in Kotlin) and an assert to demonstrate this.

    +The bot receives the user’s choices as regular updates, the code above has explicit types (generally optional in Kotlin) and an assert to demonstrate this.

    And here is how it works (the user selects the options in the order):

    - + -

    Note how you handle reply keyboards: you process regular messages. For instance, a simple text button sends a regular text message indistinguishable from a case when a user simply types the same text manually.

    -

    And don’t be a rat in real life: remove the keyboards with the ReplyKeyboardRemove after you’ve received the input! +

    And don’t be a rat in real life: remove the keyboards with the ReplyKeyboardRemove after you’ve received the input! Otherwise, a keyboard will stay there indefinitely.

    -

    Inline keyboards

    +

    Inline keyboards

    Finally, to master the keyboards, you need to know how to handle the inline ones.

    -

    Again, let’s explore the example. -Imagine you’re making a quiz where users are given a question and a set of answers. +

    Again, let’s explore the example. +Imagine you’re making a quiz where users are given a question and a set of answers. Additionally, users are given a link to the wiki page to help with the question and a Google button.

    The quiz could be implemented this way:

    -
    // A simple data class to represent a question
    -val question = Question(
    -    image = "https://upload.wikimedia.org/wikipedia/commons/a/a5/Tsunami_by_hokusai_19th_century.jpg",
    -    question = "Who painted this?",
    -    answers = listOf(
    -        Answer("Hokusai", correct = true),
    -        Answer("Sukenobu"),
    -        Answer("Chōshun"),
    -        Answer("Kiyonobu I"),
    -    ),
    -    wiki = "https://en.wikipedia.org/wiki/Ukiyo-e",
    -)
    -
    -bot.buildBehaviourWithLongPolling {
    -    bot.sendPhoto(
    -        chatId = chat,
    -        fileId = InputFile.fromUrl(question.image),
    -        text = question.question,
    -        replyMarkup = inlineKeyboard {
    -            // First row: answers
    -            row {
    -                for (answer in question.answers.shuffled()) {
    -                    dataButton(
    -                        text = answer.answer,
    -                        data = "${answer.answer}:${answer.correct}",
    -                    )
    -                }
    -            }
    -
    -            // Second row: help buttons
    -            row {
    -                urlButton("Wiki 💁", question.wiki)
    -                webAppButton("Google 🔍", "https://google.com")
    -            }
    -        }
    -    )
    -
    -    onDataCallbackQuery { callback: DataCallbackQuery ->
    -        val (answer, correct) = callback.data.split(":")
    -
    -        if (correct.toBoolean()) {
    -            bot.answerCallbackQuery(
    -                callback,
    -                text = "$answer is a ✅ correct answer!",
    -                showAlert = true
    -            )
    -        } else {
    -            bot.answerCallbackQuery(
    -                callback,
    -                text = "❌ Try again, $answer is not a correct answer…",
    -                showAlert = true
    -            )
    -        }
    -    }
    -}.join()
    +
    // A simple data class to represent a question
    +val question = Question(
    +    image = "https://upload.wikimedia.org/wikipedia/commons/a/a5/Tsunami_by_hokusai_19th_century.jpg",
    +    question = "Who painted this?",
    +    answers = listOf(
    +        Answer("Hokusai", correct = true),
    +        Answer("Sukenobu"),
    +        Answer("Chōshun"),
    +        Answer("Kiyonobu I"),
    +    ),
    +    wiki = "https://en.wikipedia.org/wiki/Ukiyo-e",
    +)
    +
    +bot.buildBehaviourWithLongPolling {
    +    bot.sendPhoto(
    +        chatId = chat,
    +        fileId = InputFile.fromUrl(question.image),
    +        text = question.question,
    +        replyMarkup = inlineKeyboard {
    +            // First row: answers
    +            row {
    +                for (answer in question.answers.shuffled()) {
    +                    dataButton(
    +                        text = answer.answer,
    +                        data = "${answer.answer}:${answer.correct}",
    +                    )
    +                }
    +            }
    +
    +            // Second row: help buttons
    +            row {
    +                urlButton("Wiki 💁", question.wiki)
    +                webAppButton("Google 🔍", "https://google.com")
    +            }
    +        }
    +    )
    +
    +    onDataCallbackQuery { callback: DataCallbackQuery ->
    +        val (answer, correct) = callback.data.split(":")
    +
    +        if (correct.toBoolean()) {
    +            bot.answerCallbackQuery(
    +                callback,
    +                text = "$answer is a ✅ correct answer!",
    +                showAlert = true
    +            )
    +        } else {
    +            bot.answerCallbackQuery(
    +                callback,
    +                text = "❌ Try again, $answer is not a correct answer…",
    +                showAlert = true
    +            )
    +        }
    +    }
    +}.join()
     

    A few important things to note here.

    First, the data buttons (they have the CallbackDataInlineKeyboardButton type, but in the code we used a neat DSL) must have unique data. If the data is not unique, Telegram clients will highlight all the buttons with the same data when a user clicks on one of them. Guess how I know that? -Well, it’s not in the docs, so trial and error is the only way to learn it (and many other things about the Telegram Bot API).

    +Well, it’s not in the docs, so trial and error is the only way to learn it (and many other things about the Telegram Bot API).

    Second, the way you handle inline keyboards is different from the way you handle reply keyboards. Bot API will send updates with a callback_query field populated. This field, of a CallbackQuery type, represents incoming callbacks from callback buttons in inline keyboards. The library turns them into multiple callback types, like the DataCallbackQuery we used in the example. Finally, to handle these callbacks you could use onDataCallbackQuery. -Alternatively, if you’re not using any DSLs, you have to handle the CallbackQueryUpdate update type.

    +Alternatively, if you’re not using any DSLs, you have to handle the CallbackQueryUpdate update type.

    Third, the buttons got highlighted when a user clicks on them. -When you’re done with the callback, you need to answer it, by using the answerCallbackQuery function. +When you’re done with the callback, you need to answer it, by using the answerCallbackQuery function. Otherwise, the button will remain highlighted. -Telegram clients will eventually remove the highlight, but it’s still frustrating.

    +Telegram clients will eventually remove the highlight, but it’s still frustrating.

    Finally, you could choose between two styles of acknowledgment: a simple toast-like message or a modal alert. The showAlert flag controls this behavior.

    And here is the demo of the quiz:

    - + - -

    Conclusion

    -

    Today we’ve learned how to use keyboards in Telegram bots. +

    Conclusion

    +

    Today we’ve learned how to use keyboards in Telegram bots. There are two types of keyboards: reply and inline. -Reply keyboards replace the device’s keyboard and make clients send a message with the predefined content. +Reply keyboards replace the device’s keyboard and make clients send a message with the predefined content. Inline keyboards are buttons attached to messages. Clicking on them causes the client to send a callback to the bot. In both scenarios the bot receives an update of a corresponding type and has to acknowledge the keayboard interaction for the client to work properly.

    - - - - - - - - - -
    - - -
    - -
    +
    + - - - -
    - -
    -
    -
    -
    - - - - - - - - +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/index.html b/tgbotapi/index.html index e05dbd1..eb49b30 100644 --- a/tgbotapi/index.html +++ b/tgbotapi/index.html @@ -1,1517 +1,591 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - TelegramBotAPI - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +TelegramBotAPI - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - +
    +
    +
    + - - - -
    -
    +
    +
    +
    +
    + + + + + + +

    TelegramBotAPI

    +

    Maven Central Supported version

    - -

    https://t.me/ktgbotapi

    +

    https://t.me/ktgbotapi

    Hello! This is a set of libraries for working with Telegram Bot API.

    -

    Examples

    +

    Examples

    There are several things you need to do to launch examples below:

    • Add mavenCentral() to your project repositories -

      Most common example

      -
      suspend fun main() {
      -  val bot = telegramBot(TOKEN)
      -
      -  bot.buildBehaviourWithLongPolling {
      -    println(getMe())
      -
      -    onCommand("start") {
      -      reply(it, "Hi:)")
      -    }
      -  }.join()
      -}
      +

      Most common example

      +
      suspend fun main() {
      +  val bot = telegramBot(TOKEN)
      +
      +  bot.buildBehaviourWithLongPolling {
      +    println(getMe())
      +
      +    onCommand("start") {
      +      reply(it, "Hi:)")
      +    }
      +  }.join()
      +}
       

      In this example you will see information about this bot at the moment of starting and answer with Hi:) every time it gets message /start

      -

      Handling only last messages

      -
      suspend fun main() {
      -  val bot = telegramBot(TOKEN)
      -
      -  val flowsUpdatesFilter = FlowsUpdatesFilter()
      -  bot.buildBehaviour(flowUpdatesFilter = flowsUpdatesFilter) {
      -    println(getMe())
      -
      -    onCommand("start") {
      -      reply(it, "Hi:)")
      -    }
      -
      -    retrieveAccumulatedUpdates(this).join()
      -  }
      -}
      +

      Handling only last messages

      +
      suspend fun main() {
      +  val bot = telegramBot(TOKEN)
      +
      +  val flowsUpdatesFilter = FlowsUpdatesFilter()
      +  bot.buildBehaviour(flowUpdatesFilter = flowsUpdatesFilter) {
      +    println(getMe())
      +
      +    onCommand("start") {
      +      reply(it, "Hi:)")
      +    }
      +
      +    retrieveAccumulatedUpdates(this).join()
      +  }
      +}
       

      The main difference with the previous example is that bot will get only last updates (accumulated before bot launch and maybe some updates it got after launch)

      -

      Build a little bit more complex behaviour

      -
      suspend fun main() {
      -  val bot = telegramBot(TOKEN)
      -
      -  bot.buildBehaviourWithLongPolling {
      -    println(getMe())
      -
      -    val nameReplyMarkup = ReplyKeyboardMarkup(
      -      matrix {
      -        row {
      -          +SimpleKeyboardButton("nope")
      -        }
      -      }
      -    )
      -    onCommand("start") {
      -      val photo = waitPhoto(
      -        SendTextMessage(it.chat.id, "Send me your photo please")
      -      ).first()
      -
      -      val name = waitText(
      -        SendTextMessage(
      -          it.chat.id,
      -          "Send me your name or choose \"nope\"",
      -          replyMarkup = nameReplyMarkup
      -        )
      -      ).first().text.takeIf { it != "nope" }
      -
      -      sendPhoto(
      -        it.chat,
      -        photo.mediaCollection,
      -        entities = buildEntities {
      -          if (name != null) regular(name) // may be collapsed up to name ?.let(::regular)
      -        }
      -      )
      -    }
      -  }.join()
      -}
      +

      Build a little bit more complex behaviour

      +
      suspend fun main() {
      +  val bot = telegramBot(TOKEN)
      +
      +  bot.buildBehaviourWithLongPolling {
      +    println(getMe())
      +
      +    val nameReplyMarkup = ReplyKeyboardMarkup(
      +      matrix {
      +        row {
      +          +SimpleKeyboardButton("nope")
      +        }
      +      }
      +    )
      +    onCommand("start") {
      +      val photo = waitPhoto(
      +        SendTextMessage(it.chat.id, "Send me your photo please")
      +      ).first()
      +
      +      val name = waitText(
      +        SendTextMessage(
      +          it.chat.id,
      +          "Send me your name or choose \"nope\"",
      +          replyMarkup = nameReplyMarkup
      +        )
      +      ).first().text.takeIf { it != "nope" }
      +
      +      sendPhoto(
      +        it.chat,
      +        photo.mediaCollection,
      +        entities = buildEntities {
      +          if (name != null) regular(name) // may be collapsed up to name ?.let(::regular)
      +        }
      +      )
      +    }
      +  }.join()
      +}
       
      -

      More examples

      +

      More examples

      You may find examples in this project. Besides, you are always welcome in our chat.

      - - - - - - - - -
    -
    - - -
    - -
    + + - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/introduction/before-any-bot-project.html b/tgbotapi/introduction/before-any-bot-project.html index d789f95..2ff29a9 100644 --- a/tgbotapi/introduction/before-any-bot-project.html +++ b/tgbotapi/introduction/before-any-bot-project.html @@ -1,1665 +1,661 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Before any bot project - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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 index 12c4683..fd3628f 100644 --- a/tgbotapi/introduction/first-bot.html +++ b/tgbotapi/introduction/first-bot.html @@ -1,1534 +1,594 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - First bot - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    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())
    -}
    +
    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:

    +

    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
      • @@ -1539,143 +599,79 @@ A lot of examples with using of Telegram Bot API you can find in 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)
    +
    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 index 0e02594..281fb36 100644 --- a/tgbotapi/introduction/including-in-your-project.html +++ b/tgbotapi/introduction/including-in-your-project.html @@ -1,1804 +1,780 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Including in your project - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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
    • @@ -1813,232 +789,162 @@

      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

    +

    Notice about repository

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

    -
    build.gradle
    -
    mavenCentral()
    +
    build.gradle
    +
    mavenCentral()
     
    -
    pom.xml
    -
    <repository>
    -  <id>central</id>
    -  <name>mavenCentral</name>
    -  <url>https://repo1.maven.org/maven2</url>
    -</repository>
    +
    pom.xml
    +
    <repository>
    +  <id>central</id>
    +  <name>mavenCentral</name>
    +  <url>https://repo1.maven.org/maven2</url>
    +</repository>
     
    -
    Dev channel
    +
    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 {
    +    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>
    +
    <repository>
    +  <id>dev.inmo</id>
    +  <name>InmoDev</name>
    +  <url>https://git.inmo.dev/api/packages/InsanusMokrassar/maven</url>
    +</repository>
     
    -
    - -

    TelegramBotAPI

    +

    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"
    +

    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>
    +
    pom.xml
    +
    <dependency>
    +    <groupId>dev.inmo</groupId>
    +    <artifactId>tgbotapi</artifactId>
    +    <version>${tgbotapi_version}</version>
    +</dependency>
     
    -

    TelegramBotAPI Core

    +

    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"
    +

    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>
    +
    pom.xml
    +
    <dependency>
    +    <groupId>dev.inmo</groupId>
    +    <artifactId>tgbotapi.core</artifactId>
    +    <version>${tgbotapi_version}</version>
    +</dependency>
     
    -

    TelegramBotAPI API Extensions

    +

    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"
    +

    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>
    +
    pom.xml
    +
    <dependency>
    +    <groupId>dev.inmo</groupId>
    +    <artifactId>tgbotapi.api</artifactId>
    +    <version>${tgbotapi_version}</version>
    +</dependency>
     
    -

    TelegramBotAPI Utils Extensions

    +

    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"
    +

    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>
    +
    pom.xml
    +
    <dependency>
    +    <groupId>dev.inmo</groupId>
    +    <artifactId>tgbotapi.utils</artifactId>
    +    <version>${tgbotapi_version}</version>
    +</dependency>
     
    -

    Next steps

    +

    Next steps

    - - - - - - - - - -
    - - -
    - -
    +
    + - - - -
    - -
    -
    -
    -
    - - - - - - - - +
    +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/introduction/proxy-setup.html b/tgbotapi/introduction/proxy-setup.html index 7f1e666..49e0008 100644 --- a/tgbotapi/introduction/proxy-setup.html +++ b/tgbotapi/introduction/proxy-setup.html @@ -1,1548 +1,604 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Proxy setup - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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
    +
    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:

      @@ -1550,10 +606,10 @@
    • System-configured VPN or proxy
    • Your own Bot API Server
    -

    Using Ktor Client built-in proxy

    +

    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"
    +
    implementation "io.ktor:ktor-client-okhttp:2.0.1"
     

    Dependency note

    @@ -1561,11 +617,11 @@ 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)
    -}
    +
    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:

      @@ -1574,145 +630,81 @@ In the snippet above was used version 2.0.1 which is actual for 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
    1. 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
    -

    Next steps

    +

    Next steps

    - - - - - - - - -
    -
    - - -
    - -
    +
    + - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/api-extensions.html b/tgbotapi/logic/api-extensions.html index b43bd6e..a01b8cc 100644 --- a/tgbotapi/logic/api-extensions.html +++ b/tgbotapi/logic/api-extensions.html @@ -1,1723 +1,707 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - API Extensions - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    +

    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 
    -  }
    -}
    +
    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

    +

    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

    +

    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?

    +

    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 index e63f7d0..10c2216 100644 --- a/tgbotapi/logic/behaviour-builder-with-fsm.html +++ b/tgbotapi/logic/behaviour-builder-with-fsm.html @@ -1,1534 +1,590 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Behaviour Builder with FSM - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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
    • @@ -1542,11 +598,11 @@
    • 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()
    +
    buildFSM<TrafficLightState> {
    +    strictlyOn<SomeState> {
    +        // state handling
    +    }
    +}.start(CoroutineScope(...)).join()
     

    Full example

    @@ -1559,7 +615,7 @@ You may find full example of FSM usage in 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

    +

    Bot with FSM

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

    • buildBehaviourWithFSM
        @@ -1573,151 +629,87 @@ You may find full example of FSM usage in 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
        -}
        +
        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

        +

        Examples

        - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/logic/behaviour-builder.html b/tgbotapi/logic/behaviour-builder.html index dd04523..9df2626 100644 --- a/tgbotapi/logic/behaviour-builder.html +++ b/tgbotapi/logic/behaviour-builder.html @@ -1,1737 +1,725 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Behaviour Builder - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    +

    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
    -  }
    -}
    +

    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
    -    }
    -  ) {
    -    // ...
    -  }
    -}
    +

    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`
    -  ) {
    -    // ...
    -  }
    -}
    +
    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 index 77dbe70..f80efa2 100644 --- a/tgbotapi/logic/exceptions-handling.html +++ b/tgbotapi/logic/exceptions-handling.html @@ -1,1798 +1,774 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Exceptions handling - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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 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

    +

    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
    +
    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

    +

    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
    -}
    +
    safely(
    +  {
    +      // handle error
    +      it.printStackTrace()
    +      null // return value
    +  }
    +) {
    +    // do something
    +}
     
    -

    Bonus: different types of handling

    +

    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(
      +    {
      +        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
      +
      safelyWithouExceptions {
      +    // do something
      +} // will returns nullable result type
       
    -

    Global exceptions handling

    +

    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()
    -    }
    -) {
    -    // ...
    -}
    +
    val bot = telegramBot("TOKEN")
    +
    +bot.buildBehaviour (
    +    scope = scope,
    +    defaultExceptionsHandler = {
    +        it.printStackTrace()
    +    }
    +) {
    +    // ...
    +}
     

    OR

    -
    val bot = telegramBotWithBehaviour (
    -    "TOKEN",
    -    scope = scope,
    -    defaultExceptionsHandler = {
    -        it.printStackTrace()
    -    }
    -) {
    -    // ...
    -}
    +
    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 index 3fdc1b3..ba2f937 100644 --- a/tgbotapi/logic/files-handling.html +++ b/tgbotapi/logic/files-handling.html @@ -1,1557 +1,635 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Files handling - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +Files handling - InMo Docs + + + + + + + + + + + + + + + + -
    - -
    - - - - +
    +
    +
    -
    - -
    - - - - - - - - - -
    -
    - - - -
    -
    -
    - - - - - - + +
  • + + - -
  • - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - -
  • + + + +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + +
  • +
  • + + - -
  • - - - - - - - - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - - - - - - - -
  • + + + + + + +
  • + + + -
  • - - - - - - - - - - - -
  • - - - - - - - - - - - - - - - - - - -
  • + + + +
  • + + + -
  • - - - - +
      +
    -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    -
    - - - -
    -
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + + + + + +

    Files handling

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

    -

    Files receiving

    +

    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?

    +

    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;
    +
    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

    +

    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)
    +

    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)
    +
    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)
    +

    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)
    +

    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)
    +

    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?

    +

    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))
    +
    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()
    +
    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.

    @@ -1563,15 +641,15 @@
  • DownloadFileStream
  • -

    Files sending

    +

    Files sending

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

    -

    FileId and FileUrl

    +

    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

    +

    Sending via file

    JS Restrictions

    @@ -1583,145 +661,81 @@ Sending via file is accessible from all supported platforms, but there is small

  • 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())
    +
    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 index 6e3f75e..548df7f 100644 --- a/tgbotapi/logic/low-level-work-with-bots.html +++ b/tgbotapi/logic/low-level-work-with-bots.html @@ -1,1625 +1,639 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Low-level work with bots - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    +

    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

    +

    How to handle updates

    As was written above, it will require some request:

    -
    val updates = bot.execute(GetUpdates())
    +
    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?

    +

    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 index d1b7024..d194b03 100644 --- a/tgbotapi/logic/media-groups.html +++ b/tgbotapi/logic/media-groups.html @@ -1,1520 +1,580 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Media Groups - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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
    • @@ -1527,27 +587,27 @@
    -

    Specific of media groups in libraries

    +

    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
    -    }
    -  }
    -}
    +
    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 index b74a37e..2cee2f9 100644 --- a/tgbotapi/logic/types-conversations.html +++ b/tgbotapi/logic/types-conversations.html @@ -1,1705 +1,693 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Types conversations - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    +
    +
    +
    +
    +
    + + + + + + +

    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

    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)
    +
    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

    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)
    +
    val message: Message;
    +println(message.requireContentMessage().content)
     

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

    -

    When

    +

    When

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

    -
    val message: Message;
    -message.whenContentMessage {
    -    println(it.content)
    -}
    +
    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 index 481e735..3233690 100644 --- a/tgbotapi/logic/updates-with-flows.html +++ b/tgbotapi/logic/updates-with-flows.html @@ -1,1579 +1,631 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Updates with flows - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    +

    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)
    -}
    +

    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

    +

    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()
    +
    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:

      @@ -1583,147 +635,83 @@
    • 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?

    +

    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 index ea8fe19..a266343 100644 --- a/tgbotapi/updates/heroku.html +++ b/tgbotapi/updates/heroku.html @@ -1,1534 +1,590 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Heroku - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    @@ -1544,221 +600,157 @@

    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()
    -}
    +

    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)
    +

    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 index 9d23da8..81732fc 100644 --- a/tgbotapi/updates/long-polling.html +++ b/tgbotapi/updates/long-polling.html @@ -1,1594 +1,634 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Long polling - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    +

    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
        @@ -1603,170 +643,106 @@
      • By yourself with GetUpdates request or RequestsExecutor#getUpdates extension
      -

      longPolling

      +

      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
      -  }
      -)
      +
      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

      +

      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
      -  }
      -)
      +
      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
      -}
      +
      val bot = telegramBot(token)
      +bot.startGettingOfUpdatesByLongPolling {
      +  println(it) // will be printed each update
      +}
       
      -

      See also

      +

      See also

      - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/updates/updates-filters.html b/tgbotapi/updates/updates-filters.html index 5d5c8d8..b32d58e 100644 --- a/tgbotapi/updates/updates-filters.html +++ b/tgbotapi/updates/updates-filters.html @@ -1,1844 +1,812 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Updates filters - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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

    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

    +

    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)
    -}
    +
    SimpleUpdatesFilter {
    +  println(it)
    +}
     
    -

    FlowsUpdatesFilter

    +

    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)
    -}
    +
    val scope = CoroutineScope(Dispatchers.Default)
    +flowsUpdatesFilter {
    +  messageFlow.onEach {
    +    println(it)
    +  }.launchIn(scope)
    +}
     
    -

    Combining of flows

    +

    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)
      -}
      +
      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 {
      +  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)
      -}
      +
      flowsUpdatesFilter {
      +  allSentMessagesFlow.onEach {
      +    println(it) // will be printed each message update from channels and chats both
      +  }.launchIn(scope)
      +}
       
    -

    Types filtering

    +

    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)
    -}
    +
    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)
    -}
    +
    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)
    -}
    +
    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

    +

    See also

    - - - - - - - - -
    -
    - - - - - - - - - - - -
    -
    -
    - - - - - - - - + +
    +
    +
    + + + + + \ No newline at end of file diff --git a/tgbotapi/updates/webhooks.html b/tgbotapi/updates/webhooks.html index 3107c6a..ed1321e 100644 --- a/tgbotapi/updates/webhooks.html +++ b/tgbotapi/updates/webhooks.html @@ -1,1562 +1,610 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - Webhooks - InMo Docs - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    - - - + + + + + + + + + + + +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
    • @@ -1564,241 +612,177 @@
    • startListenWebhooks
    • RequestsExecutor#setWebhookInfoAndStartListenWebhooks
    -

    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
    -)
    +
    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
    -)
    +
    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

    +

    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
    -)
    +
    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

    +

    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
    +
    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

    +

    See also

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