1"use strict";(self.webpackChunkwebsite=self.webpackChunkwebsite||[]).push([[3866],{3905:(e,n,t)=>{t.d(n,{Zo:()=>d,kt:()=>u});var a=t(7294);function o(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);n&&(a=a.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,a)}return t}function r(e){for(var n=1;n<arguments.length;n++){var t=null!=arguments[n]?arguments[n]:{};n%2?i(Object(t),!0).forEach((function(n){o(e,n,t[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(t)):i(Object(t)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(t,n))}))}return e}function l(e,n){if(null==e)return{};var t,a,o=function(e,n){if(null==e)return{};var t,a,o={},i=Object.keys(e);for(a=0;a<i.length;a++)t=i[a],n.indexOf(t)>=0||(o[t]=e[t]);return o}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(a=0;a<i.length;a++)t=i[a],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(o[t]=e[t])}return o}var s=a.createContext({}),c=function(e){var n=a.useContext(s),t=n;return e&&(t="function"==typeof e?e(n):r(r({},n),e)),t},d=function(e){var n=c(e.components);return a.createElement(s.Provider,{value:n},e.children)},m="mdxType",h={inlineCode:"code",wrapper:function(e){var n=e.children;return a.createElement(a.Fragment,{},n)}},p=a.forwardRef((function(e,n){var t=e.components,o=e.mdxType,i=e.originalType,s=e.parentName,d=l(e,["components","mdxType","originalType","parentName"]),m=c(t),p=o,u=m["".concat(s,".").concat(p)]||m[p]||h[p]||i;return t?a.createElement(u,r(r({ref:n},d),{},{components:t})):a.createElement(u,r({ref:n},d))}));function u(e,n){var t=arguments,o=n&&n.mdxType;if("string"==typeof e||o){var i=t.length,r=new Array(i);r[0]=p;var l={};for(var s in n)hasOwnProperty.call(n,s)&&(l[s]=n[s]);l.originalType=e,l[m]="string"==typeof e?e:o,r[1]=l;for(var c=2;c<i;c++)r[c]=t[c];return a.createElement.apply(null,r)}return a.createElement.apply(null,t)}p.displayName="MDXCreateElement"},4548:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>s,contentTitle:()=>r,default:()=>h,frontMatter:()=>i,metadata:()=>l,toc:()=>c});var a=t(7462),o=(t(7294),t(3905));const i={},r="Channels",l={unversionedId:"coding-guides/channels",id:"coding-guides/channels",title:"Channels",description:"Overview",source:"@site/docs/coding-guides/channels.md",sourceDirName:"coding-guides",slug:"/coding-guides/channels",permalink:"/coding-guides/channels",draft:!1,tags:[],version:"current",frontMatter:{},sidebar:"docsSidebar",previous:{title:"Resources",permalink:"/coding-guides/resources"},next:{title:"Extensions",permalink:"/coding-guides/extensions"}},s={},c=[{value:"Overview",id:"overview",level:2},{value:"Using Channels",id:"using-channels",level:2},{value:"Receivers and Senders",id:"receivers-and-senders",level:2},{value:"Mixins",id:"mixins",level:2},{value:"Adding Channels",id:"adding-channels",level:2},{value:"Extending channels",id:"extending-channels",level:2},{value:"Custom Receivers",id:"custom-receivers",level:3},{value:"Custom senders",id:"custom-senders",level:3},{value:"Webhooks",id:"webhooks",level:2}],d={toc:c},m="wrapper";function h(e){let{components:n,...t}=e;return(0,o.kt)(m,(0,a.Z)({},d,t,{components:n,mdxType:"MDXLayout"}),(0,o.kt)("h1",{id:"channels"},"Channels"),(0,o.kt)("h2",{id:"overview"},"Overview"),(0,o.kt)("p",null,"Channels are an abstractions for messaging platforms which connect your bot to the intended user. There are a lot of such platforms: Telegram, Facebook Messenger and more."),(0,o.kt)("p",null,"Another purpose of channels is to provide implementations for conversational applications. (TODO: link to applications)"),(0,o.kt)("p",null,(0,o.kt)("strong",{parentName:"p"},"maxbot")," provides pre-built channels for the many famous platforms. (TODO: link to the list)"),(0,o.kt)("h2",{id:"using-channels"},"Using Channels"),(0,o.kt)("p",null,"For each platform, there is a channel class that derives from the abstract ",(0,o.kt)("inlineCode",{parentName:"p"},"Channel"),". A bot can use one or more channels to interact with the user. The channel class instance is implicitly created when a channel configuration is added to bot resources."),(0,o.kt)("h2",{id:"receivers-and-senders"},"Receivers and Senders"),(0,o.kt)("p",null,"The ",(0,o.kt)("inlineCode",{parentName:"p"},"Channel.call_receivers")," and ",(0,o.kt)("inlineCode",{parentName:"p"},"Channel.call_senders")," methods internally use hooks called senders and receivers. These hooks are used to receive messages and send commands of different types. A channel class discover its hooks among its members using a naming convention: the ",(0,o.kt)("inlineCode",{parentName:"p"},"receive_")," or ",(0,o.kt)("inlineCode",{parentName:"p"},"send_")," prefix followed by the message or command type."),(0,o.kt)("h2",{id:"mixins"},"Mixins"),(0,o.kt)("p",null,"It's easy to create new and extend existing channels thanks to the mixins technique based on Python's multiple inheritance."),(0,o.kt)("p",null,"The particular channel class is created at run time and does not contain any methods itself. It inherits the abstract ",(0,o.kt)("inlineCode",{parentName:"p"},"Channel")," class and multiple mixin classes. Mixins implement all the necessary methods. They can be added using the ",(0,o.kt)("inlineCode",{parentName:"p"},"BotBuilder.add_channel_mixin")," method or the ",(0,o.kt)("inlineCode",{parentName:"p"},"BotBuilder.channel_mixin")," decorator. A channel method can be implemented in more than one mixin. The actual implementation is chosen based on the ",(0,o.kt)("a",{parentName:"p",href:"https://docs.python.org/3/tutorial/classes.html#multiple-inheritance"},"multiple inheritance")," rules."),(0,o.kt)("p",null,"Mixins are added to the list of base classes from right to left. The abstract class ",(0,o.kt)("inlineCode",{parentName:"p"},"Channel")," is rightmost. For example, let's create a channel using a couple of mixins."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-python"},"builder.add_channel_mixin(MyChannel1, 'my_channel')\nbuilder.add_channel_mixin(MyChannel2, 'my_channel')\n")),(0,o.kt)("p",null,"The generated class will look like this."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-python"}
1,"class GeneratedChannel_MyChannel(MyChannel2, MyChannel1, Channel):\n pass\n")),(0,o.kt)("p",null,"Thus, any channel method is searched for in ",(0,o.kt)("inlineCode",{parentName:"p"},"MyChannel2"),", then in ",(0,o.kt)("inlineCode",{parentName:"p"},"MyChannel1"),", and then in ",(0,o.kt)("inlineCode",{parentName:"p"},"Channel"),"."),(0,o.kt)("h2",{id:"adding-channels"},"Adding Channels"),(0,o.kt)("p",null,"To create a channel, you need to create a mixin that implements at least all the abstract methods of the ",(0,o.kt)("inlineCode",{parentName:"p"},"Channel")," class and register it using the ",(0,o.kt)("inlineCode",{parentName:"p"},"BotBuilder.channel_mixin")," with the desired name."),(0,o.kt)("p",null,'In the example we will create a simple "repl" channel that will allow us to communicate with the bot through an interactive shell.'),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre",className:"language-python"},"import asyncio\nfrom marshmallow import fields, Schema\nfrom maxbot import MaxBot, Channel\n\nbuilder = MaxBot.builder()\n\[email protected]_mixin(\"repl\")\nclass ReplChannel(Channel):\n class ConfigSchema(Schema):\n user_prompt = fields.String(load_default='User')\n bot_prompt = fields.String(load_default=' Bot')\n\n async def create_dialog(self, data):\n return {'channel_name': 'cli', 'user_id': 'John'}\n\n async def receive_text(self, data):\n return {'text': data}\n\n async def send_text(self, command, dialog):\n print(self.config[\"bot_prompt\"] + \":\", command[\"text\"])\n\n\nbuilder.use_inline_resources(\"\"\"\n channels:\n repl:\n user_prompt: \ud83e\uddd1\n bot_prompt: \ud83e\udd16\n dialog:\n - condition: message.text in ['hello', 'hi']\n response: |\n Good day to you!\n - condition: message.text in ['good bye', 'bye']\n response: |\n OK. See you later.\n - condition: true\n response: |\n Sorry I don\"t understand.\n\"\"\")\nbot = builder.build()\nchannel = bot.channels.repl\n\ndef main():\n while True:\n # Getting channel-specific channel arguments\n text = input(channel.config[\"user_prompt\"] + \": \")\n\n message = await channel.call_receivers(text)\n dialog = await channel.create_dialog(text)\n commands = await bot.dialog_manager.process_message(message, dialog)\n for command in commands:\n await channel.call_senders(command, dialog)\n\nasyncio.run(main())\n")),(0,o.kt)("p",null,"In the example above we have implemented:"),(0,o.kt)("ul",null,(0,o.kt)("li",{parentName:"ul"},"configuration schema for prompt strings for the bot and users;"),(0,o.kt)("li",{parentName:"ul"},"three abstract methods of the ",(0,o.kt)("inlineCode",{parentName:"li"},"Channel")," class: ",(0,o.kt)("inlineCode",{parentName:"li"},"create_dialog")," and a minimal set of mandatory hooks ",(0,o.kt)("inlineCode",{parentName:"li"},"receive_text")," and ",(0,o.kt)("inlineCode",{parentName:"li"},"send_text"),";"),(0,o.kt)("li",{parentName:"ul"},"the ",(0,o.kt)("inlineCode",{parentName:"li"},"main")," function which runs the channel-specific conversational application as a ",(0,o.kt)("a",{parentName:"li",href:"https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop"},"read\u2013eval\u2013print")," loop.")),(0,o.kt)("h2",{id:"extending-channels"},"Extending channels"),(0,o.kt)("p",null,"To extend a channel you need to create an additional mixin with the methods you wish to add or overwrite and register it with the channel name."),(0,o.kt)("h3",{id:"custom-receivers"},"Custom Receivers"),(0,o.kt)("p",null,"When you add a custom message to your bot you also need to add a message receiver for each of channels used in your application. In this example we'll add the custom message that represents a phone contact and message receiver for the pre-built Telegram channel."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre"},'from marshmallow import fields, Schema\nfrom maxbot import MaxBot\nfrom telegram import Update, Bot\n\nbuilder = MaxBot.builder()\n\[email protected]("contact")\nclass ContactMessage(Schema):\n phone_number = fields.String(required=True)\n name = fields.String()\n\[email protected]_mixin("telegram")\nclass TelegramContact:\n\n async def receive_contact(self, update: Upd
1ate, bot: Bot):\n """\n @see https://core.telegram.org/bots/api#contact\n """\n if update.message.contact:\n contact = update.message.contact\n return {\'contact\': {\n \'phone_number\': contact.phone_number,\n \'name\': contact.first_name\n }}\n\nbuilder.use_inline_resources("""\n channels:\n telegram:\n api_token: !ENV ${TELEGRAM_API_KEY}\n dialog:\n - condition: message.contact\n response: |\n Received {{ message.contact.phone_number }}\n""")\nbot = builder.build()\n')),(0,o.kt)("h3",{id:"custom-senders"},"Custom senders"),(0,o.kt)("p",null,"When you add a custom command to your bot you also need to add a command sender for each of channels used in your application. In this example we'll add the custom command that represents a location on the map and command sender for the pre-built Telegram channel."),(0,o.kt)("pre",null,(0,o.kt)("code",{parentName:"pre"},'from marshmallow import fields, Schema\nfrom maxbot import MaxBot\nfrom telegram import Bot\n\nbuilder = MaxBot.builder()\n\[email protected]("location")\nclass LocationCommand(Schema):\n longitude = fields.Float(required=True)\n latitude = fields.Float(required=True)\n\[email protected]_mixin("telegram")\nclass TelegramLocation:\n async def send_location(self, command, channel, bot: Bot):\n """\n @see https://core.telegram.org/bots/api#sendlocation\n """\n location = command["location"]\n await bot.send_location(\n channel[\'user_id\'],\n latitude=location["latitude"],\n longitude=location["longitude"],\n )\n\nbuilder.use_inline_resources("""\n channels:\n telegram:\n api_token: !ENV ${TELEGRAM_API_KEY}\n dialog:\n - condition: message.text == "Where are you?"\n response: |\n I am here!\n <location latitude="40.7580" longitude="-73.9855" />\n""")\nbot = builder.build()\n')),(0,o.kt)("h2",{id:"webhooks"},"Webhooks"),(0,o.kt)("p",null,"Channels can receive data in two modes: webhook and polling. Polling means that the bot polls the server at some intervals to see if there are any changes. Please note: only the built-in ",(0,o.kt)("inlineCode",{parentName:"p"},"telegram")," channel can run in polling mode. Webhook mode means that the bot has an external web address that will be called when there is new data from the messenger."),(0,o.kt)("p",null,"The processing time of an incoming webhook request is limited. We recommend completing the handling within 5 seconds. Keep in mind that if you take too long to process a message from a user, the messenger may resend the same message. In order not to process the same message again, it will be necessary to check the identifiers of the processed messages. This code can be represented as ",(0,o.kt)("inlineCode",{parentName:"p"},"middleware")," and, for example, use a database as storage."))}h.isMDXComponent=!0}}]);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.