aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--telegabber.go2
-rw-r--r--telegram/commands.go20
-rw-r--r--telegram/utils.go129
-rw-r--r--xmpp/handlers.go106
4 files changed, 177 insertions, 80 deletions
diff --git a/telegabber.go b/telegabber.go
index 72353bb..c8d6a8f 100644
--- a/telegabber.go
+++ b/telegabber.go
@@ -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
diff --git a/telegram/commands.go b/telegram/commands.go
index 2a72219..ec06f36 100644
--- a/telegram/commands.go
+++ b/telegram/commands.go
@@ -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
diff --git a/telegram/utils.go b/telegram/utils.go
index 9a247c4..851c6c1 100644
--- a/telegram/utils.go
+++ b/telegram/utils.go
@@ -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,18 +458,57 @@ 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
- // move
- err = os.Rename(src, dest)
- if err != nil {
- linkErr := err.(*os.LinkError)
- if linkErr.Err.Error() == "file exists" {
- log.Warn(err.Error())
+ 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("File moving error: %v", err)
+ log.Errorf("PHOTO: %#v", err.Error())
return "<ERROR>", ""
}
+ } else {
+ // move
+ err = os.Rename(src, dest)
+ if err != nil {
+ linkErr := err.(*os.LinkError)
+ if linkErr.Err.Error() == "file exists" {
+ log.Warn(err.Error())
+ } else {
+ log.Errorf("File moving error: %v", err)
+ 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)
}
}
- }
- if compact {
- return link, link
- } else {
- return fmt.Sprintf("%s (%v kbytes) | %s", filepath.Base(src), file.Size/1024, link), link
+ // copy or move should have succeeded at this point
+ gateway.CachedStorageSize += size64
}
+
+ 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
+}
diff --git a/xmpp/handlers.go b/xmpp/handlers.go
index db2b6ea..780478a 100644
--- a/xmpp/handlers.go
+++ b/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,
},
},
})