-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
347 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
}); |