mirror of
https://github.com/muun/recovery.git
synced 2025-02-23 11:32:33 -05:00
203 lines
4.8 KiB
Go
203 lines
4.8 KiB
Go
|
package lnurl
|
||
|
|
||
|
import (
|
||
|
"encoding/base64"
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"net/url"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
"time"
|
||
|
|
||
|
"github.com/tidwall/gjson"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
f bool = false
|
||
|
t bool = true
|
||
|
FALSE *bool = &f
|
||
|
TRUE *bool = &t
|
||
|
)
|
||
|
|
||
|
func Action(text string, url string) *SuccessAction {
|
||
|
if url == "" {
|
||
|
return &SuccessAction{
|
||
|
Tag: "message",
|
||
|
Message: text,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if text == "" {
|
||
|
text = " "
|
||
|
}
|
||
|
return &SuccessAction{
|
||
|
Tag: "url",
|
||
|
Description: text,
|
||
|
URL: url,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func AESAction(description string, preimage []byte, content string) (*SuccessAction, error) {
|
||
|
plaintext := []byte(content)
|
||
|
|
||
|
ciphertext, iv, err := AESCipher(preimage, plaintext)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
return &SuccessAction{
|
||
|
Tag: "aes",
|
||
|
Description: description,
|
||
|
Ciphertext: base64.StdEncoding.EncodeToString(ciphertext),
|
||
|
IV: base64.StdEncoding.EncodeToString(iv),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
type LNURLPayResponse1 struct {
|
||
|
LNURLResponse
|
||
|
Callback string `json:"callback"`
|
||
|
CallbackURL *url.URL `json:"-"`
|
||
|
Tag string `json:"tag"`
|
||
|
MaxSendable int64 `json:"maxSendable"`
|
||
|
MinSendable int64 `json:"minSendable"`
|
||
|
EncodedMetadata string `json:"metadata"`
|
||
|
Metadata Metadata `json:"-"`
|
||
|
CommentAllowed int64 `json:"commentAllowed"`
|
||
|
}
|
||
|
|
||
|
type LNURLPayResponse2 struct {
|
||
|
LNURLResponse
|
||
|
SuccessAction *SuccessAction `json:"successAction"`
|
||
|
Routes [][]RouteInfo `json:"routes"`
|
||
|
PR string `json:"pr"`
|
||
|
Disposable *bool `json:"disposable,omitempty"`
|
||
|
}
|
||
|
|
||
|
type RouteInfo struct {
|
||
|
NodeId string `json:"nodeId"`
|
||
|
ChannelUpdate string `json:"channelUpdate"`
|
||
|
}
|
||
|
|
||
|
type SuccessAction struct {
|
||
|
Tag string `json:"tag"`
|
||
|
Description string `json:"description,omitempty"`
|
||
|
URL string `json:"url,omitempty"`
|
||
|
Message string `json:"message,omitempty"`
|
||
|
Ciphertext string `json:"ciphertext,omitempty"`
|
||
|
IV string `json:"iv,omitempty"`
|
||
|
}
|
||
|
|
||
|
func (sa *SuccessAction) Decipher(preimage []byte) (content string, err error) {
|
||
|
ciphertext, err := base64.StdEncoding.DecodeString(sa.Ciphertext)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
iv, err := base64.StdEncoding.DecodeString(sa.IV)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
plaintext, err := AESDecipher(preimage, ciphertext, iv)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
return string(plaintext), nil
|
||
|
}
|
||
|
|
||
|
func (_ LNURLPayResponse1) LNURLKind() string { return "lnurl-pay" }
|
||
|
|
||
|
func HandlePay(j gjson.Result) (LNURLParams, error) {
|
||
|
strmetadata := j.Get("metadata").String()
|
||
|
var metadata Metadata
|
||
|
err := json.Unmarshal([]byte(strmetadata), &metadata)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
|
||
|
callback := j.Get("callback").String()
|
||
|
|
||
|
// parse url
|
||
|
callbackURL, err := url.Parse(callback)
|
||
|
if err != nil {
|
||
|
return nil, errors.New("callback is not a valid URL")
|
||
|
}
|
||
|
|
||
|
// add random nonce to avoid caches
|
||
|
qs := callbackURL.Query()
|
||
|
qs.Set("nonce", strconv.FormatInt(time.Now().Unix(), 10))
|
||
|
callbackURL.RawQuery = qs.Encode()
|
||
|
|
||
|
return LNURLPayResponse1{
|
||
|
Tag: "payRequest",
|
||
|
Callback: callback,
|
||
|
CallbackURL: callbackURL,
|
||
|
EncodedMetadata: strmetadata,
|
||
|
Metadata: metadata,
|
||
|
MaxSendable: j.Get("maxSendable").Int(),
|
||
|
MinSendable: j.Get("minSendable").Int(),
|
||
|
CommentAllowed: j.Get("commentAllowed").Int(),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
type Metadata [][]string
|
||
|
|
||
|
// Description returns the content of text/plain metadata entry.
|
||
|
func (m Metadata) Description() string {
|
||
|
for _, entry := range m {
|
||
|
if len(entry) == 2 && entry[0] == "text/plain" {
|
||
|
return entry[1]
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// ImageDataURI returns image in the form data:image/type;base64,... if an image exists
|
||
|
// or an empty string if not.
|
||
|
func (m Metadata) ImageDataURI() string {
|
||
|
for _, entry := range m {
|
||
|
if len(entry) == 2 && strings.Split(entry[0], "/")[0] == "image" {
|
||
|
return "data:" + entry[0] + "," + entry[1]
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// ImageBytes returns image as bytes, decoded from base64 if an image exists
|
||
|
// or nil if not.
|
||
|
func (m Metadata) ImageBytes() []byte {
|
||
|
for _, entry := range m {
|
||
|
if len(entry) == 2 && strings.Split(entry[0], "/")[0] == "image" {
|
||
|
if decoded, err := base64.StdEncoding.DecodeString(entry[1]); err == nil {
|
||
|
return decoded
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// ImageExtension returns the file extension for the image, either "png" or "jpeg"
|
||
|
func (m Metadata) ImageExtension() string {
|
||
|
for _, entry := range m {
|
||
|
if len(entry) == 2 && strings.Split(entry[0], "/")[0] == "image" {
|
||
|
spl := strings.Split(entry[0], "/")
|
||
|
if len(spl) == 2 {
|
||
|
return strings.Split(spl[1], ";")[0]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|
||
|
|
||
|
// Entry returns an arbitrary entry from the metadata array.
|
||
|
// eg.: "video/mp4" or "application/vnd.some-specific-thing-from-a-specific-app".
|
||
|
func (m Metadata) Entry(key string) string {
|
||
|
for _, entry := range m {
|
||
|
if len(entry) == 2 && entry[0] == key {
|
||
|
return entry[1]
|
||
|
}
|
||
|
}
|
||
|
return ""
|
||
|
}
|