mirror of
https://github.com/lukaszraczylo/go-telegram.git
synced 2026-06-05 22:43:59 +00:00
ac7cae8fa7
A fully-generated, strongly-typed Go client for the Telegram Bot API. * 176 methods + 301 types generated from Bot API v10.0 * 1408 auto-generated tests (8 scenarios per method) * Typed unions throughout — no 'any' in the public surface * Pluggable HTTP transport and JSON codec (default goccy/go-json) * Built-in retry middleware honouring Telegram's retry_after * Generic dispatcher with filters and conversation handlers * Self-verifying codegen pipeline (regen → audit → emit → run tests) * 14 example bots covering common patterns
138 lines
3.2 KiB
Go
138 lines
3.2 KiB
Go
package main
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"golang.org/x/net/html"
|
|
)
|
|
|
|
// section is an h4-anchored block of the docs page. Title is the
|
|
// heading text (e.g. "User" or "sendMessage"). Description is the
|
|
// concatenation of immediately-following <p> paragraphs (until the
|
|
// next h4 / h3 / table / list). Tables and Lists hold raw nodes for
|
|
// later parsing by the table/oneof extractors.
|
|
type section struct {
|
|
Title string
|
|
Description string
|
|
Tables []*html.Node // <table> nodes
|
|
Lists []*html.Node // <ul> nodes (used for oneof variant lists)
|
|
}
|
|
|
|
// walk parses the page and returns sections in document order.
|
|
// Sections whose title contains a space (e.g. "Bot API 7.10") are
|
|
// included; later passes ignore them or treat them specially.
|
|
func walk(doc *html.Node) []section {
|
|
var (
|
|
sections []section
|
|
current *section
|
|
)
|
|
|
|
var visit func(n *html.Node)
|
|
visit = func(n *html.Node) {
|
|
if n.Type == html.ElementNode {
|
|
switch n.Data {
|
|
case "h4":
|
|
if current != nil {
|
|
sections = append(sections, *current)
|
|
}
|
|
current = §ion{Title: textOf(n)}
|
|
// Don't recurse into the heading; we already have its text.
|
|
return
|
|
case "h3":
|
|
// h3 (e.g. "Available methods") delimits a section;
|
|
// flush the current h4 section but do not start a new one.
|
|
if current != nil {
|
|
sections = append(sections, *current)
|
|
current = nil
|
|
}
|
|
return
|
|
case "p":
|
|
if current != nil {
|
|
if current.Description != "" {
|
|
current.Description += "\n"
|
|
}
|
|
current.Description += strings.TrimSpace(textOf(n))
|
|
}
|
|
return
|
|
case "table":
|
|
if current != nil {
|
|
current.Tables = append(current.Tables, n)
|
|
}
|
|
return
|
|
case "ul":
|
|
if current != nil {
|
|
current.Lists = append(current.Lists, n)
|
|
}
|
|
return
|
|
}
|
|
}
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
visit(c)
|
|
}
|
|
}
|
|
visit(doc)
|
|
if current != nil {
|
|
sections = append(sections, *current)
|
|
}
|
|
return sections
|
|
}
|
|
|
|
// textOf returns the concatenated text content of n and descendants,
|
|
// with adjacent whitespace collapsed to single spaces.
|
|
func textOf(n *html.Node) string {
|
|
var sb strings.Builder
|
|
var w func(*html.Node)
|
|
w = func(n *html.Node) {
|
|
if n.Type == html.TextNode {
|
|
sb.WriteString(n.Data)
|
|
return
|
|
}
|
|
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
w(c)
|
|
}
|
|
}
|
|
w(n)
|
|
return collapseWS(sb.String())
|
|
}
|
|
|
|
func collapseWS(s string) string {
|
|
var b strings.Builder
|
|
prevSpace := false
|
|
for _, r := range s {
|
|
if r == ' ' || r == '\t' || r == '\n' || r == '\r' {
|
|
if !prevSpace {
|
|
b.WriteByte(' ')
|
|
}
|
|
prevSpace = true
|
|
continue
|
|
}
|
|
prevSpace = false
|
|
b.WriteRune(r)
|
|
}
|
|
return strings.TrimSpace(b.String())
|
|
}
|
|
|
|
// isMethodTitle returns true for headings that look like method names
|
|
// (camelCase starting with a lowercase letter; e.g. "sendMessage").
|
|
func isMethodTitle(s string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
r := s[0]
|
|
return r >= 'a' && r <= 'z'
|
|
}
|
|
|
|
// isTypeTitle returns true for headings that look like type names
|
|
// (PascalCase; e.g. "Message"). Allows a leading-uppercase only;
|
|
// excludes spaces (which would denote a header like "Bot API 7.10").
|
|
func isTypeTitle(s string) bool {
|
|
if s == "" {
|
|
return false
|
|
}
|
|
r := s[0]
|
|
if r < 'A' || r > 'Z' {
|
|
return false
|
|
}
|
|
return !strings.Contains(s, " ")
|
|
}
|