mirror of
https://github.com/ChuckNorrison/LightningTipBot.git
synced 2026-08-13 12:33:14 +02:00
commit
6bc6ef215a
20 changed files with 499 additions and 8 deletions
|
|
@ -25,3 +25,6 @@ database:
|
|||
transactions_path: "data/transactions.db"
|
||||
shop_buntdb_path: "data/shop.db"
|
||||
groupsdb_path: "data/groups.db"
|
||||
generate:
|
||||
dalle_key: "asd"
|
||||
dalle_price: 1000
|
||||
1
data/dalle/.placeholder
Normal file
1
data/dalle/.placeholder
Normal file
|
|
@ -0,0 +1 @@
|
|||
this is where dalle images are stored
|
||||
1
go.mod
1
go.mod
|
|
@ -55,6 +55,7 @@ require (
|
|||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/decred/dcrd/lru v1.0.0 // indirect
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/dillonstreator/dalle v0.0.0-20220826164958-b15c236ceadc // indirect
|
||||
github.com/go-errors/errors v1.0.1 // indirect
|
||||
github.com/go-redis/redis/v8 v8.8.2 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
|
|
|
|||
3
go.sum
3
go.sum
|
|
@ -219,6 +219,8 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUn
|
|||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dillonstreator/dalle v0.0.0-20220826164958-b15c236ceadc h1:gI9DEfWBOylVhQSIAuvKbaTkqvtlDmOTKGcg7i3Y3uE=
|
||||
github.com/dillonstreator/dalle v0.0.0-20220826164958-b15c236ceadc/go.mod h1:FyvK9n5UiK1wDTgGSkyMckbBAYVZiqaTH1LINKCgbIQ=
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
|
||||
github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q=
|
||||
github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo=
|
||||
|
|
@ -1111,6 +1113,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde h1:ejfdSekXMDxDLbRrJMwUk6KnSLZ2McaUCVcIKM+N6jc=
|
||||
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
|
|
|||
|
|
@ -14,8 +14,14 @@ var Configuration = struct {
|
|||
Telegram TelegramConfiguration `yaml:"telegram"`
|
||||
Database DatabaseConfiguration `yaml:"database"`
|
||||
Lnbits LnbitsConfiguration `yaml:"lnbits"`
|
||||
Generate GenerateConfiguration `yaml:"generate"`
|
||||
}{}
|
||||
|
||||
type GenerateConfiguration struct {
|
||||
DalleKey string `yaml:"dalle_key"`
|
||||
DallePrice int64 `yaml:"dalle_price"`
|
||||
}
|
||||
|
||||
type SocksConfiguration struct {
|
||||
Host string `yaml:"host"`
|
||||
Username string `yaml:"username"`
|
||||
|
|
|
|||
14
internal/dalle/client.go
Normal file
14
internal/dalle/client.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package dalle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Generate(ctx context.Context, prompt string) (*Task, error)
|
||||
ListTasks(ctx context.Context, req *ListTasksRequest) (*ListTasksResponse, error)
|
||||
GetTask(ctx context.Context, taskID string) (*Task, error)
|
||||
Download(ctx context.Context, generationID string) (io.ReadCloser, error)
|
||||
Share(ctx context.Context, generationID string) (string, error)
|
||||
}
|
||||
11
internal/dalle/dalle.go
Normal file
11
internal/dalle/dalle.go
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package dalle
|
||||
|
||||
const (
|
||||
StatusPending = "pending"
|
||||
StatusRejected = "rejected"
|
||||
StatusSucceeded = "succeeded"
|
||||
|
||||
TaskTypeText2Im = "text2im"
|
||||
|
||||
defaultBatchSize = 4
|
||||
)
|
||||
242
internal/dalle/httpclient.go
Normal file
242
internal/dalle/httpclient.go
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
package dalle
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
libraryVersion = "1.0.0"
|
||||
defaultUserAgent = "dalle/" + libraryVersion
|
||||
baseURL = "https://labs.openai.com/api/labs"
|
||||
|
||||
defaultHTTPClientTimeout = 15 * time.Second
|
||||
)
|
||||
|
||||
type option func(*HTTPClient) error
|
||||
|
||||
func WithHTTPClient(httpClient *http.Client) option {
|
||||
return func(c *HTTPClient) error {
|
||||
c.httpClient = httpClient
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithUserAgent(userAgent string) option {
|
||||
return func(c *HTTPClient) error {
|
||||
c.userAgent = userAgent
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type HTTPClient struct {
|
||||
httpClient *http.Client
|
||||
userAgent string
|
||||
apiKey string
|
||||
}
|
||||
|
||||
var _ Client = (*HTTPClient)(nil)
|
||||
|
||||
func NewHTTPClient(apiKey string, opts ...option) (*HTTPClient, error) {
|
||||
c := &HTTPClient{
|
||||
httpClient: &http.Client{Timeout: defaultHTTPClientTimeout},
|
||||
userAgent: defaultUserAgent,
|
||||
apiKey: apiKey,
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
if err := opt(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type Task struct {
|
||||
Object string `json:"object"`
|
||||
ID string `json:"id"`
|
||||
Created int64 `json:"created"`
|
||||
TaskType string `json:"task_type"`
|
||||
Status string `json:"status"`
|
||||
PromptID string `json:"prompt_id"`
|
||||
Prompt Prompt `json:"prompt"`
|
||||
Generations Generations `json:"generations"`
|
||||
}
|
||||
type Generations struct {
|
||||
Data []GenerationData `json:"data"`
|
||||
Object string `json:"object"`
|
||||
}
|
||||
type GenerationData struct {
|
||||
Created int64 `json:"created"`
|
||||
Generation Generation `json:"generation"`
|
||||
GenerationType string `json:"generation_type"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
type Generation struct {
|
||||
ImagePath string `json:"image_path"`
|
||||
}
|
||||
|
||||
type Prompt struct {
|
||||
ID string `json:"id"`
|
||||
Object string `json:"object"`
|
||||
Created int64 `json:"created"`
|
||||
PromptType string `json:"prompt_type"`
|
||||
Prompt struct {
|
||||
Caption string `json:"caption"`
|
||||
} `json:"prompt"`
|
||||
ParentGenerationID string `json:"parent_generation_id"`
|
||||
}
|
||||
|
||||
type GenerateRequest struct {
|
||||
Prompt GenerateRequestPrompt `json:"prompt"`
|
||||
TaskType string `json:"task_type"`
|
||||
}
|
||||
type GenerateRequestPrompt struct {
|
||||
BatchSize int32 `json:"batch_size"`
|
||||
Caption string `json:"caption"`
|
||||
}
|
||||
|
||||
func (c *HTTPClient) Generate(ctx context.Context, caption string) (*Task, error) {
|
||||
task := &Task{}
|
||||
req := &GenerateRequest{
|
||||
Prompt: GenerateRequestPrompt{
|
||||
BatchSize: defaultBatchSize,
|
||||
Caption: caption,
|
||||
},
|
||||
TaskType: TaskTypeText2Im,
|
||||
}
|
||||
return task, c.request(ctx, "POST", "/tasks", nil, req, task)
|
||||
}
|
||||
|
||||
type ListTasksResponse struct {
|
||||
Object string `json:"object"`
|
||||
Data []Task `json:"data"`
|
||||
}
|
||||
|
||||
type ListTasksRequest struct {
|
||||
Limit int32 `json:"limit"`
|
||||
}
|
||||
|
||||
func (c *HTTPClient) ListTasks(ctx context.Context, req *ListTasksRequest) (*ListTasksResponse, error) {
|
||||
res := &ListTasksResponse{}
|
||||
url := "/tasks"
|
||||
if req != nil {
|
||||
if req.Limit != 0 {
|
||||
url += fmt.Sprintf("?limit=%d", req.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
return res, c.request(ctx, "GET", url, nil, nil, res)
|
||||
}
|
||||
|
||||
func (c *HTTPClient) GetTask(ctx context.Context, taskID string) (*Task, error) {
|
||||
task := &Task{}
|
||||
return task, c.request(ctx, "GET", "/tasks/"+taskID, nil, nil, task)
|
||||
}
|
||||
|
||||
func (c *HTTPClient) Download(ctx context.Context, generationID string) (io.ReadCloser, error) {
|
||||
req, err := c.createRequest(ctx, "/generations/"+generationID+"/download", "GET", nil, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("performing request: %w", err)
|
||||
}
|
||||
|
||||
return resp.Body, nil
|
||||
}
|
||||
|
||||
// Share makes the generation public and returns the public url
|
||||
func (c *HTTPClient) Share(ctx context.Context, generationID string) (string, error) {
|
||||
res := &GenerationData{}
|
||||
|
||||
err := c.request(ctx, "POST", "/generations/"+generationID+"/share", nil, nil, res)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Generation.ImagePath, nil
|
||||
}
|
||||
|
||||
func (c *HTTPClient) createRequest(ctx context.Context, path, method string, values *url.Values, data interface{}) (*http.Request, error) {
|
||||
url := baseURL + path
|
||||
|
||||
if values != nil {
|
||||
url += "?" + values.Encode()
|
||||
}
|
||||
|
||||
var body io.Reader
|
||||
if data != nil {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing request data: %w", err)
|
||||
}
|
||||
body = bytes.NewReader(b)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("building request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Add("Authorization", "Bearer "+c.apiKey)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
req.Header.Set("User-Agent", c.userAgent)
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
||||
func (c *HTTPClient) request(ctx context.Context, method, path string, values *url.Values, body interface{}, result interface{}) error {
|
||||
req, err := c.createRequest(ctx, path, method, values, body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("performing request: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, _ := io.ReadAll(resp.Body)
|
||||
|
||||
// TODO: improve error handling...
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return Error{
|
||||
Message: "unexpected non 200 status code",
|
||||
StatusCode: resp.StatusCode,
|
||||
Details: string(respBody),
|
||||
}
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(respBody, result); err != nil {
|
||||
return Error{
|
||||
Message: err.Error(),
|
||||
StatusCode: resp.StatusCode,
|
||||
Details: string(respBody),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Message string
|
||||
StatusCode int
|
||||
Details string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("dalle: %s (status: %d, details: %s)", e.Message, e.StatusCode, e.Details)
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ const (
|
|||
UserStateShopItemSendPrice
|
||||
UserStateShopItemSendItemFile
|
||||
UserEnterShopsDescription
|
||||
UserEnterDallePrompt
|
||||
)
|
||||
|
||||
type UserStateKey int
|
||||
|
|
|
|||
178
internal/telegram/generate.go
Normal file
178
internal/telegram/generate.go
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
package telegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/LightningTipBot/LightningTipBot/internal"
|
||||
"github.com/LightningTipBot/LightningTipBot/internal/dalle"
|
||||
"github.com/LightningTipBot/LightningTipBot/internal/lnbits"
|
||||
"github.com/LightningTipBot/LightningTipBot/internal/runtime"
|
||||
"github.com/LightningTipBot/LightningTipBot/internal/telegram/intercept"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/skip2/go-qrcode"
|
||||
tb "gopkg.in/lightningtipbot/telebot.v3"
|
||||
)
|
||||
|
||||
// generateImages is called when the user enters /generate or /generate <prompt>
|
||||
// asks the user for a prompt if not given
|
||||
func (bot *TipBot) generateImages(ctx intercept.Context) (intercept.Context, error) {
|
||||
user := LoadUser(ctx)
|
||||
if user.Wallet == nil {
|
||||
return ctx, fmt.Errorf("user has no wallet")
|
||||
}
|
||||
|
||||
if len(strings.Split(ctx.Message().Text, " ")) < 2 {
|
||||
// We need to save the pay state in the user state so we can load the payment in the next handler
|
||||
SetUserState(user, bot, lnbits.UserEnterDallePrompt, "")
|
||||
bot.trySendMessage(ctx.Message().Sender, "⌨️ Enter image prompt.", tb.ForceReply)
|
||||
return ctx, nil
|
||||
}
|
||||
// write the prompt into the command and call confirm
|
||||
m := ctx.Message()
|
||||
m.Text = GetMemoFromCommand(m.Text, 1)
|
||||
return bot.confirmGenerateImages(ctx)
|
||||
}
|
||||
|
||||
// confirmGenerateImages is called when the user has entered a prompt through /generate <prompt>
|
||||
// or because he answered to the request to enter it in generateImages()
|
||||
// confirmGenerateImages will create an invoice that the user can pay and if they pay
|
||||
// generateDalleImages will fetch the images and send it to the user
|
||||
func (bot *TipBot) confirmGenerateImages(ctx intercept.Context) (intercept.Context, error) {
|
||||
user := LoadUser(ctx)
|
||||
|
||||
ResetUserState(user, bot)
|
||||
m := ctx.Message()
|
||||
prompt := m.Text
|
||||
if user.Wallet == nil {
|
||||
return ctx, fmt.Errorf("user has no wallet")
|
||||
}
|
||||
me, err := GetUser(bot.Telegram.Me, *bot)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
invoice, err := bot.createInvoiceWithEvent(ctx, me, internal.Configuration.Generate.DallePrice, fmt.Sprintf("DALLE2 %s", GetUserStr(user.Telegram)), InvoiceCallbackGenerateDalle, prompt)
|
||||
invoice.Payer = user
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
runtime.IgnoreError(bot.Bunt.Set(invoice))
|
||||
|
||||
balance, err := bot.GetUserBalance(user)
|
||||
if err != nil {
|
||||
errmsg := fmt.Sprintf("[inlineReceive] Error: Could not get user balance: %s", err.Error())
|
||||
log.Warnln(errmsg)
|
||||
}
|
||||
|
||||
bot.trySendMessage(ctx.Message().Sender, Translate(ctx, "generateDallePayInvoiceMessage"))
|
||||
|
||||
// invoke internal pay if enough balance
|
||||
if balance >= internal.Configuration.Generate.DallePrice {
|
||||
m.Text = fmt.Sprintf("/pay %s", invoice.PaymentRequest)
|
||||
return bot.payHandler(ctx)
|
||||
}
|
||||
|
||||
// create qr code
|
||||
qr, err := qrcode.Encode(invoice.PaymentRequest, qrcode.Medium, 256)
|
||||
if err != nil {
|
||||
bot.tryEditMessage(invoice.Message, Translate(ctx, "errorTryLaterMessage"))
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// send the invoice data to user
|
||||
msg := bot.trySendMessage(ctx.Message().Sender, &tb.Photo{File: tb.File{FileReader: bytes.NewReader(qr)}, Caption: fmt.Sprintf("`%s`", invoice.PaymentRequest)})
|
||||
invoice.InvoiceMessage = msg
|
||||
runtime.IgnoreError(bot.Bunt.Set(invoice))
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// generateDalleImages is called by the invoice event when the user has paid
|
||||
func (bot *TipBot) generateDalleImages(event Event) {
|
||||
invoiceEvent := event.(*InvoiceEvent)
|
||||
user := invoiceEvent.Payer
|
||||
if user == nil || user.Wallet == nil {
|
||||
log.Errorf("[generateDalleImages] invalid user")
|
||||
return
|
||||
}
|
||||
|
||||
bot.trySendMessage(user.Telegram, "Your images are being generated. Please wait...")
|
||||
|
||||
// create the client with the bearer token api key
|
||||
dalleClient, err := dalle.NewHTTPClient(internal.Configuration.Generate.DalleKey)
|
||||
// handle err
|
||||
if err != nil {
|
||||
log.Errorf("[NewHTTPClient] %v", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
|
||||
defer cancel()
|
||||
// generate a task to create an image with a prompt
|
||||
task, err := dalleClient.Generate(ctx, invoiceEvent.CallbackData)
|
||||
if err != nil {
|
||||
log.Errorf("[Generate] %v", err.Error())
|
||||
return
|
||||
}
|
||||
// poll the task.ID until status is succeeded
|
||||
var t *dalle.Task
|
||||
for {
|
||||
time.Sleep(time.Second * 3)
|
||||
|
||||
t, err = dalleClient.GetTask(ctx, task.ID)
|
||||
// handle err
|
||||
if err != nil {
|
||||
log.Errorf("[GetTask] %v", err.Error())
|
||||
return
|
||||
}
|
||||
if t.Status == dalle.StatusSucceeded {
|
||||
fmt.Println("task succeeded")
|
||||
break
|
||||
} else if t.Status == dalle.StatusRejected {
|
||||
log.Errorf("rejected: %s", t.ID)
|
||||
}
|
||||
|
||||
fmt.Println("task still pending")
|
||||
}
|
||||
|
||||
// download the first generated image
|
||||
for _, data := range t.Generations.Data {
|
||||
err = downloadAndSendImages(ctx, bot, dalleClient, data, invoiceEvent)
|
||||
if err != nil {
|
||||
log.Errorf("[downloadAndSendImages] %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// handle err and close readCloser
|
||||
}
|
||||
|
||||
// downloadAndSendImages will download dalle images and send them to the payer.
|
||||
func downloadAndSendImages(ctx context.Context, bot *TipBot, dalleClient dalle.Client, data dalle.GenerationData, event *InvoiceEvent) error {
|
||||
reader, err := dalleClient.Download(ctx, data.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
image := "data/dalle/" + data.ID + ".png"
|
||||
file, err := os.Create(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
_, err = io.Copy(file, reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f, err := os.OpenFile(image, 0, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
bot.trySendMessage(event.Payer.Telegram, &tb.Photo{File: tb.File{FileReader: f}})
|
||||
return nil
|
||||
}
|
||||
|
|
@ -99,6 +99,20 @@ func (bot TipBot) getHandler() []InterceptionWrapper {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Endpoints: []interface{}{"/generate"},
|
||||
Handler: bot.generateImages,
|
||||
Interceptor: &Interceptor{
|
||||
Before: []intercept.Func{
|
||||
bot.localizerInterceptor,
|
||||
bot.loadUserInterceptor,
|
||||
bot.lockInterceptor,
|
||||
},
|
||||
OnDefer: []intercept.Func{
|
||||
bot.unlockInterceptor,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Endpoints: []interface{}{"/tip", "/t", "/honk"},
|
||||
Handler: bot.tipHandler,
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ func initInvoiceEventCallbacks(bot *TipBot) {
|
|||
InvoiceCallbackLNURLPayReceive: EventHandler{Function: bot.lnurlReceiveEvent, Type: EventTypeInvoice},
|
||||
InvoiceCallbackGroupTicket: EventHandler{Function: bot.groupGetInviteLinkHandler, Type: EventTypeInvoice},
|
||||
InvoiceCallbackSatdressProxy: EventHandler{Function: bot.satdressProxyRelayPaymentHandler, Type: EventTypeInvoice},
|
||||
InvoiceCallbackGenerateDalle: EventHandler{Function: bot.generateDalleImages, Type: EventTypeInvoice},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,6 +52,7 @@ const (
|
|||
InvoiceCallbackLNURLPayReceive
|
||||
InvoiceCallbackGroupTicket
|
||||
InvoiceCallbackSatdressProxy
|
||||
InvoiceCallbackGenerateDalle
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@ func initializeStateCallbackMessage(bot *TipBot) {
|
|||
lnbits.UserStateShopItemSendTitle: bot.enterShopItemTitleHandler,
|
||||
lnbits.UserStateShopItemSendItemFile: bot.addItemFileHandler,
|
||||
lnbits.UserEnterShopsDescription: bot.enterShopsDescriptionHandler,
|
||||
lnbits.UserEnterDallePrompt: bot.confirmGenerateImages,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,8 @@ advancedMessage = """%s
|
|||
*/faucet* 🚰 Erzeuge einen Zapfhahn: `/faucet <gesamt> <pro_user>`
|
||||
*/tipjar* 🍯 Erzeuge eine Spendendose: `/tipjar <gesamt> <pro_user>`
|
||||
*/group* 🎟 Tickets für Gruppenchats: `/group add <meine_gruppe> [<ticket_preis>]`
|
||||
*/shop* 🛍 Durchsuche shops: `/shop` oder `/shop <user/shop_id>`"""
|
||||
*/shop* 🛍 Durchsuche Shops: `/shop` oder `/shop <user/shop_id>`
|
||||
*/generate* 🎆 Generiere Bilder mit DALLE-2: `/generate <beschreibung>`"""
|
||||
|
||||
# GENERIC
|
||||
enterAmountRangeMessage = """💯 Gebe Betrag zwuschen %d und %d sat ein."""
|
||||
|
|
|
|||
|
|
@ -127,12 +127,15 @@ advancedMessage = """%s
|
|||
*/faucet* 🚰 Create a faucet: `/faucet <capacity> <per_user>`
|
||||
*/tipjar* 🍯 Create a tipjar: `/tipjar <capacity> <per_user>`
|
||||
*/group* 🎟 Create group tickets: `/group add <mygroup> [<ticket_price>]`
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`"""
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`
|
||||
*/generate* 🎆 Generate DALLE-2 images: `/generate <prompt>`"""
|
||||
|
||||
|
||||
# GENERIC
|
||||
enterAmountRangeMessage = """💯 Enter an amount between %d and %d sat."""
|
||||
enterAmountMessage = """💯 Enter an amount."""
|
||||
enterUserMessage = """👤 Enter a user."""
|
||||
enterTextMessage = """⌨️ Enter text."""
|
||||
errorReasonMessage = """🚫 Error: %s"""
|
||||
|
||||
# START
|
||||
|
|
@ -393,4 +396,9 @@ To join a group, talk to %s and write in a private message `/join <mygroup>`.
|
|||
|
||||
📖 *Usage:*
|
||||
*For admins (in group chat):* `/group add <group_name> [<ticket_price>]`\nExample: `/group add TheBestBitcoinGroup 1000`
|
||||
*For users (in private chat):* `/join <group_name>`\nExample: `/join TheBestBitcoinGroup`"""
|
||||
*For users (in private chat):* `/join <group_name>`\nExample: `/join TheBestBitcoinGroup`"""
|
||||
|
||||
# DALLE GENERATE
|
||||
generateDalleHelpMessage = """Generate images using OpenAI DALLE 2.\nUsage: `/generate <prompt>`\nPrice: 1000 sat"""
|
||||
generateDallePayInvoiceMessage = """Pay this invoice to generate four images 👇"""
|
||||
generateDalleGeneratingMessage = """Your images are being generated. Please wait..."""
|
||||
|
|
@ -123,7 +123,8 @@ advancedMessage = """%s
|
|||
*/faucet* 🚰 Crear un grifo: `/faucet <capacidad> <por_usuario>`
|
||||
*/tipjar* 🍯 Crear un tipjar: `/tipjar <capacidad> <por_usuario>`
|
||||
*/group* 🎟 Create group tickets: `/group add <mygroup> [<ticket_price>]`
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`"""
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`
|
||||
*/generate* 🎆 Generate DALLE-2 images: `/generate <prompt>`"""
|
||||
|
||||
# GENERIC
|
||||
enterAmountRangeMessage = """💯 Introduce un monto entre %d y %d sat."""
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ advancedMessage = """%s
|
|||
*/faucet* 🚰 Créer un faucet: `/faucet <capacité> <par_utilisateur>`
|
||||
*/tipjar* 🍯 Créer un tipjar: `/tipjar <capacité> <par_utilisateur>`
|
||||
*/group* 🎟 Create group tickets: `/group add <mygroup> [<ticket_price>]`
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`"""
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`
|
||||
*/generate* 🎆 Generate DALLE-2 images: `/generate <prompt>`"""
|
||||
|
||||
# GENERIC
|
||||
enterAmountRangeMessage = """💯 Choisissez un montant entre %d et %d sat."""
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ advancedMessage = """%s
|
|||
*/faucet* 🚰 Membuat sebuah keran `/faucet <kapasitas> <per_pengguna>`
|
||||
*/tipjar* 🍯 Create a tipjar: `/tipjar <capacity> <per_user>`
|
||||
*/group* 🎟 Create group tickets: `/group add <mygroup> [<ticket_price>]`
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`"""
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`
|
||||
*/generate* 🎆 Generate DALLE-2 images: `/generate <prompt>`"""
|
||||
|
||||
# GENERIC
|
||||
enterAmountRangeMessage = """💯 Masukkan jumlah diantara %d dan %d sat."""
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ advancedMessage = """%s
|
|||
*/faucet* 🚰 Crea una distribuzione: `/faucet <totale> <per_utente>`
|
||||
*/tipjar* 🍯 Crea un tipjar: `/tipjar <totale> <per_utente>`
|
||||
*/group* 🎟 Create group tickets: `/group add <mygroup> [<ticket_price>]`
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`"""
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`
|
||||
*/generate* 🎆 Generate DALLE-2 images: `/generate <prompt>`"""
|
||||
|
||||
# GENERIC
|
||||
enterAmountRangeMessage = """💯 Imposta un ammontare tra %d e %d sat."""
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ advancedMessage = """%s
|
|||
*/faucet* 🚰 Maak een kraan: `/faucet <capacity> <per_user>`
|
||||
*/tipjar* 🍯 Maak een tipjar: `/tipjar <capacity> <per_user>`
|
||||
*/group* 🎟 Create group tickets: `/group add <mygroup> [<ticket_price>]`
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`"""
|
||||
*/shop* 🛍 Browse shops: `/shop` or `/shop <user/shop_id>`
|
||||
*/generate* 🎆 Generate DALLE-2 images: `/generate <prompt>`"""
|
||||
|
||||
# GENERIC
|
||||
enterAmountRangeMessage = """💯 Voer een bedrag in tussen %d en %d sat."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue