Add /vcard command
This commit is contained in:
parent
75f0532193
commit
8663a29e15
|
@ -15,7 +15,7 @@ import (
|
|||
goxmpp "gosrc.io/xmpp"
|
||||
)
|
||||
|
||||
var version string = "1.5.0"
|
||||
var version string = "1.6.0-dev"
|
||||
var commit string
|
||||
|
||||
var sm *goxmpp.StreamManager
|
||||
|
|
|
@ -64,6 +64,7 @@ var chatCommands = map[string]command{
|
|||
"silent": command{"message", "send a message without sound"},
|
||||
"schedule": command{"{online | 2006-01-02T15:04:05 | 15:04:05} message", "schedules a message either to timestamp or to whenever the user goes online"},
|
||||
"forward": command{"message_id target_chat", "forwards a message"},
|
||||
"vcard": command{"", "print vCard as text"},
|
||||
"add": command{"@username", "add @username to your chat list"},
|
||||
"join": command{"https://t.me/invite_link", "join to chat via invite link or @publicname"},
|
||||
"group": command{"title", "create groupchat «title» with current user"},
|
||||
|
@ -172,6 +173,10 @@ func rawCmdArguments(cmdline string, start uint8) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func keyValueString(key, value string) string {
|
||||
return fmt.Sprintf("%s: %s", key, value)
|
||||
}
|
||||
|
||||
func (c *Client) unsubscribe(chatID int64) error {
|
||||
return gateway.SendPresence(
|
||||
c.xmpp,
|
||||
|
@ -636,6 +641,21 @@ func (c *Client) ProcessChatCommand(chatID int64, cmdline string) (string, bool)
|
|||
c.ProcessIncomingMessage(targetChatId, message)
|
||||
}
|
||||
}
|
||||
// print vCard
|
||||
case "vcard":
|
||||
info, err := c.GetVcardInfo(chatID)
|
||||
if err != nil {
|
||||
return err.Error(), true
|
||||
}
|
||||
_, link := c.PermastoreFile(info.Photo, true)
|
||||
entries := []string{
|
||||
keyValueString("Chat title", info.Fn),
|
||||
keyValueString("Photo", link),
|
||||
keyValueString("Username", info.Nickname),
|
||||
keyValueString("Full name", info.Given + " " + info.Family),
|
||||
keyValueString("Phone number", info.Tel),
|
||||
}
|
||||
return strings.Join(entries, "\n"), true
|
||||
// add @contact
|
||||
case "add":
|
||||
return c.cmdAdd(args), true
|
||||
|
|
|
@ -24,6 +24,16 @@ import (
|
|||
"github.com/zelenin/go-tdlib/client"
|
||||
)
|
||||
|
||||
type VCardInfo struct {
|
||||
Fn string
|
||||
Photo *client.File
|
||||
Nickname string
|
||||
Given string
|
||||
Family string
|
||||
Tel string
|
||||
Info string
|
||||
}
|
||||
|
||||
var errOffline = errors.New("TDlib instance is offline")
|
||||
|
||||
var spaceRegex = regexp.MustCompile(`\s+`)
|
||||
|
@ -207,7 +217,7 @@ func (c *Client) ProcessStatusUpdate(chatID int64, status string, show string, o
|
|||
|
||||
var photo string
|
||||
if chat != nil && chat.Photo != nil {
|
||||
file, path, err := c.OpenPhotoFile(chat.Photo.Small, 1)
|
||||
file, path, err := c.ForceOpenFile(chat.Photo.Small, 1)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
|
||||
|
@ -408,6 +418,20 @@ func (c *Client) formatForward(fwd *client.MessageForwardInfo) string {
|
|||
}
|
||||
|
||||
func (c *Client) formatFile(file *client.File, compact bool) (string, string) {
|
||||
if file == nil {
|
||||
return "", ""
|
||||
}
|
||||
src, link := c.PermastoreFile(file, false)
|
||||
|
||||
if compact {
|
||||
return link, link
|
||||
} else {
|
||||
return fmt.Sprintf("%s (%v kbytes) | %s", filepath.Base(src), file.Size/1024, link), link
|
||||
}
|
||||
}
|
||||
|
||||
// PermastoreFile steals a file out of TDlib control into an independent shared directory
|
||||
func (c *Client) PermastoreFile(file *client.File, clone bool) (string, string) {
|
||||
log.Debugf("file: %#v", file)
|
||||
if file == nil || file.Local == nil || file.Remote == nil {
|
||||
return "", ""
|
||||
|
@ -434,6 +458,45 @@ func (c *Client) formatFile(file *client.File, compact bool) (string, string) {
|
|||
dest := c.content.Path + "/" + basename // destination path
|
||||
link = c.content.Link + "/" + basename // download link
|
||||
|
||||
if clone {
|
||||
file, path, err := c.ForceOpenFile(file, 1)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
|
||||
// mode
|
||||
mode := os.FileMode(0644)
|
||||
fi, err := os.Stat(path)
|
||||
if err == nil {
|
||||
mode = fi.Mode().Perm()
|
||||
}
|
||||
|
||||
// create destination
|
||||
tempFile, err := os.OpenFile(dest, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode)
|
||||
if err != nil {
|
||||
pathErr := err.(*os.PathError)
|
||||
if pathErr.Err.Error() == "file exists" {
|
||||
log.Warn(err.Error())
|
||||
return src, link
|
||||
} else {
|
||||
log.Errorf("File creation error: %v", err)
|
||||
return "<ERROR>", ""
|
||||
}
|
||||
}
|
||||
defer tempFile.Close()
|
||||
// copy
|
||||
_, err = io.Copy(tempFile, file)
|
||||
if err != nil {
|
||||
log.Errorf("File copying error: %v", err)
|
||||
return "<ERROR>", ""
|
||||
}
|
||||
} else if path != "" {
|
||||
log.Errorf("Source file does not exist: %v", path)
|
||||
return "<ERROR>", ""
|
||||
} else {
|
||||
log.Errorf("PHOTO: %#v", err.Error())
|
||||
return "<ERROR>", ""
|
||||
}
|
||||
} else {
|
||||
// move
|
||||
err = os.Rename(src, dest)
|
||||
if err != nil {
|
||||
|
@ -445,7 +508,7 @@ func (c *Client) formatFile(file *client.File, compact bool) (string, string) {
|
|||
return "<ERROR>", ""
|
||||
}
|
||||
}
|
||||
gateway.CachedStorageSize += size64
|
||||
}
|
||||
|
||||
// chown
|
||||
if c.content.User != "" {
|
||||
|
@ -464,13 +527,12 @@ func (c *Client) formatFile(file *client.File, compact bool) (string, string) {
|
|||
log.Errorf("Wrong user name for chown: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// copy or move should have succeeded at this point
|
||||
gateway.CachedStorageSize += size64
|
||||
}
|
||||
|
||||
if compact {
|
||||
return link, link
|
||||
} else {
|
||||
return fmt.Sprintf("%s (%v kbytes) | %s", filepath.Base(src), file.Size/1024, link), link
|
||||
}
|
||||
return src, link
|
||||
}
|
||||
|
||||
func (c *Client) formatBantime(hours int64) int32 {
|
||||
|
@ -1148,20 +1210,20 @@ func (c *Client) DownloadFile(id int32, priority int32, synchronous bool) (*clie
|
|||
})
|
||||
}
|
||||
|
||||
// OpenPhotoFile reliably obtains a photo if possible
|
||||
func (c *Client) OpenPhotoFile(photoFile *client.File, priority int32) (*os.File, string, error) {
|
||||
if photoFile == nil {
|
||||
return nil, "", errors.New("Photo file not found")
|
||||
// ForceOpenFile reliably obtains a file if possible
|
||||
func (c *Client) ForceOpenFile(tgFile *client.File, priority int32) (*os.File, string, error) {
|
||||
if tgFile == nil {
|
||||
return nil, "", errors.New("File not found")
|
||||
}
|
||||
|
||||
path := photoFile.Local.Path
|
||||
path := tgFile.Local.Path
|
||||
file, err := os.Open(path)
|
||||
if err == nil {
|
||||
return file, path, nil
|
||||
} else
|
||||
// obtain the photo right now if still not downloaded
|
||||
if !photoFile.Local.IsDownloadingCompleted {
|
||||
tdFile, tdErr := c.DownloadFile(photoFile.Id, priority, true)
|
||||
if !tgFile.Local.IsDownloadingCompleted {
|
||||
tdFile, tdErr := c.DownloadFile(tgFile.Id, priority, true)
|
||||
if tdErr == nil {
|
||||
path = tdFile.Local.Path
|
||||
file, err = os.Open(path)
|
||||
|
@ -1248,3 +1310,28 @@ func (c *Client) prepareDiskSpace(size uint64) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) GetVcardInfo(toID int64) (VCardInfo, error) {
|
||||
var info VCardInfo
|
||||
chat, user, err := c.GetContactByID(toID, nil)
|
||||
if err != nil {
|
||||
return info, err
|
||||
}
|
||||
|
||||
if chat != nil {
|
||||
info.Fn = chat.Title
|
||||
|
||||
if chat.Photo != nil {
|
||||
info.Photo = chat.Photo.Small
|
||||
}
|
||||
info.Info = c.GetChatDescription(chat)
|
||||
}
|
||||
if user != nil {
|
||||
info.Nickname = user.Username
|
||||
info.Given = user.FirstName
|
||||
info.Family = user.LastName
|
||||
info.Tel = user.PhoneNumber
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
|
106
xmpp/handlers.go
106
xmpp/handlers.go
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"dev.narayana.im/narayana/telegabber/persistence"
|
||||
"dev.narayana.im/narayana/telegabber/telegram"
|
||||
"dev.narayana.im/narayana/telegabber/xmpp/extensions"
|
||||
"dev.narayana.im/narayana/telegabber/xmpp/gateway"
|
||||
|
||||
|
@ -319,45 +320,12 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
|||
log.Error("Invalid IQ to")
|
||||
return
|
||||
}
|
||||
chat, user, err := session.GetContactByID(toID, nil)
|
||||
info, err := session.GetVcardInfo(toID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
var fn, photo, nickname, given, family, tel, info string
|
||||
if chat != nil {
|
||||
fn = chat.Title
|
||||
|
||||
if chat.Photo != nil {
|
||||
file, path, err := session.OpenPhotoFile(chat.Photo.Small, 32)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
binval := base64.NewEncoder(base64.StdEncoding, buf)
|
||||
_, err = io.Copy(binval, file)
|
||||
binval.Close()
|
||||
if err == nil {
|
||||
photo = buf.String()
|
||||
} else {
|
||||
log.Errorf("Error calculating base64: %v", path)
|
||||
}
|
||||
} else if path != "" {
|
||||
log.Errorf("Photo does not exist: %v", path)
|
||||
} else {
|
||||
log.Errorf("PHOTO: %#v", err.Error())
|
||||
}
|
||||
}
|
||||
info = session.GetChatDescription(chat)
|
||||
}
|
||||
if user != nil {
|
||||
nickname = user.Username
|
||||
given = user.FirstName
|
||||
family = user.LastName
|
||||
tel = user.PhoneNumber
|
||||
}
|
||||
|
||||
answer := stanza.IQ{
|
||||
Attrs: stanza.Attrs{
|
||||
From: iq.To,
|
||||
|
@ -365,7 +333,7 @@ func handleGetVcardIq(s xmpp.Sender, iq *stanza.IQ, typ byte) {
|
|||
Id: iq.Id,
|
||||
Type: "result",
|
||||
},
|
||||
Payload: makeVCardPayload(typ, iq.To, fn, photo, nickname, given, family, tel, info),
|
||||
Payload: makeVCardPayload(typ, iq.To, info, session),
|
||||
}
|
||||
log.Debugf("%#v", answer)
|
||||
|
||||
|
@ -426,53 +394,75 @@ func toToID(to string) (int64, bool) {
|
|||
return toID, true
|
||||
}
|
||||
|
||||
func makeVCardPayload(typ byte, id, fn, photo, nickname, given, family, tel, info string) stanza.IQPayload {
|
||||
func makeVCardPayload(typ byte, id string, info telegram.VCardInfo, session *telegram.Client) stanza.IQPayload {
|
||||
var base64Photo string
|
||||
if info.Photo != nil {
|
||||
file, path, err := session.ForceOpenFile(info.Photo, 32)
|
||||
if err == nil {
|
||||
defer file.Close()
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
binval := base64.NewEncoder(base64.StdEncoding, buf)
|
||||
_, err = io.Copy(binval, file)
|
||||
binval.Close()
|
||||
if err == nil {
|
||||
base64Photo = buf.String()
|
||||
} else {
|
||||
log.Errorf("Error calculating base64: %v", path)
|
||||
}
|
||||
} else if path != "" {
|
||||
log.Errorf("Photo does not exist: %v", path)
|
||||
} else {
|
||||
log.Errorf("PHOTO: %#v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
if typ == TypeVCardTemp {
|
||||
vcard := &extensions.IqVcardTemp{}
|
||||
|
||||
vcard.Fn.Text = fn
|
||||
if photo != "" {
|
||||
vcard.Fn.Text = info.Fn
|
||||
if base64Photo != "" {
|
||||
vcard.Photo.Type.Text = "image/jpeg"
|
||||
vcard.Photo.Binval.Text = photo
|
||||
vcard.Photo.Binval.Text = base64Photo
|
||||
}
|
||||
vcard.Nickname.Text = nickname
|
||||
vcard.N.Given.Text = given
|
||||
vcard.N.Family.Text = family
|
||||
vcard.Tel.Number.Text = tel
|
||||
vcard.Desc.Text = info
|
||||
vcard.Nickname.Text = info.Nickname
|
||||
vcard.N.Given.Text = info.Given
|
||||
vcard.N.Family.Text = info.Family
|
||||
vcard.Tel.Number.Text = info.Tel
|
||||
vcard.Desc.Text = info.Info
|
||||
|
||||
return vcard
|
||||
} else if typ == TypeVCard4 {
|
||||
nodes := []stanza.Node{}
|
||||
if fn != "" {
|
||||
if info.Fn != "" {
|
||||
nodes = append(nodes, stanza.Node{
|
||||
XMLName: xml.Name{Local: "fn"},
|
||||
Nodes: []stanza.Node{
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "text"},
|
||||
Content: fn,
|
||||
Content: info.Fn,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if photo != "" {
|
||||
if base64Photo != "" {
|
||||
nodes = append(nodes, stanza.Node{
|
||||
XMLName: xml.Name{Local: "photo"},
|
||||
Nodes: []stanza.Node{
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "uri"},
|
||||
Content: "data:image/jpeg;base64," + photo,
|
||||
Content: "data:image/jpeg;base64," + base64Photo,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if nickname != "" {
|
||||
if info.Nickname != "" {
|
||||
nodes = append(nodes, stanza.Node{
|
||||
XMLName: xml.Name{Local: "nickname"},
|
||||
Nodes: []stanza.Node{
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "text"},
|
||||
Content: nickname,
|
||||
Content: info.Nickname,
|
||||
},
|
||||
},
|
||||
}, stanza.Node{
|
||||
|
@ -480,44 +470,44 @@ func makeVCardPayload(typ byte, id, fn, photo, nickname, given, family, tel, inf
|
|||
Nodes: []stanza.Node{
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "uri"},
|
||||
Content: "https://t.me/" + nickname,
|
||||
Content: "https://t.me/" + info.Nickname,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if family != "" || given != "" {
|
||||
if info.Family != "" || info.Given != "" {
|
||||
nodes = append(nodes, stanza.Node{
|
||||
XMLName: xml.Name{Local: "n"},
|
||||
Nodes: []stanza.Node{
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "surname"},
|
||||
Content: family,
|
||||
Content: info.Family,
|
||||
},
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "given"},
|
||||
Content: given,
|
||||
Content: info.Given,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if tel != "" {
|
||||
if info.Tel != "" {
|
||||
nodes = append(nodes, stanza.Node{
|
||||
XMLName: xml.Name{Local: "tel"},
|
||||
Nodes: []stanza.Node{
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "uri"},
|
||||
Content: "tel:" + tel,
|
||||
Content: "tel:" + info.Tel,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
if info != "" {
|
||||
if info.Info != "" {
|
||||
nodes = append(nodes, stanza.Node{
|
||||
XMLName: xml.Name{Local: "note"},
|
||||
Nodes: []stanza.Node{
|
||||
stanza.Node{
|
||||
XMLName: xml.Name{Local: "text"},
|
||||
Content: info,
|
||||
Content: info.Info,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue