<?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: Satori Henry</title>
    <description>The latest articles on DEV Community by Satori Henry (@lxh13995699298).</description>
    <link>https://dev.to/lxh13995699298</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4081451%2F55dce15d-dc68-49c3-bdc1-c5214ae1e8c6.jpeg</url>
      <title>DEV Community: Satori Henry</title>
      <link>https://dev.to/lxh13995699298</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lxh13995699298"/>
    <language>en</language>
    <item>
      <title>Import a WhatsApp Group CSV into SQLite Without Losing the + Sign</title>
      <dc:creator>Satori Henry</dc:creator>
      <pubDate>Wed, 11 Nov 2026 02:53:56 +0000</pubDate>
      <link>https://dev.to/lxh13995699298/import-a-whatsapp-group-csv-into-sqlite-without-losing-the-sign-5hi6</link>
      <guid>https://dev.to/lxh13995699298/import-a-whatsapp-group-csv-into-sqlite-without-losing-the-sign-5hi6</guid>
      <description>&lt;p&gt;A phone number like &lt;code&gt;+12025550148&lt;/code&gt; looks numeric enough for a CSV importer to treat it as an integer. That works until you need the &lt;code&gt;+&lt;/code&gt; back. The same problem turns &lt;code&gt;07700900123&lt;/code&gt; into &lt;code&gt;7700900123&lt;/code&gt;, and the database no longer knows how many zeros were at the start.&lt;/p&gt;

&lt;p&gt;Here's a small local import that keeps those values intact. It uses Python's standard library and an in-memory SQLite database, so you can run it without installing a package or setting up a database server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproduce the problem with two rows
&lt;/h2&gt;

&lt;p&gt;Run this in a fresh SQLite session.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="kt"&gt;NUMERIC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'+12025550148'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'07700900123'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;example&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;rowid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stored value&lt;/th&gt;
&lt;th&gt;Storage type&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;12025550148&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;7700900123&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;integer&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The quotes in the &lt;code&gt;INSERT&lt;/code&gt; make the inputs strings, but SQLite's &lt;code&gt;NUMERIC&lt;/code&gt; affinity still converts them to integers. To preserve the original representation, keep the values as strings in Python and use a &lt;code&gt;TEXT&lt;/code&gt; column in SQLite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Import the CSV as text
&lt;/h2&gt;

&lt;p&gt;Save this example as &lt;code&gt;members.csv&lt;/code&gt;. It uses fictional data and two columns from a group CSV exported by WAExport, a Chrome extension we build for WhatsApp Web. The extension can export member numbers from selected groups and include the source group for each row. &lt;a href="https://waexport.wadesk.io/" rel="noopener noreferrer"&gt;whatsapp group number extractor&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Phone Number,Group Name
+12025550148,"Launch, US"
07700900123,QA Sandbox
+12025550148,Beta Testers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save the following as &lt;code&gt;inspect_members.py&lt;/code&gt; in the same folder. The importer accepts extra columns and reads UTF-8 CSV files with or without a byte order mark.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="n"&gt;closing&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;closing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;":memory:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""
        CREATE TABLE group_members (
            phone TEXT NOT NULL,
            source_group TEXT NOT NULL
        )
    """&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"members.csv"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"utf-8-sig"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newline&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;reader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DictReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"Phone Number"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"Group Name"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;issubset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fieldnames&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"CSV must contain Phone Number and Group Name."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Phone Number"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Group Name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;SystemExit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"Missing value at CSV line {reader.line_num}."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s2"&gt;"INSERT INTO group_members (phone, source_group) VALUES (?, ?)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;storage_type&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"""
        SELECT phone, source_group, typeof(phone)
        FROM group_members
        ORDER BY rowid
    """&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"{phone} | {group} | {storage_type}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;python inspect_members.py&lt;/code&gt;. You should see three rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+12025550148 | Launch, US | text
07700900123 | QA Sandbox | text
+12025550148 | Beta Testers | text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;csv.DictReader&lt;/code&gt; keeps the phone field as a string and handles the comma inside the quoted group name. SQL placeholders pass each field as a value, and &lt;code&gt;typeof(phone)&lt;/code&gt; confirms the storage type. The repeated number still appears twice because it belongs to two source groups.&lt;/p&gt;

&lt;p&gt;The import preserves the phone field exactly as it appears in the CSV. If you later need to match numbers from different sources, keep this raw column and derive a separate normalized value using the country information available to you. Once an earlier spreadsheet or import has stripped a leading zero, this script can't recover it from the altered file.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: ChatGPT drafted this article and its code example. The example was run with the fictional CSV shown above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>sqlite</category>
      <category>python</category>
    </item>
    <item>
      <title>Architecture Decoded: How to Build a Complete Browser Extension-Based WhatsApp Bulk Sending Engine</title>
      <dc:creator>Satori Henry</dc:creator>
      <pubDate>Mon, 17 Aug 2026 11:05:09 +0000</pubDate>
      <link>https://dev.to/lxh13995699298/architecture-decoded-how-to-build-a-complete-browser-extension-based-whatsapp-bulk-sending-engine-1b1p</link>
      <guid>https://dev.to/lxh13995699298/architecture-decoded-how-to-build-a-complete-browser-extension-based-whatsapp-bulk-sending-engine-1b1p</guid>
      <description>&lt;p&gt;When building WhatsApp automation tools, the technology stack often determines the product's stability and the user's risk of getting banned. Compared to headless browser solutions like Puppeteer, operating directly within the DOM of the WhatsApp Web runtime via a Chrome Browser Extension is currently the more mainstream and secure technical path.&lt;/p&gt;

&lt;p&gt;This article will deconstruct, from a purely technical perspective, how to implement a bulk sending engine equipped with frontend interaction, task scheduling, and state validation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Core Architecture Design (Manifest V3)
The underlying architecture of the entire sending engine is based on the Chrome Manifest V3 standard, primarily consisting of the following core modules working in synergy:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Background Service Worker: Responsible for global state management, API communication, and coordinating different page tabs. Due to the lifecycle limits of V3, it requires designing a keep-alive mechanism or persisting state into chrome.storage.&lt;/p&gt;

&lt;p&gt;Content Scripts: Injected directly into the context of web.whatsapp.com. Responsible for reading page elements, hijacking native input events, and rendering dashboard components onto the user interface.&lt;/p&gt;

&lt;p&gt;Popup UI: The frontend interface used for hosting user uploads of Excel / CSV files, configuring dynamic variables, and setting sending intervals.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Key Technical Implementation Modules
2.1 Task Queue &amp;amp; State Management
The core of bulk sending is a highly available asynchronous task queue. You cannot simply use a synchronous for loop, as this will block the main thread and easily trigger WhatsApp's anti-automation detection mechanisms.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The engine needs to maintain a state machine to record the progress of the current task (waiting, sending, success, failed). By wrapping setTimeout and combining it with async/await, you can implement randomized sending intervals (e.g., 10 to 15 seconds) to mimic the operational rhythm of a real human user.&lt;/p&gt;

&lt;p&gt;2.2 DOM Manipulation &amp;amp; Event Dispatching&lt;br&gt;
WhatsApp Web is built on React, and its DOM structure uses a massive amount of dynamically obfuscated class names. We need to locate key elements using stable attributes (such as title or data-icon).&lt;/p&gt;

&lt;p&gt;Implementing the sending action typically involves the following low-level steps:&lt;/p&gt;

&lt;p&gt;Establish a Chat Channel: Force open the dialog box of the target number via the URL scheme &lt;a href="https://web.whatsapp.com/send?phone=xxx" rel="noopener noreferrer"&gt;https://web.whatsapp.com/send?phone=xxx&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Locate the Input Box: Find the message input div (which usually carries the contenteditable="true" attribute).&lt;/p&gt;

&lt;p&gt;Simulate Input: You cannot directly modify innerHTML or value, as this will not trigger React's underlying state updates. You must create a native InputEvent or TextEvent and dispatch it to the input box node using dispatchEvent to force React to bind the data.&lt;/p&gt;

&lt;p&gt;Trigger Send: Locate the DOM node for the send button and dispatch a MouseEvent('click') to simulate a real user click.&lt;/p&gt;

&lt;p&gt;2.3 Pre-send Number Verification &amp;amp; Filtering Module&lt;br&gt;
Before pushing numbers into the sending queue, the system needs to sanitize the targets. To ensure client-side processing speed and keep the overall system lightweight, the number detection logic here should be simplified.&lt;/p&gt;

&lt;p&gt;In terms of technical implementation, there is no need for deep carrier network penetration checks or complex protocol-level WhatsApp authentication. The engine simply needs to capture the native error prompt layer returned by WhatsApp Web (such as the "Phone number shared via url is invalid" popup) when attempting to establish the chat DOM structure. It can then output a simple Valid or Invalid status identifier directly in the logs. This minimalist validation logic significantly reduces API dependencies and memory consumption.&lt;/p&gt;

&lt;p&gt;2.4 Dashboard Interaction &amp;amp; Subscription Flow&lt;br&gt;
A commercial-grade SaaS engine must not only handle underlying logic but also possess a comprehensive UI feedback mechanism. When the Content Script is injected into the page, an independent DOM tree must be built to render real-time data of the sending progress as a floating sidebar.&lt;/p&gt;

&lt;p&gt;Meanwhile, every time a bulk sending task is triggered, an asynchronous authentication middleware layer needs to be inserted:&lt;/p&gt;

&lt;p&gt;Quota Validation: Call the server-side API to verify the current account's subscription tier.&lt;/p&gt;

&lt;p&gt;Frontend Interception: If the system detects that the user has exceeded their daily sending quota, the state machine must immediately suspend the current sending queue and render a preset Upgrade Popup Box directly in the center of the page via DOM injection.&lt;/p&gt;

&lt;p&gt;Checkout Flow: The popup embeds interactive logic to guide the user through a plan upgrade. Upon a successful payment callback, the Service Worker's authentication state is updated via WebSockets or polling, after which the engine automatically resumes and continues executing the suspended sending queue.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Exception Handling &amp;amp; Defensive Programming
In engineering implementation, the biggest technical challenges stem from the asynchronous loading nature of frontend frameworks and network fluctuations:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;React Rendering Latency: When loading a chat window, DOM rendering is entirely asynchronous. The engine must implement a mechanism based on MutationObserver or polling with a timeout limit. This ensures the target node is fully rendered and in an interactive state before executing the next input operation, preventing the program from throwing Element Not Found exceptions.&lt;/p&gt;

&lt;p&gt;Resource Reclamation: When bulk sending a large volume of rich media messages containing images, page memory can spike sharply, easily leading to browser crashes. After each round of message sending, the engine must actively clean up and destroy any injected temporary DOM nodes and redundant object references.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Conclusion
Building a complete &lt;a href="https://wawebsender.wadesk.io/" rel="noopener noreferrer"&gt;whatspp bulk sender&lt;/a&gt; from scratch is far more than just writing a few lines of auto-click scripts. It requires developers to perfectly integrate frontend DOM reverse engineering, granular asynchronous queue scheduling, lightweight state validation, and seamless subscription checkout interaction logic into the lifecycle of a single browser extension.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>marketing</category>
      <category>tooling</category>
      <category>whatsapp</category>
    </item>
  </channel>
</rss>
