portal.go 108 KB

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