portal.go 108 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256
  1. // mautrix-whatsapp - A Matrix-WhatsApp puppeting bridge.
  2. // Copyright (C) 2021 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. "context"
  20. "encoding/json"
  21. "errors"
  22. "fmt"
  23. "html"
  24. "image"
  25. _ "image/gif"
  26. "image/jpeg"
  27. "image/png"
  28. "math"
  29. "mime"
  30. "net/http"
  31. "strconv"
  32. "strings"
  33. "sync"
  34. "time"
  35. "github.com/tidwall/gjson"
  36. "golang.org/x/image/draw"
  37. "golang.org/x/image/webp"
  38. "google.golang.org/protobuf/proto"
  39. log "maunium.net/go/maulogger/v2"
  40. "maunium.net/go/mautrix"
  41. "maunium.net/go/mautrix/appservice"
  42. "maunium.net/go/mautrix/crypto/attachment"
  43. "maunium.net/go/mautrix/event"
  44. "maunium.net/go/mautrix/format"
  45. "maunium.net/go/mautrix/id"
  46. "maunium.net/go/mautrix/util"
  47. "maunium.net/go/mautrix/util/ffmpeg"
  48. "maunium.net/go/mautrix/util/variationselector"
  49. "go.mau.fi/whatsmeow"
  50. waProto "go.mau.fi/whatsmeow/binary/proto"
  51. "go.mau.fi/whatsmeow/types"
  52. "go.mau.fi/whatsmeow/types/events"
  53. "maunium.net/go/mautrix-whatsapp/database"
  54. )
  55. const StatusBroadcastTopic = "WhatsApp status updates from your contacts"
  56. const StatusBroadcastName = "WhatsApp Status Broadcast"
  57. const BroadcastTopic = "WhatsApp broadcast list"
  58. const UnnamedBroadcastName = "Unnamed broadcast list"
  59. const PrivateChatTopic = "WhatsApp private chat"
  60. var ErrStatusBroadcastDisabled = errors.New("status bridging is disabled")
  61. func (bridge *Bridge) GetPortalByMXID(mxid id.RoomID) *Portal {
  62. bridge.portalsLock.Lock()
  63. defer bridge.portalsLock.Unlock()
  64. portal, ok := bridge.portalsByMXID[mxid]
  65. if !ok {
  66. return bridge.loadDBPortal(bridge.DB.Portal.GetByMXID(mxid), nil)
  67. }
  68. return portal
  69. }
  70. func (bridge *Bridge) GetPortalByJID(key database.PortalKey) *Portal {
  71. bridge.portalsLock.Lock()
  72. defer bridge.portalsLock.Unlock()
  73. portal, ok := bridge.portalsByJID[key]
  74. if !ok {
  75. return bridge.loadDBPortal(bridge.DB.Portal.GetByJID(key), &key)
  76. }
  77. return portal
  78. }
  79. func (bridge *Bridge) GetAllPortals() []*Portal {
  80. return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAll())
  81. }
  82. func (bridge *Bridge) GetAllPortalsForUser(userID id.UserID) []*Portal {
  83. return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAllForUser(userID))
  84. }
  85. func (bridge *Bridge) GetAllPortalsByJID(jid types.JID) []*Portal {
  86. return bridge.dbPortalsToPortals(bridge.DB.Portal.GetAllByJID(jid))
  87. }
  88. func (bridge *Bridge) dbPortalsToPortals(dbPortals []*database.Portal) []*Portal {
  89. bridge.portalsLock.Lock()
  90. defer bridge.portalsLock.Unlock()
  91. output := make([]*Portal, len(dbPortals))
  92. for index, dbPortal := range dbPortals {
  93. if dbPortal == nil {
  94. continue
  95. }
  96. portal, ok := bridge.portalsByJID[dbPortal.Key]
  97. if !ok {
  98. portal = bridge.loadDBPortal(dbPortal, nil)
  99. }
  100. output[index] = portal
  101. }
  102. return output
  103. }
  104. func (bridge *Bridge) loadDBPortal(dbPortal *database.Portal, key *database.PortalKey) *Portal {
  105. if dbPortal == nil {
  106. if key == nil {
  107. return nil
  108. }
  109. dbPortal = bridge.DB.Portal.New()
  110. dbPortal.Key = *key
  111. dbPortal.Insert()
  112. }
  113. portal := bridge.NewPortal(dbPortal)
  114. bridge.portalsByJID[portal.Key] = portal
  115. if len(portal.MXID) > 0 {
  116. bridge.portalsByMXID[portal.MXID] = portal
  117. }
  118. return portal
  119. }
  120. func (portal *Portal) GetUsers() []*User {
  121. return nil
  122. }
  123. func (bridge *Bridge) newBlankPortal(key database.PortalKey) *Portal {
  124. portal := &Portal{
  125. bridge: bridge,
  126. log: bridge.Log.Sub(fmt.Sprintf("Portal/%s", key)),
  127. messages: make(chan PortalMessage, bridge.Config.Bridge.PortalMessageBuffer),
  128. receipts: make(chan PortalReceipt, bridge.Config.Bridge.PortalMessageBuffer),
  129. matrixMessages: make(chan PortalMatrixMessage, bridge.Config.Bridge.PortalMessageBuffer),
  130. mediaRetries: make(chan PortalMediaRetry, bridge.Config.Bridge.PortalMessageBuffer),
  131. mediaErrorCache: make(map[types.MessageID]*FailedMediaMeta),
  132. }
  133. go portal.handleMessageLoop()
  134. return portal
  135. }
  136. func (bridge *Bridge) NewManualPortal(key database.PortalKey) *Portal {
  137. portal := bridge.newBlankPortal(key)
  138. portal.Portal = bridge.DB.Portal.New()
  139. portal.Key = key
  140. return portal
  141. }
  142. func (bridge *Bridge) NewPortal(dbPortal *database.Portal) *Portal {
  143. portal := bridge.newBlankPortal(dbPortal.Key)
  144. portal.Portal = dbPortal
  145. return portal
  146. }
  147. const recentlyHandledLength = 100
  148. type fakeMessage struct {
  149. Sender types.JID
  150. Text string
  151. ID string
  152. Time time.Time
  153. Important bool
  154. }
  155. type PortalMessage struct {
  156. evt *events.Message
  157. undecryptable *events.UndecryptableMessage
  158. fake *fakeMessage
  159. source *User
  160. }
  161. type PortalReceipt struct {
  162. evt *events.Receipt
  163. source *User
  164. }
  165. type PortalMatrixMessage struct {
  166. evt *event.Event
  167. user *User
  168. }
  169. type PortalMediaRetry struct {
  170. evt *events.MediaRetry
  171. source *User
  172. }
  173. type recentlyHandledWrapper struct {
  174. id types.MessageID
  175. err database.MessageErrorType
  176. }
  177. type Portal struct {
  178. *database.Portal
  179. bridge *Bridge
  180. log log.Logger
  181. roomCreateLock sync.Mutex
  182. encryptLock sync.Mutex
  183. backfillLock sync.Mutex
  184. avatarLock sync.Mutex
  185. recentlyHandled [recentlyHandledLength]recentlyHandledWrapper
  186. recentlyHandledLock sync.Mutex
  187. recentlyHandledIndex uint8
  188. privateChatBackfillInvitePuppet func()
  189. currentlyTyping []id.UserID
  190. currentlyTypingLock sync.Mutex
  191. messages chan PortalMessage
  192. receipts chan PortalReceipt
  193. matrixMessages chan PortalMatrixMessage
  194. mediaRetries chan PortalMediaRetry
  195. mediaErrorCache map[types.MessageID]*FailedMediaMeta
  196. relayUser *User
  197. }
  198. func (portal *Portal) handleMessageLoopItem(msg PortalMessage) {
  199. if len(portal.MXID) == 0 {
  200. if msg.fake == nil && msg.undecryptable == nil && (msg.evt == nil || !containsSupportedMessage(msg.evt.Message)) {
  201. portal.log.Debugln("Not creating portal room for incoming message: message is not a chat message")
  202. return
  203. }
  204. portal.log.Debugln("Creating Matrix room from incoming message")
  205. err := portal.CreateMatrixRoom(msg.source, nil, false, true)
  206. if err != nil {
  207. portal.log.Errorln("Failed to create portal room:", err)
  208. return
  209. }
  210. }
  211. if msg.evt != nil {
  212. portal.handleMessage(msg.source, msg.evt)
  213. } else if msg.undecryptable != nil {
  214. portal.handleUndecryptableMessage(msg.source, msg.undecryptable)
  215. } else if msg.fake != nil {
  216. msg.fake.ID = "FAKE::" + msg.fake.ID
  217. portal.handleFakeMessage(*msg.fake)
  218. } else {
  219. portal.log.Warnln("Unexpected PortalMessage with no message: %+v", msg)
  220. }
  221. }
  222. func (portal *Portal) handleMatrixMessageLoopItem(msg PortalMatrixMessage) {
  223. portal.HandleMatrixReadReceipt(msg.user, "", time.UnixMilli(msg.evt.Timestamp), false)
  224. switch msg.evt.Type {
  225. case event.EventMessage, event.EventSticker:
  226. portal.HandleMatrixMessage(msg.user, msg.evt)
  227. case event.EventRedaction:
  228. portal.HandleMatrixRedaction(msg.user, msg.evt)
  229. default:
  230. portal.log.Warnln("Unsupported event type %+v in portal message channel", msg.evt.Type)
  231. }
  232. }
  233. func (portal *Portal) handleReceipt(receipt *events.Receipt, source *User) {
  234. // The order of the message ID array depends on the sender's platform, so we just have to find
  235. // the last message based on timestamp. Also, timestamps only have second precision, so if
  236. // there are many messages at the same second just mark them all as read, because we don't
  237. // know which one is last
  238. markAsRead := make([]*database.Message, 0, 1)
  239. var bestTimestamp time.Time
  240. for _, msgID := range receipt.MessageIDs {
  241. msg := portal.bridge.DB.Message.GetByJID(portal.Key, msgID)
  242. if msg == nil || msg.IsFakeMXID() {
  243. continue
  244. }
  245. if msg.Timestamp.After(bestTimestamp) {
  246. bestTimestamp = msg.Timestamp
  247. markAsRead = append(markAsRead[:0], msg)
  248. } else if msg != nil && msg.Timestamp.Equal(bestTimestamp) {
  249. markAsRead = append(markAsRead, msg)
  250. }
  251. }
  252. if receipt.Sender.User == source.JID.User {
  253. if len(markAsRead) > 0 {
  254. source.SetLastReadTS(portal.Key, markAsRead[0].Timestamp)
  255. } else {
  256. source.SetLastReadTS(portal.Key, receipt.Timestamp)
  257. }
  258. }
  259. intent := portal.bridge.GetPuppetByJID(receipt.Sender).IntentFor(portal)
  260. for _, msg := range markAsRead {
  261. err := intent.SetReadMarkers(portal.MXID, makeReadMarkerContent(msg.MXID, intent.IsCustomPuppet))
  262. if err != nil {
  263. portal.log.Warnfln("Failed to mark message %s as read by %s: %v", msg.MXID, intent.UserID, err)
  264. } else {
  265. portal.log.Debugfln("Marked %s as read by %s", msg.MXID, intent.UserID)
  266. }
  267. }
  268. }
  269. func (portal *Portal) handleMessageLoop() {
  270. for {
  271. select {
  272. case msg := <-portal.messages:
  273. portal.handleMessageLoopItem(msg)
  274. case receipt := <-portal.receipts:
  275. portal.handleReceipt(receipt.evt, receipt.source)
  276. case msg := <-portal.matrixMessages:
  277. portal.handleMatrixMessageLoopItem(msg)
  278. case retry := <-portal.mediaRetries:
  279. portal.handleMediaRetry(retry.evt, retry.source)
  280. }
  281. }
  282. }
  283. func containsSupportedMessage(waMsg *waProto.Message) bool {
  284. if waMsg == nil {
  285. return false
  286. }
  287. return waMsg.Conversation != nil || waMsg.ExtendedTextMessage != nil || waMsg.ImageMessage != nil ||
  288. waMsg.StickerMessage != nil || waMsg.AudioMessage != nil || waMsg.VideoMessage != nil ||
  289. waMsg.DocumentMessage != nil || waMsg.ContactMessage != nil || waMsg.LocationMessage != nil ||
  290. waMsg.LiveLocationMessage != nil || waMsg.GroupInviteMessage != nil || waMsg.ContactsArrayMessage != nil
  291. }
  292. func getMessageType(waMsg *waProto.Message) string {
  293. switch {
  294. case waMsg == nil:
  295. return "ignore"
  296. case waMsg.Conversation != nil, waMsg.ExtendedTextMessage != nil:
  297. return "text"
  298. case waMsg.ImageMessage != nil:
  299. return fmt.Sprintf("image %s", waMsg.GetImageMessage().GetMimetype())
  300. case waMsg.StickerMessage != nil:
  301. return fmt.Sprintf("sticker %s", waMsg.GetStickerMessage().GetMimetype())
  302. case waMsg.VideoMessage != nil:
  303. return fmt.Sprintf("video %s", waMsg.GetVideoMessage().GetMimetype())
  304. case waMsg.AudioMessage != nil:
  305. return fmt.Sprintf("audio %s", waMsg.GetAudioMessage().GetMimetype())
  306. case waMsg.DocumentMessage != nil:
  307. return fmt.Sprintf("document %s", waMsg.GetDocumentMessage().GetMimetype())
  308. case waMsg.ContactMessage != nil:
  309. return "contact"
  310. case waMsg.ContactsArrayMessage != nil:
  311. return "contact array"
  312. case waMsg.LocationMessage != nil:
  313. return "location"
  314. case waMsg.LiveLocationMessage != nil:
  315. return "live location start"
  316. case waMsg.GroupInviteMessage != nil:
  317. return "group invite"
  318. case waMsg.ReactionMessage != nil:
  319. return "reaction"
  320. case waMsg.ProtocolMessage != nil:
  321. switch waMsg.GetProtocolMessage().GetType() {
  322. case waProto.ProtocolMessage_REVOKE:
  323. if waMsg.GetProtocolMessage().GetKey() == nil {
  324. return "ignore"
  325. }
  326. return "revoke"
  327. case waProto.ProtocolMessage_EPHEMERAL_SETTING:
  328. return "disappearing timer change"
  329. case waProto.ProtocolMessage_APP_STATE_SYNC_KEY_SHARE, waProto.ProtocolMessage_HISTORY_SYNC_NOTIFICATION, waProto.ProtocolMessage_INITIAL_SECURITY_NOTIFICATION_SETTING_SYNC:
  330. return "ignore"
  331. default:
  332. return fmt.Sprintf("unknown_protocol_%d", waMsg.GetProtocolMessage().GetType())
  333. }
  334. case waMsg.ButtonsMessage != nil:
  335. return "buttons"
  336. case waMsg.ButtonsResponseMessage != nil:
  337. return "buttons response"
  338. case waMsg.TemplateMessage != nil:
  339. return "template"
  340. case waMsg.HighlyStructuredMessage != nil:
  341. return "highly structured template"
  342. case waMsg.TemplateButtonReplyMessage != nil:
  343. return "template button reply"
  344. case waMsg.InteractiveMessage != nil:
  345. return "interactive"
  346. case waMsg.ListMessage != nil:
  347. return "list"
  348. case waMsg.ProductMessage != nil:
  349. return "product"
  350. case waMsg.ListResponseMessage != nil:
  351. return "list response"
  352. case waMsg.OrderMessage != nil:
  353. return "order"
  354. case waMsg.InvoiceMessage != nil:
  355. return "invoice"
  356. case waMsg.SendPaymentMessage != nil, waMsg.RequestPaymentMessage != nil,
  357. waMsg.DeclinePaymentRequestMessage != nil, waMsg.CancelPaymentRequestMessage != nil,
  358. waMsg.PaymentInviteMessage != nil:
  359. return "payment"
  360. case waMsg.Call != nil:
  361. return "call"
  362. case waMsg.Chat != nil:
  363. return "chat"
  364. case waMsg.SenderKeyDistributionMessage != nil, waMsg.StickerSyncRmrMessage != nil:
  365. return "ignore"
  366. default:
  367. return "unknown"
  368. }
  369. }
  370. func pluralUnit(val int, name string) string {
  371. if val == 1 {
  372. return fmt.Sprintf("%d %s", val, name)
  373. } else if val == 0 {
  374. return ""
  375. }
  376. return fmt.Sprintf("%d %ss", val, name)
  377. }
  378. func naturalJoin(parts []string) string {
  379. if len(parts) == 0 {
  380. return ""
  381. } else if len(parts) == 1 {
  382. return parts[0]
  383. } else if len(parts) == 2 {
  384. return fmt.Sprintf("%s and %s", parts[0], parts[1])
  385. } else {
  386. return fmt.Sprintf("%s and %s", strings.Join(parts[:len(parts)-1], ", "), parts[len(parts)-1])
  387. }
  388. }
  389. func formatDuration(d time.Duration) string {
  390. const Day = time.Hour * 24
  391. var days, hours, minutes, seconds int
  392. days, d = int(d/Day), d%Day
  393. hours, d = int(d/time.Hour), d%time.Hour
  394. minutes, d = int(d/time.Minute), d%time.Minute
  395. seconds = int(d / time.Second)
  396. parts := make([]string, 0, 4)
  397. if days > 0 {
  398. parts = append(parts, pluralUnit(days, "day"))
  399. }
  400. if hours > 0 {
  401. parts = append(parts, pluralUnit(hours, "hour"))
  402. }
  403. if minutes > 0 {
  404. parts = append(parts, pluralUnit(seconds, "minute"))
  405. }
  406. if seconds > 0 {
  407. parts = append(parts, pluralUnit(seconds, "second"))
  408. }
  409. return naturalJoin(parts)
  410. }
  411. func (portal *Portal) convertMessage(intent *appservice.IntentAPI, source *User, info *types.MessageInfo, waMsg *waProto.Message) *ConvertedMessage {
  412. switch {
  413. case waMsg.Conversation != nil || waMsg.ExtendedTextMessage != nil:
  414. return portal.convertTextMessage(intent, source, waMsg)
  415. case waMsg.ImageMessage != nil:
  416. return portal.convertMediaMessage(intent, source, info, waMsg.GetImageMessage())
  417. case waMsg.StickerMessage != nil:
  418. return portal.convertMediaMessage(intent, source, info, waMsg.GetStickerMessage())
  419. case waMsg.VideoMessage != nil:
  420. return portal.convertMediaMessage(intent, source, info, waMsg.GetVideoMessage())
  421. case waMsg.AudioMessage != nil:
  422. return portal.convertMediaMessage(intent, source, info, waMsg.GetAudioMessage())
  423. case waMsg.DocumentMessage != nil:
  424. return portal.convertMediaMessage(intent, source, info, waMsg.GetDocumentMessage())
  425. case waMsg.ContactMessage != nil:
  426. return portal.convertContactMessage(intent, waMsg.GetContactMessage())
  427. case waMsg.ContactsArrayMessage != nil:
  428. return portal.convertContactsArrayMessage(intent, waMsg.GetContactsArrayMessage())
  429. case waMsg.LocationMessage != nil:
  430. return portal.convertLocationMessage(intent, waMsg.GetLocationMessage())
  431. case waMsg.LiveLocationMessage != nil:
  432. return portal.convertLiveLocationMessage(intent, waMsg.GetLiveLocationMessage())
  433. case waMsg.GroupInviteMessage != nil:
  434. return portal.convertGroupInviteMessage(intent, info, waMsg.GetGroupInviteMessage())
  435. case waMsg.ProtocolMessage != nil && waMsg.ProtocolMessage.GetType() == waProto.ProtocolMessage_EPHEMERAL_SETTING:
  436. portal.ExpirationTime = waMsg.ProtocolMessage.GetEphemeralExpiration()
  437. portal.Update()
  438. return &ConvertedMessage{
  439. Intent: intent,
  440. Type: event.EventMessage,
  441. Content: &event.MessageEventContent{
  442. Body: portal.formatDisappearingMessageNotice(),
  443. MsgType: event.MsgNotice,
  444. },
  445. }
  446. default:
  447. return nil
  448. }
  449. }
  450. func (portal *Portal) UpdateGroupDisappearingMessages(sender *types.JID, timestamp time.Time, timer uint32) {
  451. portal.ExpirationTime = timer
  452. portal.Update()
  453. intent := portal.MainIntent()
  454. if sender != nil {
  455. intent = portal.bridge.GetPuppetByJID(sender.ToNonAD()).IntentFor(portal)
  456. } else {
  457. sender = &types.EmptyJID
  458. }
  459. _, err := portal.sendMessage(intent, event.EventMessage, &event.MessageEventContent{
  460. Body: portal.formatDisappearingMessageNotice(),
  461. MsgType: event.MsgNotice,
  462. }, nil, timestamp.UnixMilli())
  463. if err != nil {
  464. portal.log.Warnfln("Failed to notify portal about disappearing message timer change by %s to %d", *sender, timer)
  465. }
  466. }
  467. func (portal *Portal) formatDisappearingMessageNotice() string {
  468. if portal.ExpirationTime == 0 {
  469. return "Turned off disappearing messages"
  470. } else {
  471. msg := fmt.Sprintf("Set the disappearing message timer to %s", formatDuration(time.Duration(portal.ExpirationTime)*time.Second))
  472. if !portal.bridge.Config.Bridge.DisappearingMessagesInGroups && portal.IsGroupChat() {
  473. msg += ". However, this bridge is not configured to disappear messages in group chats."
  474. }
  475. return msg
  476. }
  477. }
  478. const UndecryptableMessageNotice = "Decrypting message from WhatsApp failed, waiting for sender to re-send... " +
  479. "([learn more](https://faq.whatsapp.com/general/security-and-privacy/seeing-waiting-for-this-message-this-may-take-a-while))"
  480. var undecryptableMessageContent event.MessageEventContent
  481. func init() {
  482. undecryptableMessageContent = format.RenderMarkdown(UndecryptableMessageNotice, true, false)
  483. undecryptableMessageContent.MsgType = event.MsgNotice
  484. }
  485. func (portal *Portal) handleUndecryptableMessage(source *User, evt *events.UndecryptableMessage) {
  486. if len(portal.MXID) == 0 {
  487. portal.log.Warnln("handleUndecryptableMessage called even though portal.MXID is empty")
  488. return
  489. } else if portal.isRecentlyHandled(evt.Info.ID, database.MsgErrDecryptionFailed) {
  490. portal.log.Debugfln("Not handling %s (undecryptable): message was recently handled", evt.Info.ID)
  491. return
  492. } else if existingMsg := portal.bridge.DB.Message.GetByJID(portal.Key, evt.Info.ID); existingMsg != nil {
  493. portal.log.Debugfln("Not handling %s (undecryptable): message is duplicate", evt.Info.ID)
  494. return
  495. }
  496. intent := portal.getMessageIntent(source, &evt.Info)
  497. if !intent.IsCustomPuppet && portal.IsPrivateChat() && evt.Info.Sender.User == portal.Key.Receiver.User {
  498. portal.log.Debugfln("Not handling %s (undecryptable): user doesn't have double puppeting enabled", evt.Info.ID)
  499. return
  500. }
  501. content := undecryptableMessageContent
  502. resp, err := portal.sendMessage(intent, event.EventMessage, &content, nil, evt.Info.Timestamp.UnixMilli())
  503. if err != nil {
  504. portal.log.Errorln("Failed to send decryption error of %s to Matrix: %v", evt.Info.ID, err)
  505. return
  506. }
  507. portal.finishHandling(nil, &evt.Info, resp.EventID, database.MsgUnknown, database.MsgErrDecryptionFailed)
  508. }
  509. func (portal *Portal) handleFakeMessage(msg fakeMessage) {
  510. if portal.isRecentlyHandled(msg.ID, database.MsgNoError) {
  511. portal.log.Debugfln("Not handling %s (fake): message was recently handled", msg.ID)
  512. return
  513. } else if existingMsg := portal.bridge.DB.Message.GetByJID(portal.Key, msg.ID); existingMsg != nil {
  514. portal.log.Debugfln("Not handling %s (fake): message is duplicate", msg.ID)
  515. return
  516. }
  517. intent := portal.bridge.GetPuppetByJID(msg.Sender).IntentFor(portal)
  518. if !intent.IsCustomPuppet && portal.IsPrivateChat() && msg.Sender.User == portal.Key.Receiver.User {
  519. portal.log.Debugfln("Not handling %s (fake): user doesn't have double puppeting enabled", msg.ID)
  520. return
  521. }
  522. msgType := event.MsgNotice
  523. if msg.Important {
  524. msgType = event.MsgText
  525. }
  526. resp, err := portal.sendMessage(intent, event.EventMessage, &event.MessageEventContent{
  527. MsgType: msgType,
  528. Body: msg.Text,
  529. }, nil, msg.Time.UnixMilli())
  530. if err != nil {
  531. portal.log.Errorfln("Failed to send %s to Matrix: %v", msg.ID, err)
  532. } else {
  533. portal.finishHandling(nil, &types.MessageInfo{
  534. ID: msg.ID,
  535. Timestamp: msg.Time,
  536. MessageSource: types.MessageSource{
  537. Sender: msg.Sender,
  538. },
  539. }, resp.EventID, database.MsgFake, database.MsgNoError)
  540. }
  541. }
  542. func (portal *Portal) handleMessage(source *User, evt *events.Message) {
  543. if len(portal.MXID) == 0 {
  544. portal.log.Warnln("handleMessage called even though portal.MXID is empty")
  545. return
  546. }
  547. msgID := evt.Info.ID
  548. msgType := getMessageType(evt.Message)
  549. if msgType == "ignore" {
  550. return
  551. } else if portal.isRecentlyHandled(msgID, database.MsgNoError) {
  552. portal.log.Debugfln("Not handling %s (%s): message was recently handled", msgID, msgType)
  553. return
  554. }
  555. existingMsg := portal.bridge.DB.Message.GetByJID(portal.Key, msgID)
  556. if existingMsg != nil {
  557. if existingMsg.Error == database.MsgErrDecryptionFailed {
  558. portal.log.Debugfln("Got decryptable version of previously undecryptable message %s (%s)", msgID, msgType)
  559. } else {
  560. portal.log.Debugfln("Not handling %s (%s): message is duplicate", msgID, msgType)
  561. return
  562. }
  563. }
  564. intent := portal.getMessageIntent(source, &evt.Info)
  565. if !intent.IsCustomPuppet && portal.IsPrivateChat() && evt.Info.Sender.User == portal.Key.Receiver.User {
  566. portal.log.Debugfln("Not handling %s (%s): user doesn't have double puppeting enabled", msgID, msgType)
  567. return
  568. }
  569. converted := portal.convertMessage(intent, source, &evt.Info, evt.Message)
  570. if converted != nil {
  571. if evt.Info.IsIncomingBroadcast() {
  572. if converted.Extra == nil {
  573. converted.Extra = map[string]interface{}{}
  574. }
  575. converted.Extra["fi.mau.whatsapp.source_broadcast_list"] = evt.Info.Chat.String()
  576. }
  577. var eventID id.EventID
  578. if existingMsg != nil {
  579. portal.MarkDisappearing(existingMsg.MXID, converted.ExpiresIn, false)
  580. converted.Content.SetEdit(existingMsg.MXID)
  581. } else if len(converted.ReplyTo) > 0 {
  582. portal.SetReply(converted.Content, converted.ReplyTo)
  583. }
  584. resp, err := portal.sendMessage(converted.Intent, converted.Type, converted.Content, converted.Extra, evt.Info.Timestamp.UnixMilli())
  585. if err != nil {
  586. portal.log.Errorfln("Failed to send %s to Matrix: %v", msgID, err)
  587. } else {
  588. portal.MarkDisappearing(resp.EventID, converted.ExpiresIn, false)
  589. eventID = resp.EventID
  590. }
  591. // TODO figure out how to handle captions with undecryptable messages turning decryptable
  592. if converted.Caption != nil && existingMsg == nil {
  593. resp, err = portal.sendMessage(converted.Intent, converted.Type, converted.Caption, nil, evt.Info.Timestamp.UnixMilli())
  594. if err != nil {
  595. portal.log.Errorfln("Failed to send caption of %s to Matrix: %v", msgID, err)
  596. } else {
  597. portal.MarkDisappearing(resp.EventID, converted.ExpiresIn, false)
  598. //eventID = resp.EventID
  599. }
  600. }
  601. if converted.MultiEvent != nil && existingMsg == nil {
  602. for index, subEvt := range converted.MultiEvent {
  603. resp, err = portal.sendMessage(converted.Intent, converted.Type, subEvt, nil, evt.Info.Timestamp.UnixMilli())
  604. if err != nil {
  605. portal.log.Errorfln("Failed to send sub-event %d of %s to Matrix: %v", index+1, msgID, err)
  606. } else {
  607. portal.MarkDisappearing(resp.EventID, converted.ExpiresIn, false)
  608. }
  609. }
  610. }
  611. if len(eventID) != 0 {
  612. portal.finishHandling(existingMsg, &evt.Info, eventID, database.MsgNormal, converted.Error)
  613. }
  614. } else if msgType == "reaction" {
  615. portal.HandleMessageReaction(intent, source, &evt.Info, evt.Message.GetReactionMessage(), existingMsg)
  616. } else if msgType == "revoke" {
  617. portal.HandleMessageRevoke(source, &evt.Info, evt.Message.GetProtocolMessage().GetKey())
  618. if existingMsg != nil {
  619. _, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
  620. Reason: "The undecryptable message was actually the deletion of another message",
  621. })
  622. existingMsg.UpdateMXID("net.maunium.whatsapp.fake::"+existingMsg.MXID, database.MsgFake, database.MsgNoError)
  623. }
  624. } else {
  625. portal.log.Warnfln("Unhandled message: %+v (%s)", evt.Info, msgType)
  626. if existingMsg != nil {
  627. _, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
  628. Reason: "The undecryptable message contained an unsupported message type",
  629. })
  630. existingMsg.UpdateMXID("net.maunium.whatsapp.fake::"+existingMsg.MXID, database.MsgFake, database.MsgNoError)
  631. }
  632. return
  633. }
  634. portal.bridge.Metrics.TrackWhatsAppMessage(evt.Info.Timestamp, strings.Split(msgType, " ")[0])
  635. }
  636. func (portal *Portal) isRecentlyHandled(id types.MessageID, error database.MessageErrorType) bool {
  637. start := portal.recentlyHandledIndex
  638. lookingForMsg := recentlyHandledWrapper{id, error}
  639. for i := start; i != start; i = (i - 1) % recentlyHandledLength {
  640. if portal.recentlyHandled[i] == lookingForMsg {
  641. return true
  642. }
  643. }
  644. return false
  645. }
  646. func (portal *Portal) markHandled(msg *database.Message, info *types.MessageInfo, mxid id.EventID, isSent, recent bool, msgType database.MessageType, error database.MessageErrorType) *database.Message {
  647. if msg == nil {
  648. msg = portal.bridge.DB.Message.New()
  649. msg.Chat = portal.Key
  650. msg.JID = info.ID
  651. msg.MXID = mxid
  652. msg.Timestamp = info.Timestamp
  653. msg.Sender = info.Sender
  654. msg.Sent = isSent
  655. msg.Type = msgType
  656. msg.Error = error
  657. if info.IsIncomingBroadcast() {
  658. msg.BroadcastListJID = info.Chat
  659. }
  660. msg.Insert()
  661. } else {
  662. msg.UpdateMXID(mxid, msgType, error)
  663. }
  664. if recent {
  665. portal.recentlyHandledLock.Lock()
  666. index := portal.recentlyHandledIndex
  667. portal.recentlyHandledIndex = (portal.recentlyHandledIndex + 1) % recentlyHandledLength
  668. portal.recentlyHandledLock.Unlock()
  669. portal.recentlyHandled[index] = recentlyHandledWrapper{msg.JID, error}
  670. }
  671. return msg
  672. }
  673. func (portal *Portal) getMessagePuppet(user *User, info *types.MessageInfo) *Puppet {
  674. if info.IsFromMe {
  675. return portal.bridge.GetPuppetByJID(user.JID)
  676. } else if portal.IsPrivateChat() {
  677. return portal.bridge.GetPuppetByJID(portal.Key.JID)
  678. } else {
  679. puppet := portal.bridge.GetPuppetByJID(info.Sender)
  680. puppet.SyncContact(user, true, "handling message")
  681. return puppet
  682. }
  683. }
  684. func (portal *Portal) getMessageIntent(user *User, info *types.MessageInfo) *appservice.IntentAPI {
  685. return portal.getMessagePuppet(user, info).IntentFor(portal)
  686. }
  687. func (portal *Portal) finishHandling(existing *database.Message, message *types.MessageInfo, mxid id.EventID, msgType database.MessageType, error database.MessageErrorType) {
  688. portal.markHandled(existing, message, mxid, true, true, msgType, error)
  689. portal.sendDeliveryReceipt(mxid)
  690. var suffix string
  691. if error == database.MsgErrDecryptionFailed {
  692. suffix = "(undecryptable message error notice)"
  693. } else if error == database.MsgErrMediaNotFound {
  694. suffix = "(media not found notice)"
  695. }
  696. portal.log.Debugfln("Handled message %s (%s) -> %s %s", message.ID, msgType, mxid, suffix)
  697. }
  698. func (portal *Portal) kickExtraUsers(participantMap map[types.JID]bool) {
  699. members, err := portal.MainIntent().JoinedMembers(portal.MXID)
  700. if err != nil {
  701. portal.log.Warnln("Failed to get member list:", err)
  702. return
  703. }
  704. for member := range members.Joined {
  705. jid, ok := portal.bridge.ParsePuppetMXID(member)
  706. if ok {
  707. _, shouldBePresent := participantMap[jid]
  708. if !shouldBePresent {
  709. _, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{
  710. UserID: member,
  711. Reason: "User had left this WhatsApp chat",
  712. })
  713. if err != nil {
  714. portal.log.Warnfln("Failed to kick user %s who had left: %v", member, err)
  715. }
  716. }
  717. }
  718. }
  719. }
  720. //func (portal *Portal) SyncBroadcastRecipients(source *User, metadata *whatsapp.BroadcastListInfo) {
  721. // participantMap := make(map[whatsapp.JID]bool)
  722. // for _, recipient := range metadata.Recipients {
  723. // participantMap[recipient.JID] = true
  724. //
  725. // puppet := portal.bridge.GetPuppetByJID(recipient.JID)
  726. // puppet.SyncContactIfNecessary(source)
  727. // err := puppet.DefaultIntent().EnsureJoined(portal.MXID)
  728. // if err != nil {
  729. // portal.log.Warnfln("Failed to make puppet of %s join %s: %v", recipient.JID, portal.MXID, err)
  730. // }
  731. // }
  732. // portal.kickExtraUsers(participantMap)
  733. //}
  734. func (portal *Portal) SyncParticipants(source *User, metadata *types.GroupInfo) {
  735. changed := false
  736. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  737. if err != nil {
  738. levels = portal.GetBasePowerLevels()
  739. changed = true
  740. }
  741. participantMap := make(map[types.JID]bool)
  742. for _, participant := range metadata.Participants {
  743. participantMap[participant.JID] = true
  744. puppet := portal.bridge.GetPuppetByJID(participant.JID)
  745. puppet.SyncContact(source, true, "group participant")
  746. user := portal.bridge.GetUserByJID(participant.JID)
  747. if user != nil && user != source {
  748. portal.ensureUserInvited(user)
  749. }
  750. if user == nil || !puppet.IntentFor(portal).IsCustomPuppet {
  751. err = puppet.IntentFor(portal).EnsureJoined(portal.MXID)
  752. if err != nil {
  753. portal.log.Warnfln("Failed to make puppet of %s join %s: %v", participant.JID, portal.MXID, err)
  754. }
  755. }
  756. expectedLevel := 0
  757. if participant.IsSuperAdmin {
  758. expectedLevel = 95
  759. } else if participant.IsAdmin {
  760. expectedLevel = 50
  761. }
  762. changed = levels.EnsureUserLevel(puppet.MXID, expectedLevel) || changed
  763. if user != nil {
  764. changed = levels.EnsureUserLevel(user.MXID, expectedLevel) || changed
  765. }
  766. }
  767. if changed {
  768. _, err = portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  769. if err != nil {
  770. portal.log.Errorln("Failed to change power levels:", err)
  771. }
  772. }
  773. portal.kickExtraUsers(participantMap)
  774. }
  775. func (portal *Portal) UpdateAvatar(user *User, setBy types.JID, updateInfo bool) bool {
  776. portal.avatarLock.Lock()
  777. defer portal.avatarLock.Unlock()
  778. avatar, err := user.Client.GetProfilePictureInfo(portal.Key.JID, false)
  779. if err != nil {
  780. if !errors.Is(err, whatsmeow.ErrProfilePictureUnauthorized) {
  781. portal.log.Warnln("Failed to get avatar URL:", err)
  782. }
  783. return false
  784. } else if avatar == nil {
  785. if portal.Avatar == "remove" {
  786. return false
  787. }
  788. portal.AvatarURL = id.ContentURI{}
  789. avatar = &types.ProfilePictureInfo{ID: "remove"}
  790. } else if avatar.ID == portal.Avatar {
  791. return false
  792. } else if len(avatar.URL) == 0 {
  793. portal.log.Warnln("Didn't get URL in response to avatar query")
  794. return false
  795. } else {
  796. url, err := reuploadAvatar(portal.MainIntent(), avatar.URL)
  797. if err != nil {
  798. portal.log.Warnln("Failed to reupload avatar:", err)
  799. return false
  800. }
  801. portal.AvatarURL = url
  802. }
  803. if len(portal.MXID) > 0 {
  804. intent := portal.MainIntent()
  805. if !setBy.IsEmpty() {
  806. intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
  807. }
  808. _, err = intent.SetRoomAvatar(portal.MXID, portal.AvatarURL)
  809. if errors.Is(err, mautrix.MForbidden) && intent != portal.MainIntent() {
  810. _, err = portal.MainIntent().SetRoomAvatar(portal.MXID, portal.AvatarURL)
  811. }
  812. if err != nil {
  813. portal.log.Warnln("Failed to set room avatar:", err)
  814. return false
  815. }
  816. }
  817. portal.Avatar = avatar.ID
  818. if updateInfo {
  819. portal.UpdateBridgeInfo()
  820. }
  821. return true
  822. }
  823. func (portal *Portal) UpdateName(name string, setBy types.JID, updateInfo bool) bool {
  824. if name == "" && portal.IsBroadcastList() {
  825. name = UnnamedBroadcastName
  826. }
  827. if portal.Name != name {
  828. portal.log.Debugfln("Updating name %s -> %s", portal.Name, name)
  829. portal.Name = name
  830. intent := portal.MainIntent()
  831. if !setBy.IsEmpty() {
  832. intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
  833. }
  834. _, err := intent.SetRoomName(portal.MXID, name)
  835. if errors.Is(err, mautrix.MForbidden) && intent != portal.MainIntent() {
  836. _, err = portal.MainIntent().SetRoomName(portal.MXID, name)
  837. }
  838. if err == nil {
  839. if updateInfo {
  840. portal.UpdateBridgeInfo()
  841. }
  842. return true
  843. } else {
  844. portal.Name = ""
  845. portal.log.Warnln("Failed to set room name:", err)
  846. }
  847. }
  848. return false
  849. }
  850. func (portal *Portal) UpdateTopic(topic string, setBy types.JID, updateInfo bool) bool {
  851. if portal.Topic != topic {
  852. portal.log.Debugfln("Updating topic %s -> %s", portal.Topic, topic)
  853. portal.Topic = topic
  854. intent := portal.MainIntent()
  855. if !setBy.IsEmpty() {
  856. intent = portal.bridge.GetPuppetByJID(setBy).IntentFor(portal)
  857. }
  858. _, err := intent.SetRoomTopic(portal.MXID, topic)
  859. if errors.Is(err, mautrix.MForbidden) && intent != portal.MainIntent() {
  860. _, err = portal.MainIntent().SetRoomTopic(portal.MXID, topic)
  861. }
  862. if err == nil {
  863. if updateInfo {
  864. portal.UpdateBridgeInfo()
  865. }
  866. return true
  867. } else {
  868. portal.Topic = ""
  869. portal.log.Warnln("Failed to set room topic:", err)
  870. }
  871. }
  872. return false
  873. }
  874. func (portal *Portal) UpdateMetadata(user *User, groupInfo *types.GroupInfo) bool {
  875. if portal.IsPrivateChat() {
  876. return false
  877. } else if portal.IsStatusBroadcastList() {
  878. update := false
  879. update = portal.UpdateName(StatusBroadcastName, types.EmptyJID, false) || update
  880. update = portal.UpdateTopic(StatusBroadcastTopic, types.EmptyJID, false) || update
  881. return update
  882. } else if portal.IsBroadcastList() {
  883. update := false
  884. //broadcastMetadata, err := user.Conn.GetBroadcastMetadata(portal.Key.JID)
  885. //if err == nil && broadcastMetadata.Status == 200 {
  886. // portal.SyncBroadcastRecipients(user, broadcastMetadata)
  887. // update = portal.UpdateName(broadcastMetadata.Name, "", nil, false) || update
  888. //} else {
  889. // user.Conn.Store.ContactsLock.RLock()
  890. // contact, _ := user.Conn.Store.Contacts[portal.Key.JID]
  891. // user.Conn.Store.ContactsLock.RUnlock()
  892. // update = portal.UpdateName(contact.Name, "", nil, false) || update
  893. //}
  894. //update = portal.UpdateTopic(BroadcastTopic, "", nil, false) || update
  895. return update
  896. }
  897. if groupInfo == nil {
  898. var err error
  899. groupInfo, err = user.Client.GetGroupInfo(portal.Key.JID)
  900. if err != nil {
  901. portal.log.Errorln("Failed to get group info:", err)
  902. return false
  903. }
  904. }
  905. portal.SyncParticipants(user, groupInfo)
  906. update := false
  907. update = portal.UpdateName(groupInfo.Name, groupInfo.NameSetBy, false) || update
  908. update = portal.UpdateTopic(groupInfo.Topic, groupInfo.TopicSetBy, false) || update
  909. if portal.ExpirationTime != groupInfo.DisappearingTimer {
  910. update = true
  911. portal.ExpirationTime = groupInfo.DisappearingTimer
  912. }
  913. portal.RestrictMessageSending(groupInfo.IsAnnounce)
  914. portal.RestrictMetadataChanges(groupInfo.IsLocked)
  915. return update
  916. }
  917. func (portal *Portal) ensureMXIDInvited(mxid id.UserID) {
  918. err := portal.MainIntent().EnsureInvited(portal.MXID, mxid)
  919. if err != nil {
  920. portal.log.Warnfln("Failed to ensure %s is invited to %s: %v", mxid, portal.MXID, err)
  921. }
  922. }
  923. func (portal *Portal) ensureUserInvited(user *User) bool {
  924. return user.ensureInvited(portal.MainIntent(), portal.MXID, portal.IsPrivateChat())
  925. }
  926. func (portal *Portal) UpdateMatrixRoom(user *User, groupInfo *types.GroupInfo) bool {
  927. if len(portal.MXID) == 0 {
  928. return false
  929. }
  930. portal.log.Infoln("Syncing portal for", user.MXID)
  931. portal.ensureUserInvited(user)
  932. go portal.addToSpace(user)
  933. update := false
  934. update = portal.UpdateMetadata(user, groupInfo) || update
  935. if !portal.IsPrivateChat() && !portal.IsBroadcastList() && portal.Avatar == "" {
  936. update = portal.UpdateAvatar(user, types.EmptyJID, false) || update
  937. }
  938. if update {
  939. portal.Update()
  940. portal.UpdateBridgeInfo()
  941. }
  942. return true
  943. }
  944. func (portal *Portal) GetBasePowerLevels() *event.PowerLevelsEventContent {
  945. anyone := 0
  946. nope := 99
  947. invite := 50
  948. if portal.bridge.Config.Bridge.AllowUserInvite {
  949. invite = 0
  950. }
  951. return &event.PowerLevelsEventContent{
  952. UsersDefault: anyone,
  953. EventsDefault: anyone,
  954. RedactPtr: &anyone,
  955. StateDefaultPtr: &nope,
  956. BanPtr: &nope,
  957. InvitePtr: &invite,
  958. Users: map[id.UserID]int{
  959. portal.MainIntent().UserID: 100,
  960. },
  961. Events: map[string]int{
  962. event.StateRoomName.Type: anyone,
  963. event.StateRoomAvatar.Type: anyone,
  964. event.StateTopic.Type: anyone,
  965. },
  966. }
  967. }
  968. func (portal *Portal) ChangeAdminStatus(jids []types.JID, setAdmin bool) id.EventID {
  969. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  970. if err != nil {
  971. levels = portal.GetBasePowerLevels()
  972. }
  973. newLevel := 0
  974. if setAdmin {
  975. newLevel = 50
  976. }
  977. changed := false
  978. for _, jid := range jids {
  979. puppet := portal.bridge.GetPuppetByJID(jid)
  980. changed = levels.EnsureUserLevel(puppet.MXID, newLevel) || changed
  981. user := portal.bridge.GetUserByJID(jid)
  982. if user != nil {
  983. changed = levels.EnsureUserLevel(user.MXID, newLevel) || changed
  984. }
  985. }
  986. if changed {
  987. resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  988. if err != nil {
  989. portal.log.Errorln("Failed to change power levels:", err)
  990. } else {
  991. return resp.EventID
  992. }
  993. }
  994. return ""
  995. }
  996. func (portal *Portal) RestrictMessageSending(restrict bool) id.EventID {
  997. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  998. if err != nil {
  999. levels = portal.GetBasePowerLevels()
  1000. }
  1001. newLevel := 0
  1002. if restrict {
  1003. newLevel = 50
  1004. }
  1005. if levels.EventsDefault == newLevel {
  1006. return ""
  1007. }
  1008. levels.EventsDefault = newLevel
  1009. resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  1010. if err != nil {
  1011. portal.log.Errorln("Failed to change power levels:", err)
  1012. return ""
  1013. } else {
  1014. return resp.EventID
  1015. }
  1016. }
  1017. func (portal *Portal) RestrictMetadataChanges(restrict bool) id.EventID {
  1018. levels, err := portal.MainIntent().PowerLevels(portal.MXID)
  1019. if err != nil {
  1020. levels = portal.GetBasePowerLevels()
  1021. }
  1022. newLevel := 0
  1023. if restrict {
  1024. newLevel = 50
  1025. }
  1026. changed := false
  1027. changed = levels.EnsureEventLevel(event.StateRoomName, newLevel) || changed
  1028. changed = levels.EnsureEventLevel(event.StateRoomAvatar, newLevel) || changed
  1029. changed = levels.EnsureEventLevel(event.StateTopic, newLevel) || changed
  1030. if changed {
  1031. resp, err := portal.MainIntent().SetPowerLevels(portal.MXID, levels)
  1032. if err != nil {
  1033. portal.log.Errorln("Failed to change power levels:", err)
  1034. } else {
  1035. return resp.EventID
  1036. }
  1037. }
  1038. return ""
  1039. }
  1040. func (portal *Portal) getBridgeInfo() (string, event.BridgeEventContent) {
  1041. bridgeInfo := event.BridgeEventContent{
  1042. BridgeBot: portal.bridge.Bot.UserID,
  1043. Creator: portal.MainIntent().UserID,
  1044. Protocol: event.BridgeInfoSection{
  1045. ID: "whatsapp",
  1046. DisplayName: "WhatsApp",
  1047. AvatarURL: portal.bridge.Config.AppService.Bot.ParsedAvatar.CUString(),
  1048. ExternalURL: "https://www.whatsapp.com/",
  1049. },
  1050. Channel: event.BridgeInfoSection{
  1051. ID: portal.Key.JID.String(),
  1052. DisplayName: portal.Name,
  1053. AvatarURL: portal.AvatarURL.CUString(),
  1054. },
  1055. }
  1056. bridgeInfoStateKey := fmt.Sprintf("net.maunium.whatsapp://whatsapp/%s", portal.Key.JID)
  1057. return bridgeInfoStateKey, bridgeInfo
  1058. }
  1059. func (portal *Portal) UpdateBridgeInfo() {
  1060. if len(portal.MXID) == 0 {
  1061. portal.log.Debugln("Not updating bridge info: no Matrix room created")
  1062. return
  1063. }
  1064. portal.log.Debugln("Updating bridge info...")
  1065. stateKey, content := portal.getBridgeInfo()
  1066. _, err := portal.MainIntent().SendStateEvent(portal.MXID, event.StateBridge, stateKey, content)
  1067. if err != nil {
  1068. portal.log.Warnln("Failed to update m.bridge:", err)
  1069. }
  1070. // TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec
  1071. _, err = portal.MainIntent().SendStateEvent(portal.MXID, event.StateHalfShotBridge, stateKey, content)
  1072. if err != nil {
  1073. portal.log.Warnln("Failed to update uk.half-shot.bridge:", err)
  1074. }
  1075. }
  1076. func (portal *Portal) CreateMatrixRoom(user *User, groupInfo *types.GroupInfo, isFullInfo, backfill bool) error {
  1077. portal.roomCreateLock.Lock()
  1078. defer portal.roomCreateLock.Unlock()
  1079. if len(portal.MXID) > 0 {
  1080. return nil
  1081. }
  1082. intent := portal.MainIntent()
  1083. if err := intent.EnsureRegistered(); err != nil {
  1084. return err
  1085. }
  1086. portal.log.Infoln("Creating Matrix room. Info source:", user.MXID)
  1087. //var broadcastMetadata *types.BroadcastListInfo
  1088. if portal.IsPrivateChat() {
  1089. puppet := portal.bridge.GetPuppetByJID(portal.Key.JID)
  1090. puppet.SyncContact(user, true, "creating private chat portal")
  1091. if portal.bridge.Config.Bridge.PrivateChatPortalMeta {
  1092. portal.Name = puppet.Displayname
  1093. portal.AvatarURL = puppet.AvatarURL
  1094. portal.Avatar = puppet.Avatar
  1095. } else {
  1096. portal.Name = ""
  1097. }
  1098. portal.Topic = PrivateChatTopic
  1099. } else if portal.IsStatusBroadcastList() {
  1100. if !portal.bridge.Config.Bridge.EnableStatusBroadcast {
  1101. portal.log.Debugln("Status bridging is disabled in config, not creating room after all")
  1102. return ErrStatusBroadcastDisabled
  1103. }
  1104. portal.Name = StatusBroadcastName
  1105. portal.Topic = StatusBroadcastTopic
  1106. } else if portal.IsBroadcastList() {
  1107. //var err error
  1108. //broadcastMetadata, err = user.Conn.GetBroadcastMetadata(portal.Key.JID)
  1109. //if err == nil && broadcastMetadata.Status == 200 {
  1110. // portal.Name = broadcastMetadata.Name
  1111. //} else {
  1112. // user.Conn.Store.ContactsLock.RLock()
  1113. // contact, _ := user.Conn.Store.Contacts[portal.Key.JID]
  1114. // user.Conn.Store.ContactsLock.RUnlock()
  1115. // portal.Name = contact.Name
  1116. //}
  1117. //if len(portal.Name) == 0 {
  1118. // portal.Name = UnnamedBroadcastName
  1119. //}
  1120. //portal.Topic = BroadcastTopic
  1121. portal.log.Debugln("Broadcast list is not yet supported, not creating room after all")
  1122. return fmt.Errorf("broadcast list bridging is currently not supported")
  1123. } else {
  1124. if groupInfo == nil || !isFullInfo {
  1125. foundInfo, err := user.Client.GetGroupInfo(portal.Key.JID)
  1126. // Ensure that the user is actually a participant in the conversation
  1127. // before creating the matrix room
  1128. if errors.Is(err, whatsmeow.ErrNotInGroup) {
  1129. user.log.Debugfln("Skipping creating matrix room for %s because the user is not a participant", portal.Key.JID)
  1130. user.bridge.DB.BackfillQuery.DeleteAllForPortal(user.MXID, portal.Key)
  1131. user.bridge.DB.HistorySyncQuery.DeleteAllMessagesForPortal(user.MXID, portal.Key)
  1132. return err
  1133. } else if err != nil {
  1134. portal.log.Warnfln("Failed to get group info through %s: %v", user.JID, err)
  1135. } else {
  1136. groupInfo = foundInfo
  1137. isFullInfo = true
  1138. }
  1139. }
  1140. if groupInfo != nil {
  1141. portal.Name = groupInfo.Name
  1142. portal.Topic = groupInfo.Topic
  1143. }
  1144. portal.UpdateAvatar(user, types.EmptyJID, false)
  1145. }
  1146. bridgeInfoStateKey, bridgeInfo := portal.getBridgeInfo()
  1147. initialState := []*event.Event{{
  1148. Type: event.StatePowerLevels,
  1149. Content: event.Content{
  1150. Parsed: portal.GetBasePowerLevels(),
  1151. },
  1152. }, {
  1153. Type: event.StateBridge,
  1154. Content: event.Content{Parsed: bridgeInfo},
  1155. StateKey: &bridgeInfoStateKey,
  1156. }, {
  1157. // TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec
  1158. Type: event.StateHalfShotBridge,
  1159. Content: event.Content{Parsed: bridgeInfo},
  1160. StateKey: &bridgeInfoStateKey,
  1161. }}
  1162. if !portal.AvatarURL.IsEmpty() {
  1163. initialState = append(initialState, &event.Event{
  1164. Type: event.StateRoomAvatar,
  1165. Content: event.Content{
  1166. Parsed: event.RoomAvatarEventContent{URL: portal.AvatarURL},
  1167. },
  1168. })
  1169. }
  1170. var invite []id.UserID
  1171. if portal.bridge.Config.Bridge.Encryption.Default {
  1172. initialState = append(initialState, &event.Event{
  1173. Type: event.StateEncryption,
  1174. Content: event.Content{
  1175. Parsed: event.EncryptionEventContent{Algorithm: id.AlgorithmMegolmV1},
  1176. },
  1177. })
  1178. portal.Encrypted = true
  1179. if portal.IsPrivateChat() {
  1180. invite = append(invite, portal.bridge.Bot.UserID)
  1181. }
  1182. }
  1183. creationContent := make(map[string]interface{})
  1184. if !portal.bridge.Config.Bridge.FederateRooms {
  1185. creationContent["m.federate"] = false
  1186. }
  1187. resp, err := intent.CreateRoom(&mautrix.ReqCreateRoom{
  1188. Visibility: "private",
  1189. Name: portal.Name,
  1190. Topic: portal.Topic,
  1191. Invite: invite,
  1192. Preset: "private_chat",
  1193. IsDirect: portal.IsPrivateChat(),
  1194. InitialState: initialState,
  1195. CreationContent: creationContent,
  1196. })
  1197. if err != nil {
  1198. return err
  1199. }
  1200. portal.MXID = resp.RoomID
  1201. portal.Update()
  1202. portal.bridge.portalsLock.Lock()
  1203. portal.bridge.portalsByMXID[portal.MXID] = portal
  1204. portal.bridge.portalsLock.Unlock()
  1205. // We set the memberships beforehand to make sure the encryption key exchange in initial backfill knows the users are here.
  1206. for _, userID := range invite {
  1207. portal.bridge.StateStore.SetMembership(portal.MXID, userID, event.MembershipInvite)
  1208. }
  1209. portal.ensureUserInvited(user)
  1210. user.syncChatDoublePuppetDetails(portal, true)
  1211. go portal.addToSpace(user)
  1212. if groupInfo != nil {
  1213. if groupInfo.IsEphemeral {
  1214. portal.ExpirationTime = groupInfo.DisappearingTimer
  1215. portal.Update()
  1216. }
  1217. portal.SyncParticipants(user, groupInfo)
  1218. if groupInfo.IsAnnounce {
  1219. portal.RestrictMessageSending(groupInfo.IsAnnounce)
  1220. }
  1221. if groupInfo.IsLocked {
  1222. portal.RestrictMetadataChanges(groupInfo.IsLocked)
  1223. }
  1224. }
  1225. //if broadcastMetadata != nil {
  1226. // portal.SyncBroadcastRecipients(user, broadcastMetadata)
  1227. //}
  1228. if portal.IsPrivateChat() {
  1229. puppet := user.bridge.GetPuppetByJID(portal.Key.JID)
  1230. if portal.bridge.Config.Bridge.Encryption.Default {
  1231. err = portal.bridge.Bot.EnsureJoined(portal.MXID)
  1232. if err != nil {
  1233. portal.log.Errorln("Failed to join created portal with bridge bot for e2be:", err)
  1234. }
  1235. }
  1236. user.UpdateDirectChats(map[id.UserID][]id.RoomID{puppet.MXID: {portal.MXID}})
  1237. }
  1238. firstEventResp, err := portal.MainIntent().SendMessageEvent(portal.MXID, PortalCreationDummyEvent, struct{}{})
  1239. if err != nil {
  1240. portal.log.Errorln("Failed to send dummy event to mark portal creation:", err)
  1241. } else {
  1242. portal.FirstEventID = firstEventResp.EventID
  1243. portal.Update()
  1244. }
  1245. if user.bridge.Config.Bridge.HistorySync.Backfill && backfill {
  1246. portals := []*Portal{portal}
  1247. user.EnqueueImmedateBackfills(portals)
  1248. user.EnqueueDeferredBackfills(portals)
  1249. user.EnqueueMediaBackfills(portals)
  1250. user.BackfillQueue.ReCheckQueue <- true
  1251. }
  1252. return nil
  1253. }
  1254. func (portal *Portal) addToSpace(user *User) {
  1255. spaceID := user.GetSpaceRoom()
  1256. if len(spaceID) == 0 || user.IsInSpace(portal.Key) {
  1257. return
  1258. }
  1259. _, err := portal.bridge.Bot.SendStateEvent(spaceID, event.StateSpaceChild, portal.MXID.String(), &event.SpaceChildEventContent{
  1260. Via: []string{portal.bridge.Config.Homeserver.Domain},
  1261. })
  1262. if err != nil {
  1263. portal.log.Errorfln("Failed to add room to %s's personal filtering space (%s): %v", user.MXID, spaceID, err)
  1264. } else {
  1265. portal.log.Debugfln("Added room to %s's personal filtering space (%s)", user.MXID, spaceID)
  1266. user.MarkInSpace(portal.Key)
  1267. }
  1268. }
  1269. func (portal *Portal) IsPrivateChat() bool {
  1270. return portal.Key.JID.Server == types.DefaultUserServer
  1271. }
  1272. func (portal *Portal) IsGroupChat() bool {
  1273. return portal.Key.JID.Server == types.GroupServer
  1274. }
  1275. func (portal *Portal) IsBroadcastList() bool {
  1276. return portal.Key.JID.Server == types.BroadcastServer
  1277. }
  1278. func (portal *Portal) IsStatusBroadcastList() bool {
  1279. return portal.Key.JID == types.StatusBroadcastJID
  1280. }
  1281. func (portal *Portal) HasRelaybot() bool {
  1282. return portal.bridge.Config.Bridge.Relay.Enabled && len(portal.RelayUserID) > 0
  1283. }
  1284. func (portal *Portal) GetRelayUser() *User {
  1285. if !portal.HasRelaybot() {
  1286. return nil
  1287. } else if portal.relayUser == nil {
  1288. portal.relayUser = portal.bridge.GetUserByMXID(portal.RelayUserID)
  1289. }
  1290. return portal.relayUser
  1291. }
  1292. func (portal *Portal) MainIntent() *appservice.IntentAPI {
  1293. if portal.IsPrivateChat() {
  1294. return portal.bridge.GetPuppetByJID(portal.Key.JID).DefaultIntent()
  1295. }
  1296. return portal.bridge.Bot
  1297. }
  1298. func (portal *Portal) SetReply(content *event.MessageEventContent, replyToID types.MessageID) bool {
  1299. if len(replyToID) == 0 {
  1300. return false
  1301. }
  1302. message := portal.bridge.DB.Message.GetByJID(portal.Key, replyToID)
  1303. if message == nil || message.IsFakeMXID() {
  1304. return false
  1305. }
  1306. evt, err := portal.MainIntent().GetEvent(portal.MXID, message.MXID)
  1307. if err != nil {
  1308. portal.log.Warnln("Failed to get reply target:", err)
  1309. content.RelatesTo = &event.RelatesTo{
  1310. EventID: message.MXID,
  1311. Type: event.RelReply,
  1312. }
  1313. return true
  1314. }
  1315. _ = evt.Content.ParseRaw(evt.Type)
  1316. if evt.Type == event.EventEncrypted {
  1317. decryptedEvt, err := portal.bridge.Crypto.Decrypt(evt)
  1318. if err != nil {
  1319. portal.log.Warnln("Failed to decrypt reply target:", err)
  1320. } else {
  1321. evt = decryptedEvt
  1322. }
  1323. }
  1324. content.SetReply(evt)
  1325. return true
  1326. }
  1327. type sendReactionContent struct {
  1328. event.ReactionEventContent
  1329. DoublePuppet string `json:"fi.mau.double_puppet_source,omitempty"`
  1330. }
  1331. func (portal *Portal) HandleMessageReaction(intent *appservice.IntentAPI, user *User, info *types.MessageInfo, reaction *waProto.ReactionMessage, existingMsg *database.Message) {
  1332. if existingMsg != nil {
  1333. _, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
  1334. Reason: "The undecryptable message was actually a reaction",
  1335. })
  1336. }
  1337. targetJID := reaction.GetKey().GetId()
  1338. if reaction.GetText() == "" {
  1339. existing := portal.bridge.DB.Reaction.GetByTargetJID(portal.Key, targetJID, info.Sender)
  1340. if existing == nil {
  1341. portal.log.Debugfln("Dropping removal %s of unknown reaction to %s from %s", info.ID, targetJID, info.Sender)
  1342. return
  1343. }
  1344. extra := make(map[string]interface{})
  1345. if intent.IsCustomPuppet {
  1346. extra[doublePuppetKey] = doublePuppetValue
  1347. }
  1348. resp, err := intent.RedactEvent(portal.MXID, existing.MXID, mautrix.ReqRedact{Extra: extra})
  1349. if err != nil {
  1350. portal.log.Errorfln("Failed to redact reaction %s/%s from %s to %s: %v", existing.MXID, existing.JID, info.Sender, targetJID, err)
  1351. }
  1352. portal.finishHandling(existingMsg, info, resp.EventID, database.MsgReaction, database.MsgNoError)
  1353. existing.Delete()
  1354. } else {
  1355. target := portal.bridge.DB.Message.GetByJID(portal.Key, targetJID)
  1356. if target == nil {
  1357. portal.log.Debugfln("Dropping reaction %s from %s to unknown message %s", info.ID, info.Sender, targetJID)
  1358. return
  1359. }
  1360. var content sendReactionContent
  1361. content.RelatesTo = event.RelatesTo{
  1362. Type: event.RelAnnotation,
  1363. EventID: target.MXID,
  1364. Key: variationselector.Add(reaction.GetText()),
  1365. }
  1366. if intent.IsCustomPuppet {
  1367. content.DoublePuppet = doublePuppetValue
  1368. }
  1369. resp, err := intent.SendMassagedMessageEvent(portal.MXID, event.EventReaction, &content, info.Timestamp.UnixMilli())
  1370. if err != nil {
  1371. portal.log.Errorfln("Failed to bridge reaction %s from %s to %s: %v", info.ID, info.Sender, target.JID, err)
  1372. return
  1373. }
  1374. portal.finishHandling(existingMsg, info, resp.EventID, database.MsgReaction, database.MsgNoError)
  1375. portal.upsertReaction(intent, target.JID, info.Sender, resp.EventID, info.ID)
  1376. }
  1377. }
  1378. func (portal *Portal) HandleMessageRevoke(user *User, info *types.MessageInfo, key *waProto.MessageKey) bool {
  1379. msg := portal.bridge.DB.Message.GetByJID(portal.Key, key.GetId())
  1380. if msg == nil || msg.IsFakeMXID() {
  1381. return false
  1382. }
  1383. intent := portal.bridge.GetPuppetByJID(info.Sender).IntentFor(portal)
  1384. redactionReq := mautrix.ReqRedact{Extra: map[string]interface{}{}}
  1385. if intent.IsCustomPuppet {
  1386. redactionReq.Extra[doublePuppetKey] = doublePuppetValue
  1387. }
  1388. _, err := intent.RedactEvent(portal.MXID, msg.MXID, redactionReq)
  1389. if err != nil {
  1390. if errors.Is(err, mautrix.MForbidden) {
  1391. _, err = portal.MainIntent().RedactEvent(portal.MXID, msg.MXID, redactionReq)
  1392. if err != nil {
  1393. portal.log.Errorln("Failed to redact %s: %v", msg.JID, err)
  1394. }
  1395. }
  1396. } else {
  1397. msg.Delete()
  1398. }
  1399. return true
  1400. }
  1401. func (portal *Portal) sendMainIntentMessage(content *event.MessageEventContent) (*mautrix.RespSendEvent, error) {
  1402. return portal.sendMessage(portal.MainIntent(), event.EventMessage, content, nil, 0)
  1403. }
  1404. func (portal *Portal) encrypt(content *event.Content, eventType event.Type) (event.Type, error) {
  1405. if portal.Encrypted && portal.bridge.Crypto != nil {
  1406. // TODO maybe the locking should be inside mautrix-go?
  1407. portal.encryptLock.Lock()
  1408. encrypted, err := portal.bridge.Crypto.Encrypt(portal.MXID, eventType, *content)
  1409. portal.encryptLock.Unlock()
  1410. if err != nil {
  1411. return eventType, fmt.Errorf("failed to encrypt event: %w", err)
  1412. }
  1413. eventType = event.EventEncrypted
  1414. content.Parsed = encrypted
  1415. }
  1416. return eventType, nil
  1417. }
  1418. const doublePuppetKey = "fi.mau.double_puppet_source"
  1419. const doublePuppetValue = "mautrix-whatsapp"
  1420. func (portal *Portal) sendMessage(intent *appservice.IntentAPI, eventType event.Type, content *event.MessageEventContent, extraContent map[string]interface{}, timestamp int64) (*mautrix.RespSendEvent, error) {
  1421. wrappedContent := event.Content{Parsed: content, Raw: extraContent}
  1422. if timestamp != 0 && intent.IsCustomPuppet {
  1423. if wrappedContent.Raw == nil {
  1424. wrappedContent.Raw = map[string]interface{}{}
  1425. }
  1426. if intent.IsCustomPuppet {
  1427. wrappedContent.Raw[doublePuppetKey] = doublePuppetValue
  1428. }
  1429. }
  1430. var err error
  1431. eventType, err = portal.encrypt(&wrappedContent, eventType)
  1432. if err != nil {
  1433. return nil, err
  1434. }
  1435. if eventType == event.EventEncrypted {
  1436. // Clear other custom keys if the event was encrypted, but keep the double puppet identifier
  1437. if intent.IsCustomPuppet {
  1438. wrappedContent.Raw = map[string]interface{}{doublePuppetKey: doublePuppetValue}
  1439. } else {
  1440. wrappedContent.Raw = nil
  1441. }
  1442. }
  1443. _, _ = intent.UserTyping(portal.MXID, false, 0)
  1444. if timestamp == 0 {
  1445. return intent.SendMessageEvent(portal.MXID, eventType, &wrappedContent)
  1446. } else {
  1447. return intent.SendMassagedMessageEvent(portal.MXID, eventType, &wrappedContent, timestamp)
  1448. }
  1449. }
  1450. type ConvertedMessage struct {
  1451. Intent *appservice.IntentAPI
  1452. Type event.Type
  1453. Content *event.MessageEventContent
  1454. Extra map[string]interface{}
  1455. Caption *event.MessageEventContent
  1456. MultiEvent []*event.MessageEventContent
  1457. ReplyTo types.MessageID
  1458. ExpiresIn uint32
  1459. Error database.MessageErrorType
  1460. }
  1461. func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, source *User, msg *waProto.Message) *ConvertedMessage {
  1462. content := &event.MessageEventContent{
  1463. Body: msg.GetConversation(),
  1464. MsgType: event.MsgText,
  1465. }
  1466. if len(msg.GetExtendedTextMessage().GetText()) > 0 {
  1467. content.Body = msg.GetExtendedTextMessage().GetText()
  1468. }
  1469. contextInfo := msg.GetExtendedTextMessage().GetContextInfo()
  1470. portal.bridge.Formatter.ParseWhatsApp(portal.MXID, content, contextInfo.GetMentionedJid())
  1471. replyTo := contextInfo.GetStanzaId()
  1472. expiresIn := contextInfo.GetExpiration()
  1473. extraAttrs := map[string]interface{}{}
  1474. extraAttrs["com.beeper.linkpreviews"] = portal.convertURLPreviewToBeeper(intent, source, msg.GetExtendedTextMessage())
  1475. return &ConvertedMessage{
  1476. Intent: intent,
  1477. Type: event.EventMessage,
  1478. Content: content,
  1479. ReplyTo: replyTo,
  1480. ExpiresIn: expiresIn,
  1481. Extra: extraAttrs,
  1482. }
  1483. }
  1484. func (portal *Portal) convertLiveLocationMessage(intent *appservice.IntentAPI, msg *waProto.LiveLocationMessage) *ConvertedMessage {
  1485. content := &event.MessageEventContent{
  1486. Body: "Started sharing live location",
  1487. MsgType: event.MsgNotice,
  1488. }
  1489. if len(msg.GetCaption()) > 0 {
  1490. content.Body += ": " + msg.GetCaption()
  1491. }
  1492. return &ConvertedMessage{
  1493. Intent: intent,
  1494. Type: event.EventMessage,
  1495. Content: content,
  1496. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1497. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1498. }
  1499. }
  1500. func (portal *Portal) convertLocationMessage(intent *appservice.IntentAPI, msg *waProto.LocationMessage) *ConvertedMessage {
  1501. url := msg.GetUrl()
  1502. if len(url) == 0 {
  1503. url = fmt.Sprintf("https://maps.google.com/?q=%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude())
  1504. }
  1505. name := msg.GetName()
  1506. if len(name) == 0 {
  1507. latChar := 'N'
  1508. if msg.GetDegreesLatitude() < 0 {
  1509. latChar = 'S'
  1510. }
  1511. longChar := 'E'
  1512. if msg.GetDegreesLongitude() < 0 {
  1513. longChar = 'W'
  1514. }
  1515. name = fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(msg.GetDegreesLatitude()), latChar, math.Abs(msg.GetDegreesLongitude()), longChar)
  1516. }
  1517. content := &event.MessageEventContent{
  1518. MsgType: event.MsgLocation,
  1519. Body: fmt.Sprintf("Location: %s\n%s\n%s", name, msg.GetAddress(), url),
  1520. Format: event.FormatHTML,
  1521. FormattedBody: fmt.Sprintf("Location: <a href='%s'>%s</a><br>%s", url, name, msg.GetAddress()),
  1522. GeoURI: fmt.Sprintf("geo:%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude()),
  1523. }
  1524. if len(msg.GetJpegThumbnail()) > 0 {
  1525. thumbnailMime := http.DetectContentType(msg.GetJpegThumbnail())
  1526. uploadedThumbnail, _ := intent.UploadBytes(msg.GetJpegThumbnail(), thumbnailMime)
  1527. if uploadedThumbnail != nil {
  1528. cfg, _, _ := image.DecodeConfig(bytes.NewReader(msg.GetJpegThumbnail()))
  1529. content.Info = &event.FileInfo{
  1530. ThumbnailInfo: &event.FileInfo{
  1531. Size: len(msg.GetJpegThumbnail()),
  1532. Width: cfg.Width,
  1533. Height: cfg.Height,
  1534. MimeType: thumbnailMime,
  1535. },
  1536. ThumbnailURL: uploadedThumbnail.ContentURI.CUString(),
  1537. }
  1538. }
  1539. }
  1540. return &ConvertedMessage{
  1541. Intent: intent,
  1542. Type: event.EventMessage,
  1543. Content: content,
  1544. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1545. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1546. }
  1547. }
  1548. const inviteMsg = `%s<hr/>This invitation to join "%s" expires at %s. Reply to this message with <code>!wa accept</code> to accept the invite.`
  1549. const inviteMetaField = "fi.mau.whatsapp.invite"
  1550. const escapedInviteMetaField = `fi\.mau\.whatsapp\.invite`
  1551. type InviteMeta struct {
  1552. JID types.JID `json:"jid"`
  1553. Code string `json:"code"`
  1554. Expiration int64 `json:"expiration,string"`
  1555. Inviter types.JID `json:"inviter"`
  1556. }
  1557. func (portal *Portal) convertGroupInviteMessage(intent *appservice.IntentAPI, info *types.MessageInfo, msg *waProto.GroupInviteMessage) *ConvertedMessage {
  1558. expiry := time.Unix(msg.GetInviteExpiration(), 0)
  1559. htmlMessage := fmt.Sprintf(inviteMsg, html.EscapeString(msg.GetCaption()), msg.GetGroupName(), expiry)
  1560. content := &event.MessageEventContent{
  1561. MsgType: event.MsgText,
  1562. Body: format.HTMLToText(htmlMessage),
  1563. Format: event.FormatHTML,
  1564. FormattedBody: htmlMessage,
  1565. }
  1566. groupJID, err := types.ParseJID(msg.GetGroupJid())
  1567. if err != nil {
  1568. portal.log.Errorfln("Failed to parse invite group JID: %v", err)
  1569. }
  1570. extraAttrs := map[string]interface{}{
  1571. inviteMetaField: InviteMeta{
  1572. JID: groupJID,
  1573. Code: msg.GetInviteCode(),
  1574. Expiration: msg.GetInviteExpiration(),
  1575. Inviter: info.Sender.ToNonAD(),
  1576. },
  1577. }
  1578. return &ConvertedMessage{
  1579. Intent: intent,
  1580. Type: event.EventMessage,
  1581. Content: content,
  1582. Extra: extraAttrs,
  1583. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1584. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1585. }
  1586. }
  1587. func (portal *Portal) convertContactMessage(intent *appservice.IntentAPI, msg *waProto.ContactMessage) *ConvertedMessage {
  1588. fileName := fmt.Sprintf("%s.vcf", msg.GetDisplayName())
  1589. data := []byte(msg.GetVcard())
  1590. mimeType := "text/vcard"
  1591. data, uploadMimeType, file := portal.encryptFile(data, mimeType)
  1592. uploadResp, err := intent.UploadBytesWithName(data, uploadMimeType, fileName)
  1593. if err != nil {
  1594. portal.log.Errorfln("Failed to upload vcard of %s: %v", msg.GetDisplayName(), err)
  1595. return nil
  1596. }
  1597. content := &event.MessageEventContent{
  1598. Body: fileName,
  1599. MsgType: event.MsgFile,
  1600. File: file,
  1601. Info: &event.FileInfo{
  1602. MimeType: mimeType,
  1603. Size: len(msg.GetVcard()),
  1604. },
  1605. }
  1606. if content.File != nil {
  1607. content.File.URL = uploadResp.ContentURI.CUString()
  1608. } else {
  1609. content.URL = uploadResp.ContentURI.CUString()
  1610. }
  1611. return &ConvertedMessage{
  1612. Intent: intent,
  1613. Type: event.EventMessage,
  1614. Content: content,
  1615. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1616. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1617. }
  1618. }
  1619. func (portal *Portal) convertContactsArrayMessage(intent *appservice.IntentAPI, msg *waProto.ContactsArrayMessage) *ConvertedMessage {
  1620. name := msg.GetDisplayName()
  1621. if len(name) == 0 {
  1622. name = fmt.Sprintf("%d contacts", len(msg.GetContacts()))
  1623. }
  1624. contacts := make([]*event.MessageEventContent, 0, len(msg.GetContacts()))
  1625. for _, contact := range msg.GetContacts() {
  1626. converted := portal.convertContactMessage(intent, contact)
  1627. if converted != nil {
  1628. contacts = append(contacts, converted.Content)
  1629. }
  1630. }
  1631. return &ConvertedMessage{
  1632. Intent: intent,
  1633. Type: event.EventMessage,
  1634. Content: &event.MessageEventContent{
  1635. MsgType: event.MsgNotice,
  1636. Body: fmt.Sprintf("Sent %s", name),
  1637. },
  1638. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1639. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1640. MultiEvent: contacts,
  1641. }
  1642. }
  1643. func (portal *Portal) tryKickUser(userID id.UserID, intent *appservice.IntentAPI) error {
  1644. _, err := intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
  1645. if err != nil {
  1646. httpErr, ok := err.(mautrix.HTTPError)
  1647. if ok && httpErr.RespError != nil && httpErr.RespError.ErrCode == "M_FORBIDDEN" {
  1648. _, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
  1649. }
  1650. }
  1651. return err
  1652. }
  1653. func (portal *Portal) removeUser(isSameUser bool, kicker *appservice.IntentAPI, target id.UserID, targetIntent *appservice.IntentAPI) {
  1654. if !isSameUser || targetIntent == nil {
  1655. err := portal.tryKickUser(target, kicker)
  1656. if err != nil {
  1657. portal.log.Warnfln("Failed to kick %s from %s: %v", target, portal.MXID, err)
  1658. if targetIntent != nil {
  1659. _, _ = portal.leaveWithPuppetMeta(targetIntent)
  1660. }
  1661. }
  1662. } else {
  1663. _, err := portal.leaveWithPuppetMeta(targetIntent)
  1664. if err != nil {
  1665. portal.log.Warnfln("Failed to leave portal as %s: %v", target, err)
  1666. _, _ = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: target})
  1667. }
  1668. }
  1669. }
  1670. func (portal *Portal) HandleWhatsAppKick(source *User, senderJID types.JID, jids []types.JID) {
  1671. sender := portal.bridge.GetPuppetByJID(senderJID)
  1672. senderIntent := sender.IntentFor(portal)
  1673. for _, jid := range jids {
  1674. //if source != nil && source.JID.User == jid.User {
  1675. // portal.log.Debugln("Ignoring self-kick by", source.MXID)
  1676. // continue
  1677. //}
  1678. puppet := portal.bridge.GetPuppetByJID(jid)
  1679. portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent())
  1680. if !portal.IsBroadcastList() {
  1681. user := portal.bridge.GetUserByJID(jid)
  1682. if user != nil {
  1683. var customIntent *appservice.IntentAPI
  1684. if puppet.CustomMXID == user.MXID {
  1685. customIntent = puppet.CustomIntent()
  1686. }
  1687. portal.removeUser(puppet.JID == sender.JID, senderIntent, user.MXID, customIntent)
  1688. }
  1689. }
  1690. }
  1691. }
  1692. func (portal *Portal) leaveWithPuppetMeta(intent *appservice.IntentAPI) (*mautrix.RespSendEvent, error) {
  1693. content := event.Content{
  1694. Parsed: event.MemberEventContent{
  1695. Membership: event.MembershipLeave,
  1696. },
  1697. Raw: map[string]interface{}{
  1698. doublePuppetKey: doublePuppetValue,
  1699. },
  1700. }
  1701. // Bypass IntentAPI, we don't want to EnsureJoined here
  1702. return intent.Client.SendStateEvent(portal.MXID, event.StateMember, intent.UserID.String(), &content)
  1703. }
  1704. func (portal *Portal) HandleWhatsAppInvite(source *User, senderJID *types.JID, jids []types.JID) (evtID id.EventID) {
  1705. intent := portal.MainIntent()
  1706. if senderJID != nil && !senderJID.IsEmpty() {
  1707. sender := portal.bridge.GetPuppetByJID(*senderJID)
  1708. intent = sender.IntentFor(portal)
  1709. }
  1710. for _, jid := range jids {
  1711. puppet := portal.bridge.GetPuppetByJID(jid)
  1712. puppet.SyncContact(source, true, "handling whatsapp invite")
  1713. content := event.Content{
  1714. Parsed: event.MemberEventContent{
  1715. Membership: "invite",
  1716. Displayname: puppet.Displayname,
  1717. AvatarURL: puppet.AvatarURL.CUString(),
  1718. },
  1719. Raw: map[string]interface{}{
  1720. doublePuppetKey: doublePuppetValue,
  1721. },
  1722. }
  1723. resp, err := intent.SendStateEvent(portal.MXID, event.StateMember, puppet.MXID.String(), &content)
  1724. if err != nil {
  1725. portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, intent.UserID, err)
  1726. _ = portal.MainIntent().EnsureInvited(portal.MXID, puppet.MXID)
  1727. } else {
  1728. evtID = resp.EventID
  1729. }
  1730. err = puppet.DefaultIntent().EnsureJoined(portal.MXID)
  1731. if err != nil {
  1732. portal.log.Errorfln("Failed to ensure %s is joined: %v", puppet.MXID, err)
  1733. }
  1734. }
  1735. return
  1736. }
  1737. const failedMediaField = "fi.mau.whatsapp.failed_media"
  1738. type FailedMediaKeys struct {
  1739. Key []byte `json:"key"`
  1740. Length int `json:"length"`
  1741. Type whatsmeow.MediaType `json:"type"`
  1742. SHA256 []byte `json:"sha256"`
  1743. EncSHA256 []byte `json:"enc_sha256"`
  1744. }
  1745. type FailedMediaMeta struct {
  1746. Type event.Type `json:"type"`
  1747. Content *event.MessageEventContent `json:"content"`
  1748. ExtraContent map[string]interface{} `json:"extra_content,omitempty"`
  1749. Media FailedMediaKeys `json:"whatsapp_media"`
  1750. }
  1751. func shallowCopyMap(data map[string]interface{}) map[string]interface{} {
  1752. newMap := make(map[string]interface{}, len(data))
  1753. for key, value := range data {
  1754. newMap[key] = value
  1755. }
  1756. return newMap
  1757. }
  1758. func (portal *Portal) makeMediaBridgeFailureMessage(info *types.MessageInfo, bridgeErr error, converted *ConvertedMessage, keys *FailedMediaKeys, userFriendlyError string) *ConvertedMessage {
  1759. portal.log.Errorfln("Failed to bridge media for %s: %v", info.ID, bridgeErr)
  1760. if keys != nil {
  1761. meta := &FailedMediaMeta{
  1762. Type: converted.Type,
  1763. Content: converted.Content,
  1764. ExtraContent: shallowCopyMap(converted.Extra),
  1765. Media: *keys,
  1766. }
  1767. converted.Extra[failedMediaField] = meta
  1768. portal.mediaErrorCache[info.ID] = meta
  1769. }
  1770. converted.Type = event.EventMessage
  1771. body := userFriendlyError
  1772. if body == "" {
  1773. body = fmt.Sprintf("Failed to bridge media: %v", bridgeErr)
  1774. }
  1775. converted.Content = &event.MessageEventContent{
  1776. MsgType: event.MsgNotice,
  1777. Body: body,
  1778. }
  1779. return converted
  1780. }
  1781. func (portal *Portal) encryptFile(data []byte, mimeType string) ([]byte, string, *event.EncryptedFileInfo) {
  1782. if !portal.Encrypted {
  1783. return data, mimeType, nil
  1784. }
  1785. file := &event.EncryptedFileInfo{
  1786. EncryptedFile: *attachment.NewEncryptedFile(),
  1787. URL: "",
  1788. }
  1789. return file.Encrypt(data), "application/octet-stream", file
  1790. }
  1791. type MediaMessage interface {
  1792. whatsmeow.DownloadableMessage
  1793. GetContextInfo() *waProto.ContextInfo
  1794. GetFileLength() uint64
  1795. GetMimetype() string
  1796. }
  1797. type MediaMessageWithThumbnail interface {
  1798. MediaMessage
  1799. GetJpegThumbnail() []byte
  1800. }
  1801. type MediaMessageWithCaption interface {
  1802. MediaMessage
  1803. GetCaption() string
  1804. }
  1805. type MediaMessageWithDimensions interface {
  1806. MediaMessage
  1807. GetHeight() uint32
  1808. GetWidth() uint32
  1809. }
  1810. type MediaMessageWithFileName interface {
  1811. MediaMessage
  1812. GetFileName() string
  1813. }
  1814. type MediaMessageWithDuration interface {
  1815. MediaMessage
  1816. GetSeconds() uint32
  1817. }
  1818. func (portal *Portal) convertMediaMessageContent(intent *appservice.IntentAPI, msg MediaMessage) *ConvertedMessage {
  1819. content := &event.MessageEventContent{
  1820. Info: &event.FileInfo{
  1821. MimeType: msg.GetMimetype(),
  1822. Size: int(msg.GetFileLength()),
  1823. },
  1824. }
  1825. extraContent := map[string]interface{}{}
  1826. messageWithDimensions, ok := msg.(MediaMessageWithDimensions)
  1827. if ok {
  1828. content.Info.Width = int(messageWithDimensions.GetWidth())
  1829. content.Info.Height = int(messageWithDimensions.GetHeight())
  1830. }
  1831. msgWithName, ok := msg.(MediaMessageWithFileName)
  1832. if ok && len(msgWithName.GetFileName()) > 0 {
  1833. content.Body = msgWithName.GetFileName()
  1834. } else {
  1835. mimeClass := strings.Split(msg.GetMimetype(), "/")[0]
  1836. switch mimeClass {
  1837. case "application":
  1838. content.Body = "file"
  1839. default:
  1840. content.Body = mimeClass
  1841. }
  1842. content.Body += util.ExtensionFromMimetype(msg.GetMimetype())
  1843. }
  1844. msgWithDuration, ok := msg.(MediaMessageWithDuration)
  1845. if ok {
  1846. content.Info.Duration = int(msgWithDuration.GetSeconds()) * 1000
  1847. }
  1848. videoMessage, ok := msg.(*waProto.VideoMessage)
  1849. var isGIF bool
  1850. if ok && videoMessage.GetGifPlayback() {
  1851. isGIF = true
  1852. extraContent["info"] = map[string]interface{}{
  1853. "fi.mau.loop": true,
  1854. "fi.mau.autoplay": true,
  1855. "fi.mau.hide_controls": true,
  1856. "fi.mau.no_audio": true,
  1857. }
  1858. }
  1859. messageWithThumbnail, ok := msg.(MediaMessageWithThumbnail)
  1860. if ok && messageWithThumbnail.GetJpegThumbnail() != nil && (portal.bridge.Config.Bridge.WhatsappThumbnail || isGIF) {
  1861. thumbnailData := messageWithThumbnail.GetJpegThumbnail()
  1862. thumbnailMime := http.DetectContentType(thumbnailData)
  1863. thumbnailCfg, _, _ := image.DecodeConfig(bytes.NewReader(thumbnailData))
  1864. thumbnailSize := len(thumbnailData)
  1865. thumbnail, thumbnailUploadMime, thumbnailFile := portal.encryptFile(thumbnailData, thumbnailMime)
  1866. uploadedThumbnail, err := intent.UploadBytes(thumbnail, thumbnailUploadMime)
  1867. if err != nil {
  1868. portal.log.Warnfln("Failed to upload thumbnail: %v", err)
  1869. } else if uploadedThumbnail != nil {
  1870. if thumbnailFile != nil {
  1871. thumbnailFile.URL = uploadedThumbnail.ContentURI.CUString()
  1872. content.Info.ThumbnailFile = thumbnailFile
  1873. } else {
  1874. content.Info.ThumbnailURL = uploadedThumbnail.ContentURI.CUString()
  1875. }
  1876. content.Info.ThumbnailInfo = &event.FileInfo{
  1877. Size: thumbnailSize,
  1878. Width: thumbnailCfg.Width,
  1879. Height: thumbnailCfg.Height,
  1880. MimeType: thumbnailMime,
  1881. }
  1882. }
  1883. }
  1884. _, isSticker := msg.(*waProto.StickerMessage)
  1885. switch strings.ToLower(strings.Split(msg.GetMimetype(), "/")[0]) {
  1886. case "image":
  1887. if !isSticker {
  1888. content.MsgType = event.MsgImage
  1889. }
  1890. case "video":
  1891. content.MsgType = event.MsgVideo
  1892. case "audio":
  1893. content.MsgType = event.MsgAudio
  1894. default:
  1895. content.MsgType = event.MsgFile
  1896. }
  1897. eventType := event.EventMessage
  1898. if isSticker {
  1899. eventType = event.EventSticker
  1900. }
  1901. audioMessage, ok := msg.(*waProto.AudioMessage)
  1902. if ok {
  1903. var waveform []int
  1904. if audioMessage.Waveform != nil {
  1905. waveform = make([]int, len(audioMessage.Waveform))
  1906. max := 0
  1907. for i, part := range audioMessage.Waveform {
  1908. waveform[i] = int(part)
  1909. if waveform[i] > max {
  1910. max = waveform[i]
  1911. }
  1912. }
  1913. multiplier := 0
  1914. if max > 0 {
  1915. multiplier = 1024 / max
  1916. }
  1917. if multiplier > 32 {
  1918. multiplier = 32
  1919. }
  1920. for i := range waveform {
  1921. waveform[i] *= multiplier
  1922. }
  1923. }
  1924. extraContent["org.matrix.msc1767.audio"] = map[string]interface{}{
  1925. "duration": int(audioMessage.GetSeconds()) * 1000,
  1926. "waveform": waveform,
  1927. }
  1928. if audioMessage.GetPtt() {
  1929. extraContent["org.matrix.msc3245.voice"] = map[string]interface{}{}
  1930. }
  1931. }
  1932. messageWithCaption, ok := msg.(MediaMessageWithCaption)
  1933. var captionContent *event.MessageEventContent
  1934. if ok && len(messageWithCaption.GetCaption()) > 0 {
  1935. captionContent = &event.MessageEventContent{
  1936. Body: messageWithCaption.GetCaption(),
  1937. MsgType: event.MsgNotice,
  1938. }
  1939. portal.bridge.Formatter.ParseWhatsApp(portal.MXID, captionContent, msg.GetContextInfo().GetMentionedJid())
  1940. }
  1941. return &ConvertedMessage{
  1942. Intent: intent,
  1943. Type: eventType,
  1944. Content: content,
  1945. Caption: captionContent,
  1946. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1947. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1948. Extra: extraContent,
  1949. }
  1950. }
  1951. func (portal *Portal) uploadMedia(intent *appservice.IntentAPI, data []byte, content *event.MessageEventContent) error {
  1952. data, uploadMimeType, file := portal.encryptFile(data, content.Info.MimeType)
  1953. req := mautrix.ReqUploadMedia{
  1954. ContentBytes: data,
  1955. ContentType: uploadMimeType,
  1956. }
  1957. var mxc id.ContentURI
  1958. if portal.bridge.Config.Homeserver.AsyncMedia {
  1959. uploaded, err := intent.UnstableUploadAsync(req)
  1960. if err != nil {
  1961. return err
  1962. }
  1963. mxc = uploaded.ContentURI
  1964. } else {
  1965. uploaded, err := intent.UploadMedia(req)
  1966. if err != nil {
  1967. return err
  1968. }
  1969. mxc = uploaded.ContentURI
  1970. }
  1971. if file != nil {
  1972. file.URL = mxc.CUString()
  1973. content.File = file
  1974. } else {
  1975. content.URL = mxc.CUString()
  1976. }
  1977. content.Info.Size = len(data)
  1978. if content.Info.Width == 0 && content.Info.Height == 0 && strings.HasPrefix(content.Info.MimeType, "image/") {
  1979. cfg, _, _ := image.DecodeConfig(bytes.NewReader(data))
  1980. content.Info.Width, content.Info.Height = cfg.Width, cfg.Height
  1981. }
  1982. return nil
  1983. }
  1984. func (portal *Portal) convertMediaMessage(intent *appservice.IntentAPI, source *User, info *types.MessageInfo, msg MediaMessage) *ConvertedMessage {
  1985. converted := portal.convertMediaMessageContent(intent, msg)
  1986. data, err := source.Client.Download(msg)
  1987. if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) {
  1988. //portal.log.Warnfln("Failed to download media for %s: %v. Requesting retry", info.ID, err)
  1989. //err = source.Client.SendMediaRetryReceipt(info, msg.GetMediaKey())
  1990. //if err != nil {
  1991. // portal.log.Errorfln("Failed to send media retry receipt for %s: %v", info.ID, err)
  1992. //}
  1993. converted.Error = database.MsgErrMediaNotFound
  1994. errorText := "Old photo or attachment."
  1995. if portal.bridge.Config.Bridge.HistorySync.BackfillMedia {
  1996. if len(portal.bridge.Config.Bridge.HistorySync.Media) > 0 {
  1997. errorText += " Media will be requested from your phone later."
  1998. } else {
  1999. errorText += ` React with the \u267b (recycle) emoji or the text "click to retry" to request this media from your phone or use the backfill command to request all missing media for this chat.`
  2000. }
  2001. } else {
  2002. errorText += ` Automatic media backfill is disabled. React with the \u267b (recycle) emoji or the text "click to retry" to request this media from your phone.`
  2003. }
  2004. return portal.makeMediaBridgeFailureMessage(info, err, converted, &FailedMediaKeys{
  2005. Key: msg.GetMediaKey(),
  2006. Length: int(msg.GetFileLength()),
  2007. Type: whatsmeow.GetMediaType(msg),
  2008. SHA256: msg.GetFileSha256(),
  2009. EncSHA256: msg.GetFileEncSha256(),
  2010. }, errorText)
  2011. } else if errors.Is(err, whatsmeow.ErrNoURLPresent) {
  2012. portal.log.Debugfln("No URL present error for media message %s, ignoring...", info.ID)
  2013. return nil
  2014. } else if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) {
  2015. portal.log.Warnfln("Mismatching media checksums in %s: %v. Ignoring because WhatsApp seems to ignore them too", info.ID, err)
  2016. } else if err != nil {
  2017. return portal.makeMediaBridgeFailureMessage(info, err, converted, nil, "")
  2018. }
  2019. err = portal.uploadMedia(intent, data, converted.Content)
  2020. if err != nil {
  2021. if errors.Is(err, mautrix.MTooLarge) {
  2022. return portal.makeMediaBridgeFailureMessage(info, errors.New("homeserver rejected too large file"), converted, nil, "")
  2023. } else if httpErr, ok := err.(mautrix.HTTPError); ok && httpErr.IsStatus(413) {
  2024. return portal.makeMediaBridgeFailureMessage(info, errors.New("proxy rejected too large file"), converted, nil, "")
  2025. } else {
  2026. return portal.makeMediaBridgeFailureMessage(info, fmt.Errorf("failed to upload media: %w", err), converted, nil, "")
  2027. }
  2028. }
  2029. return converted
  2030. }
  2031. func (portal *Portal) fetchMediaRetryEvent(msg *database.Message) (*FailedMediaMeta, error) {
  2032. errorMeta, ok := portal.mediaErrorCache[msg.JID]
  2033. if ok {
  2034. return errorMeta, nil
  2035. }
  2036. evt, err := portal.MainIntent().GetEvent(portal.MXID, msg.MXID)
  2037. if err != nil {
  2038. return nil, fmt.Errorf("failed to fetch event %s: %w", msg.MXID, err)
  2039. }
  2040. if evt.Type == event.EventEncrypted {
  2041. err = evt.Content.ParseRaw(evt.Type)
  2042. if err != nil {
  2043. return nil, fmt.Errorf("failed to parse encrypted content in %s: %w", msg.MXID, err)
  2044. }
  2045. evt, err = portal.bridge.Crypto.Decrypt(evt)
  2046. if err != nil {
  2047. return nil, fmt.Errorf("failed to decrypt event %s: %w", msg.MXID, err)
  2048. }
  2049. }
  2050. errorMetaResult := gjson.GetBytes(evt.Content.VeryRaw, strings.ReplaceAll(failedMediaField, ".", "\\."))
  2051. if !errorMetaResult.Exists() || !errorMetaResult.IsObject() {
  2052. return nil, fmt.Errorf("didn't find failed media metadata in %s", msg.MXID)
  2053. }
  2054. var errorMetaBytes []byte
  2055. if errorMetaResult.Index > 0 {
  2056. errorMetaBytes = evt.Content.VeryRaw[errorMetaResult.Index : errorMetaResult.Index+len(errorMetaResult.Raw)]
  2057. } else {
  2058. errorMetaBytes = []byte(errorMetaResult.Raw)
  2059. }
  2060. err = json.Unmarshal(errorMetaBytes, &errorMeta)
  2061. if err != nil {
  2062. return nil, fmt.Errorf("failed to unmarshal failed media metadata in %s: %w", msg.MXID, err)
  2063. }
  2064. return errorMeta, nil
  2065. }
  2066. func (portal *Portal) handleMediaRetry(retry *events.MediaRetry, source *User) {
  2067. msg := portal.bridge.DB.Message.GetByJID(portal.Key, retry.MessageID)
  2068. if msg == nil {
  2069. portal.log.Warnfln("Dropping media retry notification for unknown message %s", retry.MessageID)
  2070. return
  2071. } else if msg.Error != database.MsgErrMediaNotFound {
  2072. portal.log.Warnfln("Dropping media retry notification for non-errored message %s / %s", retry.MessageID, msg.MXID)
  2073. return
  2074. }
  2075. meta, err := portal.fetchMediaRetryEvent(msg)
  2076. if err != nil {
  2077. portal.log.Warnfln("Can't handle media retry notification for %s: %v", retry.MessageID, err)
  2078. return
  2079. }
  2080. retryData, err := whatsmeow.DecryptMediaRetryNotification(retry, meta.Media.Key)
  2081. if err != nil {
  2082. portal.log.Warnfln("Failed to handle media retry notification for %s: %v", retry.MessageID, err)
  2083. return
  2084. } else if retryData.GetResult() != waProto.MediaRetryNotification_SUCCESS {
  2085. portal.log.Warnfln("Got error response in media retry notification for %s: %s", retry.MessageID, waProto.MediaRetryNotification_MediaRetryNotificationResultType_name[int32(retryData.GetResult())])
  2086. return
  2087. }
  2088. var puppet *Puppet
  2089. if retry.FromMe {
  2090. puppet = portal.bridge.GetPuppetByJID(source.JID)
  2091. } else if retry.ChatID.Server == types.DefaultUserServer {
  2092. puppet = portal.bridge.GetPuppetByJID(retry.ChatID)
  2093. } else {
  2094. puppet = portal.bridge.GetPuppetByJID(retry.SenderID)
  2095. }
  2096. intent := puppet.IntentFor(portal)
  2097. data, err := source.Client.DownloadMediaWithPath(retryData.GetDirectPath(), meta.Media.EncSHA256, meta.Media.SHA256, meta.Media.Key, meta.Media.Length, meta.Media.Type, "")
  2098. if err != nil {
  2099. portal.log.Warnfln("Failed to download media in %s after retry notification: %v", retry.MessageID, err)
  2100. return
  2101. }
  2102. err = portal.uploadMedia(intent, data, meta.Content)
  2103. if err != nil {
  2104. portal.log.Warnfln("Failed to re-upload media for %s after retry notification: %v", retry.MessageID, err)
  2105. return
  2106. }
  2107. replaceContent := &event.MessageEventContent{
  2108. MsgType: meta.Content.MsgType,
  2109. Body: "* " + meta.Content.Body,
  2110. NewContent: meta.Content,
  2111. RelatesTo: &event.RelatesTo{
  2112. EventID: msg.MXID,
  2113. Type: event.RelReplace,
  2114. },
  2115. }
  2116. // Move the extra content into m.new_content too
  2117. meta.ExtraContent = map[string]interface{}{
  2118. "m.new_content": shallowCopyMap(meta.ExtraContent),
  2119. }
  2120. resp, err := portal.sendMessage(intent, meta.Type, replaceContent, meta.ExtraContent, time.Now().UnixMilli())
  2121. if err != nil {
  2122. portal.log.Warnfln("Failed to edit %s after retry notification for %s: %v", msg.MXID, retry.MessageID, err)
  2123. return
  2124. }
  2125. portal.log.Debugfln("Successfully edited %s -> %s after retry notification for %s", msg.MXID, resp.EventID, retry.MessageID)
  2126. msg.UpdateMXID(resp.EventID, database.MsgNormal, database.MsgNoError)
  2127. }
  2128. func (portal *Portal) requestMediaRetry(user *User, eventID id.EventID) {
  2129. msg := portal.bridge.DB.Message.GetByMXID(eventID)
  2130. if msg == nil {
  2131. portal.log.Debugfln("%s requested a media retry for unknown event %s", user.MXID, eventID)
  2132. return
  2133. } else if msg.Error != database.MsgErrMediaNotFound {
  2134. portal.log.Debugfln("%s requested a media retry for non-errored event %s", user.MXID, eventID)
  2135. return
  2136. }
  2137. evt, err := portal.fetchMediaRetryEvent(msg)
  2138. if err != nil {
  2139. portal.log.Warnfln("Can't send media retry request for %s: %v", msg.JID, err)
  2140. return
  2141. }
  2142. err = user.Client.SendMediaRetryReceipt(&types.MessageInfo{
  2143. ID: msg.JID,
  2144. MessageSource: types.MessageSource{
  2145. IsFromMe: msg.Sender.User == user.JID.User,
  2146. IsGroup: !portal.IsPrivateChat(),
  2147. Sender: msg.Sender,
  2148. Chat: portal.Key.JID,
  2149. },
  2150. }, evt.Media.Key)
  2151. if err != nil {
  2152. portal.log.Warnfln("Failed to send media retry request for %s: %v", msg.JID, err)
  2153. } else {
  2154. portal.log.Debugfln("Sent media retry request for %s", msg.JID)
  2155. }
  2156. }
  2157. const thumbnailMaxSize = 72
  2158. const thumbnailMinSize = 24
  2159. func createJPEGThumbnailAndGetSize(source []byte) ([]byte, int, int, error) {
  2160. src, _, err := image.Decode(bytes.NewReader(source))
  2161. if err != nil {
  2162. return nil, 0, 0, fmt.Errorf("failed to decode thumbnail: %w", err)
  2163. }
  2164. imageBounds := src.Bounds()
  2165. width, height := imageBounds.Max.X, imageBounds.Max.Y
  2166. var img image.Image
  2167. if width <= thumbnailMaxSize && height <= thumbnailMaxSize {
  2168. // No need to resize
  2169. img = src
  2170. } else {
  2171. if width == height {
  2172. width = thumbnailMaxSize
  2173. height = thumbnailMaxSize
  2174. } else if width < height {
  2175. width /= height / thumbnailMaxSize
  2176. height = thumbnailMaxSize
  2177. } else {
  2178. height /= width / thumbnailMaxSize
  2179. width = thumbnailMaxSize
  2180. }
  2181. if width < thumbnailMinSize {
  2182. width = thumbnailMinSize
  2183. }
  2184. if height < thumbnailMinSize {
  2185. height = thumbnailMinSize
  2186. }
  2187. dst := image.NewRGBA(image.Rect(0, 0, width, height))
  2188. draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)
  2189. img = dst
  2190. }
  2191. var buf bytes.Buffer
  2192. err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: jpeg.DefaultQuality})
  2193. if err != nil {
  2194. return nil, width, height, fmt.Errorf("failed to re-encode thumbnail: %w", err)
  2195. }
  2196. return buf.Bytes(), width, height, nil
  2197. }
  2198. func createJPEGThumbnail(source []byte) ([]byte, error) {
  2199. data, _, _, err := createJPEGThumbnailAndGetSize(source)
  2200. return data, err
  2201. }
  2202. func (portal *Portal) downloadThumbnail(original []byte, thumbnailURL id.ContentURIString, eventID id.EventID) ([]byte, error) {
  2203. if len(thumbnailURL) == 0 {
  2204. // just fall back to making thumbnail of original
  2205. } else if mxc, err := thumbnailURL.Parse(); err != nil {
  2206. portal.log.Warnfln("Malformed thumbnail URL in %s: %v (falling back to generating thumbnail from source)", eventID, err)
  2207. } else if thumbnail, err := portal.MainIntent().DownloadBytes(mxc); err != nil {
  2208. portal.log.Warnfln("Failed to download thumbnail in %s: %v (falling back to generating thumbnail from source)", eventID, err)
  2209. } else {
  2210. return createJPEGThumbnail(thumbnail)
  2211. }
  2212. return createJPEGThumbnail(original)
  2213. }
  2214. func (portal *Portal) convertWebPtoPNG(webpImage []byte) ([]byte, error) {
  2215. webpDecoded, err := webp.Decode(bytes.NewReader(webpImage))
  2216. if err != nil {
  2217. return nil, fmt.Errorf("failed to decode webp image: %w", err)
  2218. }
  2219. var pngBuffer bytes.Buffer
  2220. if err := png.Encode(&pngBuffer, webpDecoded); err != nil {
  2221. return nil, fmt.Errorf("failed to encode png image: %w", err)
  2222. }
  2223. return pngBuffer.Bytes(), nil
  2224. }
  2225. func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, content *event.MessageEventContent, eventID id.EventID, mediaType whatsmeow.MediaType) *MediaUpload {
  2226. var caption string
  2227. var mentionedJIDs []string
  2228. if relaybotFormatted {
  2229. caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
  2230. }
  2231. var file *event.EncryptedFileInfo
  2232. rawMXC := content.URL
  2233. if content.File != nil {
  2234. file = content.File
  2235. rawMXC = file.URL
  2236. }
  2237. mxc, err := rawMXC.Parse()
  2238. if err != nil {
  2239. portal.log.Errorln("Malformed content URL in %s: %v", eventID, err)
  2240. return nil
  2241. }
  2242. data, err := portal.MainIntent().DownloadBytes(mxc)
  2243. if err != nil {
  2244. portal.log.Errorfln("Failed to download media in %s: %v", eventID, err)
  2245. return nil
  2246. }
  2247. if file != nil {
  2248. data, err = file.Decrypt(data)
  2249. if err != nil {
  2250. portal.log.Errorfln("Failed to decrypt media in %s: %v", eventID, err)
  2251. return nil
  2252. }
  2253. }
  2254. if mediaType == whatsmeow.MediaVideo && content.GetInfo().MimeType == "image/gif" {
  2255. data, err = ffmpeg.ConvertBytes(data, ".mp4", []string{"-f", "gif"}, []string{
  2256. "-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart",
  2257. "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'",
  2258. }, content.GetInfo().MimeType)
  2259. if err != nil {
  2260. portal.log.Errorfln("Failed to convert gif to mp4 in %s: %v", eventID, err)
  2261. return nil
  2262. }
  2263. content.Info.MimeType = "video/mp4"
  2264. }
  2265. if mediaType == whatsmeow.MediaImage && content.GetInfo().MimeType == "image/webp" {
  2266. data, err = portal.convertWebPtoPNG(data)
  2267. if err != nil {
  2268. portal.log.Errorfln("Failed to convert webp to png in %s: %v", eventID, err)
  2269. return nil
  2270. }
  2271. content.Info.MimeType = "image/png"
  2272. }
  2273. uploadResp, err := sender.Client.Upload(context.Background(), data, mediaType)
  2274. if err != nil {
  2275. portal.log.Errorfln("Failed to upload media in %s: %v", eventID, err)
  2276. return nil
  2277. }
  2278. // Audio doesn't have thumbnails
  2279. var thumbnail []byte
  2280. if mediaType != whatsmeow.MediaAudio {
  2281. thumbnail, err = portal.downloadThumbnail(data, content.GetInfo().ThumbnailURL, eventID)
  2282. // Ignore format errors for non-image files, we don't care about those thumbnails
  2283. if err != nil && (!errors.Is(err, image.ErrFormat) || mediaType == whatsmeow.MediaImage) {
  2284. portal.log.Errorfln("Failed to generate thumbnail for %s: %v", eventID, err)
  2285. }
  2286. }
  2287. return &MediaUpload{
  2288. UploadResponse: uploadResp,
  2289. Caption: caption,
  2290. MentionedJIDs: mentionedJIDs,
  2291. Thumbnail: thumbnail,
  2292. FileLength: len(data),
  2293. }
  2294. }
  2295. type MediaUpload struct {
  2296. whatsmeow.UploadResponse
  2297. Caption string
  2298. MentionedJIDs []string
  2299. Thumbnail []byte
  2300. FileLength int
  2301. }
  2302. func (portal *Portal) addRelaybotFormat(sender *User, content *event.MessageEventContent) bool {
  2303. member := portal.MainIntent().Member(portal.MXID, sender.MXID)
  2304. if member == nil {
  2305. member = &event.MemberEventContent{}
  2306. }
  2307. if content.Format != event.FormatHTML {
  2308. content.FormattedBody = strings.Replace(html.EscapeString(content.Body), "\n", "<br/>", -1)
  2309. content.Format = event.FormatHTML
  2310. }
  2311. data, err := portal.bridge.Config.Bridge.Relay.FormatMessage(content, sender.MXID, *member)
  2312. if err != nil {
  2313. portal.log.Errorln("Failed to apply relaybot format:", err)
  2314. }
  2315. content.FormattedBody = data
  2316. return true
  2317. }
  2318. func addCodecToMime(mimeType, codec string) string {
  2319. mediaType, params, err := mime.ParseMediaType(mimeType)
  2320. if err != nil {
  2321. return mimeType
  2322. }
  2323. if _, ok := params["codecs"]; !ok {
  2324. params["codecs"] = codec
  2325. }
  2326. return mime.FormatMediaType(mediaType, params)
  2327. }
  2328. func parseGeoURI(uri string) (lat, long float64, err error) {
  2329. if !strings.HasPrefix(uri, "geo:") {
  2330. err = fmt.Errorf("uri doesn't have geo: prefix")
  2331. return
  2332. }
  2333. // Remove geo: prefix and anything after ;
  2334. coordinates := strings.Split(strings.TrimPrefix(uri, "geo:"), ";")[0]
  2335. if splitCoordinates := strings.Split(coordinates, ","); len(splitCoordinates) != 2 {
  2336. err = fmt.Errorf("didn't find exactly two numbers separated by a comma")
  2337. } else if lat, err = strconv.ParseFloat(splitCoordinates[0], 64); err != nil {
  2338. err = fmt.Errorf("latitude is not a number: %w", err)
  2339. } else if long, err = strconv.ParseFloat(splitCoordinates[1], 64); err != nil {
  2340. err = fmt.Errorf("longitude is not a number: %w", err)
  2341. }
  2342. return
  2343. }
  2344. func getUnstableWaveform(content map[string]interface{}) []byte {
  2345. audioInfo, ok := content["org.matrix.msc1767.audio"].(map[string]interface{})
  2346. if !ok {
  2347. return nil
  2348. }
  2349. waveform, ok := audioInfo["waveform"].([]interface{})
  2350. if !ok {
  2351. return nil
  2352. }
  2353. output := make([]byte, len(waveform))
  2354. var val float64
  2355. for i, part := range waveform {
  2356. val, ok = part.(float64)
  2357. if ok {
  2358. output[i] = byte(val / 4)
  2359. }
  2360. }
  2361. return output
  2362. }
  2363. func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*waProto.Message, *User) {
  2364. content, ok := evt.Content.Parsed.(*event.MessageEventContent)
  2365. if !ok {
  2366. portal.log.Debugfln("Failed to handle event %s: unexpected parsed content type %T", evt.ID, evt.Content.Parsed)
  2367. return nil, sender
  2368. }
  2369. var msg waProto.Message
  2370. var ctxInfo waProto.ContextInfo
  2371. replyToID := content.GetReplyTo()
  2372. if len(replyToID) > 0 {
  2373. replyToMsg := portal.bridge.DB.Message.GetByMXID(replyToID)
  2374. if replyToMsg != nil && !replyToMsg.IsFakeJID() && replyToMsg.Type == database.MsgNormal {
  2375. ctxInfo.StanzaId = &replyToMsg.JID
  2376. ctxInfo.Participant = proto.String(replyToMsg.Sender.ToNonAD().String())
  2377. // Using blank content here seems to work fine on all official WhatsApp apps.
  2378. //
  2379. // We could probably invent a slightly more accurate version of the quoted message
  2380. // by fetching the Matrix event and converting it to the WhatsApp format, but that's
  2381. // a lot of work and this works fine.
  2382. ctxInfo.QuotedMessage = &waProto.Message{Conversation: proto.String("")}
  2383. }
  2384. }
  2385. if portal.ExpirationTime != 0 {
  2386. ctxInfo.Expiration = proto.Uint32(portal.ExpirationTime)
  2387. }
  2388. relaybotFormatted := false
  2389. if !sender.IsLoggedIn() || (portal.IsPrivateChat() && sender.JID.User != portal.Key.Receiver.User) {
  2390. if !portal.HasRelaybot() {
  2391. portal.log.Warnln("Ignoring message from", sender.MXID, "in chat with no relaybot (convertMatrixMessage)")
  2392. return nil, sender
  2393. }
  2394. relaybotFormatted = portal.addRelaybotFormat(sender, content)
  2395. sender = portal.GetRelayUser()
  2396. }
  2397. if evt.Type == event.EventSticker {
  2398. content.MsgType = event.MsgImage
  2399. }
  2400. if content.MsgType == event.MsgImage && content.GetInfo().MimeType == "image/gif" {
  2401. content.MsgType = event.MsgVideo
  2402. }
  2403. switch content.MsgType {
  2404. case event.MsgText, event.MsgEmote, event.MsgNotice:
  2405. text := content.Body
  2406. if content.MsgType == event.MsgNotice && !portal.bridge.Config.Bridge.BridgeNotices {
  2407. return nil, sender
  2408. }
  2409. if content.Format == event.FormatHTML {
  2410. text, ctxInfo.MentionedJid = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
  2411. }
  2412. if content.MsgType == event.MsgEmote && !relaybotFormatted {
  2413. text = "/me " + text
  2414. }
  2415. msg.ExtendedTextMessage = &waProto.ExtendedTextMessage{
  2416. Text: &text,
  2417. ContextInfo: &ctxInfo,
  2418. }
  2419. hasPreview := portal.convertURLPreviewToWhatsApp(sender, evt, msg.ExtendedTextMessage)
  2420. if ctxInfo.StanzaId == nil && ctxInfo.MentionedJid == nil && ctxInfo.Expiration == nil && !hasPreview {
  2421. // No need for extended message
  2422. msg.ExtendedTextMessage = nil
  2423. msg.Conversation = &text
  2424. }
  2425. case event.MsgImage:
  2426. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaImage)
  2427. if media == nil {
  2428. return nil, sender
  2429. }
  2430. ctxInfo.MentionedJid = media.MentionedJIDs
  2431. msg.ImageMessage = &waProto.ImageMessage{
  2432. ContextInfo: &ctxInfo,
  2433. Caption: &media.Caption,
  2434. JpegThumbnail: media.Thumbnail,
  2435. Url: &media.URL,
  2436. MediaKey: media.MediaKey,
  2437. Mimetype: &content.GetInfo().MimeType,
  2438. FileEncSha256: media.FileEncSHA256,
  2439. FileSha256: media.FileSHA256,
  2440. FileLength: proto.Uint64(uint64(media.FileLength)),
  2441. }
  2442. case event.MsgVideo:
  2443. gifPlayback := content.GetInfo().MimeType == "image/gif"
  2444. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaVideo)
  2445. if media == nil {
  2446. return nil, sender
  2447. }
  2448. duration := uint32(content.GetInfo().Duration / 1000)
  2449. ctxInfo.MentionedJid = media.MentionedJIDs
  2450. msg.VideoMessage = &waProto.VideoMessage{
  2451. ContextInfo: &ctxInfo,
  2452. Caption: &media.Caption,
  2453. JpegThumbnail: media.Thumbnail,
  2454. Url: &media.URL,
  2455. MediaKey: media.MediaKey,
  2456. Mimetype: &content.GetInfo().MimeType,
  2457. GifPlayback: &gifPlayback,
  2458. Seconds: &duration,
  2459. FileEncSha256: media.FileEncSHA256,
  2460. FileSha256: media.FileSHA256,
  2461. FileLength: proto.Uint64(uint64(media.FileLength)),
  2462. }
  2463. case event.MsgAudio:
  2464. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaAudio)
  2465. if media == nil {
  2466. return nil, sender
  2467. }
  2468. duration := uint32(content.GetInfo().Duration / 1000)
  2469. msg.AudioMessage = &waProto.AudioMessage{
  2470. ContextInfo: &ctxInfo,
  2471. Url: &media.URL,
  2472. MediaKey: media.MediaKey,
  2473. Mimetype: &content.GetInfo().MimeType,
  2474. Seconds: &duration,
  2475. FileEncSha256: media.FileEncSHA256,
  2476. FileSha256: media.FileSHA256,
  2477. FileLength: proto.Uint64(uint64(media.FileLength)),
  2478. }
  2479. _, isMSC3245Voice := evt.Content.Raw["org.matrix.msc3245.voice"]
  2480. if isMSC3245Voice {
  2481. msg.AudioMessage.Waveform = getUnstableWaveform(evt.Content.Raw)
  2482. msg.AudioMessage.Ptt = proto.Bool(true)
  2483. // hacky hack to add the codecs param that whatsapp seems to require
  2484. msg.AudioMessage.Mimetype = proto.String(addCodecToMime(content.GetInfo().MimeType, "opus"))
  2485. }
  2486. case event.MsgFile:
  2487. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaDocument)
  2488. if media == nil {
  2489. return nil, sender
  2490. }
  2491. msg.DocumentMessage = &waProto.DocumentMessage{
  2492. ContextInfo: &ctxInfo,
  2493. JpegThumbnail: media.Thumbnail,
  2494. Url: &media.URL,
  2495. Title: &content.Body,
  2496. FileName: &content.Body,
  2497. MediaKey: media.MediaKey,
  2498. Mimetype: &content.GetInfo().MimeType,
  2499. FileEncSha256: media.FileEncSHA256,
  2500. FileSha256: media.FileSHA256,
  2501. FileLength: proto.Uint64(uint64(media.FileLength)),
  2502. }
  2503. case event.MsgLocation:
  2504. lat, long, err := parseGeoURI(content.GeoURI)
  2505. if err != nil {
  2506. portal.log.Debugfln("Invalid geo URI on Matrix event %s: %v", evt.ID, err)
  2507. return nil, sender
  2508. }
  2509. msg.LocationMessage = &waProto.LocationMessage{
  2510. DegreesLatitude: &lat,
  2511. DegreesLongitude: &long,
  2512. Comment: &content.Body,
  2513. ContextInfo: &ctxInfo,
  2514. }
  2515. default:
  2516. portal.log.Debugfln("Unhandled Matrix event %s: unknown msgtype %s", evt.ID, content.MsgType)
  2517. return nil, sender
  2518. }
  2519. return &msg, sender
  2520. }
  2521. func (portal *Portal) sendErrorMessage(message string, confirmed bool) id.EventID {
  2522. certainty := "may not have been"
  2523. if confirmed {
  2524. certainty = "was not"
  2525. }
  2526. resp, err := portal.sendMainIntentMessage(&event.MessageEventContent{
  2527. MsgType: event.MsgNotice,
  2528. Body: fmt.Sprintf("\u26a0 Your message %s bridged: %v", certainty, message),
  2529. })
  2530. if err != nil {
  2531. portal.log.Warnfln("Failed to send bridging error message:", err)
  2532. return ""
  2533. }
  2534. return resp.EventID
  2535. }
  2536. func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) {
  2537. if portal.bridge.Config.Bridge.DeliveryReceipts {
  2538. err := portal.bridge.Bot.MarkRead(portal.MXID, eventID)
  2539. if err != nil {
  2540. portal.log.Debugfln("Failed to send delivery receipt for %s: %v", eventID, err)
  2541. }
  2542. }
  2543. }
  2544. func (portal *Portal) generateMessageInfo(sender *User) *types.MessageInfo {
  2545. return &types.MessageInfo{
  2546. ID: whatsmeow.GenerateMessageID(),
  2547. Timestamp: time.Now(),
  2548. MessageSource: types.MessageSource{
  2549. Sender: sender.JID,
  2550. Chat: portal.Key.JID,
  2551. IsFromMe: true,
  2552. IsGroup: portal.Key.JID.Server == types.GroupServer || portal.Key.JID.Server == types.BroadcastServer,
  2553. },
  2554. }
  2555. }
  2556. func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
  2557. if !portal.canBridgeFrom(sender, "message") {
  2558. return
  2559. }
  2560. portal.log.Debugfln("Received event %s from %s", evt.ID, evt.Sender)
  2561. msg, sender := portal.convertMatrixMessage(sender, evt)
  2562. if msg == nil {
  2563. return
  2564. }
  2565. portal.MarkDisappearing(evt.ID, portal.ExpirationTime, true)
  2566. info := portal.generateMessageInfo(sender)
  2567. dbMsg := portal.markHandled(nil, info, evt.ID, false, true, database.MsgNormal, database.MsgNoError)
  2568. portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID)
  2569. ts, err := sender.Client.SendMessage(portal.Key.JID, info.ID, msg)
  2570. if err != nil {
  2571. portal.log.Errorfln("Error sending message: %v", err)
  2572. portal.sendErrorMessage(err.Error(), true)
  2573. status := appservice.StatusPermFailure
  2574. if errors.Is(err, whatsmeow.ErrBroadcastListUnsupported) {
  2575. status = appservice.StatusUnsupported
  2576. }
  2577. checkpoint := appservice.NewMessageSendCheckpoint(evt, appservice.StepRemote, status, 0)
  2578. checkpoint.Info = err.Error()
  2579. go checkpoint.Send(portal.bridge.AS)
  2580. } else {
  2581. portal.log.Debugfln("Handled Matrix event %s", evt.ID)
  2582. portal.bridge.AS.SendMessageSendCheckpoint(evt, appservice.StepRemote, 0)
  2583. portal.sendDeliveryReceipt(evt.ID)
  2584. dbMsg.MarkSent(ts)
  2585. }
  2586. }
  2587. func (portal *Portal) HandleMatrixReaction(sender *User, evt *event.Event) {
  2588. portal.log.Debugfln("Received reaction event %s from %s", evt.ID, evt.Sender)
  2589. err := portal.handleMatrixReaction(sender, evt)
  2590. if err != nil {
  2591. portal.log.Errorfln("Error sending reaction %s: %v", evt.ID, err)
  2592. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, err, true, 0)
  2593. } else {
  2594. portal.log.Debugfln("Handled Matrix reaction %s", evt.ID)
  2595. portal.bridge.AS.SendMessageSendCheckpoint(evt, appservice.StepRemote, 0)
  2596. portal.sendDeliveryReceipt(evt.ID)
  2597. }
  2598. }
  2599. func (portal *Portal) handleMatrixReaction(sender *User, evt *event.Event) error {
  2600. content, ok := evt.Content.Parsed.(*event.ReactionEventContent)
  2601. if !ok {
  2602. return fmt.Errorf("unexpected parsed content type %T", evt.Content.Parsed)
  2603. }
  2604. target := portal.bridge.DB.Message.GetByMXID(content.RelatesTo.EventID)
  2605. if target == nil || target.Type == database.MsgReaction {
  2606. return fmt.Errorf("unknown target event %s", content.RelatesTo.EventID)
  2607. }
  2608. info := portal.generateMessageInfo(sender)
  2609. dbMsg := portal.markHandled(nil, info, evt.ID, false, true, database.MsgReaction, database.MsgNoError)
  2610. portal.upsertReaction(nil, target.JID, sender.JID, evt.ID, info.ID)
  2611. portal.log.Debugln("Sending reaction", evt.ID, "to WhatsApp", info.ID)
  2612. ts, err := portal.sendReactionToWhatsApp(sender, info.ID, target, content.RelatesTo.Key, evt.Timestamp)
  2613. if err != nil {
  2614. dbMsg.MarkSent(ts)
  2615. }
  2616. return err
  2617. }
  2618. func (portal *Portal) sendReactionToWhatsApp(sender *User, id types.MessageID, target *database.Message, key string, timestamp int64) (time.Time, error) {
  2619. var messageKeyParticipant *string
  2620. if !portal.IsPrivateChat() {
  2621. messageKeyParticipant = proto.String(target.Sender.ToNonAD().String())
  2622. }
  2623. key = variationselector.Remove(key)
  2624. return sender.Client.SendMessage(portal.Key.JID, id, &waProto.Message{
  2625. ReactionMessage: &waProto.ReactionMessage{
  2626. Key: &waProto.MessageKey{
  2627. RemoteJid: proto.String(portal.Key.JID.String()),
  2628. FromMe: proto.Bool(target.Sender.User == sender.JID.User),
  2629. Id: proto.String(target.JID),
  2630. Participant: messageKeyParticipant,
  2631. },
  2632. Text: proto.String(key),
  2633. GroupingKey: proto.String(key), // TODO is this correct?
  2634. SenderTimestampMs: proto.Int64(timestamp),
  2635. },
  2636. })
  2637. }
  2638. func (portal *Portal) upsertReaction(intent *appservice.IntentAPI, targetJID types.MessageID, senderJID types.JID, mxid id.EventID, jid types.MessageID) {
  2639. dbReaction := portal.bridge.DB.Reaction.GetByTargetJID(portal.Key, targetJID, senderJID)
  2640. if dbReaction == nil {
  2641. dbReaction = portal.bridge.DB.Reaction.New()
  2642. dbReaction.Chat = portal.Key
  2643. dbReaction.TargetJID = targetJID
  2644. dbReaction.Sender = senderJID
  2645. } else {
  2646. portal.log.Debugfln("Redacting old Matrix reaction %s after new one (%s) was sent", dbReaction.MXID, mxid)
  2647. var err error
  2648. if intent != nil {
  2649. extra := make(map[string]interface{})
  2650. if intent.IsCustomPuppet {
  2651. extra[doublePuppetKey] = doublePuppetValue
  2652. }
  2653. _, err = intent.RedactEvent(portal.MXID, dbReaction.MXID, mautrix.ReqRedact{Extra: extra})
  2654. }
  2655. if intent == nil || errors.Is(err, mautrix.MForbidden) {
  2656. _, err = portal.MainIntent().RedactEvent(portal.MXID, dbReaction.MXID)
  2657. }
  2658. if err != nil {
  2659. portal.log.Warnfln("Failed to remove old reaction %s: %v", dbReaction.MXID, err)
  2660. }
  2661. }
  2662. dbReaction.MXID = mxid
  2663. dbReaction.JID = jid
  2664. dbReaction.Upsert()
  2665. }
  2666. func (portal *Portal) HandleMatrixRedaction(sender *User, evt *event.Event) {
  2667. if !portal.canBridgeFrom(sender, "redaction") {
  2668. return
  2669. }
  2670. portal.log.Debugfln("Received redaction %s from %s", evt.ID, evt.Sender)
  2671. senderLogIdentifier := sender.MXID
  2672. if !sender.HasSession() {
  2673. sender = portal.GetRelayUser()
  2674. senderLogIdentifier += " (through relaybot)"
  2675. }
  2676. msg := portal.bridge.DB.Message.GetByMXID(evt.Redacts)
  2677. if msg == nil {
  2678. portal.log.Debugfln("Ignoring redaction %s of unknown event by %s", evt.ID, senderLogIdentifier)
  2679. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("target not found"), true, 0)
  2680. return
  2681. } else if msg.IsFakeJID() {
  2682. portal.log.Debugfln("Ignoring redaction %s of fake event by %s", evt.ID, senderLogIdentifier)
  2683. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("target is a fake event"), true, 0)
  2684. return
  2685. } else if msg.Sender.User != sender.JID.User {
  2686. portal.log.Debugfln("Ignoring redaction %s of %s/%s by %s: message was sent by someone else (%s, not %s)", evt.ID, msg.MXID, msg.JID, senderLogIdentifier, msg.Sender, sender.JID)
  2687. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("message was sent by someone else"), true, 0)
  2688. return
  2689. }
  2690. var err error
  2691. if msg.Type == database.MsgReaction {
  2692. if reaction := portal.bridge.DB.Reaction.GetByMXID(evt.Redacts); reaction == nil {
  2693. portal.log.Debugfln("Ignoring redaction of reaction %s: reaction database entry not found", evt.ID)
  2694. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("reaction database entry not found"), true, 0)
  2695. return
  2696. } else if reactionTarget := reaction.GetTarget(); reactionTarget == nil {
  2697. portal.log.Debugfln("Ignoring redaction of reaction %s: reaction target message not found", evt.ID)
  2698. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("reaction target message not found"), true, 0)
  2699. return
  2700. } else {
  2701. portal.log.Debugfln("Sending redaction reaction %s of %s/%s to WhatsApp", evt.ID, msg.MXID, msg.JID)
  2702. _, err = portal.sendReactionToWhatsApp(sender, "", reactionTarget, "", evt.Timestamp)
  2703. }
  2704. } else {
  2705. portal.log.Debugfln("Sending redaction %s of %s/%s to WhatsApp", evt.ID, msg.MXID, msg.JID)
  2706. _, err = sender.Client.RevokeMessage(portal.Key.JID, msg.JID)
  2707. }
  2708. if err != nil {
  2709. portal.log.Errorfln("Error handling Matrix redaction %s: %v", evt.ID, err)
  2710. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, err, true, 0)
  2711. } else {
  2712. portal.log.Debugfln("Handled Matrix redaction %s of %s", evt.ID, evt.Redacts)
  2713. portal.bridge.AS.SendMessageSendCheckpoint(evt, appservice.StepRemote, 0)
  2714. portal.sendDeliveryReceipt(evt.ID)
  2715. }
  2716. }
  2717. func (portal *Portal) HandleMatrixReadReceipt(sender *User, eventID id.EventID, receiptTimestamp time.Time, isExplicit bool) {
  2718. if !sender.IsLoggedIn() {
  2719. if isExplicit {
  2720. portal.log.Debugfln("Ignoring read receipt by %s: user is not connected to WhatsApp", sender.JID)
  2721. }
  2722. return
  2723. }
  2724. maxTimestamp := receiptTimestamp
  2725. // Implicit read receipts don't have an event ID that's already bridged
  2726. if isExplicit {
  2727. if message := portal.bridge.DB.Message.GetByMXID(eventID); message != nil {
  2728. maxTimestamp = message.Timestamp
  2729. }
  2730. }
  2731. prevTimestamp := sender.GetLastReadTS(portal.Key)
  2732. lastReadIsZero := false
  2733. if prevTimestamp.IsZero() {
  2734. prevTimestamp = maxTimestamp.Add(-2 * time.Second)
  2735. lastReadIsZero = true
  2736. }
  2737. messages := portal.bridge.DB.Message.GetMessagesBetween(portal.Key, prevTimestamp, maxTimestamp)
  2738. if len(messages) > 0 {
  2739. sender.SetLastReadTS(portal.Key, messages[len(messages)-1].Timestamp)
  2740. }
  2741. groupedMessages := make(map[types.JID][]types.MessageID)
  2742. for _, msg := range messages {
  2743. var key types.JID
  2744. if msg.IsFakeJID() || msg.Sender.User == sender.JID.User {
  2745. // Don't send read receipts for own messages or fake messages
  2746. continue
  2747. } else if !portal.IsPrivateChat() {
  2748. key = msg.Sender
  2749. } else if !msg.BroadcastListJID.IsEmpty() {
  2750. key = msg.BroadcastListJID
  2751. } // else: blank key (participant field isn't needed in direct chat read receipts)
  2752. groupedMessages[key] = append(groupedMessages[key], msg.JID)
  2753. }
  2754. // For explicit read receipts, log even if there are no targets. For implicit ones only log when there are targets
  2755. if len(groupedMessages) > 0 || isExplicit {
  2756. portal.log.Debugfln("Sending read receipts by %s (last read: %d, was zero: %t, explicit: %t): %v",
  2757. sender.JID, prevTimestamp.Unix(), lastReadIsZero, isExplicit, groupedMessages)
  2758. }
  2759. for messageSender, ids := range groupedMessages {
  2760. chatJID := portal.Key.JID
  2761. if messageSender.Server == types.BroadcastServer {
  2762. chatJID = messageSender
  2763. messageSender = portal.Key.JID
  2764. }
  2765. err := sender.Client.MarkRead(ids, receiptTimestamp, chatJID, messageSender)
  2766. if err != nil {
  2767. portal.log.Warnfln("Failed to mark %v as read by %s: %v", ids, sender.JID, err)
  2768. }
  2769. }
  2770. if isExplicit {
  2771. portal.ScheduleDisappearing()
  2772. }
  2773. }
  2774. func typingDiff(prev, new []id.UserID) (started, stopped []id.UserID) {
  2775. OuterNew:
  2776. for _, userID := range new {
  2777. for _, previousUserID := range prev {
  2778. if userID == previousUserID {
  2779. continue OuterNew
  2780. }
  2781. }
  2782. started = append(started, userID)
  2783. }
  2784. OuterPrev:
  2785. for _, userID := range prev {
  2786. for _, previousUserID := range new {
  2787. if userID == previousUserID {
  2788. continue OuterPrev
  2789. }
  2790. }
  2791. stopped = append(stopped, userID)
  2792. }
  2793. return
  2794. }
  2795. func (portal *Portal) setTyping(userIDs []id.UserID, state types.ChatPresence) {
  2796. for _, userID := range userIDs {
  2797. user := portal.bridge.GetUserByMXIDIfExists(userID)
  2798. if user == nil || !user.IsLoggedIn() {
  2799. continue
  2800. }
  2801. portal.log.Debugfln("Bridging typing change from %s to chat presence %s", state, user.MXID)
  2802. err := user.Client.SendChatPresence(portal.Key.JID, state, types.ChatPresenceMediaText)
  2803. if err != nil {
  2804. portal.log.Warnln("Error sending chat presence:", err)
  2805. }
  2806. if portal.bridge.Config.Bridge.SendPresenceOnTyping {
  2807. err = user.Client.SendPresence(types.PresenceAvailable)
  2808. if err != nil {
  2809. user.log.Warnln("Failed to set presence:", err)
  2810. }
  2811. }
  2812. }
  2813. }
  2814. func (portal *Portal) HandleMatrixTyping(newTyping []id.UserID) {
  2815. portal.currentlyTypingLock.Lock()
  2816. defer portal.currentlyTypingLock.Unlock()
  2817. startedTyping, stoppedTyping := typingDiff(portal.currentlyTyping, newTyping)
  2818. portal.currentlyTyping = newTyping
  2819. portal.setTyping(startedTyping, types.ChatPresenceComposing)
  2820. portal.setTyping(stoppedTyping, types.ChatPresencePaused)
  2821. }
  2822. func (portal *Portal) canBridgeFrom(sender *User, evtType string) bool {
  2823. if !sender.IsLoggedIn() {
  2824. if portal.HasRelaybot() {
  2825. return true
  2826. } else if sender.Session != nil {
  2827. portal.log.Debugfln("Ignoring %s from %s as user is not connected", evtType, sender.MXID)
  2828. msg := format.RenderMarkdown(fmt.Sprintf("\u26a0 You are not connected to WhatsApp, so your %s was not bridged.", evtType), true, false)
  2829. msg.MsgType = event.MsgNotice
  2830. _, err := portal.sendMainIntentMessage(&msg)
  2831. if err != nil {
  2832. portal.log.Errorln("Failed to send bridging failure message:", err)
  2833. }
  2834. } else {
  2835. portal.log.Debugfln("Ignoring %s from non-logged-in user %s in chat with no relay user", evtType, sender.MXID)
  2836. }
  2837. return false
  2838. } else if portal.IsPrivateChat() && sender.JID.User != portal.Key.Receiver.User && !portal.HasRelaybot() {
  2839. portal.log.Debugfln("Ignoring %s from different user %s/%s in private chat with no relay user", evtType, sender.MXID, sender.JID)
  2840. return false
  2841. }
  2842. return true
  2843. }
  2844. func (portal *Portal) Delete() {
  2845. portal.Portal.Delete()
  2846. portal.bridge.portalsLock.Lock()
  2847. delete(portal.bridge.portalsByJID, portal.Key)
  2848. if len(portal.MXID) > 0 {
  2849. delete(portal.bridge.portalsByMXID, portal.MXID)
  2850. }
  2851. portal.bridge.portalsLock.Unlock()
  2852. }
  2853. func (portal *Portal) GetMatrixUsers() ([]id.UserID, error) {
  2854. members, err := portal.MainIntent().JoinedMembers(portal.MXID)
  2855. if err != nil {
  2856. return nil, fmt.Errorf("failed to get member list: %w", err)
  2857. }
  2858. var users []id.UserID
  2859. for userID := range members.Joined {
  2860. _, isPuppet := portal.bridge.ParsePuppetMXID(userID)
  2861. if !isPuppet && userID != portal.bridge.Bot.UserID {
  2862. users = append(users, userID)
  2863. }
  2864. }
  2865. return users, nil
  2866. }
  2867. func (portal *Portal) CleanupIfEmpty() {
  2868. users, err := portal.GetMatrixUsers()
  2869. if err != nil {
  2870. portal.log.Errorfln("Failed to get Matrix user list to determine if portal needs to be cleaned up: %v", err)
  2871. return
  2872. }
  2873. if len(users) == 0 {
  2874. portal.log.Infoln("Room seems to be empty, cleaning up...")
  2875. portal.Delete()
  2876. portal.Cleanup(false)
  2877. }
  2878. }
  2879. func (portal *Portal) Cleanup(puppetsOnly bool) {
  2880. if len(portal.MXID) == 0 {
  2881. return
  2882. }
  2883. if portal.IsPrivateChat() {
  2884. _, err := portal.MainIntent().LeaveRoom(portal.MXID)
  2885. if err != nil {
  2886. portal.log.Warnln("Failed to leave private chat portal with main intent:", err)
  2887. }
  2888. return
  2889. }
  2890. intent := portal.MainIntent()
  2891. members, err := intent.JoinedMembers(portal.MXID)
  2892. if err != nil {
  2893. portal.log.Errorln("Failed to get portal members for cleanup:", err)
  2894. return
  2895. }
  2896. for member := range members.Joined {
  2897. if member == intent.UserID {
  2898. continue
  2899. }
  2900. puppet := portal.bridge.GetPuppetByMXID(member)
  2901. if puppet != nil {
  2902. _, err = puppet.DefaultIntent().LeaveRoom(portal.MXID)
  2903. if err != nil {
  2904. portal.log.Errorln("Error leaving as puppet while cleaning up portal:", err)
  2905. }
  2906. } else if !puppetsOnly {
  2907. _, err = intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: member, Reason: "Deleting portal"})
  2908. if err != nil {
  2909. portal.log.Errorln("Error kicking user while cleaning up portal:", err)
  2910. }
  2911. }
  2912. }
  2913. _, err = intent.LeaveRoom(portal.MXID)
  2914. if err != nil {
  2915. portal.log.Errorln("Error leaving with main intent while cleaning up portal:", err)
  2916. }
  2917. }
  2918. func (portal *Portal) HandleMatrixLeave(sender *User) {
  2919. if portal.IsPrivateChat() {
  2920. portal.log.Debugln("User left private chat portal, cleaning up and deleting...")
  2921. portal.Delete()
  2922. portal.Cleanup(false)
  2923. return
  2924. } else if portal.bridge.Config.Bridge.BridgeMatrixLeave {
  2925. err := sender.Client.LeaveGroup(portal.Key.JID)
  2926. if err != nil {
  2927. portal.log.Errorfln("Failed to leave group as %s: %v", sender.MXID, err)
  2928. return
  2929. }
  2930. //portal.log.Infoln("Leave response:", <-resp)
  2931. }
  2932. portal.CleanupIfEmpty()
  2933. }
  2934. func (portal *Portal) HandleMatrixKick(sender *User, target *Puppet) {
  2935. _, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
  2936. target.JID: whatsmeow.ParticipantChangeRemove,
  2937. })
  2938. if err != nil {
  2939. portal.log.Errorfln("Failed to kick %s from group as %s: %v", target.JID, sender.MXID, err)
  2940. return
  2941. }
  2942. //portal.log.Infoln("Kick %s response: %s", puppet.JID, <-resp)
  2943. }
  2944. func (portal *Portal) HandleMatrixInvite(sender *User, target *Puppet) {
  2945. _, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
  2946. target.JID: whatsmeow.ParticipantChangeAdd,
  2947. })
  2948. if err != nil {
  2949. portal.log.Errorfln("Failed to add %s to group as %s: %v", target.JID, sender.MXID, err)
  2950. return
  2951. }
  2952. //portal.log.Infofln("Add %s response: %s", puppet.JID, <-resp)
  2953. }
  2954. func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
  2955. switch content := evt.Content.Parsed.(type) {
  2956. case *event.RoomNameEventContent:
  2957. if content.Name == portal.Name {
  2958. return
  2959. }
  2960. portal.Name = content.Name
  2961. err := sender.Client.SetGroupName(portal.Key.JID, content.Name)
  2962. if err != nil {
  2963. portal.log.Errorln("Failed to update group name:", err)
  2964. }
  2965. case *event.TopicEventContent:
  2966. if content.Topic == portal.Topic {
  2967. return
  2968. }
  2969. portal.Topic = content.Topic
  2970. err := sender.Client.SetGroupTopic(portal.Key.JID, "", "", content.Topic)
  2971. if err != nil {
  2972. portal.log.Errorln("Failed to update group description:", err)
  2973. }
  2974. case *event.RoomAvatarEventContent:
  2975. portal.avatarLock.Lock()
  2976. defer portal.avatarLock.Unlock()
  2977. if content.URL == portal.AvatarURL || (content.URL.IsEmpty() && portal.Avatar == "remove") {
  2978. return
  2979. }
  2980. var data []byte
  2981. var err error
  2982. if !content.URL.IsEmpty() {
  2983. data, err = portal.MainIntent().DownloadBytes(content.URL)
  2984. if err != nil {
  2985. portal.log.Errorfln("Failed to download updated avatar %s: %v", content.URL, err)
  2986. return
  2987. }
  2988. portal.log.Debugfln("%s set the group avatar to %s", sender.MXID, content.URL)
  2989. } else {
  2990. portal.log.Debugfln("%s removed the group avatar", sender.MXID)
  2991. }
  2992. newID, err := sender.Client.SetGroupPhoto(portal.Key.JID, data)
  2993. if err != nil {
  2994. portal.log.Errorfln("Failed to update group avatar: %v", err)
  2995. return
  2996. }
  2997. portal.log.Debugfln("Successfully updated group avatar to %s", newID)
  2998. portal.Avatar = newID
  2999. portal.AvatarURL = content.URL
  3000. portal.UpdateBridgeInfo()
  3001. portal.Update()
  3002. }
  3003. }