commands.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. package bridge
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/alecthomas/kong"
  6. "maunium.net/go/mautrix/appservice"
  7. "maunium.net/go/mautrix/event"
  8. "maunium.net/go/mautrix/format"
  9. "maunium.net/go/mautrix/id"
  10. "go.mau.fi/mautrix-discord/consts"
  11. "go.mau.fi/mautrix-discord/remoteauth"
  12. "go.mau.fi/mautrix-discord/version"
  13. )
  14. type globals struct {
  15. context *kong.Context
  16. bridge *Bridge
  17. bot *appservice.IntentAPI
  18. portal *Portal
  19. handler *commandHandler
  20. roomID id.RoomID
  21. user *User
  22. replyTo id.EventID
  23. }
  24. func (g *globals) reply(msg string) {
  25. content := format.RenderMarkdown(msg, true, false)
  26. content.MsgType = event.MsgNotice
  27. intent := g.bot
  28. if g.portal != nil && g.portal.IsPrivateChat() {
  29. intent = g.portal.MainIntent()
  30. }
  31. _, err := intent.SendMessageEvent(g.roomID, event.EventMessage, content)
  32. if err != nil {
  33. g.handler.log.Warnfln("Failed to reply to command from %q: %v", g.user.MXID, err)
  34. }
  35. }
  36. type commands struct {
  37. globals
  38. Disconnect disconnectCmd `kong:"cmd,help='Disconnect from Discord'"`
  39. Help helpCmd `kong:"cmd,help='Displays this message.'"`
  40. Login loginCmd `kong:"cmd,help='Log in to Discord.'"`
  41. Logout logoutCmd `kong:"cmd,help='Log out of Discord.'"`
  42. Reconnect reconnectCmd `kong:"cmd,help='Reconnect to Discord'"`
  43. Version versionCmd `kong:"cmd,help='Displays the version of the bridge.'"`
  44. Guilds guildsCmd `kong:"cmd,help='Guild bridging management.'"`
  45. LoginMatrix loginMatrixCmd `kong:"cmd,help='Replace the puppet for your Discord account with your real Matrix account.'"`
  46. LogoutMatrix logoutMatrixCmd `kong:"cmd,help='Switch the puppet for your Discord account back to the default one.'"`
  47. PingMatrix pingMatrixCmd `kong:"cmd,help='check if your double puppet is working properly'"`
  48. }
  49. ///////////////////////////////////////////////////////////////////////////////
  50. // Help Command
  51. ///////////////////////////////////////////////////////////////////////////////
  52. type helpCmd struct {
  53. Command []string `kong:"arg,optional,help='The command to get help on.'"`
  54. }
  55. func (c *helpCmd) Run(g *globals) error {
  56. ctx, err := kong.Trace(g.context.Kong, c.Command)
  57. if err != nil {
  58. return err
  59. }
  60. if ctx.Error != nil {
  61. return err
  62. }
  63. err = ctx.PrintUsage(true)
  64. if err != nil {
  65. return err
  66. }
  67. fmt.Fprintln(g.context.Stdout)
  68. return nil
  69. }
  70. ///////////////////////////////////////////////////////////////////////////////
  71. // Version Command
  72. ///////////////////////////////////////////////////////////////////////////////
  73. type versionCmd struct{}
  74. func (c *versionCmd) Run(g *globals) error {
  75. fmt.Fprintln(g.context.Stdout, consts.Name, version.String)
  76. return nil
  77. }
  78. ///////////////////////////////////////////////////////////////////////////////
  79. // Login Command
  80. ///////////////////////////////////////////////////////////////////////////////
  81. type loginCmd struct{}
  82. func (l *loginCmd) Run(g *globals) error {
  83. if g.user.LoggedIn() {
  84. fmt.Fprintf(g.context.Stdout, "You are already logged in")
  85. return fmt.Errorf("user already logged in")
  86. }
  87. client, err := remoteauth.New()
  88. if err != nil {
  89. return err
  90. }
  91. qrChan := make(chan string)
  92. doneChan := make(chan struct{})
  93. var qrCodeEvent id.EventID
  94. go func() {
  95. code := <-qrChan
  96. resp, err := g.user.sendQRCode(g.bot, g.roomID, code)
  97. if err != nil {
  98. fmt.Fprintln(g.context.Stdout, "Failed to generate the qrcode")
  99. return
  100. }
  101. qrCodeEvent = resp
  102. }()
  103. ctx := context.Background()
  104. if err := client.Dial(ctx, qrChan, doneChan); err != nil {
  105. close(qrChan)
  106. close(doneChan)
  107. return err
  108. }
  109. <-doneChan
  110. if qrCodeEvent != "" {
  111. _, err := g.bot.RedactEvent(g.roomID, qrCodeEvent)
  112. if err != nil {
  113. fmt.Errorf("Failed to redact the qrcode: %v", err)
  114. }
  115. }
  116. user, err := client.Result()
  117. if err != nil {
  118. fmt.Fprintln(g.context.Stdout, "Failed to log in")
  119. return err
  120. }
  121. if err := g.user.Login(user.Token); err != nil {
  122. fmt.Fprintln(g.context.Stdout, "Failed to login", err)
  123. return err
  124. }
  125. g.user.Lock()
  126. g.user.ID = user.UserID
  127. g.user.Update()
  128. g.user.Unlock()
  129. fmt.Fprintln(g.context.Stdout, "Successfully logged in")
  130. return nil
  131. }
  132. ///////////////////////////////////////////////////////////////////////////////
  133. // Logout Command
  134. ///////////////////////////////////////////////////////////////////////////////
  135. type logoutCmd struct{}
  136. func (l *logoutCmd) Run(g *globals) error {
  137. if !g.user.LoggedIn() {
  138. fmt.Fprintln(g.context.Stdout, "You are not logged in")
  139. return fmt.Errorf("user is not logged in")
  140. }
  141. err := g.user.Logout()
  142. if err != nil {
  143. fmt.Fprintln(g.context.Stdout, "Failed to log out")
  144. return err
  145. }
  146. fmt.Fprintln(g.context.Stdout, "Successfully logged out")
  147. return nil
  148. }
  149. ///////////////////////////////////////////////////////////////////////////////
  150. // Disconnect Command
  151. ///////////////////////////////////////////////////////////////////////////////
  152. type disconnectCmd struct{}
  153. func (d *disconnectCmd) Run(g *globals) error {
  154. if !g.user.Connected() {
  155. fmt.Fprintln(g.context.Stdout, "You are not connected")
  156. return fmt.Errorf("user is not connected")
  157. }
  158. if err := g.user.Disconnect(); err != nil {
  159. fmt.Fprintln(g.context.Stdout, "Failed to disconnect")
  160. return err
  161. }
  162. fmt.Fprintln(g.context.Stdout, "Successfully disconnected")
  163. return nil
  164. }
  165. ///////////////////////////////////////////////////////////////////////////////
  166. // Reconnect Command
  167. ///////////////////////////////////////////////////////////////////////////////
  168. type reconnectCmd struct{}
  169. func (r *reconnectCmd) Run(g *globals) error {
  170. if g.user.Connected() {
  171. fmt.Fprintln(g.context.Stdout, "You are already connected")
  172. return fmt.Errorf("user is already connected")
  173. }
  174. if err := g.user.Connect(); err != nil {
  175. fmt.Fprintln(g.context.Stdout, "Failed to connect")
  176. return err
  177. }
  178. fmt.Fprintln(g.context.Stdout, "Successfully connected")
  179. return nil
  180. }
  181. ///////////////////////////////////////////////////////////////////////////////
  182. // LoginMatrix Command
  183. ///////////////////////////////////////////////////////////////////////////////
  184. type loginMatrixCmd struct {
  185. AccessToken string `kong:"arg,help='The shared secret to use the bridge'"`
  186. }
  187. func (m *loginMatrixCmd) Run(g *globals) error {
  188. puppet := g.bridge.GetPuppetByID(g.user.ID)
  189. err := puppet.SwitchCustomMXID(m.AccessToken, g.user.MXID)
  190. if err != nil {
  191. fmt.Fprintf(g.context.Stdout, "Failed to switch puppet: %v", err)
  192. return err
  193. }
  194. fmt.Fprintf(g.context.Stdout, "Successfully switched puppet")
  195. return nil
  196. }
  197. ///////////////////////////////////////////////////////////////////////////////
  198. // LogoutMatrix Command
  199. ///////////////////////////////////////////////////////////////////////////////
  200. type logoutMatrixCmd struct{}
  201. func (m *logoutMatrixCmd) Run(g *globals) error {
  202. return nil
  203. }
  204. ///////////////////////////////////////////////////////////////////////////////
  205. // PingMatrix Command
  206. ///////////////////////////////////////////////////////////////////////////////
  207. type pingMatrixCmd struct{}
  208. func (m *pingMatrixCmd) Run(g *globals) error {
  209. puppet := g.bridge.GetPuppetByCustomMXID(g.user.MXID)
  210. if puppet == nil || puppet.CustomIntent() == nil {
  211. fmt.Fprintf(g.context.Stdout, "You have not changed your Discord account's Matrix puppet.")
  212. return fmt.Errorf("double puppet not configured")
  213. }
  214. resp, err := puppet.CustomIntent().Whoami()
  215. if err != nil {
  216. fmt.Fprintf(g.context.Stdout, "Failed to validate Matrix login: %v", err)
  217. return err
  218. }
  219. fmt.Fprintf(g.context.Stdout, "Confirmed valid access token for %s / %s", resp.UserID, resp.DeviceID)
  220. return nil
  221. }
  222. ///////////////////////////////////////////////////////////////////////////////
  223. // Guilds Commands
  224. ///////////////////////////////////////////////////////////////////////////////
  225. type guildsCmd struct {
  226. Status guildStatusCmd `kong:"cmd,help='Show the bridge status for the guilds you are in'"`
  227. Bridge guildBridgeCmd `kong:"cmd,help='Bridge a guild'"`
  228. Unbridge guildUnbridgeCmd `kong:"cmd,help='Unbridge a guild'"`
  229. }
  230. type guildStatusCmd struct{}
  231. func (c *guildStatusCmd) Run(g *globals) error {
  232. g.user.guildsLock.Lock()
  233. defer g.user.guildsLock.Unlock()
  234. if len(g.user.guilds) == 0 {
  235. fmt.Fprintf(g.context.Stdout, "you haven't joined any guilds.")
  236. } else {
  237. for _, guild := range g.user.guilds {
  238. status := "not bridged"
  239. if guild.Bridge {
  240. status = "bridged"
  241. }
  242. fmt.Fprintf(g.context.Stdout, "%s %s %s\n", guild.GuildName, guild.GuildID, status)
  243. }
  244. }
  245. return nil
  246. }
  247. type guildBridgeCmd struct {
  248. GuildID string `kong:"arg,help='the id of the guild to unbridge'"`
  249. Entire bool `kong:"flag,help='whether or not to bridge all channels'"`
  250. }
  251. func (c *guildBridgeCmd) Run(g *globals) error {
  252. if err := g.user.bridgeGuild(c.GuildID, c.Entire); err != nil {
  253. return err
  254. }
  255. fmt.Fprintf(g.context.Stdout, "Successfully bridged guild %s", c.GuildID)
  256. return nil
  257. }
  258. type guildUnbridgeCmd struct {
  259. GuildID string `kong:"arg,help='the id of the guild to unbridge'"`
  260. }
  261. func (c *guildUnbridgeCmd) Run(g *globals) error {
  262. if err := g.user.unbridgeGuild(c.GuildID); err != nil {
  263. return err
  264. }
  265. fmt.Fprintf(g.context.Stdout, "Successfully unbridged guild %s", c.GuildID)
  266. return nil
  267. }