<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maksim Lamanov</title>
    <description>The latest articles on DEV Community by Maksim Lamanov (@lamanoff).</description>
    <link>https://dev.to/lamanoff</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1107554%2Fcc7b7099-38d3-44ca-8fe7-c63e2d67ec77.jpeg</url>
      <title>DEV Community: Maksim Lamanov</title>
      <link>https://dev.to/lamanoff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lamanoff"/>
    <language>en</language>
    <item>
      <title>Do analytics for the bot from scratch. Part 1 - writing a bot</title>
      <dc:creator>Maksim Lamanov</dc:creator>
      <pubDate>Sat, 24 Jun 2023 12:32:33 +0000</pubDate>
      <link>https://dev.to/lamanoff/do-analytics-for-the-bot-from-scratch-part-1-writing-a-bot-1ik7</link>
      <guid>https://dev.to/lamanoff/do-analytics-for-the-bot-from-scratch-part-1-writing-a-bot-1ik7</guid>
      <description>&lt;p&gt;In this article, we will look at the importance of connecting basic analytics to a bot and what benefits it can bring.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vuMBTw1s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4tliftwu8ovpj8ft2do.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vuMBTw1s--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s4tliftwu8ovpj8ft2do.png" alt="" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today's digital world, where interaction with customers is increasingly happening through online platforms, creating and maintaining effective chatbots is becoming an important aspect of a successful business. However, building a bot that simply provides information or answers basic questions is no longer enough for a competitive advantage. Today, it is important to understand user behaviour and needs, analyse data and make informed decisions based on this information.&lt;/p&gt;

&lt;p&gt;This is where connecting analytics to the bot comes to the rescue. The integration of analytical tools and methods allows you to collect, analyse and interpret data about user interaction with the bot. This gives us the opportunity to gain valuable insights that will help optimise the performance of the bot, improve the user experience and make informed decisions based on data.&lt;/p&gt;

&lt;p&gt;Connecting analytics to a bot is an integral part of the development and improvement of chatbots in our dynamic digital age. It allows you to turn a bot from a simple communication tool into a powerful data analysis tool that will help your business form an effective strategy and achieve its goals. Let's look at all the advantages and opportunities that the connection of analytics to the bot opens up for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  What data will we receive today
&lt;/h2&gt;

&lt;p&gt;In this example, we will learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;see the number of "live" and "dead" users;&lt;/li&gt;
&lt;li&gt;see the number of messages sent through the bot;&lt;/li&gt;
&lt;li&gt;see the messages themselves;&lt;/li&gt;
&lt;li&gt;build charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Tools
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python 3&lt;/strong&gt; (aiogram - a framework for creating bots);&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MongoDB&lt;/strong&gt; - a database that will serve as a source for building a dashboard;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Telegram&lt;/strong&gt; - is the best messenger for working with bots;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Redash&lt;/strong&gt; - is a self-hosted service for building dashboards.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  First, let's write a bot
&lt;/h2&gt;

&lt;p&gt;As an example, let's write a simple Python 3 bot that allows you to exchange anonymous messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Principle of operation
&lt;/h3&gt;

&lt;p&gt;The user is provided with a personal link to the bot, which contains the identifier of the user to whom the message needs to be sent. He can post it on his social networks. Anyone who wants to send an anonymous message to a user must follow this link. After that, the bot will receive the &lt;code&gt;/start&lt;/code&gt; command with a payload in the form of a user ID in Telegram.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a bot in Telegram
&lt;/h3&gt;

&lt;p&gt;Go to &lt;code&gt;@BotFather&lt;/code&gt; and create a bot:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W3pySexe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7h2dmb5hu1rv3rnx2s1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W3pySexe--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7h2dmb5hu1rv3rnx2s1.png" alt="" width="706" height="816"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting libraries
&lt;/h3&gt;

&lt;p&gt;We connect the library for creating telegram bots:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip3 install aiogram
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And also for working with MongoDB:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip3 install pymongo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Write the code
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;main.py&lt;/code&gt; file and write the beginning for any bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bot = Bot(token='BotFather TOKEN HERE')
storage = MemoryStorage() # storage that allows you to remember the states of dialogs with users
dp = Dispatcher(bot, storage=storage)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's add one single state that we need to send messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Stage(StatesGroup):
    send_message_to = State()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's write a handler for the &lt;code&gt;/start&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@dp.message_handler(commands=['start'], state=['*', Stage.send_message_to])
async def start(message: types.Message, state: FSMContext):
    args = message.get_args() # get payload from command /start
    payload = decode_payload(args)

    if payload: # we go here if we clicked on someone's link
        async with state.proxy() as data:
            data['user-id'] = payload # save the recipient ID in the context of the dialog

        await Stage.send_message_to.set() # set the state of sending the message to the recipient

        start_text = 'Hello! I am a bot for anonymous communication. Send a message to the person who posted this link. He won't know who sent the message.'
        await message.reply(start_text)
        return

    # if there is no payload, then the user just pressed /start
    link = await get_start_link(message.from_user.id, encode=True) # create a personal link
    content = f'Your personal link: {link}'
    await message.reply(content)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's write a handler for sending messages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@dp.message_handler(state=Stage.send_message_to, content_types=ContentType.ANY)
async def process_message(message: types.Message, state: FSMContext):
    inline_btn = InlineKeyboardButton('Reply', callback_data=f'answer_{message.from_user.id}')
    inline_kb = InlineKeyboardMarkup().add(inline_btn)
    async with state.proxy() as data:
        if 'answer_user_id' in data:
            user_id = data['answer_user_id'] # if we reply to a message
            await message.answer('Your response has been delivered.')
            await message.bot.send_message(user_id, 'You have received an answer.')
        else:
            user_id = data['user-id'] # if we send a new message
            await message.answer('Your message has been sent.')
            await message.bot.send_message(user_id, 'You have received a new anonymous message.')
        if message.video:
            await message.bot.send_video(user_id, message.video.file_id, reply_markup=inline_kb)
            content = message.video.as_json()
        elif message.video_note:
            await message.bot.send_video_note(user_id, message.video_note.file_id, reply_markup=inline_kb)
            content = message.video_note.as_json()
        elif message.voice:
            await message.bot.send_voice(user_id, message.voice.file_id, reply_markup=inline_kb)
            content = message.voice.as_json()
        elif message.photo:
            await message.bot.send_photo(user_id, message.photo[-1].file_id, reply_markup=inline_kb)
            content = message.photo[-1].as_json()
        elif message.audio:
            await message.bot.send_audio(user_id, message.audio.file_id, reply_markup=inline_kb)
            content = message.audio.as_json()
        elif message.sticker:
            await message.bot.send_sticker(user_id, message.sticker.file_id, reply_markup=inline_kb)
            content = message.sticker.file_id
        elif message.document:
            await message.bot.send_document(user_id, message.document.file_id, reply_markup=inline_kb)
            content = message.document.as_json()
        else:
            await message.bot.send_message(user_id, message.text, reply_markup=inline_kb)
            content = message.text
    await state.finish()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the example above, we implemented the ability to send various types of messages, but you can leave only text.&lt;/p&gt;

&lt;p&gt;Let's write a click handler for the "Reply" button:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;answer_regexp = re.compile("answer_(.*)")


@dp.callback_query_handler(lambda c: answer_regexp.match(c.data), state=['*', Stage.send_message_to])
async def process_callback_answer(callback_query: types.CallbackQuery, state: FSMContext):
    await bot.answer_callback_query(callback_query.id)
    await bot.send_message(callback_query.from_user.id, 'Enter your reply.')
    async with state.proxy() as data:
        data['answer_user_id'] = answer_regexp.match(callback_query.data).group(1)
    await Stage.send_message_to.set()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It remains to run the bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if __name__ == '__main__':
    executor.start_polling(dp, skip_updates=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Working with the database
&lt;/h3&gt;

&lt;p&gt;Let's create a &lt;code&gt;db.py&lt;/code&gt; file and mark up methods for working with the database.&lt;/p&gt;

&lt;p&gt;Connecting to the database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;client = MongoClient('CONNECTION_STRING HERE')
db = client['AskFM']

users = db['users']
messages = db['messages']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def add_user(user_id, username, datetime):
    user_filter = {
        'user_id': user_id
    }
    user = users.find_one(user_filter)
    if user and user['status'] == 'deleted':
        update = {
            'status': 'active'
        }
        users.update_one(user_filter, {'$set': update})
        return
    elif user:
        return
    data = {
        'user_id': user_id,
        'username': username,
        'datetime': datetime,
        'status': 'active'
    }
    users.insert_one(data)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def delete_user(user_id):
    user_filter = {
        'user_id': user_id
    }
    update = {
        'status': 'deleted'
    }
    users.update_one(user_filter, {'$set': update})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adding a message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def add_message(user_id_from, user_id_to, username_from, username_to, content, message_type, datetime):
    message = {
        "user_id_from": user_id_from,
        "user_id_to": user_id_to,
        "username_from": username_from,
        "username_to": username_to,
        "content": content,
        "message_type": message_type,
        "datetime": datetime
    }
    messages.insert_one(message)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Integrating work with the database into the bot
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's write a handler that tracks the activation and deactivation of the bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@dp.my_chat_member_handler()
async def add_to_channel(update: ChatMemberUpdated):
    if update.new_chat_member.status == 'member':
        db.add_user(update.from_user.id, update.from_user.username, time.time())
    elif update.new_chat_member.status == 'kicked':
        db.delete_user(update.from_user.id)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add user registration to the &lt;code&gt;/start&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;db.add_user(message.from_user.id, message.from_user.username, time.time())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final handler code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@dp.message_handler(commands=['start'], state=['*', Stage.send_message_to])
async def start(message: types.Message, state: FSMContext):
    args = message.get_args() # get payload from /start command
    payload = decode_payload(args)

    if payload: # we go here if we clicked on someone's link
        async with state.proxy() as data:
            data['user-id'] = payload # save the recipient ID in the context of the dialog

        await Stage.send_message_to.set() # set the state of sending the message to the recipient

        start_text = 'Hello! I am a bot for anonymous communication. Send a message to the person who posted this link. He won't know who sent the message.'
        await message.reply(start_text)

        db.add_user(message.from_user.id, message.from_user.username, time.time()) # added here

        return

    # if there is no payload, then the user just pressed /start
    link = await get_start_link(message.from_user.id, encode=True) # create a personal link
    content = f'Your personal link: {link}'
    await message.reply(content)

    db.add_user(message.from_user.id, message.from_user.username, time.time()) # and added here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why add user registration to the &lt;code&gt;/start&lt;/code&gt; method if we have a separate handler that looks at the activation and deactivation of the bot? Answer: that method does not always work correctly.&lt;/p&gt;

&lt;p&gt;Add message registration to the very end of the message send handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;user_to = await bot.get_chat_member(user_id, user_id)
if 'answer_user_id' in data:
    message_type = 'answer'
else:
    message_type = 'question'
db.add_message(message.from_user.id,
               user_id, message.from_user.username,
               user_to.user.username, content,
               message_type, time.time())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the end of the code. The finished code can be viewed &lt;a href="https://github.com/lamanoff/ask-fm-bot"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We start the bot and try to test it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python3 main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the next part, we will look at creating the dashboard itself.&lt;/p&gt;

</description>
      <category>python</category>
      <category>beginners</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Configuring Applications at Runtime: Feature Flags</title>
      <dc:creator>Maksim Lamanov</dc:creator>
      <pubDate>Sat, 24 Jun 2023 11:50:03 +0000</pubDate>
      <link>https://dev.to/lamanoff/configuring-applications-at-runtime-feature-flags-409m</link>
      <guid>https://dev.to/lamanoff/configuring-applications-at-runtime-feature-flags-409m</guid>
      <description>&lt;p&gt;In modern programming, more and more attention is paid to the flexibility and configurability of applications. Developers strive to create applications that can adapt to different environments and user requirements without the need for recompilation or redeployment. In this context, feature flags are an increasingly popular tool for configuring applications at runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are feature flags?
&lt;/h2&gt;

&lt;p&gt;Feature flags are a mechanism that allows you to enable or disable certain parts of the application's functionality depending on configuration or runtime conditions. Instead of hard-coding functionality into an application and releasing a new version to make changes, feature flags allow you to change the behavior of an application on the fly without restarting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Advantages
&lt;/h2&gt;

&lt;p&gt;One of the benefits of feature flags is the ability to roll out new functionality in stages. Instead of enabling new functionality for all users at once, developers can use feature flags to incrementally enable new features for only certain groups of users. For example, you can enable new functionality for only a small group of users to test its stability and gather feedback before enabling it for everyone.&lt;/p&gt;

&lt;p&gt;Another advantage of feature flags is the ability to quickly respond to changes in requirements and conditions. If you need to temporarily disable a piece of functionality or change its behaviour, this can be done simply by changing the feature flag configuration, without the need to release a new version of the application. This is especially useful when you need to quickly make changes or fix problems.&lt;/p&gt;

&lt;p&gt;Feature flags also contribute to the division of responsibilities between developers and system operators. Developers can add feature flags to the code to define the behaviour of the functionality, but the configuration of the flags can be changed by system operators without the need for developer intervention. This allows operators to quickly respond to requirements and changes in the application environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Risks
&lt;/h2&gt;

&lt;p&gt;However, there are some risks and challenges associated with using feature flags. As the number of flags and their combinations grows, it can become difficult to manage and keep track of all configurations. Incorrect use of feature flags can lead to difficult to track and unpredictable application behaviour. Therefore, it is important to carefully plan and manage feature flags, especially in large and complex projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Configuring an application at runtime using feature flags is a powerful tool for creating flexible and adaptive applications. They allow developers to control the behaviour of functionality and quickly respond to changing requirements and conditions without having to restart or redeploy the application. However, feature flags must be carefully planned and managed to avoid potential problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  P.S.
&lt;/h2&gt;

&lt;p&gt;I started making a tool for working with feature flags in C# (&lt;a href="https://github.com/lamanoff/feature-flags"&gt;GitHub&lt;/a&gt;). I will be glad if you join the development :)&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
