Aditya
Aditya

Reputation: 817

How do I send Tables with Telegram Bot API?

I need to send stock market data and the formatting sucks right now. Need to send something liike this

| Symbol | Price | Change | 
|--------|-------|--------|
| ABC | 20.85 | 1.626 | 
| DEF | 78.95 | 0.099 | 
| GHI | 23.45 | 0.192 | 
| JKL | 98.85 | 0.292 |

This is what I have tried.

| Symbol | Price | Change |  
|--------|-------|--------|  
| ABC | 20.85 | 1.626 |   
| DEF | 78.95 | 0.099 |   
| GHI | 23.45 | 0.192 |    
| JKL | 98.85 | 0.292 |

Upvotes: 42

Views: 89388

Answers (11)

Aref Solaimany
Aref Solaimany

Reputation: 367

The easiest and most professional method is to use Telegram MiniApp, which was added in the recent update.


step 1: create html file and write your table.

step2: add this script to your html file. <script src="https://telegram.org/js/telegram-web-app.js"></script>

step 3: redirect user to page with this method of api

{
    "text": "Test web_app",
    "web_app": {
        "url": "https://yourDomain/yourFile.html"
    }
}

note: page will show in bot page not browser

for more info read official document: https://core.telegram.org/bots/webapps#initializing-web-apps

Upvotes: 6

m-sarabi
m-sarabi

Reputation: 2305

I had to send tables in Telegram recently, and none of the formatting felt good enough. So I wrote a little function to convert a markdown table to a box-drawing table. It supports text alignment as well.

The code and demo are available on GitHub: box-table-drawing.

function convertMarkdownTableToBoxDrawing(markdownTable) {
  const lines = markdownTable.trim().split('\n');
  const headers = lines[0].split('|').map(header => header.trim()).filter(header => header);
  const alignments = lines[1].split('|').map(header => header.trim()).filter(header => header);
  const rows = lines.slice(2).map(line => line.split('|').map(cell => cell.trim()).filter(cell => cell));

  const columnWidths = headers.map((header, i) => {
    return Math.max(header.length, ...rows.map(row => row[i].length));
  });

  const drawLine = (char, junction, start, end) => {
    return start + columnWidths.map(width => char.repeat(width + 2)).join(junction) + end;
  };

  const drawRow = (cells, left, middle, right, alignments) => {
    return left + cells.map((cell, i) => {
      const width = columnWidths[i];
      if (alignments[i].startsWith(':') && alignments[i].endsWith(':')) {
        return ' ' + cell.padStart((width + cell.length) / 2).padEnd(width) + ' ';
      } else if (alignments[i].endsWith(':')) {
        return ' ' + cell.padStart(width) + ' ';
      } else {
        return ' ' + cell.padEnd(width) + ' ';
      }
    }).join(middle) + right;
  };

  const topLine = drawLine('─', '┬', '┌', '┐');
  const headerLine = drawRow(headers, '│', '│', '│', alignments);
  const separatorLine = drawLine('─', '┼', '├', '┤');
  const bottomLine = drawLine('─', '┴', '└', '┘');
  const bodyLines = rows.map(row => drawRow(row, '│', '│', '│', alignments));

  return [topLine, headerLine, separatorLine, ...bodyLines, bottomLine].join('\n');
}

Input:

| Quadrant | sin | cos | tan | cot |
|-----|:---:|:---:|:---:|:---:|
| 1 (0-90) | + | + | + | + |
| 2 (90-180) | + | - | - | - |
| 3 (180-270) | - | - | + | + |
| 4 (270-360) | - | + | - | - |

Output:

┌─────────────┬─────┬─────┬─────┬─────┐
│ Quadrant    │ sin │ cos │ tan │ cot │
├─────────────┼─────┼─────┼─────┼─────┤
│ 1 (0-90)    │  +  │  +  │  +  │  +  │
│ 2 (90-180)  │  +  │  -  │  -  │  -  │
│ 3 (180-270) │  -  │  -  │  +  │  +  │
│ 4 (270-360) │  -  │  +  │  -  │  -  │
└─────────────┴─────┴─────┴─────┴─────┘

Just make sure to send it in the <pre> tag with parse_mode: "HTML".

Upvotes: 1

Артем Нерин
Артем Нерин

Reputation: 11

import warnings
from PIL import Image, ImageDraw, ImageFont

def table_to_image(my_table):
    warnings.filterwarnings('ignore', category=DeprecationWarning)
    font = ImageFont.truetype("courbd.ttf", 15)
    text_width, text_height = font.getsize_multiline(my_table.get_string())
    im = Image.new("RGB", (text_width + 15, text_height + 15), "white")
    draw = ImageDraw.Draw(im)
    draw.text((7, 7), my_table.get_string(), font=font, fill="black")
    im.show()
    im.save(my_table_image.png, 'PNG')

Upvotes: 1

OnTheRoad
OnTheRoad

Reputation: 613

Here is my solution using puppeteer to screenshot on the table element

First of all you need to generate the table HTML code here is the code to generate that code

async function generateHtml(rows) {
    return `<!DOCTYPE html>
    <html>
    <head>
    <style>
    thead,
tfoot {
    background-color: #3f87a6;
    color: #fff;
}

tbody {
    background-color: #e4f0f5;
}

caption {
    padding: 10px;
    caption-side: bottom;
}

table {
    border-collapse: collapse;
    border: 2px solid rgb(200, 200, 200);
    letter-spacing: 1px;
    font-family: sans-serif;
    font-size: .8rem;
}

td,
th {
    border: 1px solid rgb(190, 190, 190);
    padding: 5px 10px;
}

td {
    text-align: center;
}

    </style>
    </head>
    <body>
    <table>
    <caption>Pornhub Pages Summary</caption>
    <thead>
        <tr>
            <th>ID</th>
            <th scope="col">Progress</th>
            <th scope="col">Stucked</th>
            <th scope="col">Finished</th>
            <th scope="col">Busy</th>
        </tr>
    </thead>
    <tbody>
        ${rows}
    </tbody>
</table>
    </body>
    </html>`
}

And here is the code for generate the rows argument of the above function

async function getTheImgOfTheSummaryOfThePages() {
    const rows = []
    for (const [index, val] of statuesOfThePages.entries()) {
        const row = `<tr>
        <th scope="row">${index}</th>
        <th>${val.progress}</th>
        <th>${val.stucked}</th>
        <th>${val.finished}</th>
        <th>${val.pageBusy}</th>
      </tr>`

        rows.push(row)
    }

    const path = './summaryOfThePagesOfPornhub.png'
    const html = await generateHtml(rows.join('\n'))
    await util.takescrrenshotOnTheHtml(html, browser, path, 'table')
    return path
}

And here is the code for screenshot on the table element

async function takescrrenshotOnTheHtml(html, browser, pathToSave, onElement) {
    const page = await newPage(browser);
    await page.setContent(html)
    const element = await page.$(onElement)
    await element.screenshot({path: pathToSave})
    await page.close()
}

Here is the result enter image description here

Well you just need to change the table headers and the rows of the table

Upvotes: 3

Allan Zeidler
Allan Zeidler

Reputation: 337

I wrote a code to build a Telegram html table from an array of strings.

Just build an array with the lines with columns data separated by ";" and this code will output the Telegram ready table.

Enjoy, figure out the parameters :)

You must use "parse_mode" = "html" when sending the message to Telegram Api.

public string BuildTelegramTable(
            List<string> table_lines,
            string tableColumnSeparator = "|", char inputArraySeparator = ';',
            int maxColumnWidth = 0, bool fixedColumnWidth = false, bool autoColumnWidth = false,
            int minimumColumnWidth = 4, int columnPadRight = 0, int columnPadLeft = 0,
            bool beginEndBorders = true)
        {
            var prereadyTable = new List<string>() { "<pre>" };
            var columnsWidth = new List<int>();
            var firstLine = table_lines[0];
            var lineVector = firstLine.Split(inputArraySeparator);

            if (fixedColumnWidth && maxColumnWidth == 0) throw new ArgumentException("For fixedColumnWidth usage must set maxColumnWidth > 0");
            else if (fixedColumnWidth && maxColumnWidth > 0)
            {
                for(var x=0;x<lineVector.Length;x++)
                    columnsWidth.Add(maxColumnWidth + columnPadRight + columnPadLeft);
            }
            else
            {
                for(var x=0;x<lineVector.Length;x++)
                {
                    var columnData = lineVector[x].Trim();
                    var columnFullLength = columnData.Length;

                    if (autoColumnWidth)
                        table_lines.ForEach(line => columnFullLength = line.Split(inputArraySeparator)[x].Length > columnFullLength ? line.Split(inputArraySeparator)[x].Length : columnFullLength);
                    
                    columnFullLength = columnFullLength < minimumColumnWidth ? minimumColumnWidth : columnFullLength;

                    var columnWidth = columnFullLength + columnPadRight + columnPadLeft;

                    if (maxColumnWidth > 0 && columnWidth > maxColumnWidth)
                        columnWidth = maxColumnWidth;

                    columnsWidth.Add(columnWidth);
                }
            }

            foreach(var line in table_lines)
            {
                lineVector = line.Split(inputArraySeparator);

                var fullLine = new string[lineVector.Length+(beginEndBorders ? 2 : 0)];
                if (beginEndBorders) fullLine[0] = "";

                for(var x=0;x<lineVector.Length;x++)
                {
                    var clearedData = lineVector[x].Trim();
                    var dataLength = clearedData.Length;
                    var columnWidth = columnsWidth[x];
                    var columnSizeWithoutTrimSize = columnWidth - columnPadRight - columnPadLeft;
                    var dataCharsToRead = columnSizeWithoutTrimSize > dataLength ? dataLength : columnSizeWithoutTrimSize;
                    var columnData = clearedData.Substring(0,dataCharsToRead);
                    columnData = columnData.PadRight(columnData.Length + columnPadRight);
                    columnData = columnData.PadLeft(columnData.Length + columnPadLeft);

                    var column = columnData.PadRight(columnWidth);

                    fullLine[x+(beginEndBorders ? 1 : 0)] = column;
                }

                if (beginEndBorders) fullLine[fullLine.Length - 1] = "";

                prereadyTable.Add(string.Join(tableColumnSeparator,fullLine));
            }

            prereadyTable.Add("</pre>");

            return string.Join("\r\n",prereadyTable);
        }

Upvotes: 0

Ruslan Novikov
Ruslan Novikov

Reputation: 1527

Set the Telegram API parse_mode parameter to HTML and wrap the message in <pre></pre> , but remember that telegram API does not support nested tags.

<pre>
| Tables   |      Are      |  Cool |
|----------|:-------------:|------:|
| col 1 is |  left-aligned | $1600 |
| col 2 is |    centered   |   $12 |
| col 3 is | right-aligned |    $1 |
</pre>

Result in Telegram messanger:

ScreenShot from telegram bot

Updated. How convert the tables in the picture

There will be a problem on the small screens of smartphones. So this method is not good. The only option is to convert the tables in the picture and so send :

  1. Or you can convert HTML to image using a headerless browser on your server.
  2. Or you can convert HTML to image using here external API services
  3. Or you can convert HTML to image using more difficult way by php GD

Upvotes: 27

Moki
Moki

Reputation: 360

Import "prettytable" library in python to format your table:

import prettytable as pt
from telegram import ParseMode
from telegram.ext import CallbackContext, Updater


def send_table(update: Updater, context: CallbackContext):
    table = pt.PrettyTable(['Symbol', 'Price', 'Change'])
    table.align['Symbol'] = 'l'
    table.align['Price'] = 'r'
    table.align['Change'] = 'r'

    data = [
        ('ABC', 20.85, 1.626),
        ('DEF', 78.95, 0.099),
        ('GHI', 23.45, 0.192),
        ('JKL', 98.85, 0.292),
    ]
    for symbol, price, change in data:
        table.add_row([symbol, f'{price:.2f}', f'{change:.3f}'])

    update.message.reply_text(f'<pre>{table}</pre>', parse_mode=ParseMode.HTML)
    # or use markdown
    update.message.reply_text(f'```{table}```', parse_mode=ParseMode.MARKDOWN_V2)

You will receive message like:

+--------+-------+--------+
| Symbol | Price | Change |
+--------+-------+--------+
| ABC    | 20.85 |  1.626 |
| DEF    | 78.95 |  0.099 |
| GHI    | 23.45 |  0.192 |
| JKL    | 98.85 |  0.292 |
+--------+-------+--------+

Upvotes: 17

dnth
dnth

Reputation: 897

Formatting the text as "Monospace" works too

Upvotes: 1

iliyesku
iliyesku

Reputation: 401

Try this

```| Symbol | Price | Change |
|--------|-------|--------|
| ABC    | 20.85 |  1.626 |
| DEF    | 78.95 |  0.099 |
| GHI    | 23.45 |  0.192 |
| JKL    | 98.85 |  0.292 |```

Upvotes: 16

Aditya
Aditya

Reputation: 817

I found this library - TableJs - that solves this problem. Works great on desktop clients however android clients didn't seem to render it properly.

Upvotes: 2

Sean Wei
Sean Wei

Reputation: 8015

You can use HTML or Markdown markup to send something like <pre> in HTML. Just like this example.

Upvotes: 8

Related Questions