-
Notifications
You must be signed in to change notification settings - Fork 44
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
0 parents
commit d6f8380
Showing
36 changed files
with
2,647 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,4 @@ | ||
config.yaml | ||
config.yaml* | ||
data/* | ||
!data/.placeholder |
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,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2020 neurolib-dev | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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,89 @@ | ||
<p align="center"> | ||
<img alt="logo" src="resources/logo_round.png" > | ||
</p> | ||
|
||
|
||
# @LightningTipBot 🏅 | ||
|
||
A tip bot for Telegram group chats that can send and receive Bitcoin via the Lightning Network ⚡️. It can also be used as a personal Lightning wallet by anyone who has a Telegram account. | ||
|
||
This repository contains everything you need to set up and run your own tip bot. If you simply want to use this bot in your group chat without having to install anything just start a conversation with [@LightningTipBot](https://t.me/LightningTipBot) and invite it into your chat. | ||
|
||
## Setting up the bot | ||
### Installation | ||
To build the bot from source, clone the repository and compile the source code. | ||
|
||
``` | ||
git clone https://github.com/LightningTipBot/LightningTipBot.git | ||
cd LightningTipBot | ||
go build . | ||
cp config.yaml-example config.yaml | ||
``` | ||
|
||
Alternatively, you can download binary releases [here](https://github.com/LightningTipBot/LightningTipBot/releases) **(note: no realses yet)**. | ||
|
||
After you have configured the bot, start it using the command | ||
|
||
``` | ||
./LightningTipBot | ||
``` | ||
|
||
### Configuration | ||
|
||
You need to edit `config.yaml` before starting the bot. | ||
|
||
#### Create a Telegram bot | ||
|
||
First, create a new Telegram bot by starting a conversation with the [@BotFather](https://core.telegram.org/bots#6-botfather). After you have created your bot, you will get an **Api Token** which you need to add to `telegram_api_key` in config.yaml accordingly. | ||
|
||
#### Set up LNbits | ||
|
||
You can either use your own LNbits instance (recommended) or create an account at [lnbits.com](https://lnbits.com/) to use their custodial service (easy). | ||
|
||
1. Create a wallet in LNbits (`lnbits_url`). | ||
2. Get the **Admin key** in the API Info tab of your user (`lnbits_admin_key`). | ||
3. Enable the User Manager extension. | ||
4. Get the **Admin ID** of the User Manager (`lnbits_admin_id`). | ||
|
||
#### Getting LNbits keys | ||
|
||
<p align="center"> | ||
<img alt="How to set up a lnbits wallet and the User Manager extension." src="resources/lnbits_setup.png" > | ||
</p> | ||
|
||
#### More configuration | ||
* `webhook_server`: URL that can reach the bot. This is used for creating webhooks with LNbits to receive notifications about payments (optional). | ||
* `db_path`: User database file path. | ||
* `transactions_path`: Transaction log file path. | ||
* `message_dispose_duration`: Duration in seconds after which commands will be deleted from channel (only if the bot is channel admin). | ||
|
||
## Features | ||
|
||
### Commands | ||
``` | ||
/tip 🏅 Reply to a message to tip it: /tip <amount> [<memo>] | ||
/balance 👑 Check your balance: /balance | ||
/send 💸 Send funds to a user: /send <amount> <@username> [<memo>] | ||
/invoice ⚡️ Create a Lightning invoice to receive payments: /invoice <amount> [<memo>] | ||
/pay ⚡️ Pay a Lightning invoice: /pay <invoice> | ||
/help 📖 Read this help. | ||
``` | ||
|
||
### Live tooltips | ||
|
||
The bot replies to a tipped message to indicate to all participants how many and what amount of tips a post has received. This tooltip will be updated as new tips are given to a post. | ||
|
||
<p align="center"> | ||
<img alt="How to set up a lnbits wallet and the User Manager extension." src="resources/tooltips.png" > | ||
</p> | ||
|
||
### Auto-delete commands | ||
|
||
To minimize the clutter all the heavy tipping can cause in a group chat, the bot will remove all failed commands (for example due to a syntax error) from the chat immediately. All successful commands will stay visible for `message_dispose_duration` seconds (default 10s) and then be removed. The tips will sill be visible for everyone in the Live tooltip. This feature only works, if the bot is made admin of the group. | ||
|
||
## Made with | ||
* [LNbits](https://github.com/lnbits/lnbits) – Free and open-source lightning-network wallet/accounts system. | ||
* [telebot](https://github.com/tucnak/telebot) – A Telegram bot framework in Go. | ||
* [gozxing](https://github.com/makiuchi-d/gozxing) – barcode image processing library in Go. | ||
* [ln-decodepay](https://github.com/fiatjaf/ln-decodepay) – Lightning Network BOLT11 invoice decoder. | ||
|
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 @@ | ||
package main | ||
|
||
import ( | ||
"errors" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
func getArgumentFromCommand(input string, which int) (output string, err error) { | ||
if len(strings.Split(input, " ")) < which+1 { | ||
return "", errors.New("Message doesn't contain enough arguments") | ||
} | ||
output = strings.Split(input, " ")[which] | ||
return output, nil | ||
} | ||
|
||
func decodeAmountFromCommand(input string) (amount int, err error) { | ||
if len(strings.Split(input, " ")) < 2 { | ||
errmsg := "Message doesn't contain any amount" | ||
// log.Errorln(errmsg) | ||
return 0, errors.New(errmsg) | ||
} | ||
amount, err = getAmount(input) | ||
return amount, nil | ||
} | ||
|
||
func getAmount(input string) (amount int, err error) { | ||
amount, err = strconv.Atoi(strings.Split(input, " ")[1]) | ||
if err != nil { | ||
return 0, err | ||
} | ||
if amount < 1 { | ||
errmsg := "Error: Amount must be greater than 0" | ||
// log.Errorln(errmsg) | ||
return 0, errors.New(errmsg) | ||
} | ||
return amount, err | ||
} |
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,40 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
|
||
log "github.com/sirupsen/logrus" | ||
|
||
tb "gopkg.in/tucnak/telebot.v2" | ||
) | ||
|
||
func (bot TipBot) balanceHandler(m *tb.Message) { | ||
log.Infof("[%s:%d %s:%d] %s", m.Chat.Title, m.Chat.ID, GetUserStr(m.Sender), m.Sender.ID, m.Text) | ||
// reply only in private message | ||
if m.Chat.Type != tb.ChatPrivate { | ||
// delete message | ||
NewMessage(m).Dispose(0, bot.telegram) | ||
} | ||
// first check whether the user is initialized | ||
fromUser, err := GetUser(m.Sender, bot) | ||
if err != nil { | ||
log.Errorf("[/balance] Error: %s", err) | ||
return | ||
} | ||
if !fromUser.Initialized { | ||
bot.startHandler(m) | ||
return | ||
} | ||
|
||
usrStr := GetUserStr(m.Sender) | ||
balance, err := bot.GetUserBalance(m.Sender) | ||
if err != nil { | ||
log.Errorf("[/balance] Error fetching %s's balance: %s", usrStr, err) | ||
bot.telegram.Send(m.Sender, "🚫 Error fetching your balance. Please try again later.") | ||
return | ||
} | ||
|
||
log.Infof("[/balance] %s's balance: %d sat\n", usrStr, balance) | ||
bot.telegram.Send(m.Sender, fmt.Sprintf("👑 *Your balance:* %d sat", balance)) | ||
return | ||
} |
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,127 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
"sync" | ||
"time" | ||
|
||
log "github.com/sirupsen/logrus" | ||
|
||
"github.com/LightningTipBot/LightningTipBot/internal/lnbits" | ||
"gopkg.in/tucnak/telebot.v2" | ||
tb "gopkg.in/tucnak/telebot.v2" | ||
|
||
"gorm.io/gorm" | ||
) | ||
|
||
type TipBot struct { | ||
database *gorm.DB | ||
logger *gorm.DB | ||
telegram *telebot.Bot | ||
client *lnbits.Client | ||
tips map[int][]*Message | ||
} | ||
|
||
var ( | ||
paymentConfirmationMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true} | ||
btnCancelPay = paymentConfirmationMenu.Data("🚫 Cancel", "cancel_pay") | ||
btnPay = paymentConfirmationMenu.Data("✅ Pay", "confirm_pay") | ||
sendConfirmationMenu = &tb.ReplyMarkup{ResizeReplyKeyboard: true} | ||
btnCancelSend = sendConfirmationMenu.Data("🚫 Cancel", "cancel_send") | ||
btnSend = sendConfirmationMenu.Data("✅ Send", "confirm_send") | ||
|
||
botWalletInitialisation = sync.Once{} | ||
telegramHandlerRegistration = sync.Once{} | ||
) | ||
|
||
// NewBot migrates data and creates a new bot | ||
func NewBot() TipBot { | ||
db, txLogger := migration() | ||
return TipBot{ | ||
database: db, | ||
logger: txLogger, | ||
tips: make(map[int][]*Message, 0), | ||
} | ||
} | ||
|
||
// newTelegramBot will create a new telegram bot. | ||
func newTelegramBot() *tb.Bot { | ||
tgb, err := tb.NewBot(tb.Settings{ | ||
Token: Configuration.ApiKey, | ||
Poller: &tb.LongPoller{Timeout: 60 * time.Second}, | ||
ParseMode: tb.ModeMarkdown, | ||
}) | ||
if err != nil { | ||
panic(err) | ||
} | ||
return tgb | ||
} | ||
|
||
// initBotWallet will create / initialize the bot wallet | ||
// todo -- may want to derive user wallets from this specific bot wallet (master wallet), since lnbits usermanager extension is able to do that. | ||
func (bot TipBot) initBotWallet() error { | ||
botWalletInitialisation.Do(func() { | ||
err := bot.initWallet(bot.telegram.Me) | ||
if err != nil { | ||
log.Errorln(fmt.Sprintf("[initBotWallet] Could not initialize bot wallet: %s", err.Error())) | ||
return | ||
} | ||
}) | ||
return nil | ||
} | ||
|
||
// registerTelegramHandlers will register all telegram handlers. | ||
func (bot TipBot) registerTelegramHandlers() { | ||
telegramHandlerRegistration.Do(func() { | ||
// Set up handlers | ||
var endpointHandler = map[string]interface{}{ | ||
"/tip": bot.tipHandler, | ||
"/pay": bot.confirmPaymentHandler, | ||
"/invoice": bot.invoiceHandler, | ||
"/balance": bot.balanceHandler, | ||
"/start": bot.startHandler, | ||
"/send": bot.confirmSendHandler, | ||
"/help": bot.helpHandler, | ||
"/info": bot.infoHandler, | ||
tb.OnPhoto: bot.privatePhotoHandler, | ||
tb.OnText: bot.anyTextHandler, | ||
} | ||
// assign handler to endpoint | ||
for endpoint, handler := range endpointHandler { | ||
log.Debugf("Registering: %s", endpoint) | ||
bot.telegram.Handle(endpoint, handler) | ||
|
||
// if the endpoint is a string command (not photo etc) | ||
if strings.HasPrefix(endpoint, "/") { | ||
// register upper case versions as well | ||
bot.telegram.Handle(strings.ToUpper(endpoint), handler) | ||
} | ||
} | ||
|
||
// button handlers | ||
// for /pay | ||
bot.telegram.Handle(&btnPay, bot.payHandler) | ||
bot.telegram.Handle(&btnCancelPay, bot.cancelPaymentHandler) | ||
// for /send | ||
bot.telegram.Handle(&btnSend, bot.sendHandler) | ||
bot.telegram.Handle(&btnCancelSend, bot.cancelSendHandler) | ||
}) | ||
} | ||
|
||
// Start will initialize the telegram bot and lnbits. | ||
func (bot TipBot) Start() { | ||
// set up lnbits api | ||
bot.client = lnbits.NewClient(Configuration.LnbitsKey, Configuration.LnbitsUrl) | ||
// set up telebot | ||
bot.telegram = newTelegramBot() | ||
log.Infof("[Telegram] Authorized on account @%s", bot.telegram.Me.Username) | ||
// initialize the bot wallet | ||
err := bot.initBotWallet() | ||
if err != nil { | ||
log.Errorf("Could not initialize bot wallet: %s", err.Error()) | ||
} | ||
bot.registerTelegramHandlers() | ||
lnbits.NewWebhook(Configuration.WebhookServer, bot.telegram, bot.client, bot.database) | ||
bot.telegram.Start() | ||
} |
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,13 @@ | ||
# Instructions: | ||
|
||
1 - Talk to the botfather | ||
2 - Use: /setcommands | ||
3 - Select your tip bot | ||
4 - Paste this: | ||
|
||
help - Read the help. | ||
balance - Check your balance. | ||
tip - Reply to a message to tip it. | ||
send - Send funds to a user. | ||
invoice - Create a Lightning invoice to receive payments. | ||
pay - Pay a Lightning invoice. |
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,21 @@ | ||
package main | ||
|
||
import "github.com/jinzhu/configor" | ||
|
||
var Configuration = struct { | ||
AdminKey string `json:"lnbits_admin_id" yaml:"lnbits_admin_id"` | ||
LnbitsKey string `json:"lnbits_admin_key" yaml:"lnbits_admin_key"` | ||
LnbitsUrl string `json:"lnbits_url" yaml:"lnbits_url"` | ||
WebhookServer string `json:"webhook_server" yaml:"webhook_server"` | ||
ApiKey string `json:"telegram_api_key" yaml:"telegram_api_key"` | ||
DbPath string `json:"db_path" yaml:"db_path"` | ||
TransactionsPath string `json:"transactions_path" yaml:"transactions_path"` | ||
MessageDisposeDuration int64 `json:"message_dispose_duration" yaml:"message_dispose_duration"` | ||
}{} | ||
|
||
func init() { | ||
err := configor.Load(&Configuration, "config.yaml") | ||
if err != nil { | ||
panic(err) | ||
} | ||
} |
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,8 @@ | ||
telegram_api_key: "1234" | ||
webhook_server: "http://0.0.0.0:1234" | ||
lnbits_url: "http://*.onion:1234" | ||
lnbits_admin_key: "1234" | ||
lnbits_admin_id: "3212" | ||
db_path: "data/bot.db" | ||
transactions_path: "data/transactions.db" | ||
message_dispose_duration: 10 |
Empty file.
Oops, something went wrong.