portal.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873
  1. // mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
  2. // Copyright (C) 2018 Tulir Asokan
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package main
  17. import (
  18. "bytes"
  19. "encoding/hex"
  20. "fmt"
  21. "image"
  22. "image/gif"
  23. "image/jpeg"
  24. "image/png"
  25. "math/rand"
  26. "mime"
  27. "net/http"
  28. "strings"
  29. "sync"
  30. "github.com/Rhymen/go-whatsapp"
  31. waProto "github.com/Rhymen/go-whatsapp/binary/proto"
  32. "maunium.net/go/gomatrix"
  33. log "maunium.net/go/maulogger"
  34. "maunium.net/go/mautrix-appservice"
  35. "maunium.net/go/mautrix-whatsapp/database"
  36. "maunium.net/go/mautrix-whatsapp/types"
  37. "maunium.net/go/mautrix-whatsapp/whatsapp-ext"
  38. )
  39. func (bridge *Bridge) GetPortalByMXID(mxid types.MatrixRoomID) *Portal {
  40. bridge.portalsLock.Lock()
  41. defer bridge.portalsLock.Unlock()
  42. portal, ok := bridge.portalsByMXID[mxid]
  43. if !ok {
  44. dbPortal := bridge.DB.Portal.GetByMXID(mxid)
  45. if dbPortal == nil {
  46. return nil
  47. }
  48. portal = bridge.NewPortal(dbPortal)
  49. bridge.portalsByJID[portal.Key] = portal
  50. if len(portal.MXID) > 0 {
  51. bridge.portalsByMXID[portal.MXID] = portal
  52. }
  53. }
  54. return portal
  55. }
  56. func (bridge *Bridge) GetPortalByJID(key database.PortalKey) *Portal {
  57. bridge.portalsLock.Lock()
  58. defer bridge.portalsLock.Unlock()
  59. portal, ok := bridge.portalsByJID[key]
  60. if !ok {
  61. dbPortal := bridge.DB.Portal.GetByJID(key)
  62. if dbPortal == nil {
  63. dbPortal = bridge.DB.Portal.New()
  64. dbPortal.Key = key
  65. dbPortal.Insert()
  66. }
  67. portal = bridge.NewPortal(dbPortal)
  68. bridge.portalsByJID[portal.Key] = portal
  69. if len(portal.MXID) > 0 {
  70. bridge.portalsByMXID[portal.MXID] = portal
  71. }
  72. }
  73. return portal
  74. }
  75. func (bridge *Bridge) GetAllPortals() []*Portal {
  76. bridge.portalsLock.Lock()
  77. defer bridge.portalsLock.Unlock()
  78. dbPortals := bridge.DB.Portal.GetAll()
  79. output := make([]*Portal, len(dbPortals))
  80. for index, dbPortal := range dbPortals {
  81. portal, ok := bridge.portalsByJID[dbPortal.Key]
  82. if !ok {
  83. portal = bridge.NewPortal(dbPortal)
  84. bridge.portalsByJID[portal.Key] = portal
  85. if len(dbPortal.MXID) > 0 {
  86. bridge.portalsByMXID[dbPortal.MXID] = portal
  87. }
  88. }
  89. output[index] = portal
  90. }
  91. return output
  92. }
  93. func (bridge *Bridge) NewPortal(dbPortal *database.Portal) *Portal {
  94. return &Portal{
  95. Portal: dbPortal,
  96. bridge: bridge,
  97. log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", dbPortal.Key)),
  98. messageLocks: make(map[types.WhatsAppMessageID]sync.Mutex),
  99. recentlyHandled: [20]types.WhatsAppMessageID{},
  100. }
  101. }
  102. type Portal struct {
  103. *database.Portal
  104. bridge *Bridge
  105. log log.Logger
  106. roomCreateLock sync.Mutex
  107. messageLocksLock sync.Mutex
  108. messageLocks map[types.WhatsAppMessageID]sync.Mutex
  109. recentlyHandled [20]types.WhatsAppMessageID
  110. recentlyHandledLock sync.Mutex
  111. recentlyHandledIndex uint8
  112. }
  113. func (portal *Portal) getMessageLock(messageID types.WhatsAppMessageID) sync.Mutex {
  114. portal.messageLocksLock.Lock()
  115. defer portal.messageLocksLock.Unlock()
  116. lock, ok := portal.messageLocks[messageID]
  117. if !ok {
  118. portal.messageLocks[messageID] = lock
  119. }
  120. return lock
  121. }
  122. func (portal *Portal) deleteMessageLock(messageID types.WhatsAppMessageID) {
  123. portal.messageLocksLock.Lock()
  124. delete(portal.messageLocks, messageID)
  125. portal.messageLocksLock.Unlock()
  126. }
  127. func (portal *Portal) isRecentlyHandled(id types.WhatsAppMessageID) bool {
  128. start := portal.recentlyHandledIndex
  129. for i := start; i != start; i = (i - 1) % 20 {
  130. if portal.recentlyHandled[i] == id {
  131. return true
  132. }
  133. }
  134. return false
  135. }
  136. func (portal *Portal) isDuplicate(id types.WhatsAppMessageID) bool {
  137. msg := portal.bridge.DB.Message.GetByJID(portal.Key, id)
  138. if msg != nil {
  139. return true
  140. }
  141. return false
  142. }
  143. func (portal *Portal) markHandled(jid types.WhatsAppMessageID, mxid types.MatrixEventID) {
  144. msg := portal.bridge.DB.Message.New()
  145. msg.Chat = portal.Key
  146. msg.JID = jid
  147. msg.MXID = mxid
  148. msg.Insert()
  149. portal.recentlyHandledLock.Lock()
  150. index := portal.recentlyHandledIndex
  151. portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % 20
  152. portal.recentlyHandledLock.Unlock()
  153. portal.recentlyHandled[index] = jid
  154. }
  155. func (portal *Portal) startHandling(id types.WhatsAppMessageID) (*sync.Mutex, bool) {
  156. if portal.isRecentlyHandled(id) {
  157. return nil, false
  158. }
  159. lock := portal.getMessageLock(id)
  160. lock.Lock()
  161. if portal.isDuplicate(id) {
  162. lock.Unlock()
  163. return nil, false
  164. }
  165. return &lock, true
  166. }
  167. func (portal *Portal) finishHandling(id types.WhatsAppMessageID, mxid types.MatrixEventID) {
  168. portal.markHandled(id, mxid)
  169. portal.deleteMessageLock(id)
  170. portal.log.Debugln("Handled message", id, "->", mxid)
  171. }
  172. func (portal *Portal) SyncParticipants(metadata *whatsappExt.GroupInfo) {
  173. changed := false
  174. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  175. if err != nil {
  176. levels = portal.GetBasePowerLevels()
  177. changed = true
  178. }
  179. for _, participant := range metadata.Participants {
  180. puppet := portal.bridge.GetPuppetByJID(participant.JID)
  181. puppet.Intent().EnsureJoined(portal.MXID)
  182. user := portal.bridge.GetUserByJID(participant.JID)
  183. if user != nil && !portal.bridge.AS.StateStore.IsInvited(portal.MXID, user.MXID) {
  184. portal.MainIntent().InviteUser(portal.MXID, &gomatrix.ReqInviteUser{
  185. UserID: user.MXID,
  186. })
  187. }
  188. expectedLevel := 0
  189. if participant.IsSuperAdmin {
  190. expectedLevel = 95
  191. } else if participant.IsAdmin {
  192. expectedLevel = 50
  193. }
  194. changed = levels.EnsureUserLevel(puppet.MXID, expectedLevel) || changed
  195. if user != nil {
  196. changed = levels.EnsureUserLevel(user.MXID, expectedLevel) || changed
  197. }
  198. }
  199. if changed {
  200. _, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  201. if err != nil {
  202. portal.log.Errorln("Failed to change power levels:", err)
  203. }
  204. }
  205. }
  206. func (portal *Portal) UpdateAvatar(user *User, avatar *whatsappExt.ProfilePicInfo) bool {
  207. if avatar == nil {
  208. var err error
  209. avatar, err = user.Conn.GetProfilePicThumb(portal.Key.JID)
  210. if err != nil {
  211. portal.log.Errorln(err)
  212. return false
  213. }
  214. }
  215. if portal.Avatar == avatar.Tag {
  216. return false
  217. }
  218. data, err := avatar.DownloadBytes()
  219. if err != nil {
  220. portal.log.Errorln("Failed to download avatar:", err)
  221. return false
  222. }
  223. mimeType := http.DetectContentType(data)
  224. resp, err := portal.MainIntent().UploadBytes(data, mimeType)
  225. if err != nil {
  226. portal.log.Errorln("Failed to upload avatar:", err)
  227. return false
  228. }
  229. _, err = portal.MainIntent().SetRoomAvatar(portal.MXID, resp.ContentURI)
  230. if err != nil {
  231. portal.log.Warnln("Failed to set room topic:", err)
  232. return false
  233. }
  234. portal.Avatar = avatar.Tag
  235. return true
  236. }
  237. func (portal *Portal) UpdateName(name string, setBy types.WhatsAppID) bool {
  238. if portal.Name != name {
  239. intent := portal.bridge.GetPuppetByJID(setBy).Intent()
  240. _, err := intent.SetRoomName(portal.MXID, name)
  241. if err == nil {
  242. portal.Name = name
  243. return true
  244. }
  245. portal.log.Warnln("Failed to set room name:", err)
  246. }
  247. return false
  248. }
  249. func (portal *Portal) UpdateTopic(topic string, setBy types.WhatsAppID) bool {
  250. if portal.Topic != topic {
  251. intent := portal.bridge.GetPuppetByJID(setBy).Intent()
  252. _, err := intent.SetRoomTopic(portal.MXID, topic)
  253. if err == nil {
  254. portal.Topic = topic
  255. return true
  256. }
  257. portal.log.Warnln("Failed to set room topic:", err)
  258. }
  259. return false
  260. }
  261. func (portal *Portal) UpdateMetadata(user *User) bool {
  262. metadata, err := user.Conn.GetGroupMetaData(portal.Key.JID)
  263. if err != nil {
  264. portal.log.Errorln(err)
  265. return false
  266. }
  267. portal.SyncParticipants(metadata)
  268. update := false
  269. update = portal.UpdateName(metadata.Name, metadata.NameSetBy) || update
  270. update = portal.UpdateTopic(metadata.Topic, metadata.TopicSetBy) || update
  271. return update
  272. }
  273. func (portal *Portal) Sync(user *User, contact whatsapp.Contact) {
  274. if portal.IsPrivateChat() {
  275. return
  276. }
  277. if len(portal.MXID) == 0 {
  278. portal.Name = contact.Name
  279. err := portal.CreateMatrixRoom([]string{user.MXID})
  280. if err != nil {
  281. portal.log.Errorln("Failed to create portal room:", err)
  282. return
  283. }
  284. } else {
  285. portal.MainIntent().EnsureInvited(portal.MXID, user.MXID)
  286. }
  287. update := false
  288. update = portal.UpdateMetadata(user) || update
  289. update = portal.UpdateAvatar(user, nil) || update
  290. if update {
  291. portal.Update()
  292. }
  293. }
  294. func (portal *Portal) GetBasePowerLevels() *gomatrix.PowerLevels {
  295. anyone := 0
  296. nope := 99
  297. return &gomatrix.PowerLevels{
  298. UsersDefault: anyone,
  299. EventsDefault: anyone,
  300. RedactPtr: &anyone,
  301. StateDefaultPtr: &nope,
  302. BanPtr: &nope,
  303. InvitePtr: &nope,
  304. Users: map[string]int{
  305. portal.MainIntent().UserID: 100,
  306. },
  307. Events: map[string]int{
  308. gomatrix.StateRoomName.Type: anyone,
  309. gomatrix.StateRoomAvatar.Type: anyone,
  310. gomatrix.StateTopic.Type: anyone,
  311. },
  312. }
  313. }
  314. func (portal *Portal) ChangeAdminStatus(jids []string, setAdmin bool) {
  315. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  316. if err != nil {
  317. levels = portal.GetBasePowerLevels()
  318. }
  319. newLevel := 0
  320. if setAdmin {
  321. newLevel = 50
  322. }
  323. changed := false
  324. for _, jid := range jids {
  325. puppet := portal.bridge.GetPuppetByJID(jid)
  326. changed = levels.EnsureUserLevel(puppet.MXID, newLevel) || changed
  327. user := portal.bridge.GetUserByJID(jid)
  328. if user != nil {
  329. changed = levels.EnsureUserLevel(user.MXID, newLevel) || changed
  330. }
  331. }
  332. if changed {
  333. _, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  334. if err != nil {
  335. portal.log.Errorln("Failed to change power levels:", err)
  336. }
  337. }
  338. }
  339. func (portal *Portal) RestrictMessageSending(restrict bool) {
  340. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  341. if err != nil {
  342. levels = portal.GetBasePowerLevels()
  343. }
  344. if restrict {
  345. levels.EventsDefault = 50
  346. } else {
  347. levels.EventsDefault = 0
  348. }
  349. _, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  350. if err != nil {
  351. portal.log.Errorln("Failed to change power levels:", err)
  352. }
  353. }
  354. func (portal *Portal) RestrictMetadataChanges(restrict bool) {
  355. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  356. if err != nil {
  357. levels = portal.GetBasePowerLevels()
  358. }
  359. newLevel := 0
  360. if restrict {
  361. newLevel = 50
  362. }
  363. changed := false
  364. changed = levels.EnsureEventLevel(gomatrix.StateRoomName, newLevel) || changed
  365. changed = levels.EnsureEventLevel(gomatrix.StateRoomAvatar, newLevel) || changed
  366. changed = levels.EnsureEventLevel(gomatrix.StateTopic, newLevel) || changed
  367. if changed {
  368. _, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  369. if err != nil {
  370. portal.log.Errorln("Failed to change power levels:", err)
  371. }
  372. }
  373. }
  374. func (portal *Portal) CreateMatrixRoom(invite []string) error {
  375. portal.roomCreateLock.Lock()
  376. defer portal.roomCreateLock.Unlock()
  377. if len(portal.MXID) > 0 {
  378. return nil
  379. }
  380. name := portal.Name
  381. topic := portal.Topic
  382. isPrivateChat := false
  383. if portal.IsPrivateChat() {
  384. name = ""
  385. topic = "WhatsApp private chat"
  386. isPrivateChat = true
  387. }
  388. resp, err := portal.MainIntent().CreateRoom(&gomatrix.ReqCreateRoom{
  389. Visibility: "private",
  390. Name: name,
  391. Topic: topic,
  392. Invite: invite,
  393. Preset: "private_chat",
  394. IsDirect: isPrivateChat,
  395. InitialState: []*gomatrix.Event{{
  396. Type: gomatrix.StatePowerLevels,
  397. Content: gomatrix.Content{
  398. PowerLevels: *portal.GetBasePowerLevels(),
  399. },
  400. }},
  401. })
  402. if err != nil {
  403. return err
  404. }
  405. portal.MXID = resp.RoomID
  406. portal.Update()
  407. return nil
  408. }
  409. func (portal *Portal) IsPrivateChat() bool {
  410. return strings.HasSuffix(portal.Key.JID, whatsappExt.NewUserSuffix)
  411. }
  412. func (portal *Portal) MainIntent() *appservice.IntentAPI {
  413. if portal.IsPrivateChat() {
  414. return portal.bridge.GetPuppetByJID(portal.Key.JID).Intent()
  415. }
  416. return portal.bridge.Bot
  417. }
  418. func (portal *Portal) GetMessageIntent(user *User, info whatsapp.MessageInfo) *appservice.IntentAPI {
  419. if info.FromMe {
  420. if portal.IsPrivateChat() {
  421. // TODO handle own messages in private chats properly
  422. return nil
  423. }
  424. return portal.bridge.GetPuppetByJID(user.JID).Intent()
  425. } else if portal.IsPrivateChat() {
  426. return portal.MainIntent()
  427. } else if len(info.SenderJid) == 0 {
  428. if len(info.Source.GetParticipant()) != 0 {
  429. info.SenderJid = info.Source.GetParticipant()
  430. } else {
  431. return nil
  432. }
  433. }
  434. return portal.bridge.GetPuppetByJID(info.SenderJid).Intent()
  435. }
  436. func (portal *Portal) SetReply(content *gomatrix.Content, info whatsapp.MessageInfo) {
  437. if len(info.QuotedMessageID) == 0 {
  438. return
  439. }
  440. message := portal.bridge.DB.Message.GetByJID(portal.Key, info.QuotedMessageID)
  441. if message != nil {
  442. event, err := portal.MainIntent().GetEvent(portal.MXID, message.MXID)
  443. if err != nil {
  444. portal.log.Warnln("Failed to get reply target:", err)
  445. return
  446. }
  447. content.SetReply(event)
  448. }
  449. return
  450. }
  451. func (portal *Portal) HandleTextMessage(source *User, message whatsapp.TextMessage) {
  452. lock, ok := portal.startHandling(message.Info.Id)
  453. if !ok {
  454. return
  455. }
  456. defer lock.Unlock()
  457. err := portal.CreateMatrixRoom([]string{source.MXID})
  458. if err != nil {
  459. portal.log.Errorln("Failed to create portal room:", err)
  460. return
  461. }
  462. intent := portal.GetMessageIntent(source, message.Info)
  463. if intent == nil {
  464. return
  465. }
  466. content := &gomatrix.Content{
  467. Body: message.Text,
  468. MsgType: gomatrix.MsgText,
  469. }
  470. portal.bridge.Formatter.ParseWhatsApp(content)
  471. portal.SetReply(content, message.Info)
  472. intent.UserTyping(portal.MXID, false, 0)
  473. resp, err := intent.SendMassagedMessageEvent(portal.MXID, gomatrix.EventMessage, content, int64(message.Info.Timestamp*1000))
  474. if err != nil {
  475. portal.log.Errorfln("Failed to handle message %s: %v", message.Info.Id, err)
  476. return
  477. }
  478. portal.finishHandling(message.Info.Id, resp.EventID)
  479. }
  480. func (portal *Portal) HandleMediaMessage(source *User, download func() ([]byte, error), thumbnail []byte, info whatsapp.MessageInfo, mimeType, caption string) {
  481. lock, ok := portal.startHandling(info.Id)
  482. if !ok {
  483. return
  484. }
  485. defer lock.Unlock()
  486. err := portal.CreateMatrixRoom([]string{source.MXID})
  487. if err != nil {
  488. portal.log.Errorln("Failed to create portal room:", err)
  489. return
  490. }
  491. intent := portal.GetMessageIntent(source, info)
  492. if intent == nil {
  493. return
  494. }
  495. data, err := download()
  496. if err != nil {
  497. portal.log.Errorln("Failed to download media:", err)
  498. return
  499. }
  500. uploaded, err := intent.UploadBytes(data, mimeType)
  501. if err != nil {
  502. portal.log.Errorln("Failed to upload media:", err)
  503. return
  504. }
  505. fileName := info.Id
  506. exts, _ := mime.ExtensionsByType(mimeType)
  507. if exts != nil && len(exts) > 0 {
  508. fileName += exts[0]
  509. }
  510. content := &gomatrix.Content{
  511. Body: fileName,
  512. URL: uploaded.ContentURI,
  513. Info: &gomatrix.FileInfo{
  514. Size: len(data),
  515. MimeType: mimeType,
  516. },
  517. }
  518. portal.SetReply(content, info)
  519. if thumbnail != nil {
  520. thumbnailMime := http.DetectContentType(thumbnail)
  521. uploadedThumbnail, _ := intent.UploadBytes(thumbnail, thumbnailMime)
  522. if uploadedThumbnail != nil {
  523. content.Info.ThumbnailURL = uploadedThumbnail.ContentURI
  524. cfg, _, _ := image.DecodeConfig(bytes.NewReader(data))
  525. content.Info.ThumbnailInfo = &gomatrix.FileInfo{
  526. Size: len(thumbnail),
  527. Width: cfg.Width,
  528. Height: cfg.Height,
  529. MimeType: thumbnailMime,
  530. }
  531. }
  532. }
  533. switch strings.ToLower(strings.Split(mimeType, "/")[0]) {
  534. case "image":
  535. content.MsgType = gomatrix.MsgImage
  536. cfg, _, _ := image.DecodeConfig(bytes.NewReader(data))
  537. content.Info.Width = cfg.Width
  538. content.Info.Height = cfg.Height
  539. case "video":
  540. content.MsgType = gomatrix.MsgVideo
  541. case "audio":
  542. content.MsgType = gomatrix.MsgAudio
  543. default:
  544. content.MsgType = gomatrix.MsgFile
  545. }
  546. intent.UserTyping(portal.MXID, false, 0)
  547. ts := int64(info.Timestamp * 1000)
  548. resp, err := intent.SendMassagedMessageEvent(portal.MXID, gomatrix.EventMessage, content, ts)
  549. if err != nil {
  550. portal.log.Errorfln("Failed to handle message %s: %v", info.Id, err)
  551. return
  552. }
  553. if len(caption) > 0 {
  554. captionContent := &gomatrix.Content{
  555. Body: caption,
  556. MsgType: gomatrix.MsgNotice,
  557. }
  558. portal.bridge.Formatter.ParseWhatsApp(captionContent)
  559. _, err := intent.SendMassagedMessageEvent(portal.MXID, gomatrix.EventMessage, captionContent, ts)
  560. if err != nil {
  561. portal.log.Warnfln("Failed to handle caption of message %s: %v", info.Id, err)
  562. }
  563. // TODO store caption mxid?
  564. }
  565. portal.finishHandling(info.Id, resp.EventID)
  566. }
  567. func makeMessageID() *string {
  568. b := make([]byte, 10)
  569. rand.Read(b)
  570. str := strings.ToUpper(hex.EncodeToString(b))
  571. return &str
  572. }
  573. func (portal *Portal) downloadThumbnail(evt *gomatrix.Event) []byte {
  574. if evt.Content.Info == nil || len(evt.Content.Info.ThumbnailURL) == 0 {
  575. return nil
  576. }
  577. thumbnail, err := portal.MainIntent().DownloadBytes(evt.Content.Info.ThumbnailURL)
  578. if err != nil {
  579. portal.log.Errorln("Failed to download thumbnail in %s: %v", evt.ID, err)
  580. return nil
  581. }
  582. thumbnailType := http.DetectContentType(thumbnail)
  583. var img image.Image
  584. switch thumbnailType {
  585. case "image/png":
  586. img, err = png.Decode(bytes.NewReader(thumbnail))
  587. case "image/gif":
  588. img, err = gif.Decode(bytes.NewReader(thumbnail))
  589. case "image/jpeg":
  590. return thumbnail
  591. default:
  592. return nil
  593. }
  594. var buf bytes.Buffer
  595. err = jpeg.Encode(&buf, img, &jpeg.Options{
  596. Quality: jpeg.DefaultQuality,
  597. })
  598. if err != nil {
  599. portal.log.Errorln("Failed to re-encode thumbnail in %s: %v", evt.ID, err)
  600. return nil
  601. }
  602. return buf.Bytes()
  603. }
  604. func (portal *Portal) preprocessMatrixMedia(sender *User, evt *gomatrix.Event, mediaType whatsapp.MediaType) *MediaUpload {
  605. if evt.Content.Info == nil {
  606. evt.Content.Info = &gomatrix.FileInfo{}
  607. }
  608. caption := evt.Content.Body
  609. exts, err := mime.ExtensionsByType(evt.Content.Info.MimeType)
  610. for _, ext := range exts {
  611. if strings.HasSuffix(caption, ext) {
  612. caption = ""
  613. break
  614. }
  615. }
  616. content, err := portal.MainIntent().DownloadBytes(evt.Content.URL)
  617. if err != nil {
  618. portal.log.Errorfln("Failed to download media in %s: %v", evt.ID, err)
  619. return nil
  620. }
  621. url, mediaKey, fileEncSHA256, fileSHA256, fileLength, err := sender.Conn.Upload(bytes.NewReader(content), mediaType)
  622. if err != nil {
  623. portal.log.Errorfln("Failed to upload media in %s: %v", evt.ID, err)
  624. return nil
  625. }
  626. return &MediaUpload{
  627. Caption: caption,
  628. URL: url,
  629. MediaKey: mediaKey,
  630. FileEncSHA256: fileEncSHA256,
  631. FileSHA256: fileSHA256,
  632. FileLength: fileLength,
  633. Thumbnail: portal.downloadThumbnail(evt),
  634. }
  635. }
  636. type MediaUpload struct {
  637. Caption string
  638. URL string
  639. MediaKey []byte
  640. FileEncSHA256 []byte
  641. FileSHA256 []byte
  642. FileLength uint64
  643. Thumbnail []byte
  644. }
  645. func (portal *Portal) GetMessage(user *User, jid types.WhatsAppMessageID) *waProto.WebMessageInfo {
  646. node, err := user.Conn.LoadMessagesBefore(portal.Key.JID, jid, 1)
  647. if err != nil {
  648. return nil
  649. }
  650. msgs, ok := node.Content.([]interface{})
  651. if !ok {
  652. return nil
  653. }
  654. msg, ok := msgs[0].(*waProto.WebMessageInfo)
  655. if !ok {
  656. return nil
  657. }
  658. node, err = user.Conn.LoadMessagesAfter(portal.Key.JID, msg.GetKey().GetId(), 1)
  659. if err != nil {
  660. return nil
  661. }
  662. msgs, ok = node.Content.([]interface{})
  663. if !ok {
  664. return nil
  665. }
  666. msg, _ = msgs[0].(*waProto.WebMessageInfo)
  667. return msg
  668. }
  669. func (portal *Portal) HandleMatrixMessage(sender *User, evt *gomatrix.Event) {
  670. if portal.IsPrivateChat() && sender.JID != portal.Key.Receiver {
  671. return
  672. }
  673. ts := uint64(evt.Timestamp / 1000)
  674. status := waProto.WebMessageInfo_ERROR
  675. fromMe := true
  676. info := &waProto.WebMessageInfo{
  677. Key: &waProto.MessageKey{
  678. FromMe: &fromMe,
  679. Id: makeMessageID(),
  680. RemoteJid: &portal.Key.JID,
  681. },
  682. MessageTimestamp: &ts,
  683. Message: &waProto.Message{},
  684. Status: &status,
  685. }
  686. ctxInfo := &waProto.ContextInfo{}
  687. replyToID := evt.Content.GetReplyTo()
  688. if len(replyToID) > 0 {
  689. evt.Content.RemoveReplyFallback()
  690. msg := portal.bridge.DB.Message.GetByMXID(replyToID)
  691. if msg != nil {
  692. origMsg := portal.GetMessage(sender, msg.JID)
  693. if origMsg != nil {
  694. ctxInfo.StanzaId = &msg.JID
  695. replyMsgSender := origMsg.GetParticipant()
  696. if origMsg.GetKey().GetFromMe() {
  697. replyMsgSender = sender.JID
  698. }
  699. ctxInfo.Participant = &replyMsgSender
  700. ctxInfo.QuotedMessage = []*waProto.Message{origMsg.Message}
  701. }
  702. }
  703. }
  704. var err error
  705. switch evt.Content.MsgType {
  706. case gomatrix.MsgText, gomatrix.MsgEmote:
  707. text := evt.Content.Body
  708. if evt.Content.Format == gomatrix.FormatHTML {
  709. text = portal.bridge.Formatter.ParseMatrix(evt.Content.FormattedBody)
  710. }
  711. if evt.Content.MsgType == gomatrix.MsgEmote {
  712. text = "/me " + text
  713. }
  714. ctxInfo.MentionedJid = mentionRegex.FindAllString(text, -1)
  715. for index, mention := range ctxInfo.MentionedJid {
  716. ctxInfo.MentionedJid[index] = mention[1:] + whatsappExt.NewUserSuffix
  717. }
  718. if ctxInfo.StanzaId != nil || ctxInfo.MentionedJid != nil {
  719. info.Message.ExtendedTextMessage = &waProto.ExtendedTextMessage{
  720. Text: &text,
  721. ContextInfo: ctxInfo,
  722. }
  723. } else {
  724. info.Message.Conversation = &text
  725. }
  726. case gomatrix.MsgImage:
  727. media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaImage)
  728. if media == nil {
  729. return
  730. }
  731. info.Message.ImageMessage = &waProto.ImageMessage{
  732. Caption: &media.Caption,
  733. JpegThumbnail: media.Thumbnail,
  734. Url: &media.URL,
  735. MediaKey: media.MediaKey,
  736. Mimetype: &evt.Content.GetInfo().MimeType,
  737. FileEncSha256: media.FileEncSHA256,
  738. FileSha256: media.FileSHA256,
  739. FileLength: &media.FileLength,
  740. }
  741. case gomatrix.MsgVideo:
  742. media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaVideo)
  743. if media == nil {
  744. return
  745. }
  746. duration := uint32(evt.Content.GetInfo().Duration)
  747. info.Message.VideoMessage = &waProto.VideoMessage{
  748. Caption: &media.Caption,
  749. JpegThumbnail: media.Thumbnail,
  750. Url: &media.URL,
  751. MediaKey: media.MediaKey,
  752. Mimetype: &evt.Content.GetInfo().MimeType,
  753. Seconds: &duration,
  754. FileEncSha256: media.FileEncSHA256,
  755. FileSha256: media.FileSHA256,
  756. FileLength: &media.FileLength,
  757. }
  758. case gomatrix.MsgAudio:
  759. media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaAudio)
  760. if media == nil {
  761. return
  762. }
  763. duration := uint32(evt.Content.GetInfo().Duration)
  764. info.Message.AudioMessage = &waProto.AudioMessage{
  765. Url: &media.URL,
  766. MediaKey: media.MediaKey,
  767. Mimetype: &evt.Content.GetInfo().MimeType,
  768. Seconds: &duration,
  769. FileEncSha256: media.FileEncSHA256,
  770. FileSha256: media.FileSHA256,
  771. FileLength: &media.FileLength,
  772. }
  773. case gomatrix.MsgFile:
  774. media := portal.preprocessMatrixMedia(sender, evt, whatsapp.MediaDocument)
  775. if media == nil {
  776. return
  777. }
  778. info.Message.DocumentMessage = &waProto.DocumentMessage{
  779. Url: &media.URL,
  780. MediaKey: media.MediaKey,
  781. Mimetype: &evt.Content.GetInfo().MimeType,
  782. FileEncSha256: media.FileEncSHA256,
  783. FileSha256: media.FileSHA256,
  784. FileLength: &media.FileLength,
  785. }
  786. default:
  787. portal.log.Debugln("Unhandled Matrix event:", evt)
  788. return
  789. }
  790. portal.markHandled(info.GetKey().GetId(), evt.ID)
  791. err = sender.Conn.Send(info)
  792. if err != nil {
  793. portal.log.Errorfln("Error handling Matrix event %s: %v", evt.ID, err)
  794. } else {
  795. portal.log.Debugln("Handled Matrix event:", evt)
  796. }
  797. }