portal.go 108 KB

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