portal.go 107 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244
  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. if err != nil {
  1127. portal.log.Warnfln("Failed to get group info through %s: %v", user.JID, err)
  1128. } else {
  1129. groupInfo = foundInfo
  1130. isFullInfo = true
  1131. }
  1132. }
  1133. if groupInfo != nil {
  1134. portal.Name = groupInfo.Name
  1135. portal.Topic = groupInfo.Topic
  1136. }
  1137. portal.UpdateAvatar(user, types.EmptyJID, false)
  1138. }
  1139. bridgeInfoStateKey, bridgeInfo := portal.getBridgeInfo()
  1140. initialState := []*event.Event{{
  1141. Type: event.StatePowerLevels,
  1142. Content: event.Content{
  1143. Parsed: portal.GetBasePowerLevels(),
  1144. },
  1145. }, {
  1146. Type: event.StateBridge,
  1147. Content: event.Content{Parsed: bridgeInfo},
  1148. StateKey: &bridgeInfoStateKey,
  1149. }, {
  1150. // TODO remove this once https://github.com/matrix-org/matrix-doc/pull/2346 is in spec
  1151. Type: event.StateHalfShotBridge,
  1152. Content: event.Content{Parsed: bridgeInfo},
  1153. StateKey: &bridgeInfoStateKey,
  1154. }}
  1155. if !portal.AvatarURL.IsEmpty() {
  1156. initialState = append(initialState, &event.Event{
  1157. Type: event.StateRoomAvatar,
  1158. Content: event.Content{
  1159. Parsed: event.RoomAvatarEventContent{URL: portal.AvatarURL},
  1160. },
  1161. })
  1162. }
  1163. var invite []id.UserID
  1164. if portal.bridge.Config.Bridge.Encryption.Default {
  1165. initialState = append(initialState, &event.Event{
  1166. Type: event.StateEncryption,
  1167. Content: event.Content{
  1168. Parsed: event.EncryptionEventContent{Algorithm: id.AlgorithmMegolmV1},
  1169. },
  1170. })
  1171. portal.Encrypted = true
  1172. if portal.IsPrivateChat() {
  1173. invite = append(invite, portal.bridge.Bot.UserID)
  1174. }
  1175. }
  1176. creationContent := make(map[string]interface{})
  1177. if !portal.bridge.Config.Bridge.FederateRooms {
  1178. creationContent["m.federate"] = false
  1179. }
  1180. resp, err := intent.CreateRoom(&mautrix.ReqCreateRoom{
  1181. Visibility: "private",
  1182. Name: portal.Name,
  1183. Topic: portal.Topic,
  1184. Invite: invite,
  1185. Preset: "private_chat",
  1186. IsDirect: portal.IsPrivateChat(),
  1187. InitialState: initialState,
  1188. CreationContent: creationContent,
  1189. })
  1190. if err != nil {
  1191. return err
  1192. }
  1193. portal.MXID = resp.RoomID
  1194. portal.Update()
  1195. portal.bridge.portalsLock.Lock()
  1196. portal.bridge.portalsByMXID[portal.MXID] = portal
  1197. portal.bridge.portalsLock.Unlock()
  1198. // We set the memberships beforehand to make sure the encryption key exchange in initial backfill knows the users are here.
  1199. for _, userID := range invite {
  1200. portal.bridge.StateStore.SetMembership(portal.MXID, userID, event.MembershipInvite)
  1201. }
  1202. portal.ensureUserInvited(user)
  1203. user.syncChatDoublePuppetDetails(portal, true)
  1204. go portal.addToSpace(user)
  1205. if groupInfo != nil {
  1206. portal.SyncParticipants(user, groupInfo)
  1207. if groupInfo.IsAnnounce {
  1208. portal.RestrictMessageSending(groupInfo.IsAnnounce)
  1209. }
  1210. if groupInfo.IsLocked {
  1211. portal.RestrictMetadataChanges(groupInfo.IsLocked)
  1212. }
  1213. }
  1214. //if broadcastMetadata != nil {
  1215. // portal.SyncBroadcastRecipients(user, broadcastMetadata)
  1216. //}
  1217. if portal.IsPrivateChat() {
  1218. puppet := user.bridge.GetPuppetByJID(portal.Key.JID)
  1219. if portal.bridge.Config.Bridge.Encryption.Default {
  1220. err = portal.bridge.Bot.EnsureJoined(portal.MXID)
  1221. if err != nil {
  1222. portal.log.Errorln("Failed to join created portal with bridge bot for e2be:", err)
  1223. }
  1224. }
  1225. user.UpdateDirectChats(map[id.UserID][]id.RoomID{puppet.MXID: {portal.MXID}})
  1226. }
  1227. firstEventResp, err := portal.MainIntent().SendMessageEvent(portal.MXID, PortalCreationDummyEvent, struct{}{})
  1228. if err != nil {
  1229. portal.log.Errorln("Failed to send dummy event to mark portal creation:", err)
  1230. } else {
  1231. portal.FirstEventID = firstEventResp.EventID
  1232. portal.Update()
  1233. }
  1234. if user.bridge.Config.Bridge.HistorySync.Backfill && backfill {
  1235. var priorityCounter int
  1236. user.EnqueueImmedateBackfill(portal, &priorityCounter)
  1237. user.EnqueueDeferredBackfills(portal, &priorityCounter)
  1238. user.EnqueueMediaBackfills(portal, &priorityCounter)
  1239. user.BackfillQueue.ReCheckQueue <- true
  1240. }
  1241. return nil
  1242. }
  1243. func (portal *Portal) addToSpace(user *User) {
  1244. spaceID := user.GetSpaceRoom()
  1245. if len(spaceID) == 0 || user.IsInSpace(portal.Key) {
  1246. return
  1247. }
  1248. _, err := portal.bridge.Bot.SendStateEvent(spaceID, event.StateSpaceChild, portal.MXID.String(), &event.SpaceChildEventContent{
  1249. Via: []string{portal.bridge.Config.Homeserver.Domain},
  1250. })
  1251. if err != nil {
  1252. portal.log.Errorfln("Failed to add room to %s's personal filtering space (%s): %v", user.MXID, spaceID, err)
  1253. } else {
  1254. portal.log.Debugfln("Added room to %s's personal filtering space (%s)", user.MXID, spaceID)
  1255. user.MarkInSpace(portal.Key)
  1256. }
  1257. }
  1258. func (portal *Portal) IsPrivateChat() bool {
  1259. return portal.Key.JID.Server == types.DefaultUserServer
  1260. }
  1261. func (portal *Portal) IsGroupChat() bool {
  1262. return portal.Key.JID.Server == types.GroupServer
  1263. }
  1264. func (portal *Portal) IsBroadcastList() bool {
  1265. return portal.Key.JID.Server == types.BroadcastServer
  1266. }
  1267. func (portal *Portal) IsStatusBroadcastList() bool {
  1268. return portal.Key.JID == types.StatusBroadcastJID
  1269. }
  1270. func (portal *Portal) HasRelaybot() bool {
  1271. return portal.bridge.Config.Bridge.Relay.Enabled && len(portal.RelayUserID) > 0
  1272. }
  1273. func (portal *Portal) GetRelayUser() *User {
  1274. if !portal.HasRelaybot() {
  1275. return nil
  1276. } else if portal.relayUser == nil {
  1277. portal.relayUser = portal.bridge.GetUserByMXID(portal.RelayUserID)
  1278. }
  1279. return portal.relayUser
  1280. }
  1281. func (portal *Portal) MainIntent() *appservice.IntentAPI {
  1282. if portal.IsPrivateChat() {
  1283. return portal.bridge.GetPuppetByJID(portal.Key.JID).DefaultIntent()
  1284. }
  1285. return portal.bridge.Bot
  1286. }
  1287. func (portal *Portal) SetReply(content *event.MessageEventContent, replyToID types.MessageID) bool {
  1288. if len(replyToID) == 0 {
  1289. return false
  1290. }
  1291. message := portal.bridge.DB.Message.GetByJID(portal.Key, replyToID)
  1292. if message == nil || message.IsFakeMXID() {
  1293. return false
  1294. }
  1295. evt, err := portal.MainIntent().GetEvent(portal.MXID, message.MXID)
  1296. if err != nil {
  1297. portal.log.Warnln("Failed to get reply target:", err)
  1298. content.RelatesTo = &event.RelatesTo{
  1299. EventID: message.MXID,
  1300. Type: event.RelReply,
  1301. }
  1302. return true
  1303. }
  1304. _ = evt.Content.ParseRaw(evt.Type)
  1305. if evt.Type == event.EventEncrypted {
  1306. decryptedEvt, err := portal.bridge.Crypto.Decrypt(evt)
  1307. if err != nil {
  1308. portal.log.Warnln("Failed to decrypt reply target:", err)
  1309. } else {
  1310. evt = decryptedEvt
  1311. }
  1312. }
  1313. content.SetReply(evt)
  1314. return true
  1315. }
  1316. type sendReactionContent struct {
  1317. event.ReactionEventContent
  1318. DoublePuppet string `json:"fi.mau.double_puppet_source,omitempty"`
  1319. }
  1320. func (portal *Portal) HandleMessageReaction(intent *appservice.IntentAPI, user *User, info *types.MessageInfo, reaction *waProto.ReactionMessage, existingMsg *database.Message) {
  1321. if existingMsg != nil {
  1322. _, _ = portal.MainIntent().RedactEvent(portal.MXID, existingMsg.MXID, mautrix.ReqRedact{
  1323. Reason: "The undecryptable message was actually a reaction",
  1324. })
  1325. }
  1326. targetJID := reaction.GetKey().GetId()
  1327. if reaction.GetText() == "" {
  1328. existing := portal.bridge.DB.Reaction.GetByTargetJID(portal.Key, targetJID, info.Sender)
  1329. if existing == nil {
  1330. portal.log.Debugfln("Dropping removal %s of unknown reaction to %s from %s", info.ID, targetJID, info.Sender)
  1331. return
  1332. }
  1333. extra := make(map[string]interface{})
  1334. if intent.IsCustomPuppet {
  1335. extra[doublePuppetKey] = doublePuppetValue
  1336. }
  1337. resp, err := intent.RedactEvent(portal.MXID, existing.MXID, mautrix.ReqRedact{Extra: extra})
  1338. if err != nil {
  1339. portal.log.Errorfln("Failed to redact reaction %s/%s from %s to %s: %v", existing.MXID, existing.JID, info.Sender, targetJID, err)
  1340. }
  1341. portal.finishHandling(existingMsg, info, resp.EventID, database.MsgReaction, database.MsgNoError)
  1342. existing.Delete()
  1343. } else {
  1344. target := portal.bridge.DB.Message.GetByJID(portal.Key, targetJID)
  1345. if target == nil {
  1346. portal.log.Debugfln("Dropping reaction %s from %s to unknown message %s", info.ID, info.Sender, targetJID)
  1347. return
  1348. }
  1349. var content sendReactionContent
  1350. content.RelatesTo = event.RelatesTo{
  1351. Type: event.RelAnnotation,
  1352. EventID: target.MXID,
  1353. Key: variationselector.Add(reaction.GetText()),
  1354. }
  1355. if intent.IsCustomPuppet {
  1356. content.DoublePuppet = doublePuppetValue
  1357. }
  1358. resp, err := intent.SendMassagedMessageEvent(portal.MXID, event.EventReaction, &content, info.Timestamp.UnixMilli())
  1359. if err != nil {
  1360. portal.log.Errorfln("Failed to bridge reaction %s from %s to %s: %v", info.ID, info.Sender, target.JID, err)
  1361. return
  1362. }
  1363. portal.finishHandling(existingMsg, info, resp.EventID, database.MsgReaction, database.MsgNoError)
  1364. portal.upsertReaction(intent, target.JID, info.Sender, resp.EventID, info.ID)
  1365. }
  1366. }
  1367. func (portal *Portal) HandleMessageRevoke(user *User, info *types.MessageInfo, key *waProto.MessageKey) bool {
  1368. msg := portal.bridge.DB.Message.GetByJID(portal.Key, key.GetId())
  1369. if msg == nil || msg.IsFakeMXID() {
  1370. return false
  1371. }
  1372. intent := portal.bridge.GetPuppetByJID(info.Sender).IntentFor(portal)
  1373. redactionReq := mautrix.ReqRedact{Extra: map[string]interface{}{}}
  1374. if intent.IsCustomPuppet {
  1375. redactionReq.Extra[doublePuppetKey] = doublePuppetValue
  1376. }
  1377. _, err := intent.RedactEvent(portal.MXID, msg.MXID, redactionReq)
  1378. if err != nil {
  1379. if errors.Is(err, mautrix.MForbidden) {
  1380. _, err = portal.MainIntent().RedactEvent(portal.MXID, msg.MXID, redactionReq)
  1381. if err != nil {
  1382. portal.log.Errorln("Failed to redact %s: %v", msg.JID, err)
  1383. }
  1384. }
  1385. } else {
  1386. msg.Delete()
  1387. }
  1388. return true
  1389. }
  1390. func (portal *Portal) sendMainIntentMessage(content *event.MessageEventContent) (*mautrix.RespSendEvent, error) {
  1391. return portal.sendMessage(portal.MainIntent(), event.EventMessage, content, nil, 0)
  1392. }
  1393. func (portal *Portal) encrypt(content *event.Content, eventType event.Type) (event.Type, error) {
  1394. if portal.Encrypted && portal.bridge.Crypto != nil {
  1395. // TODO maybe the locking should be inside mautrix-go?
  1396. portal.encryptLock.Lock()
  1397. encrypted, err := portal.bridge.Crypto.Encrypt(portal.MXID, eventType, *content)
  1398. portal.encryptLock.Unlock()
  1399. if err != nil {
  1400. return eventType, fmt.Errorf("failed to encrypt event: %w", err)
  1401. }
  1402. eventType = event.EventEncrypted
  1403. content.Parsed = encrypted
  1404. }
  1405. return eventType, nil
  1406. }
  1407. const doublePuppetKey = "fi.mau.double_puppet_source"
  1408. const doublePuppetValue = "mautrix-whatsapp"
  1409. func (portal *Portal) sendMessage(intent *appservice.IntentAPI, eventType event.Type, content *event.MessageEventContent, extraContent map[string]interface{}, timestamp int64) (*mautrix.RespSendEvent, error) {
  1410. wrappedContent := event.Content{Parsed: content, Raw: extraContent}
  1411. if timestamp != 0 && intent.IsCustomPuppet {
  1412. if wrappedContent.Raw == nil {
  1413. wrappedContent.Raw = map[string]interface{}{}
  1414. }
  1415. if intent.IsCustomPuppet {
  1416. wrappedContent.Raw[doublePuppetKey] = doublePuppetValue
  1417. }
  1418. }
  1419. var err error
  1420. eventType, err = portal.encrypt(&wrappedContent, eventType)
  1421. if err != nil {
  1422. return nil, err
  1423. }
  1424. if eventType == event.EventEncrypted {
  1425. // Clear other custom keys if the event was encrypted, but keep the double puppet identifier
  1426. if intent.IsCustomPuppet {
  1427. wrappedContent.Raw = map[string]interface{}{doublePuppetKey: doublePuppetValue}
  1428. } else {
  1429. wrappedContent.Raw = nil
  1430. }
  1431. }
  1432. _, _ = intent.UserTyping(portal.MXID, false, 0)
  1433. if timestamp == 0 {
  1434. return intent.SendMessageEvent(portal.MXID, eventType, &wrappedContent)
  1435. } else {
  1436. return intent.SendMassagedMessageEvent(portal.MXID, eventType, &wrappedContent, timestamp)
  1437. }
  1438. }
  1439. type ConvertedMessage struct {
  1440. Intent *appservice.IntentAPI
  1441. Type event.Type
  1442. Content *event.MessageEventContent
  1443. Extra map[string]interface{}
  1444. Caption *event.MessageEventContent
  1445. MultiEvent []*event.MessageEventContent
  1446. ReplyTo types.MessageID
  1447. ExpiresIn uint32
  1448. Error database.MessageErrorType
  1449. }
  1450. func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, source *User, msg *waProto.Message) *ConvertedMessage {
  1451. content := &event.MessageEventContent{
  1452. Body: msg.GetConversation(),
  1453. MsgType: event.MsgText,
  1454. }
  1455. if len(msg.GetExtendedTextMessage().GetText()) > 0 {
  1456. content.Body = msg.GetExtendedTextMessage().GetText()
  1457. }
  1458. contextInfo := msg.GetExtendedTextMessage().GetContextInfo()
  1459. portal.bridge.Formatter.ParseWhatsApp(portal.MXID, content, contextInfo.GetMentionedJid())
  1460. replyTo := contextInfo.GetStanzaId()
  1461. expiresIn := contextInfo.GetExpiration()
  1462. extraAttrs := map[string]interface{}{}
  1463. extraAttrs["com.beeper.linkpreviews"] = portal.convertURLPreviewToBeeper(intent, source, msg.GetExtendedTextMessage())
  1464. return &ConvertedMessage{
  1465. Intent: intent,
  1466. Type: event.EventMessage,
  1467. Content: content,
  1468. ReplyTo: replyTo,
  1469. ExpiresIn: expiresIn,
  1470. Extra: extraAttrs,
  1471. }
  1472. }
  1473. func (portal *Portal) convertLiveLocationMessage(intent *appservice.IntentAPI, msg *waProto.LiveLocationMessage) *ConvertedMessage {
  1474. content := &event.MessageEventContent{
  1475. Body: "Started sharing live location",
  1476. MsgType: event.MsgNotice,
  1477. }
  1478. if len(msg.GetCaption()) > 0 {
  1479. content.Body += ": " + msg.GetCaption()
  1480. }
  1481. return &ConvertedMessage{
  1482. Intent: intent,
  1483. Type: event.EventMessage,
  1484. Content: content,
  1485. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1486. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1487. }
  1488. }
  1489. func (portal *Portal) convertLocationMessage(intent *appservice.IntentAPI, msg *waProto.LocationMessage) *ConvertedMessage {
  1490. url := msg.GetUrl()
  1491. if len(url) == 0 {
  1492. url = fmt.Sprintf("https://maps.google.com/?q=%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude())
  1493. }
  1494. name := msg.GetName()
  1495. if len(name) == 0 {
  1496. latChar := 'N'
  1497. if msg.GetDegreesLatitude() < 0 {
  1498. latChar = 'S'
  1499. }
  1500. longChar := 'E'
  1501. if msg.GetDegreesLongitude() < 0 {
  1502. longChar = 'W'
  1503. }
  1504. name = fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(msg.GetDegreesLatitude()), latChar, math.Abs(msg.GetDegreesLongitude()), longChar)
  1505. }
  1506. content := &event.MessageEventContent{
  1507. MsgType: event.MsgLocation,
  1508. Body: fmt.Sprintf("Location: %s\n%s\n%s", name, msg.GetAddress(), url),
  1509. Format: event.FormatHTML,
  1510. FormattedBody: fmt.Sprintf("Location: <a href='%s'>%s</a><br>%s", url, name, msg.GetAddress()),
  1511. GeoURI: fmt.Sprintf("geo:%.5f,%.5f", msg.GetDegreesLatitude(), msg.GetDegreesLongitude()),
  1512. }
  1513. if len(msg.GetJpegThumbnail()) > 0 {
  1514. thumbnailMime := http.DetectContentType(msg.GetJpegThumbnail())
  1515. uploadedThumbnail, _ := intent.UploadBytes(msg.GetJpegThumbnail(), thumbnailMime)
  1516. if uploadedThumbnail != nil {
  1517. cfg, _, _ := image.DecodeConfig(bytes.NewReader(msg.GetJpegThumbnail()))
  1518. content.Info = &event.FileInfo{
  1519. ThumbnailInfo: &event.FileInfo{
  1520. Size: len(msg.GetJpegThumbnail()),
  1521. Width: cfg.Width,
  1522. Height: cfg.Height,
  1523. MimeType: thumbnailMime,
  1524. },
  1525. ThumbnailURL: uploadedThumbnail.ContentURI.CUString(),
  1526. }
  1527. }
  1528. }
  1529. return &ConvertedMessage{
  1530. Intent: intent,
  1531. Type: event.EventMessage,
  1532. Content: content,
  1533. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1534. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1535. }
  1536. }
  1537. 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.`
  1538. const inviteMetaField = "fi.mau.whatsapp.invite"
  1539. const escapedInviteMetaField = `fi\.mau\.whatsapp\.invite`
  1540. type InviteMeta struct {
  1541. JID types.JID `json:"jid"`
  1542. Code string `json:"code"`
  1543. Expiration int64 `json:"expiration,string"`
  1544. Inviter types.JID `json:"inviter"`
  1545. }
  1546. func (portal *Portal) convertGroupInviteMessage(intent *appservice.IntentAPI, info *types.MessageInfo, msg *waProto.GroupInviteMessage) *ConvertedMessage {
  1547. expiry := time.Unix(msg.GetInviteExpiration(), 0)
  1548. htmlMessage := fmt.Sprintf(inviteMsg, html.EscapeString(msg.GetCaption()), msg.GetGroupName(), expiry)
  1549. content := &event.MessageEventContent{
  1550. MsgType: event.MsgText,
  1551. Body: format.HTMLToText(htmlMessage),
  1552. Format: event.FormatHTML,
  1553. FormattedBody: htmlMessage,
  1554. }
  1555. groupJID, err := types.ParseJID(msg.GetGroupJid())
  1556. if err != nil {
  1557. portal.log.Errorfln("Failed to parse invite group JID: %v", err)
  1558. }
  1559. extraAttrs := map[string]interface{}{
  1560. inviteMetaField: InviteMeta{
  1561. JID: groupJID,
  1562. Code: msg.GetInviteCode(),
  1563. Expiration: msg.GetInviteExpiration(),
  1564. Inviter: info.Sender.ToNonAD(),
  1565. },
  1566. }
  1567. return &ConvertedMessage{
  1568. Intent: intent,
  1569. Type: event.EventMessage,
  1570. Content: content,
  1571. Extra: extraAttrs,
  1572. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1573. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1574. }
  1575. }
  1576. func (portal *Portal) convertContactMessage(intent *appservice.IntentAPI, msg *waProto.ContactMessage) *ConvertedMessage {
  1577. fileName := fmt.Sprintf("%s.vcf", msg.GetDisplayName())
  1578. data := []byte(msg.GetVcard())
  1579. mimeType := "text/vcard"
  1580. data, uploadMimeType, file := portal.encryptFile(data, mimeType)
  1581. uploadResp, err := intent.UploadBytesWithName(data, uploadMimeType, fileName)
  1582. if err != nil {
  1583. portal.log.Errorfln("Failed to upload vcard of %s: %v", msg.GetDisplayName(), err)
  1584. return nil
  1585. }
  1586. content := &event.MessageEventContent{
  1587. Body: fileName,
  1588. MsgType: event.MsgFile,
  1589. File: file,
  1590. Info: &event.FileInfo{
  1591. MimeType: mimeType,
  1592. Size: len(msg.GetVcard()),
  1593. },
  1594. }
  1595. if content.File != nil {
  1596. content.File.URL = uploadResp.ContentURI.CUString()
  1597. } else {
  1598. content.URL = uploadResp.ContentURI.CUString()
  1599. }
  1600. return &ConvertedMessage{
  1601. Intent: intent,
  1602. Type: event.EventMessage,
  1603. Content: content,
  1604. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1605. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1606. }
  1607. }
  1608. func (portal *Portal) convertContactsArrayMessage(intent *appservice.IntentAPI, msg *waProto.ContactsArrayMessage) *ConvertedMessage {
  1609. name := msg.GetDisplayName()
  1610. if len(name) == 0 {
  1611. name = fmt.Sprintf("%d contacts", len(msg.GetContacts()))
  1612. }
  1613. contacts := make([]*event.MessageEventContent, 0, len(msg.GetContacts()))
  1614. for _, contact := range msg.GetContacts() {
  1615. converted := portal.convertContactMessage(intent, contact)
  1616. if converted != nil {
  1617. contacts = append(contacts, converted.Content)
  1618. }
  1619. }
  1620. return &ConvertedMessage{
  1621. Intent: intent,
  1622. Type: event.EventMessage,
  1623. Content: &event.MessageEventContent{
  1624. MsgType: event.MsgNotice,
  1625. Body: fmt.Sprintf("Sent %s", name),
  1626. },
  1627. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1628. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1629. MultiEvent: contacts,
  1630. }
  1631. }
  1632. func (portal *Portal) tryKickUser(userID id.UserID, intent *appservice.IntentAPI) error {
  1633. _, err := intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
  1634. if err != nil {
  1635. httpErr, ok := err.(mautrix.HTTPError)
  1636. if ok && httpErr.RespError != nil && httpErr.RespError.ErrCode == "M_FORBIDDEN" {
  1637. _, err = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: userID})
  1638. }
  1639. }
  1640. return err
  1641. }
  1642. func (portal *Portal) removeUser(isSameUser bool, kicker *appservice.IntentAPI, target id.UserID, targetIntent *appservice.IntentAPI) {
  1643. if !isSameUser || targetIntent == nil {
  1644. err := portal.tryKickUser(target, kicker)
  1645. if err != nil {
  1646. portal.log.Warnfln("Failed to kick %s from %s: %v", target, portal.MXID, err)
  1647. if targetIntent != nil {
  1648. _, _ = portal.leaveWithPuppetMeta(targetIntent)
  1649. }
  1650. }
  1651. } else {
  1652. _, err := portal.leaveWithPuppetMeta(targetIntent)
  1653. if err != nil {
  1654. portal.log.Warnfln("Failed to leave portal as %s: %v", target, err)
  1655. _, _ = portal.MainIntent().KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: target})
  1656. }
  1657. }
  1658. }
  1659. func (portal *Portal) HandleWhatsAppKick(source *User, senderJID types.JID, jids []types.JID) {
  1660. sender := portal.bridge.GetPuppetByJID(senderJID)
  1661. senderIntent := sender.IntentFor(portal)
  1662. for _, jid := range jids {
  1663. //if source != nil && source.JID.User == jid.User {
  1664. // portal.log.Debugln("Ignoring self-kick by", source.MXID)
  1665. // continue
  1666. //}
  1667. puppet := portal.bridge.GetPuppetByJID(jid)
  1668. portal.removeUser(puppet.JID == sender.JID, senderIntent, puppet.MXID, puppet.DefaultIntent())
  1669. if !portal.IsBroadcastList() {
  1670. user := portal.bridge.GetUserByJID(jid)
  1671. if user != nil {
  1672. var customIntent *appservice.IntentAPI
  1673. if puppet.CustomMXID == user.MXID {
  1674. customIntent = puppet.CustomIntent()
  1675. }
  1676. portal.removeUser(puppet.JID == sender.JID, senderIntent, user.MXID, customIntent)
  1677. }
  1678. }
  1679. }
  1680. }
  1681. func (portal *Portal) leaveWithPuppetMeta(intent *appservice.IntentAPI) (*mautrix.RespSendEvent, error) {
  1682. content := event.Content{
  1683. Parsed: event.MemberEventContent{
  1684. Membership: event.MembershipLeave,
  1685. },
  1686. Raw: map[string]interface{}{
  1687. doublePuppetKey: doublePuppetValue,
  1688. },
  1689. }
  1690. // Bypass IntentAPI, we don't want to EnsureJoined here
  1691. return intent.Client.SendStateEvent(portal.MXID, event.StateMember, intent.UserID.String(), &content)
  1692. }
  1693. func (portal *Portal) HandleWhatsAppInvite(source *User, senderJID *types.JID, jids []types.JID) (evtID id.EventID) {
  1694. intent := portal.MainIntent()
  1695. if senderJID != nil && !senderJID.IsEmpty() {
  1696. sender := portal.bridge.GetPuppetByJID(*senderJID)
  1697. intent = sender.IntentFor(portal)
  1698. }
  1699. for _, jid := range jids {
  1700. puppet := portal.bridge.GetPuppetByJID(jid)
  1701. puppet.SyncContact(source, true, "handling whatsapp invite")
  1702. content := event.Content{
  1703. Parsed: event.MemberEventContent{
  1704. Membership: "invite",
  1705. Displayname: puppet.Displayname,
  1706. AvatarURL: puppet.AvatarURL.CUString(),
  1707. },
  1708. Raw: map[string]interface{}{
  1709. doublePuppetKey: doublePuppetValue,
  1710. },
  1711. }
  1712. resp, err := intent.SendStateEvent(portal.MXID, event.StateMember, puppet.MXID.String(), &content)
  1713. if err != nil {
  1714. portal.log.Warnfln("Failed to invite %s as %s: %v", puppet.MXID, intent.UserID, err)
  1715. _ = portal.MainIntent().EnsureInvited(portal.MXID, puppet.MXID)
  1716. } else {
  1717. evtID = resp.EventID
  1718. }
  1719. err = puppet.DefaultIntent().EnsureJoined(portal.MXID)
  1720. if err != nil {
  1721. portal.log.Errorfln("Failed to ensure %s is joined: %v", puppet.MXID, err)
  1722. }
  1723. }
  1724. return
  1725. }
  1726. const failedMediaField = "fi.mau.whatsapp.failed_media"
  1727. type FailedMediaKeys struct {
  1728. Key []byte `json:"key"`
  1729. Length int `json:"length"`
  1730. Type whatsmeow.MediaType `json:"type"`
  1731. SHA256 []byte `json:"sha256"`
  1732. EncSHA256 []byte `json:"enc_sha256"`
  1733. }
  1734. type FailedMediaMeta struct {
  1735. Type event.Type `json:"type"`
  1736. Content *event.MessageEventContent `json:"content"`
  1737. ExtraContent map[string]interface{} `json:"extra_content,omitempty"`
  1738. Media FailedMediaKeys `json:"whatsapp_media"`
  1739. }
  1740. func shallowCopyMap(data map[string]interface{}) map[string]interface{} {
  1741. newMap := make(map[string]interface{}, len(data))
  1742. for key, value := range data {
  1743. newMap[key] = value
  1744. }
  1745. return newMap
  1746. }
  1747. func (portal *Portal) makeMediaBridgeFailureMessage(info *types.MessageInfo, bridgeErr error, converted *ConvertedMessage, keys *FailedMediaKeys, userFriendlyError string) *ConvertedMessage {
  1748. portal.log.Errorfln("Failed to bridge media for %s: %v", info.ID, bridgeErr)
  1749. if keys != nil {
  1750. meta := &FailedMediaMeta{
  1751. Type: converted.Type,
  1752. Content: converted.Content,
  1753. ExtraContent: shallowCopyMap(converted.Extra),
  1754. Media: *keys,
  1755. }
  1756. converted.Extra[failedMediaField] = meta
  1757. portal.mediaErrorCache[info.ID] = meta
  1758. }
  1759. converted.Type = event.EventMessage
  1760. body := userFriendlyError
  1761. if body == "" {
  1762. body = fmt.Sprintf("Failed to bridge media: %v", bridgeErr)
  1763. }
  1764. converted.Content = &event.MessageEventContent{
  1765. MsgType: event.MsgNotice,
  1766. Body: body,
  1767. }
  1768. return converted
  1769. }
  1770. func (portal *Portal) encryptFile(data []byte, mimeType string) ([]byte, string, *event.EncryptedFileInfo) {
  1771. if !portal.Encrypted {
  1772. return data, mimeType, nil
  1773. }
  1774. file := &event.EncryptedFileInfo{
  1775. EncryptedFile: *attachment.NewEncryptedFile(),
  1776. URL: "",
  1777. }
  1778. return file.Encrypt(data), "application/octet-stream", file
  1779. }
  1780. type MediaMessage interface {
  1781. whatsmeow.DownloadableMessage
  1782. GetContextInfo() *waProto.ContextInfo
  1783. GetFileLength() uint64
  1784. GetMimetype() string
  1785. }
  1786. type MediaMessageWithThumbnail interface {
  1787. MediaMessage
  1788. GetJpegThumbnail() []byte
  1789. }
  1790. type MediaMessageWithCaption interface {
  1791. MediaMessage
  1792. GetCaption() string
  1793. }
  1794. type MediaMessageWithDimensions interface {
  1795. MediaMessage
  1796. GetHeight() uint32
  1797. GetWidth() uint32
  1798. }
  1799. type MediaMessageWithFileName interface {
  1800. MediaMessage
  1801. GetFileName() string
  1802. }
  1803. type MediaMessageWithDuration interface {
  1804. MediaMessage
  1805. GetSeconds() uint32
  1806. }
  1807. func (portal *Portal) convertMediaMessageContent(intent *appservice.IntentAPI, msg MediaMessage) *ConvertedMessage {
  1808. content := &event.MessageEventContent{
  1809. Info: &event.FileInfo{
  1810. MimeType: msg.GetMimetype(),
  1811. Size: int(msg.GetFileLength()),
  1812. },
  1813. }
  1814. extraContent := map[string]interface{}{}
  1815. messageWithDimensions, ok := msg.(MediaMessageWithDimensions)
  1816. if ok {
  1817. content.Info.Width = int(messageWithDimensions.GetWidth())
  1818. content.Info.Height = int(messageWithDimensions.GetHeight())
  1819. }
  1820. msgWithName, ok := msg.(MediaMessageWithFileName)
  1821. if ok && len(msgWithName.GetFileName()) > 0 {
  1822. content.Body = msgWithName.GetFileName()
  1823. } else {
  1824. mimeClass := strings.Split(msg.GetMimetype(), "/")[0]
  1825. switch mimeClass {
  1826. case "application":
  1827. content.Body = "file"
  1828. default:
  1829. content.Body = mimeClass
  1830. }
  1831. content.Body += util.ExtensionFromMimetype(msg.GetMimetype())
  1832. }
  1833. msgWithDuration, ok := msg.(MediaMessageWithDuration)
  1834. if ok {
  1835. content.Info.Duration = int(msgWithDuration.GetSeconds()) * 1000
  1836. }
  1837. videoMessage, ok := msg.(*waProto.VideoMessage)
  1838. var isGIF bool
  1839. if ok && videoMessage.GetGifPlayback() {
  1840. isGIF = true
  1841. extraContent["info"] = map[string]interface{}{
  1842. "fi.mau.loop": true,
  1843. "fi.mau.autoplay": true,
  1844. "fi.mau.hide_controls": true,
  1845. "fi.mau.no_audio": true,
  1846. }
  1847. }
  1848. messageWithThumbnail, ok := msg.(MediaMessageWithThumbnail)
  1849. if ok && messageWithThumbnail.GetJpegThumbnail() != nil && (portal.bridge.Config.Bridge.WhatsappThumbnail || isGIF) {
  1850. thumbnailData := messageWithThumbnail.GetJpegThumbnail()
  1851. thumbnailMime := http.DetectContentType(thumbnailData)
  1852. thumbnailCfg, _, _ := image.DecodeConfig(bytes.NewReader(thumbnailData))
  1853. thumbnailSize := len(thumbnailData)
  1854. thumbnail, thumbnailUploadMime, thumbnailFile := portal.encryptFile(thumbnailData, thumbnailMime)
  1855. uploadedThumbnail, err := intent.UploadBytes(thumbnail, thumbnailUploadMime)
  1856. if err != nil {
  1857. portal.log.Warnfln("Failed to upload thumbnail: %v", err)
  1858. } else if uploadedThumbnail != nil {
  1859. if thumbnailFile != nil {
  1860. thumbnailFile.URL = uploadedThumbnail.ContentURI.CUString()
  1861. content.Info.ThumbnailFile = thumbnailFile
  1862. } else {
  1863. content.Info.ThumbnailURL = uploadedThumbnail.ContentURI.CUString()
  1864. }
  1865. content.Info.ThumbnailInfo = &event.FileInfo{
  1866. Size: thumbnailSize,
  1867. Width: thumbnailCfg.Width,
  1868. Height: thumbnailCfg.Height,
  1869. MimeType: thumbnailMime,
  1870. }
  1871. }
  1872. }
  1873. _, isSticker := msg.(*waProto.StickerMessage)
  1874. switch strings.ToLower(strings.Split(msg.GetMimetype(), "/")[0]) {
  1875. case "image":
  1876. if !isSticker {
  1877. content.MsgType = event.MsgImage
  1878. }
  1879. case "video":
  1880. content.MsgType = event.MsgVideo
  1881. case "audio":
  1882. content.MsgType = event.MsgAudio
  1883. default:
  1884. content.MsgType = event.MsgFile
  1885. }
  1886. eventType := event.EventMessage
  1887. if isSticker {
  1888. eventType = event.EventSticker
  1889. }
  1890. audioMessage, ok := msg.(*waProto.AudioMessage)
  1891. if ok {
  1892. var waveform []int
  1893. if audioMessage.Waveform != nil {
  1894. waveform = make([]int, len(audioMessage.Waveform))
  1895. max := 0
  1896. for i, part := range audioMessage.Waveform {
  1897. waveform[i] = int(part)
  1898. if waveform[i] > max {
  1899. max = waveform[i]
  1900. }
  1901. }
  1902. multiplier := 0
  1903. if max > 0 {
  1904. multiplier = 1024 / max
  1905. }
  1906. if multiplier > 32 {
  1907. multiplier = 32
  1908. }
  1909. for i := range waveform {
  1910. waveform[i] *= multiplier
  1911. }
  1912. }
  1913. extraContent["org.matrix.msc1767.audio"] = map[string]interface{}{
  1914. "duration": int(audioMessage.GetSeconds()) * 1000,
  1915. "waveform": waveform,
  1916. }
  1917. if audioMessage.GetPtt() {
  1918. extraContent["org.matrix.msc3245.voice"] = map[string]interface{}{}
  1919. }
  1920. }
  1921. messageWithCaption, ok := msg.(MediaMessageWithCaption)
  1922. var captionContent *event.MessageEventContent
  1923. if ok && len(messageWithCaption.GetCaption()) > 0 {
  1924. captionContent = &event.MessageEventContent{
  1925. Body: messageWithCaption.GetCaption(),
  1926. MsgType: event.MsgNotice,
  1927. }
  1928. portal.bridge.Formatter.ParseWhatsApp(portal.MXID, captionContent, msg.GetContextInfo().GetMentionedJid())
  1929. }
  1930. return &ConvertedMessage{
  1931. Intent: intent,
  1932. Type: eventType,
  1933. Content: content,
  1934. Caption: captionContent,
  1935. ReplyTo: msg.GetContextInfo().GetStanzaId(),
  1936. ExpiresIn: msg.GetContextInfo().GetExpiration(),
  1937. Extra: extraContent,
  1938. }
  1939. }
  1940. func (portal *Portal) uploadMedia(intent *appservice.IntentAPI, data []byte, content *event.MessageEventContent) error {
  1941. data, uploadMimeType, file := portal.encryptFile(data, content.Info.MimeType)
  1942. req := mautrix.ReqUploadMedia{
  1943. ContentBytes: data,
  1944. ContentType: uploadMimeType,
  1945. }
  1946. var mxc id.ContentURI
  1947. if portal.bridge.Config.Homeserver.AsyncMedia {
  1948. uploaded, err := intent.UnstableUploadAsync(req)
  1949. if err != nil {
  1950. return err
  1951. }
  1952. mxc = uploaded.ContentURI
  1953. } else {
  1954. uploaded, err := intent.UploadMedia(req)
  1955. if err != nil {
  1956. return err
  1957. }
  1958. mxc = uploaded.ContentURI
  1959. }
  1960. if file != nil {
  1961. file.URL = mxc.CUString()
  1962. content.File = file
  1963. } else {
  1964. content.URL = mxc.CUString()
  1965. }
  1966. content.Info.Size = len(data)
  1967. if content.Info.Width == 0 && content.Info.Height == 0 && strings.HasPrefix(content.Info.MimeType, "image/") {
  1968. cfg, _, _ := image.DecodeConfig(bytes.NewReader(data))
  1969. content.Info.Width, content.Info.Height = cfg.Width, cfg.Height
  1970. }
  1971. return nil
  1972. }
  1973. func (portal *Portal) convertMediaMessage(intent *appservice.IntentAPI, source *User, info *types.MessageInfo, msg MediaMessage) *ConvertedMessage {
  1974. converted := portal.convertMediaMessageContent(intent, msg)
  1975. data, err := source.Client.Download(msg)
  1976. if errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith404) || errors.Is(err, whatsmeow.ErrMediaDownloadFailedWith410) {
  1977. //portal.log.Warnfln("Failed to download media for %s: %v. Requesting retry", info.ID, err)
  1978. //err = source.Client.SendMediaRetryReceipt(info, msg.GetMediaKey())
  1979. //if err != nil {
  1980. // portal.log.Errorfln("Failed to send media retry receipt for %s: %v", info.ID, err)
  1981. //}
  1982. converted.Error = database.MsgErrMediaNotFound
  1983. errorText := "Old photo or attachment."
  1984. if portal.bridge.Config.Bridge.HistorySync.BackfillMedia {
  1985. if len(portal.bridge.Config.Bridge.HistorySync.Media) > 0 {
  1986. errorText += " Media will be requested from your phone later."
  1987. } else {
  1988. 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.`
  1989. }
  1990. } else {
  1991. 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.`
  1992. }
  1993. return portal.makeMediaBridgeFailureMessage(info, err, converted, &FailedMediaKeys{
  1994. Key: msg.GetMediaKey(),
  1995. Length: int(msg.GetFileLength()),
  1996. Type: whatsmeow.GetMediaType(msg),
  1997. SHA256: msg.GetFileSha256(),
  1998. EncSHA256: msg.GetFileEncSha256(),
  1999. }, errorText)
  2000. } else if errors.Is(err, whatsmeow.ErrNoURLPresent) {
  2001. portal.log.Debugfln("No URL present error for media message %s, ignoring...", info.ID)
  2002. return nil
  2003. } else if errors.Is(err, whatsmeow.ErrFileLengthMismatch) || errors.Is(err, whatsmeow.ErrInvalidMediaSHA256) {
  2004. portal.log.Warnfln("Mismatching media checksums in %s: %v. Ignoring because WhatsApp seems to ignore them too", info.ID, err)
  2005. } else if err != nil {
  2006. return portal.makeMediaBridgeFailureMessage(info, err, converted, nil, "")
  2007. }
  2008. err = portal.uploadMedia(intent, data, converted.Content)
  2009. if err != nil {
  2010. if errors.Is(err, mautrix.MTooLarge) {
  2011. return portal.makeMediaBridgeFailureMessage(info, errors.New("homeserver rejected too large file"), converted, nil, "")
  2012. } else if httpErr, ok := err.(mautrix.HTTPError); ok && httpErr.IsStatus(413) {
  2013. return portal.makeMediaBridgeFailureMessage(info, errors.New("proxy rejected too large file"), converted, nil, "")
  2014. } else {
  2015. return portal.makeMediaBridgeFailureMessage(info, fmt.Errorf("failed to upload media: %w", err), converted, nil, "")
  2016. }
  2017. }
  2018. return converted
  2019. }
  2020. func (portal *Portal) fetchMediaRetryEvent(msg *database.Message) (*FailedMediaMeta, error) {
  2021. errorMeta, ok := portal.mediaErrorCache[msg.JID]
  2022. if ok {
  2023. return errorMeta, nil
  2024. }
  2025. evt, err := portal.MainIntent().GetEvent(portal.MXID, msg.MXID)
  2026. if err != nil {
  2027. return nil, fmt.Errorf("failed to fetch event %s: %w", msg.MXID, err)
  2028. }
  2029. if evt.Type == event.EventEncrypted {
  2030. err = evt.Content.ParseRaw(evt.Type)
  2031. if err != nil {
  2032. return nil, fmt.Errorf("failed to parse encrypted content in %s: %w", msg.MXID, err)
  2033. }
  2034. evt, err = portal.bridge.Crypto.Decrypt(evt)
  2035. if err != nil {
  2036. return nil, fmt.Errorf("failed to decrypt event %s: %w", msg.MXID, err)
  2037. }
  2038. }
  2039. errorMetaResult := gjson.GetBytes(evt.Content.VeryRaw, strings.ReplaceAll(failedMediaField, ".", "\\."))
  2040. if !errorMetaResult.Exists() || !errorMetaResult.IsObject() {
  2041. return nil, fmt.Errorf("didn't find failed media metadata in %s", msg.MXID)
  2042. }
  2043. var errorMetaBytes []byte
  2044. if errorMetaResult.Index > 0 {
  2045. errorMetaBytes = evt.Content.VeryRaw[errorMetaResult.Index : errorMetaResult.Index+len(errorMetaResult.Raw)]
  2046. } else {
  2047. errorMetaBytes = []byte(errorMetaResult.Raw)
  2048. }
  2049. err = json.Unmarshal(errorMetaBytes, &errorMeta)
  2050. if err != nil {
  2051. return nil, fmt.Errorf("failed to unmarshal failed media metadata in %s: %w", msg.MXID, err)
  2052. }
  2053. return errorMeta, nil
  2054. }
  2055. func (portal *Portal) handleMediaRetry(retry *events.MediaRetry, source *User) {
  2056. msg := portal.bridge.DB.Message.GetByJID(portal.Key, retry.MessageID)
  2057. if msg == nil {
  2058. portal.log.Warnfln("Dropping media retry notification for unknown message %s", retry.MessageID)
  2059. return
  2060. } else if msg.Error != database.MsgErrMediaNotFound {
  2061. portal.log.Warnfln("Dropping media retry notification for non-errored message %s / %s", retry.MessageID, msg.MXID)
  2062. return
  2063. }
  2064. meta, err := portal.fetchMediaRetryEvent(msg)
  2065. if err != nil {
  2066. portal.log.Warnfln("Can't handle media retry notification for %s: %v", retry.MessageID, err)
  2067. return
  2068. }
  2069. retryData, err := whatsmeow.DecryptMediaRetryNotification(retry, meta.Media.Key)
  2070. if err != nil {
  2071. portal.log.Warnfln("Failed to handle media retry notification for %s: %v", retry.MessageID, err)
  2072. return
  2073. } else if retryData.GetResult() != waProto.MediaRetryNotification_SUCCESS {
  2074. portal.log.Warnfln("Got error response in media retry notification for %s: %s", retry.MessageID, waProto.MediaRetryNotification_MediaRetryNotificationResultType_name[int32(retryData.GetResult())])
  2075. return
  2076. }
  2077. var puppet *Puppet
  2078. if retry.FromMe {
  2079. puppet = portal.bridge.GetPuppetByJID(source.JID)
  2080. } else if retry.ChatID.Server == types.DefaultUserServer {
  2081. puppet = portal.bridge.GetPuppetByJID(retry.ChatID)
  2082. } else {
  2083. puppet = portal.bridge.GetPuppetByJID(retry.SenderID)
  2084. }
  2085. intent := puppet.IntentFor(portal)
  2086. data, err := source.Client.DownloadMediaWithPath(retryData.GetDirectPath(), meta.Media.EncSHA256, meta.Media.SHA256, meta.Media.Key, meta.Media.Length, meta.Media.Type, "")
  2087. if err != nil {
  2088. portal.log.Warnfln("Failed to download media in %s after retry notification: %v", retry.MessageID, err)
  2089. return
  2090. }
  2091. err = portal.uploadMedia(intent, data, meta.Content)
  2092. if err != nil {
  2093. portal.log.Warnfln("Failed to re-upload media for %s after retry notification: %v", retry.MessageID, err)
  2094. return
  2095. }
  2096. replaceContent := &event.MessageEventContent{
  2097. MsgType: meta.Content.MsgType,
  2098. Body: "* " + meta.Content.Body,
  2099. NewContent: meta.Content,
  2100. RelatesTo: &event.RelatesTo{
  2101. EventID: msg.MXID,
  2102. Type: event.RelReplace,
  2103. },
  2104. }
  2105. // Move the extra content into m.new_content too
  2106. meta.ExtraContent = map[string]interface{}{
  2107. "m.new_content": shallowCopyMap(meta.ExtraContent),
  2108. }
  2109. resp, err := portal.sendMessage(intent, meta.Type, replaceContent, meta.ExtraContent, time.Now().UnixMilli())
  2110. if err != nil {
  2111. portal.log.Warnfln("Failed to edit %s after retry notification for %s: %v", msg.MXID, retry.MessageID, err)
  2112. return
  2113. }
  2114. portal.log.Debugfln("Successfully edited %s -> %s after retry notification for %s", msg.MXID, resp.EventID, retry.MessageID)
  2115. msg.UpdateMXID(resp.EventID, database.MsgNormal, database.MsgNoError)
  2116. }
  2117. func (portal *Portal) requestMediaRetry(user *User, eventID id.EventID) {
  2118. msg := portal.bridge.DB.Message.GetByMXID(eventID)
  2119. if msg == nil {
  2120. portal.log.Debugfln("%s requested a media retry for unknown event %s", user.MXID, eventID)
  2121. return
  2122. } else if msg.Error != database.MsgErrMediaNotFound {
  2123. portal.log.Debugfln("%s requested a media retry for non-errored event %s", user.MXID, eventID)
  2124. return
  2125. }
  2126. evt, err := portal.fetchMediaRetryEvent(msg)
  2127. if err != nil {
  2128. portal.log.Warnfln("Can't send media retry request for %s: %v", msg.JID, err)
  2129. return
  2130. }
  2131. err = user.Client.SendMediaRetryReceipt(&types.MessageInfo{
  2132. ID: msg.JID,
  2133. MessageSource: types.MessageSource{
  2134. IsFromMe: msg.Sender.User == user.JID.User,
  2135. IsGroup: !portal.IsPrivateChat(),
  2136. Sender: msg.Sender,
  2137. Chat: portal.Key.JID,
  2138. },
  2139. }, evt.Media.Key)
  2140. if err != nil {
  2141. portal.log.Warnfln("Failed to send media retry request for %s: %v", msg.JID, err)
  2142. } else {
  2143. portal.log.Debugfln("Sent media retry request for %s", msg.JID)
  2144. }
  2145. }
  2146. const thumbnailMaxSize = 72
  2147. const thumbnailMinSize = 24
  2148. func createJPEGThumbnailAndGetSize(source []byte) ([]byte, int, int, error) {
  2149. src, _, err := image.Decode(bytes.NewReader(source))
  2150. if err != nil {
  2151. return nil, 0, 0, fmt.Errorf("failed to decode thumbnail: %w", err)
  2152. }
  2153. imageBounds := src.Bounds()
  2154. width, height := imageBounds.Max.X, imageBounds.Max.Y
  2155. var img image.Image
  2156. if width <= thumbnailMaxSize && height <= thumbnailMaxSize {
  2157. // No need to resize
  2158. img = src
  2159. } else {
  2160. if width == height {
  2161. width = thumbnailMaxSize
  2162. height = thumbnailMaxSize
  2163. } else if width < height {
  2164. width /= height / thumbnailMaxSize
  2165. height = thumbnailMaxSize
  2166. } else {
  2167. height /= width / thumbnailMaxSize
  2168. width = thumbnailMaxSize
  2169. }
  2170. if width < thumbnailMinSize {
  2171. width = thumbnailMinSize
  2172. }
  2173. if height < thumbnailMinSize {
  2174. height = thumbnailMinSize
  2175. }
  2176. dst := image.NewRGBA(image.Rect(0, 0, width, height))
  2177. draw.NearestNeighbor.Scale(dst, dst.Rect, src, src.Bounds(), draw.Over, nil)
  2178. img = dst
  2179. }
  2180. var buf bytes.Buffer
  2181. err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: jpeg.DefaultQuality})
  2182. if err != nil {
  2183. return nil, width, height, fmt.Errorf("failed to re-encode thumbnail: %w", err)
  2184. }
  2185. return buf.Bytes(), width, height, nil
  2186. }
  2187. func createJPEGThumbnail(source []byte) ([]byte, error) {
  2188. data, _, _, err := createJPEGThumbnailAndGetSize(source)
  2189. return data, err
  2190. }
  2191. func (portal *Portal) downloadThumbnail(original []byte, thumbnailURL id.ContentURIString, eventID id.EventID) ([]byte, error) {
  2192. if len(thumbnailURL) == 0 {
  2193. // just fall back to making thumbnail of original
  2194. } else if mxc, err := thumbnailURL.Parse(); err != nil {
  2195. portal.log.Warnfln("Malformed thumbnail URL in %s: %v (falling back to generating thumbnail from source)", eventID, err)
  2196. } else if thumbnail, err := portal.MainIntent().DownloadBytes(mxc); err != nil {
  2197. portal.log.Warnfln("Failed to download thumbnail in %s: %v (falling back to generating thumbnail from source)", eventID, err)
  2198. } else {
  2199. return createJPEGThumbnail(thumbnail)
  2200. }
  2201. return createJPEGThumbnail(original)
  2202. }
  2203. func (portal *Portal) convertWebPtoPNG(webpImage []byte) ([]byte, error) {
  2204. webpDecoded, err := webp.Decode(bytes.NewReader(webpImage))
  2205. if err != nil {
  2206. return nil, fmt.Errorf("failed to decode webp image: %w", err)
  2207. }
  2208. var pngBuffer bytes.Buffer
  2209. if err := png.Encode(&pngBuffer, webpDecoded); err != nil {
  2210. return nil, fmt.Errorf("failed to encode png image: %w", err)
  2211. }
  2212. return pngBuffer.Bytes(), nil
  2213. }
  2214. func (portal *Portal) preprocessMatrixMedia(sender *User, relaybotFormatted bool, content *event.MessageEventContent, eventID id.EventID, mediaType whatsmeow.MediaType) *MediaUpload {
  2215. var caption string
  2216. var mentionedJIDs []string
  2217. if relaybotFormatted {
  2218. caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
  2219. }
  2220. var file *event.EncryptedFileInfo
  2221. rawMXC := content.URL
  2222. if content.File != nil {
  2223. file = content.File
  2224. rawMXC = file.URL
  2225. }
  2226. mxc, err := rawMXC.Parse()
  2227. if err != nil {
  2228. portal.log.Errorln("Malformed content URL in %s: %v", eventID, err)
  2229. return nil
  2230. }
  2231. data, err := portal.MainIntent().DownloadBytes(mxc)
  2232. if err != nil {
  2233. portal.log.Errorfln("Failed to download media in %s: %v", eventID, err)
  2234. return nil
  2235. }
  2236. if file != nil {
  2237. data, err = file.Decrypt(data)
  2238. if err != nil {
  2239. portal.log.Errorfln("Failed to decrypt media in %s: %v", eventID, err)
  2240. return nil
  2241. }
  2242. }
  2243. if mediaType == whatsmeow.MediaVideo && content.GetInfo().MimeType == "image/gif" {
  2244. data, err = ffmpeg.ConvertBytes(data, ".mp4", []string{"-f", "gif"}, []string{
  2245. "-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart",
  2246. "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'",
  2247. }, content.GetInfo().MimeType)
  2248. if err != nil {
  2249. portal.log.Errorfln("Failed to convert gif to mp4 in %s: %v", eventID, err)
  2250. return nil
  2251. }
  2252. content.Info.MimeType = "video/mp4"
  2253. }
  2254. if mediaType == whatsmeow.MediaImage && content.GetInfo().MimeType == "image/webp" {
  2255. data, err = portal.convertWebPtoPNG(data)
  2256. if err != nil {
  2257. portal.log.Errorfln("Failed to convert webp to png in %s: %v", eventID, err)
  2258. return nil
  2259. }
  2260. content.Info.MimeType = "image/png"
  2261. }
  2262. uploadResp, err := sender.Client.Upload(context.Background(), data, mediaType)
  2263. if err != nil {
  2264. portal.log.Errorfln("Failed to upload media in %s: %v", eventID, err)
  2265. return nil
  2266. }
  2267. // Audio doesn't have thumbnails
  2268. var thumbnail []byte
  2269. if mediaType != whatsmeow.MediaAudio {
  2270. thumbnail, err = portal.downloadThumbnail(data, content.GetInfo().ThumbnailURL, eventID)
  2271. // Ignore format errors for non-image files, we don't care about those thumbnails
  2272. if err != nil && (!errors.Is(err, image.ErrFormat) || mediaType == whatsmeow.MediaImage) {
  2273. portal.log.Errorfln("Failed to generate thumbnail for %s: %v", eventID, err)
  2274. }
  2275. }
  2276. return &MediaUpload{
  2277. UploadResponse: uploadResp,
  2278. Caption: caption,
  2279. MentionedJIDs: mentionedJIDs,
  2280. Thumbnail: thumbnail,
  2281. FileLength: len(data),
  2282. }
  2283. }
  2284. type MediaUpload struct {
  2285. whatsmeow.UploadResponse
  2286. Caption string
  2287. MentionedJIDs []string
  2288. Thumbnail []byte
  2289. FileLength int
  2290. }
  2291. func (portal *Portal) addRelaybotFormat(sender *User, content *event.MessageEventContent) bool {
  2292. member := portal.MainIntent().Member(portal.MXID, sender.MXID)
  2293. if member == nil {
  2294. member = &event.MemberEventContent{}
  2295. }
  2296. if content.Format != event.FormatHTML {
  2297. content.FormattedBody = strings.Replace(html.EscapeString(content.Body), "\n", "<br/>", -1)
  2298. content.Format = event.FormatHTML
  2299. }
  2300. data, err := portal.bridge.Config.Bridge.Relay.FormatMessage(content, sender.MXID, *member)
  2301. if err != nil {
  2302. portal.log.Errorln("Failed to apply relaybot format:", err)
  2303. }
  2304. content.FormattedBody = data
  2305. return true
  2306. }
  2307. func addCodecToMime(mimeType, codec string) string {
  2308. mediaType, params, err := mime.ParseMediaType(mimeType)
  2309. if err != nil {
  2310. return mimeType
  2311. }
  2312. if _, ok := params["codecs"]; !ok {
  2313. params["codecs"] = codec
  2314. }
  2315. return mime.FormatMediaType(mediaType, params)
  2316. }
  2317. func parseGeoURI(uri string) (lat, long float64, err error) {
  2318. if !strings.HasPrefix(uri, "geo:") {
  2319. err = fmt.Errorf("uri doesn't have geo: prefix")
  2320. return
  2321. }
  2322. // Remove geo: prefix and anything after ;
  2323. coordinates := strings.Split(strings.TrimPrefix(uri, "geo:"), ";")[0]
  2324. if splitCoordinates := strings.Split(coordinates, ","); len(splitCoordinates) != 2 {
  2325. err = fmt.Errorf("didn't find exactly two numbers separated by a comma")
  2326. } else if lat, err = strconv.ParseFloat(splitCoordinates[0], 64); err != nil {
  2327. err = fmt.Errorf("latitude is not a number: %w", err)
  2328. } else if long, err = strconv.ParseFloat(splitCoordinates[1], 64); err != nil {
  2329. err = fmt.Errorf("longitude is not a number: %w", err)
  2330. }
  2331. return
  2332. }
  2333. func getUnstableWaveform(content map[string]interface{}) []byte {
  2334. audioInfo, ok := content["org.matrix.msc1767.audio"].(map[string]interface{})
  2335. if !ok {
  2336. return nil
  2337. }
  2338. waveform, ok := audioInfo["waveform"].([]interface{})
  2339. if !ok {
  2340. return nil
  2341. }
  2342. output := make([]byte, len(waveform))
  2343. var val float64
  2344. for i, part := range waveform {
  2345. val, ok = part.(float64)
  2346. if ok {
  2347. output[i] = byte(val / 4)
  2348. }
  2349. }
  2350. return output
  2351. }
  2352. func (portal *Portal) convertMatrixMessage(sender *User, evt *event.Event) (*waProto.Message, *User) {
  2353. content, ok := evt.Content.Parsed.(*event.MessageEventContent)
  2354. if !ok {
  2355. portal.log.Debugfln("Failed to handle event %s: unexpected parsed content type %T", evt.ID, evt.Content.Parsed)
  2356. return nil, sender
  2357. }
  2358. var msg waProto.Message
  2359. var ctxInfo waProto.ContextInfo
  2360. replyToID := content.GetReplyTo()
  2361. if len(replyToID) > 0 {
  2362. replyToMsg := portal.bridge.DB.Message.GetByMXID(replyToID)
  2363. if replyToMsg != nil && !replyToMsg.IsFakeJID() && replyToMsg.Type == database.MsgNormal {
  2364. ctxInfo.StanzaId = &replyToMsg.JID
  2365. ctxInfo.Participant = proto.String(replyToMsg.Sender.ToNonAD().String())
  2366. // Using blank content here seems to work fine on all official WhatsApp apps.
  2367. //
  2368. // We could probably invent a slightly more accurate version of the quoted message
  2369. // by fetching the Matrix event and converting it to the WhatsApp format, but that's
  2370. // a lot of work and this works fine.
  2371. ctxInfo.QuotedMessage = &waProto.Message{Conversation: proto.String("")}
  2372. }
  2373. }
  2374. if portal.ExpirationTime != 0 {
  2375. ctxInfo.Expiration = proto.Uint32(portal.ExpirationTime)
  2376. }
  2377. relaybotFormatted := false
  2378. if !sender.IsLoggedIn() || (portal.IsPrivateChat() && sender.JID.User != portal.Key.Receiver.User) {
  2379. if !portal.HasRelaybot() {
  2380. portal.log.Warnln("Ignoring message from", sender.MXID, "in chat with no relaybot (convertMatrixMessage)")
  2381. return nil, sender
  2382. }
  2383. relaybotFormatted = portal.addRelaybotFormat(sender, content)
  2384. sender = portal.GetRelayUser()
  2385. }
  2386. if evt.Type == event.EventSticker {
  2387. content.MsgType = event.MsgImage
  2388. }
  2389. if content.MsgType == event.MsgImage && content.GetInfo().MimeType == "image/gif" {
  2390. content.MsgType = event.MsgVideo
  2391. }
  2392. switch content.MsgType {
  2393. case event.MsgText, event.MsgEmote, event.MsgNotice:
  2394. text := content.Body
  2395. if content.MsgType == event.MsgNotice && !portal.bridge.Config.Bridge.BridgeNotices {
  2396. return nil, sender
  2397. }
  2398. if content.Format == event.FormatHTML {
  2399. text, ctxInfo.MentionedJid = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
  2400. }
  2401. if content.MsgType == event.MsgEmote && !relaybotFormatted {
  2402. text = "/me " + text
  2403. }
  2404. msg.ExtendedTextMessage = &waProto.ExtendedTextMessage{
  2405. Text: &text,
  2406. ContextInfo: &ctxInfo,
  2407. }
  2408. hasPreview := portal.convertURLPreviewToWhatsApp(sender, evt, msg.ExtendedTextMessage)
  2409. if ctxInfo.StanzaId == nil && ctxInfo.MentionedJid == nil && ctxInfo.Expiration == nil && !hasPreview {
  2410. // No need for extended message
  2411. msg.ExtendedTextMessage = nil
  2412. msg.Conversation = &text
  2413. }
  2414. case event.MsgImage:
  2415. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaImage)
  2416. if media == nil {
  2417. return nil, sender
  2418. }
  2419. ctxInfo.MentionedJid = media.MentionedJIDs
  2420. msg.ImageMessage = &waProto.ImageMessage{
  2421. ContextInfo: &ctxInfo,
  2422. Caption: &media.Caption,
  2423. JpegThumbnail: media.Thumbnail,
  2424. Url: &media.URL,
  2425. MediaKey: media.MediaKey,
  2426. Mimetype: &content.GetInfo().MimeType,
  2427. FileEncSha256: media.FileEncSHA256,
  2428. FileSha256: media.FileSHA256,
  2429. FileLength: proto.Uint64(uint64(media.FileLength)),
  2430. }
  2431. case event.MsgVideo:
  2432. gifPlayback := content.GetInfo().MimeType == "image/gif"
  2433. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaVideo)
  2434. if media == nil {
  2435. return nil, sender
  2436. }
  2437. duration := uint32(content.GetInfo().Duration / 1000)
  2438. ctxInfo.MentionedJid = media.MentionedJIDs
  2439. msg.VideoMessage = &waProto.VideoMessage{
  2440. ContextInfo: &ctxInfo,
  2441. Caption: &media.Caption,
  2442. JpegThumbnail: media.Thumbnail,
  2443. Url: &media.URL,
  2444. MediaKey: media.MediaKey,
  2445. Mimetype: &content.GetInfo().MimeType,
  2446. GifPlayback: &gifPlayback,
  2447. Seconds: &duration,
  2448. FileEncSha256: media.FileEncSHA256,
  2449. FileSha256: media.FileSHA256,
  2450. FileLength: proto.Uint64(uint64(media.FileLength)),
  2451. }
  2452. case event.MsgAudio:
  2453. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaAudio)
  2454. if media == nil {
  2455. return nil, sender
  2456. }
  2457. duration := uint32(content.GetInfo().Duration / 1000)
  2458. msg.AudioMessage = &waProto.AudioMessage{
  2459. ContextInfo: &ctxInfo,
  2460. Url: &media.URL,
  2461. MediaKey: media.MediaKey,
  2462. Mimetype: &content.GetInfo().MimeType,
  2463. Seconds: &duration,
  2464. FileEncSha256: media.FileEncSHA256,
  2465. FileSha256: media.FileSHA256,
  2466. FileLength: proto.Uint64(uint64(media.FileLength)),
  2467. }
  2468. _, isMSC3245Voice := evt.Content.Raw["org.matrix.msc3245.voice"]
  2469. if isMSC3245Voice {
  2470. msg.AudioMessage.Waveform = getUnstableWaveform(evt.Content.Raw)
  2471. msg.AudioMessage.Ptt = proto.Bool(true)
  2472. // hacky hack to add the codecs param that whatsapp seems to require
  2473. msg.AudioMessage.Mimetype = proto.String(addCodecToMime(content.GetInfo().MimeType, "opus"))
  2474. }
  2475. case event.MsgFile:
  2476. media := portal.preprocessMatrixMedia(sender, relaybotFormatted, content, evt.ID, whatsmeow.MediaDocument)
  2477. if media == nil {
  2478. return nil, sender
  2479. }
  2480. msg.DocumentMessage = &waProto.DocumentMessage{
  2481. ContextInfo: &ctxInfo,
  2482. JpegThumbnail: media.Thumbnail,
  2483. Url: &media.URL,
  2484. Title: &content.Body,
  2485. FileName: &content.Body,
  2486. MediaKey: media.MediaKey,
  2487. Mimetype: &content.GetInfo().MimeType,
  2488. FileEncSha256: media.FileEncSHA256,
  2489. FileSha256: media.FileSHA256,
  2490. FileLength: proto.Uint64(uint64(media.FileLength)),
  2491. }
  2492. case event.MsgLocation:
  2493. lat, long, err := parseGeoURI(content.GeoURI)
  2494. if err != nil {
  2495. portal.log.Debugfln("Invalid geo URI on Matrix event %s: %v", evt.ID, err)
  2496. return nil, sender
  2497. }
  2498. msg.LocationMessage = &waProto.LocationMessage{
  2499. DegreesLatitude: &lat,
  2500. DegreesLongitude: &long,
  2501. Comment: &content.Body,
  2502. ContextInfo: &ctxInfo,
  2503. }
  2504. default:
  2505. portal.log.Debugfln("Unhandled Matrix event %s: unknown msgtype %s", evt.ID, content.MsgType)
  2506. return nil, sender
  2507. }
  2508. return &msg, sender
  2509. }
  2510. func (portal *Portal) sendErrorMessage(message string, confirmed bool) id.EventID {
  2511. certainty := "may not have been"
  2512. if confirmed {
  2513. certainty = "was not"
  2514. }
  2515. resp, err := portal.sendMainIntentMessage(&event.MessageEventContent{
  2516. MsgType: event.MsgNotice,
  2517. Body: fmt.Sprintf("\u26a0 Your message %s bridged: %v", certainty, message),
  2518. })
  2519. if err != nil {
  2520. portal.log.Warnfln("Failed to send bridging error message:", err)
  2521. return ""
  2522. }
  2523. return resp.EventID
  2524. }
  2525. func (portal *Portal) sendDeliveryReceipt(eventID id.EventID) {
  2526. if portal.bridge.Config.Bridge.DeliveryReceipts {
  2527. err := portal.bridge.Bot.MarkRead(portal.MXID, eventID)
  2528. if err != nil {
  2529. portal.log.Debugfln("Failed to send delivery receipt for %s: %v", eventID, err)
  2530. }
  2531. }
  2532. }
  2533. func (portal *Portal) generateMessageInfo(sender *User) *types.MessageInfo {
  2534. return &types.MessageInfo{
  2535. ID: whatsmeow.GenerateMessageID(),
  2536. Timestamp: time.Now(),
  2537. MessageSource: types.MessageSource{
  2538. Sender: sender.JID,
  2539. Chat: portal.Key.JID,
  2540. IsFromMe: true,
  2541. IsGroup: portal.Key.JID.Server == types.GroupServer || portal.Key.JID.Server == types.BroadcastServer,
  2542. },
  2543. }
  2544. }
  2545. func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event) {
  2546. if !portal.canBridgeFrom(sender, "message") {
  2547. return
  2548. }
  2549. portal.log.Debugfln("Received event %s from %s", evt.ID, evt.Sender)
  2550. msg, sender := portal.convertMatrixMessage(sender, evt)
  2551. if msg == nil {
  2552. return
  2553. }
  2554. portal.MarkDisappearing(evt.ID, portal.ExpirationTime, true)
  2555. info := portal.generateMessageInfo(sender)
  2556. dbMsg := portal.markHandled(nil, info, evt.ID, false, true, database.MsgNormal, database.MsgNoError)
  2557. portal.log.Debugln("Sending event", evt.ID, "to WhatsApp", info.ID)
  2558. ts, err := sender.Client.SendMessage(portal.Key.JID, info.ID, msg)
  2559. if err != nil {
  2560. portal.log.Errorfln("Error sending message: %v", err)
  2561. portal.sendErrorMessage(err.Error(), true)
  2562. status := appservice.StatusPermFailure
  2563. if errors.Is(err, whatsmeow.ErrBroadcastListUnsupported) {
  2564. status = appservice.StatusUnsupported
  2565. }
  2566. checkpoint := appservice.NewMessageSendCheckpoint(evt, appservice.StepRemote, status, 0)
  2567. checkpoint.Info = err.Error()
  2568. go checkpoint.Send(portal.bridge.AS)
  2569. } else {
  2570. portal.log.Debugfln("Handled Matrix event %s", evt.ID)
  2571. portal.bridge.AS.SendMessageSendCheckpoint(evt, appservice.StepRemote, 0)
  2572. portal.sendDeliveryReceipt(evt.ID)
  2573. dbMsg.MarkSent(ts)
  2574. }
  2575. }
  2576. func (portal *Portal) HandleMatrixReaction(sender *User, evt *event.Event) {
  2577. portal.log.Debugfln("Received reaction event %s from %s", evt.ID, evt.Sender)
  2578. err := portal.handleMatrixReaction(sender, evt)
  2579. if err != nil {
  2580. portal.log.Errorfln("Error sending reaction %s: %v", evt.ID, err)
  2581. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, err, true, 0)
  2582. } else {
  2583. portal.log.Debugfln("Handled Matrix reaction %s", evt.ID)
  2584. portal.bridge.AS.SendMessageSendCheckpoint(evt, appservice.StepRemote, 0)
  2585. portal.sendDeliveryReceipt(evt.ID)
  2586. }
  2587. }
  2588. func (portal *Portal) handleMatrixReaction(sender *User, evt *event.Event) error {
  2589. content, ok := evt.Content.Parsed.(*event.ReactionEventContent)
  2590. if !ok {
  2591. return fmt.Errorf("unexpected parsed content type %T", evt.Content.Parsed)
  2592. }
  2593. target := portal.bridge.DB.Message.GetByMXID(content.RelatesTo.EventID)
  2594. if target == nil || target.Type == database.MsgReaction {
  2595. return fmt.Errorf("unknown target event %s", content.RelatesTo.EventID)
  2596. }
  2597. info := portal.generateMessageInfo(sender)
  2598. dbMsg := portal.markHandled(nil, info, evt.ID, false, true, database.MsgReaction, database.MsgNoError)
  2599. portal.upsertReaction(nil, target.JID, sender.JID, evt.ID, info.ID)
  2600. portal.log.Debugln("Sending reaction", evt.ID, "to WhatsApp", info.ID)
  2601. ts, err := portal.sendReactionToWhatsApp(sender, info.ID, target, content.RelatesTo.Key, evt.Timestamp)
  2602. if err != nil {
  2603. dbMsg.MarkSent(ts)
  2604. }
  2605. return err
  2606. }
  2607. func (portal *Portal) sendReactionToWhatsApp(sender *User, id types.MessageID, target *database.Message, key string, timestamp int64) (time.Time, error) {
  2608. var messageKeyParticipant *string
  2609. if !portal.IsPrivateChat() {
  2610. messageKeyParticipant = proto.String(target.Sender.ToNonAD().String())
  2611. }
  2612. key = variationselector.Remove(key)
  2613. return sender.Client.SendMessage(portal.Key.JID, id, &waProto.Message{
  2614. ReactionMessage: &waProto.ReactionMessage{
  2615. Key: &waProto.MessageKey{
  2616. RemoteJid: proto.String(portal.Key.JID.String()),
  2617. FromMe: proto.Bool(target.Sender.User == sender.JID.User),
  2618. Id: proto.String(target.JID),
  2619. Participant: messageKeyParticipant,
  2620. },
  2621. Text: proto.String(key),
  2622. GroupingKey: proto.String(key), // TODO is this correct?
  2623. SenderTimestampMs: proto.Int64(timestamp),
  2624. },
  2625. })
  2626. }
  2627. func (portal *Portal) upsertReaction(intent *appservice.IntentAPI, targetJID types.MessageID, senderJID types.JID, mxid id.EventID, jid types.MessageID) {
  2628. dbReaction := portal.bridge.DB.Reaction.GetByTargetJID(portal.Key, targetJID, senderJID)
  2629. if dbReaction == nil {
  2630. dbReaction = portal.bridge.DB.Reaction.New()
  2631. dbReaction.Chat = portal.Key
  2632. dbReaction.TargetJID = targetJID
  2633. dbReaction.Sender = senderJID
  2634. } else {
  2635. portal.log.Debugfln("Redacting old Matrix reaction %s after new one (%s) was sent", dbReaction.MXID, mxid)
  2636. var err error
  2637. if intent != nil {
  2638. extra := make(map[string]interface{})
  2639. if intent.IsCustomPuppet {
  2640. extra[doublePuppetKey] = doublePuppetValue
  2641. }
  2642. _, err = intent.RedactEvent(portal.MXID, dbReaction.MXID, mautrix.ReqRedact{Extra: extra})
  2643. }
  2644. if intent == nil || errors.Is(err, mautrix.MForbidden) {
  2645. _, err = portal.MainIntent().RedactEvent(portal.MXID, dbReaction.MXID)
  2646. }
  2647. if err != nil {
  2648. portal.log.Warnfln("Failed to remove old reaction %s: %v", dbReaction.MXID, err)
  2649. }
  2650. }
  2651. dbReaction.MXID = mxid
  2652. dbReaction.JID = jid
  2653. dbReaction.Upsert()
  2654. }
  2655. func (portal *Portal) HandleMatrixRedaction(sender *User, evt *event.Event) {
  2656. if !portal.canBridgeFrom(sender, "redaction") {
  2657. return
  2658. }
  2659. portal.log.Debugfln("Received redaction %s from %s", evt.ID, evt.Sender)
  2660. senderLogIdentifier := sender.MXID
  2661. if !sender.HasSession() {
  2662. sender = portal.GetRelayUser()
  2663. senderLogIdentifier += " (through relaybot)"
  2664. }
  2665. msg := portal.bridge.DB.Message.GetByMXID(evt.Redacts)
  2666. if msg == nil {
  2667. portal.log.Debugfln("Ignoring redaction %s of unknown event by %s", evt.ID, senderLogIdentifier)
  2668. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("target not found"), true, 0)
  2669. return
  2670. } else if msg.IsFakeJID() {
  2671. portal.log.Debugfln("Ignoring redaction %s of fake event by %s", evt.ID, senderLogIdentifier)
  2672. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("target is a fake event"), true, 0)
  2673. return
  2674. } else if msg.Sender.User != sender.JID.User {
  2675. 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)
  2676. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("message was sent by someone else"), true, 0)
  2677. return
  2678. }
  2679. var err error
  2680. if msg.Type == database.MsgReaction {
  2681. if reaction := portal.bridge.DB.Reaction.GetByMXID(evt.Redacts); reaction == nil {
  2682. portal.log.Debugfln("Ignoring redaction of reaction %s: reaction database entry not found", evt.ID)
  2683. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("reaction database entry not found"), true, 0)
  2684. return
  2685. } else if reactionTarget := reaction.GetTarget(); reactionTarget == nil {
  2686. portal.log.Debugfln("Ignoring redaction of reaction %s: reaction target message not found", evt.ID)
  2687. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, errors.New("reaction target message not found"), true, 0)
  2688. return
  2689. } else {
  2690. portal.log.Debugfln("Sending redaction reaction %s of %s/%s to WhatsApp", evt.ID, msg.MXID, msg.JID)
  2691. _, err = portal.sendReactionToWhatsApp(sender, "", reactionTarget, "", evt.Timestamp)
  2692. }
  2693. } else {
  2694. portal.log.Debugfln("Sending redaction %s of %s/%s to WhatsApp", evt.ID, msg.MXID, msg.JID)
  2695. _, err = sender.Client.RevokeMessage(portal.Key.JID, msg.JID)
  2696. }
  2697. if err != nil {
  2698. portal.log.Errorfln("Error handling Matrix redaction %s: %v", evt.ID, err)
  2699. portal.bridge.AS.SendErrorMessageSendCheckpoint(evt, appservice.StepRemote, err, true, 0)
  2700. } else {
  2701. portal.log.Debugfln("Handled Matrix redaction %s of %s", evt.ID, evt.Redacts)
  2702. portal.bridge.AS.SendMessageSendCheckpoint(evt, appservice.StepRemote, 0)
  2703. portal.sendDeliveryReceipt(evt.ID)
  2704. }
  2705. }
  2706. func (portal *Portal) HandleMatrixReadReceipt(sender *User, eventID id.EventID, receiptTimestamp time.Time, isExplicit bool) {
  2707. if !sender.IsLoggedIn() {
  2708. if isExplicit {
  2709. portal.log.Debugfln("Ignoring read receipt by %s: user is not connected to WhatsApp", sender.JID)
  2710. }
  2711. return
  2712. }
  2713. maxTimestamp := receiptTimestamp
  2714. // Implicit read receipts don't have an event ID that's already bridged
  2715. if isExplicit {
  2716. if message := portal.bridge.DB.Message.GetByMXID(eventID); message != nil {
  2717. maxTimestamp = message.Timestamp
  2718. }
  2719. }
  2720. prevTimestamp := sender.GetLastReadTS(portal.Key)
  2721. lastReadIsZero := false
  2722. if prevTimestamp.IsZero() {
  2723. prevTimestamp = maxTimestamp.Add(-2 * time.Second)
  2724. lastReadIsZero = true
  2725. }
  2726. messages := portal.bridge.DB.Message.GetMessagesBetween(portal.Key, prevTimestamp, maxTimestamp)
  2727. if len(messages) > 0 {
  2728. sender.SetLastReadTS(portal.Key, messages[len(messages)-1].Timestamp)
  2729. }
  2730. groupedMessages := make(map[types.JID][]types.MessageID)
  2731. for _, msg := range messages {
  2732. var key types.JID
  2733. if msg.IsFakeJID() || msg.Sender.User == sender.JID.User {
  2734. // Don't send read receipts for own messages or fake messages
  2735. continue
  2736. } else if !portal.IsPrivateChat() {
  2737. key = msg.Sender
  2738. } else if !msg.BroadcastListJID.IsEmpty() {
  2739. key = msg.BroadcastListJID
  2740. } // else: blank key (participant field isn't needed in direct chat read receipts)
  2741. groupedMessages[key] = append(groupedMessages[key], msg.JID)
  2742. }
  2743. // For explicit read receipts, log even if there are no targets. For implicit ones only log when there are targets
  2744. if len(groupedMessages) > 0 || isExplicit {
  2745. portal.log.Debugfln("Sending read receipts by %s (last read: %d, was zero: %t, explicit: %t): %v",
  2746. sender.JID, prevTimestamp.Unix(), lastReadIsZero, isExplicit, groupedMessages)
  2747. }
  2748. for messageSender, ids := range groupedMessages {
  2749. chatJID := portal.Key.JID
  2750. if messageSender.Server == types.BroadcastServer {
  2751. chatJID = messageSender
  2752. messageSender = portal.Key.JID
  2753. }
  2754. err := sender.Client.MarkRead(ids, receiptTimestamp, chatJID, messageSender)
  2755. if err != nil {
  2756. portal.log.Warnfln("Failed to mark %v as read by %s: %v", ids, sender.JID, err)
  2757. }
  2758. }
  2759. if isExplicit {
  2760. portal.ScheduleDisappearing()
  2761. }
  2762. }
  2763. func typingDiff(prev, new []id.UserID) (started, stopped []id.UserID) {
  2764. OuterNew:
  2765. for _, userID := range new {
  2766. for _, previousUserID := range prev {
  2767. if userID == previousUserID {
  2768. continue OuterNew
  2769. }
  2770. }
  2771. started = append(started, userID)
  2772. }
  2773. OuterPrev:
  2774. for _, userID := range prev {
  2775. for _, previousUserID := range new {
  2776. if userID == previousUserID {
  2777. continue OuterPrev
  2778. }
  2779. }
  2780. stopped = append(stopped, userID)
  2781. }
  2782. return
  2783. }
  2784. func (portal *Portal) setTyping(userIDs []id.UserID, state types.ChatPresence) {
  2785. for _, userID := range userIDs {
  2786. user := portal.bridge.GetUserByMXIDIfExists(userID)
  2787. if user == nil || !user.IsLoggedIn() {
  2788. continue
  2789. }
  2790. portal.log.Debugfln("Bridging typing change from %s to chat presence %s", state, user.MXID)
  2791. err := user.Client.SendChatPresence(portal.Key.JID, state, types.ChatPresenceMediaText)
  2792. if err != nil {
  2793. portal.log.Warnln("Error sending chat presence:", err)
  2794. }
  2795. if portal.bridge.Config.Bridge.SendPresenceOnTyping {
  2796. err = user.Client.SendPresence(types.PresenceAvailable)
  2797. if err != nil {
  2798. user.log.Warnln("Failed to set presence:", err)
  2799. }
  2800. }
  2801. }
  2802. }
  2803. func (portal *Portal) HandleMatrixTyping(newTyping []id.UserID) {
  2804. portal.currentlyTypingLock.Lock()
  2805. defer portal.currentlyTypingLock.Unlock()
  2806. startedTyping, stoppedTyping := typingDiff(portal.currentlyTyping, newTyping)
  2807. portal.currentlyTyping = newTyping
  2808. portal.setTyping(startedTyping, types.ChatPresenceComposing)
  2809. portal.setTyping(stoppedTyping, types.ChatPresencePaused)
  2810. }
  2811. func (portal *Portal) canBridgeFrom(sender *User, evtType string) bool {
  2812. if !sender.IsLoggedIn() {
  2813. if portal.HasRelaybot() {
  2814. return true
  2815. } else if sender.Session != nil {
  2816. portal.log.Debugfln("Ignoring %s from %s as user is not connected", evtType, sender.MXID)
  2817. msg := format.RenderMarkdown(fmt.Sprintf("\u26a0 You are not connected to WhatsApp, so your %s was not bridged.", evtType), true, false)
  2818. msg.MsgType = event.MsgNotice
  2819. _, err := portal.sendMainIntentMessage(&msg)
  2820. if err != nil {
  2821. portal.log.Errorln("Failed to send bridging failure message:", err)
  2822. }
  2823. } else {
  2824. portal.log.Debugfln("Ignoring %s from non-logged-in user %s in chat with no relay user", evtType, sender.MXID)
  2825. }
  2826. return false
  2827. } else if portal.IsPrivateChat() && sender.JID.User != portal.Key.Receiver.User && !portal.HasRelaybot() {
  2828. portal.log.Debugfln("Ignoring %s from different user %s/%s in private chat with no relay user", evtType, sender.MXID, sender.JID)
  2829. return false
  2830. }
  2831. return true
  2832. }
  2833. func (portal *Portal) Delete() {
  2834. portal.Portal.Delete()
  2835. portal.bridge.portalsLock.Lock()
  2836. delete(portal.bridge.portalsByJID, portal.Key)
  2837. if len(portal.MXID) > 0 {
  2838. delete(portal.bridge.portalsByMXID, portal.MXID)
  2839. }
  2840. portal.bridge.portalsLock.Unlock()
  2841. }
  2842. func (portal *Portal) GetMatrixUsers() ([]id.UserID, error) {
  2843. members, err := portal.MainIntent().JoinedMembers(portal.MXID)
  2844. if err != nil {
  2845. return nil, fmt.Errorf("failed to get member list: %w", err)
  2846. }
  2847. var users []id.UserID
  2848. for userID := range members.Joined {
  2849. _, isPuppet := portal.bridge.ParsePuppetMXID(userID)
  2850. if !isPuppet && userID != portal.bridge.Bot.UserID {
  2851. users = append(users, userID)
  2852. }
  2853. }
  2854. return users, nil
  2855. }
  2856. func (portal *Portal) CleanupIfEmpty() {
  2857. users, err := portal.GetMatrixUsers()
  2858. if err != nil {
  2859. portal.log.Errorfln("Failed to get Matrix user list to determine if portal needs to be cleaned up: %v", err)
  2860. return
  2861. }
  2862. if len(users) == 0 {
  2863. portal.log.Infoln("Room seems to be empty, cleaning up...")
  2864. portal.Delete()
  2865. portal.Cleanup(false)
  2866. }
  2867. }
  2868. func (portal *Portal) Cleanup(puppetsOnly bool) {
  2869. if len(portal.MXID) == 0 {
  2870. return
  2871. }
  2872. if portal.IsPrivateChat() {
  2873. _, err := portal.MainIntent().LeaveRoom(portal.MXID)
  2874. if err != nil {
  2875. portal.log.Warnln("Failed to leave private chat portal with main intent:", err)
  2876. }
  2877. return
  2878. }
  2879. intent := portal.MainIntent()
  2880. members, err := intent.JoinedMembers(portal.MXID)
  2881. if err != nil {
  2882. portal.log.Errorln("Failed to get portal members for cleanup:", err)
  2883. return
  2884. }
  2885. for member := range members.Joined {
  2886. if member == intent.UserID {
  2887. continue
  2888. }
  2889. puppet := portal.bridge.GetPuppetByMXID(member)
  2890. if puppet != nil {
  2891. _, err = puppet.DefaultIntent().LeaveRoom(portal.MXID)
  2892. if err != nil {
  2893. portal.log.Errorln("Error leaving as puppet while cleaning up portal:", err)
  2894. }
  2895. } else if !puppetsOnly {
  2896. _, err = intent.KickUser(portal.MXID, &mautrix.ReqKickUser{UserID: member, Reason: "Deleting portal"})
  2897. if err != nil {
  2898. portal.log.Errorln("Error kicking user while cleaning up portal:", err)
  2899. }
  2900. }
  2901. }
  2902. _, err = intent.LeaveRoom(portal.MXID)
  2903. if err != nil {
  2904. portal.log.Errorln("Error leaving with main intent while cleaning up portal:", err)
  2905. }
  2906. }
  2907. func (portal *Portal) HandleMatrixLeave(sender *User) {
  2908. if portal.IsPrivateChat() {
  2909. portal.log.Debugln("User left private chat portal, cleaning up and deleting...")
  2910. portal.Delete()
  2911. portal.Cleanup(false)
  2912. return
  2913. } else if portal.bridge.Config.Bridge.BridgeMatrixLeave {
  2914. err := sender.Client.LeaveGroup(portal.Key.JID)
  2915. if err != nil {
  2916. portal.log.Errorfln("Failed to leave group as %s: %v", sender.MXID, err)
  2917. return
  2918. }
  2919. //portal.log.Infoln("Leave response:", <-resp)
  2920. }
  2921. portal.CleanupIfEmpty()
  2922. }
  2923. func (portal *Portal) HandleMatrixKick(sender *User, target *Puppet) {
  2924. _, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
  2925. target.JID: whatsmeow.ParticipantChangeRemove,
  2926. })
  2927. if err != nil {
  2928. portal.log.Errorfln("Failed to kick %s from group as %s: %v", target.JID, sender.MXID, err)
  2929. return
  2930. }
  2931. //portal.log.Infoln("Kick %s response: %s", puppet.JID, <-resp)
  2932. }
  2933. func (portal *Portal) HandleMatrixInvite(sender *User, target *Puppet) {
  2934. _, err := sender.Client.UpdateGroupParticipants(portal.Key.JID, map[types.JID]whatsmeow.ParticipantChange{
  2935. target.JID: whatsmeow.ParticipantChangeAdd,
  2936. })
  2937. if err != nil {
  2938. portal.log.Errorfln("Failed to add %s to group as %s: %v", target.JID, sender.MXID, err)
  2939. return
  2940. }
  2941. //portal.log.Infofln("Add %s response: %s", puppet.JID, <-resp)
  2942. }
  2943. func (portal *Portal) HandleMatrixMeta(sender *User, evt *event.Event) {
  2944. switch content := evt.Content.Parsed.(type) {
  2945. case *event.RoomNameEventContent:
  2946. if content.Name == portal.Name {
  2947. return
  2948. }
  2949. portal.Name = content.Name
  2950. err := sender.Client.SetGroupName(portal.Key.JID, content.Name)
  2951. if err != nil {
  2952. portal.log.Errorln("Failed to update group name:", err)
  2953. }
  2954. case *event.TopicEventContent:
  2955. if content.Topic == portal.Topic {
  2956. return
  2957. }
  2958. portal.Topic = content.Topic
  2959. err := sender.Client.SetGroupTopic(portal.Key.JID, "", "", content.Topic)
  2960. if err != nil {
  2961. portal.log.Errorln("Failed to update group description:", err)
  2962. }
  2963. case *event.RoomAvatarEventContent:
  2964. portal.avatarLock.Lock()
  2965. defer portal.avatarLock.Unlock()
  2966. if content.URL == portal.AvatarURL || (content.URL.IsEmpty() && portal.Avatar == "remove") {
  2967. return
  2968. }
  2969. var data []byte
  2970. var err error
  2971. if !content.URL.IsEmpty() {
  2972. data, err = portal.MainIntent().DownloadBytes(content.URL)
  2973. if err != nil {
  2974. portal.log.Errorfln("Failed to download updated avatar %s: %v", content.URL, err)
  2975. return
  2976. }
  2977. portal.log.Debugfln("%s set the group avatar to %s", sender.MXID, content.URL)
  2978. } else {
  2979. portal.log.Debugfln("%s removed the group avatar", sender.MXID)
  2980. }
  2981. newID, err := sender.Client.SetGroupPhoto(portal.Key.JID, data)
  2982. if err != nil {
  2983. portal.log.Errorfln("Failed to update group avatar: %v", err)
  2984. return
  2985. }
  2986. portal.log.Debugfln("Successfully updated group avatar to %s", newID)
  2987. portal.Avatar = newID
  2988. portal.AvatarURL = content.URL
  2989. portal.UpdateBridgeInfo()
  2990. portal.Update()
  2991. }
  2992. }