portal.go 108 KB

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