commands.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  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. "gitlab.com/beeper/discord/consts"
  11. "gitlab.com/beeper/discord/remoteauth"
  12. "gitlab.com/beeper/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. LoginMatrix loginMatrixCmd `kong:"cmd,help='Replace the puppet for your Discord account with your real Matrix account.'"`
  45. LogoutMatrix logoutMatrixCmd `kong:"cmd,help='Switch the puppet for your Discord account back to the default one.'"`
  46. PingMatrix pingMatrixCmd `kong:"cmd,help='check if your double puppet is working properly'"`
  47. }
  48. ///////////////////////////////////////////////////////////////////////////////
  49. // Help Command
  50. ///////////////////////////////////////////////////////////////////////////////
  51. type helpCmd struct {
  52. Command []string `kong:"arg,optional,help='The command to get help on.'"`
  53. }
  54. func (c *helpCmd) Run(g *globals) error {
  55. ctx, err := kong.Trace(g.context.Kong, c.Command)
  56. if err != nil {
  57. return err
  58. }
  59. if ctx.Error != nil {
  60. return err
  61. }
  62. err = ctx.PrintUsage(true)
  63. if err != nil {
  64. return err
  65. }
  66. fmt.Fprintln(g.context.Stdout)
  67. return nil
  68. }
  69. ///////////////////////////////////////////////////////////////////////////////
  70. // Version Command
  71. ///////////////////////////////////////////////////////////////////////////////
  72. type versionCmd struct{}
  73. func (c *versionCmd) Run(g *globals) error {
  74. fmt.Fprintln(g.context.Stdout, consts.Name, version.String)
  75. return nil
  76. }
  77. ///////////////////////////////////////////////////////////////////////////////
  78. // Login Command
  79. ///////////////////////////////////////////////////////////////////////////////
  80. type loginCmd struct{}
  81. func (l *loginCmd) Run(g *globals) error {
  82. if g.user.LoggedIn() {
  83. fmt.Fprintf(g.context.Stdout, "You are already logged in")
  84. return fmt.Errorf("user already logged in")
  85. }
  86. client, err := remoteauth.New()
  87. if err != nil {
  88. return err
  89. }
  90. qrChan := make(chan string)
  91. doneChan := make(chan struct{})
  92. go func() {
  93. code := <-qrChan
  94. _, err := g.user.sendQRCode(g.bot, g.roomID, code)
  95. if err != nil {
  96. fmt.Fprintln(g.context.Stdout, "Failed to generate the qrcode")
  97. return
  98. }
  99. }()
  100. ctx := context.Background()
  101. if err := client.Dial(ctx, qrChan, doneChan); err != nil {
  102. close(qrChan)
  103. close(doneChan)
  104. return err
  105. }
  106. <-doneChan
  107. user, err := client.Result()
  108. if err != nil {
  109. fmt.Fprintln(g.context.Stdout, "Failed to log in")
  110. return err
  111. }
  112. if err := g.user.Login(user.Token); err != nil {
  113. fmt.Fprintln(g.context.Stdout, "Failed to login", err)
  114. return err
  115. }
  116. g.user.Lock()
  117. g.user.ID = user.UserID
  118. g.user.Update()
  119. g.user.Unlock()
  120. fmt.Fprintln(g.context.Stdout, "Successfully logged in")
  121. return nil
  122. }
  123. ///////////////////////////////////////////////////////////////////////////////
  124. // Logout Command
  125. ///////////////////////////////////////////////////////////////////////////////
  126. type logoutCmd struct{}
  127. func (l *logoutCmd) Run(g *globals) error {
  128. if !g.user.LoggedIn() {
  129. fmt.Fprintln(g.context.Stdout, "You are not logged in")
  130. return fmt.Errorf("user is not logged in")
  131. }
  132. err := g.user.Logout()
  133. if err != nil {
  134. fmt.Fprintln(g.context.Stdout, "Failed to log out")
  135. return err
  136. }
  137. fmt.Fprintln(g.context.Stdout, "Successfully logged out")
  138. return nil
  139. }
  140. ///////////////////////////////////////////////////////////////////////////////
  141. // Disconnect Command
  142. ///////////////////////////////////////////////////////////////////////////////
  143. type disconnectCmd struct{}
  144. func (d *disconnectCmd) Run(g *globals) error {
  145. if !g.user.Connected() {
  146. fmt.Fprintln(g.context.Stdout, "You are not connected")
  147. return fmt.Errorf("user is not connected")
  148. }
  149. if err := g.user.Disconnect(); err != nil {
  150. fmt.Fprintln(g.context.Stdout, "Failed to disconnect")
  151. return err
  152. }
  153. fmt.Fprintln(g.context.Stdout, "Successfully disconnected")
  154. return nil
  155. }
  156. ///////////////////////////////////////////////////////////////////////////////
  157. // Reconnect Command
  158. ///////////////////////////////////////////////////////////////////////////////
  159. type reconnectCmd struct{}
  160. func (r *reconnectCmd) Run(g *globals) error {
  161. if g.user.Connected() {
  162. fmt.Fprintln(g.context.Stdout, "You are already connected")
  163. return fmt.Errorf("user is already connected")
  164. }
  165. if err := g.user.Connect(); err != nil {
  166. fmt.Fprintln(g.context.Stdout, "Failed to connect")
  167. return err
  168. }
  169. fmt.Fprintln(g.context.Stdout, "Successfully connected")
  170. return nil
  171. }
  172. ///////////////////////////////////////////////////////////////////////////////
  173. // LoginMatrix Command
  174. ///////////////////////////////////////////////////////////////////////////////
  175. type loginMatrixCmd struct {
  176. AccessToken string `kong:"arg,help='The shared secret to use the bridge'"`
  177. }
  178. func (m *loginMatrixCmd) Run(g *globals) error {
  179. puppet := g.bridge.GetPuppetByID(g.user.ID)
  180. err := puppet.SwitchCustomMXID(m.AccessToken, g.user.MXID)
  181. if err != nil {
  182. fmt.Fprintf(g.context.Stdout, "Failed to switch puppet: %v", err)
  183. return err
  184. }
  185. fmt.Fprintf(g.context.Stdout, "Successfully switched puppet")
  186. return nil
  187. }
  188. ///////////////////////////////////////////////////////////////////////////////
  189. // LogoutMatrix Command
  190. ///////////////////////////////////////////////////////////////////////////////
  191. type logoutMatrixCmd struct{}
  192. func (m *logoutMatrixCmd) Run(g *globals) error {
  193. return nil
  194. }
  195. ///////////////////////////////////////////////////////////////////////////////
  196. // PingMatrix Command
  197. ///////////////////////////////////////////////////////////////////////////////
  198. type pingMatrixCmd struct{}
  199. func (m *pingMatrixCmd) Run(g *globals) error {
  200. puppet := g.bridge.GetPuppetByCustomMXID(g.user.MXID)
  201. if puppet == nil || puppet.CustomIntent() == nil {
  202. fmt.Fprintf(g.context.Stdout, "You have not changed your Discord account's Matrix puppet.")
  203. return fmt.Errorf("double puppet not configured")
  204. }
  205. resp, err := puppet.CustomIntent().Whoami()
  206. if err != nil {
  207. fmt.Fprintf(g.context.Stdout, "Failed to validate Matrix login: %v", err)
  208. return err
  209. }
  210. fmt.Fprintf(g.context.Stdout, "Confirmed valid access token for %s / %s", resp.UserID, resp.DeviceID)
  211. return nil
  212. }