Skip to content

Commit

Permalink
feat(examples): webapps
Browse files Browse the repository at this point in the history
  • Loading branch information
mr-linch committed Jul 3, 2022
1 parent 0b15482 commit 5a6694e
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 0 deletions.
186 changes: 186 additions & 0 deletions examples/webapps/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
// Package contains example of using tgb.ChatType filter.
package main

import (
"context"
"embed"
_ "embed"
"encoding/json"
"errors"
"flag"
"fmt"
"io/fs"
"log"
"net/http"
"os"
"os/signal"
"strings"
"syscall"
"time"

"github.com/mr-linch/go-tg"
"github.com/mr-linch/go-tg/tgb"
)

var (
flagToken string
flagBaseURL string
flagListen string

//go:embed site
webAppFS embed.FS
)

func main() {
flag.StringVar(&flagToken, "token", "", "Telegram Bot API token")
flag.StringVar(&flagBaseURL, "base-url", "", "Base URL for incoming http requests")
flag.StringVar(&flagListen, "listen", ":8080", "Listen address")
flag.Parse()

if flagToken == "" {
log.Fatal("-token is required")
}

if flagBaseURL == "" {
log.Fatal("-base-url is required")
}

flagBaseURL = strings.TrimSuffix(flagBaseURL, "/")

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, os.Kill, syscall.SIGTERM)
defer cancel()

if err := run(ctx); err != nil {
log.Fatal(err)
}
}

func run(ctx context.Context) error {
if flagToken == "" {
return fmt.Errorf("token is required")
}

client := tg.New(flagToken)

me, err := client.Me(ctx)
if err != nil {
return fmt.Errorf("get me: %w", err)
}
log.Printf("auth as https://t.me/%s", me.Username)

bot := newBot(flagBaseURL)

webhook := tgb.NewWebhook(bot, client, flagBaseURL+"/webhook",
tgb.WithWebhookLogger(log.Default()),
)

if err := webhook.Setup(ctx); err != nil {
return fmt.Errorf("setup webhook: %w", err)
}

mux := http.NewServeMux()

mux.Handle("/webhook", webhook)

mux.Handle("/login-url", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
authWidget, err := tg.ParseAuthWidgetQuery(r.URL.Query())
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if authWidget.Valid(flagToken) {
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "✅ You are authorized as Telegram User #%d\n", authWidget.ID)
fmt.Fprintf(w, "🧪 You can change some URL parameters to see how signature checking works.")
} else {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, "🛑 You are not authorized, because of invalid signature")
}
}))

stripped, err := fs.Sub(webAppFS, "site")
if err != nil {
return fmt.Errorf("sub static: %w", err)
}

mux.Handle("/webapp/check", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
data, err := tg.ParseWebAppInitData(r.URL.Query())
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

if data.Valid(flagToken) {
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
encoder := json.NewEncoder(w)
encoder.SetIndent("", " ")
encoder.Encode(data)
}

}))

mux.Handle("/webapp/", http.StripPrefix(
"/webapp/",
http.FileServer(http.FS(stripped)),
))

return runServer(ctx, mux, flagListen)
}

func newBot(baseURL string) *tgb.Bot {
return tgb.New().
Message(func(ctx context.Context, msg *tgb.MessageUpdate) error {
err := msg.Answer("hey, this is buttons demo").ReplyMarkup(tg.NewInlineKeyboardMarkup(
tg.NewButtonColumn(
tg.NewInlineKeyboardButtonLoginURL("Login URL", tg.LoginURL{
URL: baseURL + "/login-url",
}),
tg.NewInlineKeyboardButtonWebApp("Web App", tg.WebAppInfo{
URL: baseURL + "/webapp",
}),
)...,
)).DoVoid(ctx)

var tgErr *tg.Error
if errors.As(err, &tgErr) && tgErr.Contains("bot_domain_invalid") {
return msg.Answer(tg.HTML.Text(
"⚠️ Bot is not configured properly. Follow the instruction:",
"",
"1. Go to @BotFather",
"2. Send /setdomain",
"3. Choise your bot",
"4. Enter your URL "+baseURL,
)).DoVoid(ctx)
}

return err

}, tgb.Command("start"))
}

func runServer(ctx context.Context, handler http.Handler, listen string) error {
server := &http.Server{
Addr: flagListen,
Handler: handler,
}

go func() {
<-ctx.Done()

shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer shutdownCancel()

if err := server.Shutdown(shutdownCtx); err != nil {
log.Printf("shutdown: %v", err)
}
}()

log.Printf("listening on %s", flagListen)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
return fmt.Errorf("listen and serve: %w", err)
}

return nil
}
38 changes: 38 additions & 0 deletions examples/webapps/site/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<title>Telegram Web App</title>

<script src="https://telegram.org/js/telegram-web-app.js"></script>
<link rel="stylesheet" href="main.css" />
</head>

<body>
<button id="expand"><code>expand()</code></button>
<hr />
<button id="backbutton"><code>BackButton.show()</code></button>
<hr />
<button id="mainbutton"><code>MainButton.show()</code></button>
<button id="mainbutton-state"><code>MainButton.disable()</code></button>
<button id="mainbutton-progress">
<code>MainButton.showProgress()</code>
</button>
<hr />
<button id="hapticfeedback-impact">
<code>HapticFeedback.impactOccurred('<span>light</span>')</code>
</button>
<button id="hapticfeedback-notification">
<code>HapticFeedback.notificationOccurred('<span>error</span>')</code>
</button>
<button id="hapticfeedback-selection">
<code>HapticFeedback.selectionChanged()</code>
</button>
<hr />
<script src="main.js"></script>
</body>

</html>
25 changes: 25 additions & 0 deletions examples/webapps/site/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
body {
--default-font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji",
"Segoe UI Symbol";

font-family: var(--default-font);
font-size: 13px;
line-height: 16px;

color-scheme: var(--tg-color-scheme);

color: var(--tg-theme-text-color);
background-color: var(--tg-theme-bg-color);
}

button {
background-color: var(--tg-theme-button-color);
color: var(--tg-theme-button-text-color);
border: none;
padding: 9px 26px;
font-size: 12px;
width: 100%;
border-radius: 10px;
margin: 5px;
}
98 changes: 98 additions & 0 deletions examples/webapps/site/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
const WebApp = Telegram.WebApp;

WebApp.ready();

document
.querySelector("button#backbutton")
.addEventListener("click", (event) => {
if (WebApp.BackButton.isVisible) {
WebApp.BackButton.hide();
event.currentTarget.firstChild.textContent = "BackButton.show()";
} else {
WebApp.BackButton.show();
event.currentTarget.firstChild.textContent = "BackButton.hide()";
}
});

document.querySelector("button#expand").addEventListener("click", (event) => {
WebApp.expand();
});

document
.querySelector("button#mainbutton")
.addEventListener("click", (event) => {
if (WebApp.MainButton.text != "MAIN BUTTON") {
WebApp.MainButton.setText("MAIN BUTTON");
}

if (WebApp.MainButton.isVisible) {
WebApp.MainButton.hide();
event.currentTarget.firstChild.textContent = "MainButton.show()";
} else {
WebApp.MainButton.show();
event.currentTarget.firstChild.textContent = "MainButton.hide()";
}
});

document
.querySelector("button#mainbutton-state")
.addEventListener("click", (event) => {
if (WebApp.MainButton.isActive) {
WebApp.MainButton.disable();
event.currentTarget.firstChild.textContent = "MainButton.enable()";
} else {
WebApp.MainButton.enable();
event.currentTarget.firstChild.textContent = "MainButton.disable()";
}
});

document
.querySelector("button#mainbutton-progress")
.addEventListener("click", (event) => {
if (WebApp.MainButton.isProgressVisible) {
WebApp.MainButton.hideProgress();
event.currentTarget.firstChild.textContent = "MainButton.showProgress()";
} else {
WebApp.MainButton.showProgress();
event.currentTarget.firstChild.textContent = "MainButton.hideProgress()";
}
});

const hapticFeedbackImpactTypes = ["light", "medium", "heavy", "rigid", "soft"];
let hapticFeedbackImpactTypeIndex = 0;

document
.querySelector("button#hapticfeedback-impact")
.addEventListener("click", (event) => {
WebApp.HapticFeedback.impactOccurred(
hapticFeedbackImpactTypes[hapticFeedbackImpactTypeIndex]
);

hapticFeedbackImpactTypeIndex =
(hapticFeedbackImpactTypeIndex + 1) % hapticFeedbackImpactTypes.length;
event.currentTarget.firstElementChild.firstElementChild.textContent =
hapticFeedbackImpactTypes[hapticFeedbackImpactTypeIndex];
});

const hapticFeedNotificationTypes = ["error", "success", "warning"];
let hapticFeedNotificationTypeIndex = 0;

document
.querySelector("button#hapticfeedback-notification")
.addEventListener("click", (event) => {
WebApp.HapticFeedback.notificationOccurred(
hapticFeedNotificationTypes[hapticFeedNotificationTypeIndex]
);

hapticFeedNotificationTypeIndex =
(hapticFeedNotificationTypeIndex + 1) %
hapticFeedNotificationTypes.length;
event.currentTarget.firstElementChild.firstElementChild.textContent =
hapticFeedNotificationTypes[hapticFeedNotificationTypeIndex];
});

document
.querySelector("button#hapticfeedback-selection")
.addEventListener("click", () => {
WebApp.HapticFeedback.selectionChanged();
});

0 comments on commit 5a6694e

Please sign in to comment.