134 lines
4.0 KiB
Go
134 lines
4.0 KiB
Go
package platforms
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"stream-bot/internal/logger"
|
|
"time"
|
|
)
|
|
|
|
type TwitchAuth struct {
|
|
clientID string
|
|
clientSecret string
|
|
redirectURI string
|
|
server *http.Server
|
|
waitCh chan string
|
|
}
|
|
|
|
func NewTwitchAuth(clientID, clientSecret string) *TwitchAuth {
|
|
return &TwitchAuth{
|
|
clientID: clientID,
|
|
clientSecret: clientSecret,
|
|
redirectURI: "http://localhost:8089",
|
|
waitCh: make(chan string, 1),
|
|
}
|
|
}
|
|
|
|
func (ta *TwitchAuth) GenerateAuthURL(scope []string, state string) string {
|
|
url := "https://id.twitch.tv/oauth2/authorize?" +
|
|
"client_id=" + ta.clientID +
|
|
"&redirect_uri=" + ta.redirectURI +
|
|
"&response_type=token" +
|
|
"&scope=" + scopeString(scope) +
|
|
"&state=" + state
|
|
return url
|
|
}
|
|
|
|
func scopeString(scopes []string) string {
|
|
s := ""
|
|
for i, sc := range scopes {
|
|
if i > 0 {
|
|
s += "+"
|
|
}
|
|
s += sc
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (ta *TwitchAuth) StartTempServer() error {
|
|
if ta.server != nil {
|
|
return nil
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/", ta.handleCallback)
|
|
ta.server = &http.Server{
|
|
Addr: ":8089",
|
|
Handler: mux,
|
|
}
|
|
go func() {
|
|
if err := ta.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
logger.Error("OAuth server error: %v", err)
|
|
}
|
|
}()
|
|
return nil
|
|
}
|
|
|
|
func (ta *TwitchAuth) StopTempServer() {
|
|
if ta.server != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
ta.server.Shutdown(ctx)
|
|
ta.server = nil
|
|
}
|
|
}
|
|
|
|
func (ta *TwitchAuth) handleCallback(w http.ResponseWriter, r *http.Request) {
|
|
html := `
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Twitch Auth</title>
|
|
<style>
|
|
body { font-family: sans-serif; text-align: center; margin-top: 50px; }
|
|
.success { color: green; }
|
|
.error { color: red; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h3>Авторизация Twitch</h3>
|
|
<p>Обработка токена...</p>
|
|
<script>
|
|
const hash = window.location.hash.substring(1);
|
|
const params = new URLSearchParams(hash);
|
|
const accessToken = params.get('access_token');
|
|
const state = params.get('state');
|
|
if (accessToken && state) {
|
|
fetch('http://localhost:8080/api/platforms/twitch/auth/callback?token=' + encodeURIComponent(accessToken) + '&state=' + encodeURIComponent(state))
|
|
.then(res => {
|
|
if (res.ok) return res.text();
|
|
throw new Error('Server error: ' + res.status);
|
|
})
|
|
.then(data => {
|
|
document.body.innerHTML = '<h3 class="success">✅ Токен успешно сохранён! Теперь можно закрыть эту вкладку.</h3>';
|
|
})
|
|
.catch(err => {
|
|
document.body.innerHTML = '<h3 class="error">❌ Ошибка сохранения токена: ' + err.message + '</h3><p>Пожалуйста, закройте это окно и проверьте настройки в боте.</p>';
|
|
});
|
|
} else {
|
|
document.body.innerHTML = '<h3 class="error">❌ Токен не получен или отсутствует state</h3>';
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
`
|
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
|
w.Write([]byte(html))
|
|
}
|
|
|
|
func (ta *TwitchAuth) WaitForToken(timeout time.Duration) (string, error) {
|
|
select {
|
|
case token := <-ta.waitCh:
|
|
return token, nil
|
|
case <-time.After(timeout):
|
|
return "", fmt.Errorf("timeout waiting for token")
|
|
}
|
|
}
|
|
|
|
func (ta *TwitchAuth) SetTokenCallback(token string) {
|
|
select {
|
|
case ta.waitCh <- token:
|
|
default:
|
|
}
|
|
} |