transport.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915
  1. package service
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "log"
  6. "mime/multipart"
  7. "net/http"
  8. "strconv"
  9. "time"
  10. "bloat/mastodon"
  11. "bloat/model"
  12. "github.com/gorilla/mux"
  13. )
  14. const (
  15. HTML int = iota
  16. JSON
  17. )
  18. const (
  19. NOAUTH int = iota
  20. SESSION
  21. CSRF
  22. )
  23. const csp = "default-src 'none';" +
  24. " img-src *;" +
  25. " media-src *;" +
  26. " font-src *;" +
  27. " child-src *;" +
  28. " connect-src 'self';" +
  29. " script-src 'self';" +
  30. " style-src 'self'"
  31. func NewHandler(s *service, verbose bool, staticDir string) http.Handler {
  32. r := mux.NewRouter()
  33. writeError := func(c *client, err error, t int, retry bool) {
  34. switch t {
  35. case HTML:
  36. c.w.WriteHeader(http.StatusInternalServerError)
  37. s.ErrorPage(c, err, retry)
  38. case JSON:
  39. c.w.WriteHeader(http.StatusInternalServerError)
  40. json.NewEncoder(c.w).Encode(map[string]string{
  41. "error": err.Error(),
  42. })
  43. }
  44. }
  45. handle := func(f func(c *client) error, at int, rt int) http.HandlerFunc {
  46. return func(w http.ResponseWriter, req *http.Request) {
  47. var err error
  48. c := &client{
  49. ctx: req.Context(),
  50. w: w,
  51. r: req,
  52. }
  53. if verbose {
  54. defer func(begin time.Time) {
  55. log.Printf("path=%s, err=%v, took=%v\n",
  56. req.URL.Path, err, time.Since(begin))
  57. }(time.Now())
  58. }
  59. h := c.w.Header()
  60. switch rt {
  61. case HTML:
  62. h.Set("Content-Type", "text/html; charset=utf-8")
  63. h.Set("Content-Security-Policy", csp)
  64. case JSON:
  65. h.Set("Content-Type", "application/json")
  66. }
  67. err = c.authenticate(at, s.instance)
  68. if err != nil {
  69. writeError(c, err, rt, req.Method == http.MethodGet)
  70. return
  71. }
  72. // Override the CSP header to allow custom CSS
  73. if rt == HTML && len(c.s.Settings.CSS) > 0 &&
  74. len(c.s.Settings.CSSHash) > 0 {
  75. v := fmt.Sprintf("%s 'sha256-%s'", csp, c.s.Settings.CSSHash)
  76. h.Set("Content-Security-Policy", v)
  77. }
  78. err = f(c)
  79. if err != nil {
  80. writeError(c, err, rt, req.Method == http.MethodGet)
  81. return
  82. }
  83. }
  84. }
  85. rootPage := handle(func(c *client) error {
  86. err := c.authenticate(SESSION, "")
  87. if err != nil {
  88. if err == errInvalidSession {
  89. c.redirect("/signin")
  90. return nil
  91. }
  92. return err
  93. }
  94. if !c.s.IsLoggedIn() {
  95. c.redirect("/signin")
  96. return nil
  97. }
  98. return s.RootPage(c)
  99. }, NOAUTH, HTML)
  100. navPage := handle(func(c *client) error {
  101. return s.NavPage(c)
  102. }, SESSION, HTML)
  103. signinPage := handle(func(c *client) error {
  104. instance, ok := s.SingleInstance()
  105. if !ok {
  106. return s.SigninPage(c)
  107. }
  108. url, sess, err := s.NewSession(c, instance)
  109. if err != nil {
  110. return err
  111. }
  112. c.setSession(sess)
  113. c.redirect(url)
  114. return nil
  115. }, NOAUTH, HTML)
  116. timelinePage := handle(func(c *client) error {
  117. tType, _ := mux.Vars(c.r)["type"]
  118. q := c.r.URL.Query()
  119. instance := q.Get("instance")
  120. list := q.Get("list")
  121. maxID := q.Get("max_id")
  122. minID := q.Get("min_id")
  123. tag := q.Get("tag")
  124. instance_type := q.Get("instance_type")
  125. return s.TimelinePage(c, tType, instance, list, maxID, minID, tag, instance_type)
  126. }, SESSION, HTML)
  127. defaultTimelinePage := handle(func(c *client) error {
  128. c.redirect("/timeline/home")
  129. return nil
  130. }, SESSION, HTML)
  131. threadPage := handle(func(c *client) error {
  132. id, _ := mux.Vars(c.r)["id"]
  133. q := c.r.URL.Query()
  134. reply := q.Get("reply")
  135. return s.ThreadPage(c, id, len(reply) > 1)
  136. }, SESSION, HTML)
  137. quickReplyPage := handle(func(c *client) error {
  138. id, _ := mux.Vars(c.r)["id"]
  139. return s.QuickReplyPage(c, id)
  140. }, SESSION, HTML)
  141. likedByPage := handle(func(c *client) error {
  142. id, _ := mux.Vars(c.r)["id"]
  143. return s.LikedByPage(c, id)
  144. }, SESSION, HTML)
  145. reactedByPage := handle(func(c *client) error {
  146. id, _ := mux.Vars(c.r)["id"]
  147. return s.ReactedByPage(c, id)
  148. }, SESSION, HTML)
  149. retweetedByPage := handle(func(c *client) error {
  150. id, _ := mux.Vars(c.r)["id"]
  151. return s.RetweetedByPage(c, id)
  152. }, SESSION, HTML)
  153. notificationsPage := handle(func(c *client) error {
  154. q := c.r.URL.Query()
  155. maxID := q.Get("max_id")
  156. minID := q.Get("min_id")
  157. return s.NotificationPage(c, maxID, minID)
  158. }, SESSION, HTML)
  159. userPage := handle(func(c *client) error {
  160. id, _ := mux.Vars(c.r)["id"]
  161. pageType, _ := mux.Vars(c.r)["type"]
  162. q := c.r.URL.Query()
  163. maxID := q.Get("max_id")
  164. minID := q.Get("min_id")
  165. return s.UserPage(c, id, pageType, maxID, minID)
  166. }, SESSION, HTML)
  167. userSearchPage := handle(func(c *client) error {
  168. id, _ := mux.Vars(c.r)["id"]
  169. q := c.r.URL.Query()
  170. sq := q.Get("q")
  171. offset, _ := strconv.Atoi(q.Get("offset"))
  172. return s.UserSearchPage(c, id, sq, offset)
  173. }, SESSION, HTML)
  174. mutePage := handle(func(c *client) error {
  175. id, _ := mux.Vars(c.r)["id"]
  176. return s.MutePage(c, id)
  177. }, SESSION, HTML)
  178. aboutPage := handle(func(c *client) error {
  179. return s.AboutPage(c)
  180. }, SESSION, HTML)
  181. aboutInstance := handle(func(c *client) error {
  182. return s.AboutInstance(c)
  183. }, SESSION, HTML)
  184. emojisPage := handle(func(c *client) error {
  185. return s.EmojiPage(c)
  186. }, SESSION, HTML)
  187. searchPage := handle(func(c *client) error {
  188. q := c.r.URL.Query()
  189. sq := q.Get("q")
  190. qType := q.Get("type")
  191. offset, _ := strconv.Atoi(q.Get("offset"))
  192. return s.SearchPage(c, sq, qType, offset)
  193. }, SESSION, HTML)
  194. settingsPage := handle(func(c *client) error {
  195. return s.SettingsPage(c)
  196. }, SESSION, HTML)
  197. filtersPage := handle(func(c *client) error {
  198. return s.FiltersPage(c)
  199. }, SESSION, HTML)
  200. profilePage := handle(func(c *client) error {
  201. return s.ProfilePage(c)
  202. }, SESSION, HTML)
  203. profileUpdate := handle(func(c *client) error {
  204. name := c.r.FormValue("name")
  205. bio := c.r.FormValue("bio")
  206. var avatar, banner *multipart.FileHeader
  207. if f := c.r.MultipartForm.File["avatar"]; len(f) > 0 {
  208. avatar = f[0]
  209. }
  210. if f := c.r.MultipartForm.File["banner"]; len(f) > 0 {
  211. banner = f[0]
  212. }
  213. var fields []mastodon.Field
  214. for i := 0; i < 16; i++ {
  215. n := c.r.FormValue(fmt.Sprintf("field-name-%d", i))
  216. v := c.r.FormValue(fmt.Sprintf("field-value-%d", i))
  217. if len(n) == 0 {
  218. continue
  219. }
  220. f := mastodon.Field{Name: n, Value: v}
  221. fields = append(fields, f)
  222. }
  223. locked := c.r.FormValue("locked") == "true"
  224. err := s.ProfileUpdate(c, name, bio, avatar, banner, fields, locked)
  225. if err != nil {
  226. return err
  227. }
  228. c.redirect("/")
  229. return nil
  230. }, CSRF, HTML)
  231. profileDelAvatar := handle(func(c *client) error {
  232. err := s.ProfileDelAvatar(c)
  233. if err != nil {
  234. return err
  235. }
  236. c.redirect(c.r.FormValue("referrer"))
  237. return nil
  238. }, CSRF, HTML)
  239. profileDelBanner := handle(func(c *client) error {
  240. err := s.ProfileDelBanner(c)
  241. if err != nil {
  242. return err
  243. }
  244. c.redirect(c.r.FormValue("referrer"))
  245. return nil
  246. }, CSRF, HTML)
  247. signin := handle(func(c *client) error {
  248. instance := c.r.FormValue("instance")
  249. url, sess, err := s.NewSession(c, instance)
  250. if err != nil {
  251. return err
  252. }
  253. c.setSession(sess)
  254. c.redirect(url)
  255. return nil
  256. }, NOAUTH, HTML)
  257. oauthCallback := handle(func(c *client) error {
  258. q := c.r.URL.Query()
  259. token := q.Get("code")
  260. err := s.Signin(c, token)
  261. if err != nil {
  262. return err
  263. }
  264. c.redirect("/")
  265. return nil
  266. }, SESSION, HTML)
  267. post := handle(func(c *client) error {
  268. spoilerText := c.r.FormValue("title")
  269. content := c.r.FormValue("content")
  270. replyToID := c.r.FormValue("reply_to_id")
  271. format := c.r.FormValue("format")
  272. visibility := c.r.FormValue("visibility")
  273. isNSFW := c.r.FormValue("is_nsfw") == "true"
  274. quickReply := c.r.FormValue("quickreply") == "true"
  275. files := c.r.MultipartForm.File["attachments"]
  276. var mediaDescription []string
  277. for i := 0; i < len(files); i++ {
  278. v := c.r.FormValue(fmt.Sprintf("media-descr-%d", i))
  279. mediaDescription = append(mediaDescription, v)
  280. }
  281. edit := c.r.FormValue("edit-status-id")
  282. language := c.r.FormValue("lang-code")
  283. expiresIn, err := strconv.Atoi(c.r.FormValue("expires-in"))
  284. if err != nil {
  285. return err
  286. }
  287. scheduledAt := c.r.FormValue("scheduled")
  288. if scheduledAt != "" {
  289. scheduled, err := time.Parse("2006-01-02T15:04", scheduledAt)
  290. if err != nil {
  291. return err
  292. }
  293. scheduledAt = string(scheduled.UTC().Format(time.RFC3339))
  294. }
  295. var pollOptions []string
  296. for i := 0; i < 20; i++ {
  297. v := c.r.FormValue(fmt.Sprintf("poll-option-%d", i))
  298. if len(v) == 0 {
  299. continue
  300. }
  301. pollOptions = append(pollOptions, v)
  302. }
  303. pollExpiresIn, err := strconv.Atoi(c.r.FormValue("poll-expires-in"))
  304. if err != nil {
  305. return err
  306. }
  307. pollHideTotals := c.r.FormValue("poll-hide-totals") == "true"
  308. pollMultiple := c.r.FormValue("poll-is-multiple") == "true"
  309. id, err := s.Post(c, content, replyToID, format, visibility, isNSFW, spoilerText, files, edit, language, expiresIn, scheduledAt, pollOptions, pollExpiresIn, pollHideTotals, pollMultiple, mediaDescription)
  310. if err != nil {
  311. return err
  312. }
  313. var location string
  314. if len(replyToID) > 0 {
  315. if quickReply {
  316. location = "/quickreply/" + id + "#status-" + id
  317. } else {
  318. location = "/thread/" + replyToID + "#status-" + id
  319. }
  320. } else {
  321. location = c.r.FormValue("referrer")
  322. }
  323. c.redirect(location)
  324. return nil
  325. }, CSRF, HTML)
  326. like := handle(func(c *client) error {
  327. id, _ := mux.Vars(c.r)["id"]
  328. rid := c.r.FormValue("retweeted_by_id")
  329. _, err := s.Like(c, id)
  330. if err != nil {
  331. return err
  332. }
  333. if len(rid) > 0 {
  334. id = rid
  335. }
  336. c.redirect(c.r.FormValue("referrer") + "#status-" + id)
  337. return nil
  338. }, CSRF, HTML)
  339. unlike := handle(func(c *client) error {
  340. id, _ := mux.Vars(c.r)["id"]
  341. rid := c.r.FormValue("retweeted_by_id")
  342. _, err := s.UnLike(c, id)
  343. if err != nil {
  344. return err
  345. }
  346. if len(rid) > 0 {
  347. id = rid
  348. }
  349. c.redirect(c.r.FormValue("referrer") + "#status-" + id)
  350. return nil
  351. }, CSRF, HTML)
  352. putreact := handle(func(c *client) error {
  353. q := c.r.URL.Query()
  354. emoji := q.Get("emoji")
  355. if len(emoji) <= 0 {
  356. emoji = c.r.FormValue("akkoma-reaction")
  357. }
  358. id, _ := mux.Vars(c.r)["id"]
  359. _, err := s.PutReact(c, id, emoji)
  360. if err != nil {
  361. return err
  362. }
  363. c.redirect(c.r.FormValue("referrer")+"#status-"+id)
  364. return nil
  365. }, CSRF, HTML)
  366. unreact := handle(func(c *client) error {
  367. q := c.r.URL.Query()
  368. emoji := q.Get("emoji")
  369. id, _ := mux.Vars(c.r)["id"]
  370. _, err := s.UnReact(c, id, emoji)
  371. if err != nil {
  372. return err
  373. }
  374. c.redirect(c.r.FormValue("referrer")+"#status-"+id)
  375. return nil
  376. }, CSRF, HTML)
  377. retweet := handle(func(c *client) error {
  378. id, _ := mux.Vars(c.r)["id"]
  379. rid := c.r.FormValue("retweeted_by_id")
  380. visibility := c.r.FormValue("retweet_visibility")
  381. _, err := s.Retweet(c, id, visibility)
  382. if err != nil {
  383. return err
  384. }
  385. if len(rid) > 0 {
  386. id = rid
  387. }
  388. c.redirect(c.r.FormValue("referrer") + "#status-" + id)
  389. return nil
  390. }, CSRF, HTML)
  391. unretweet := handle(func(c *client) error {
  392. id, _ := mux.Vars(c.r)["id"]
  393. rid := c.r.FormValue("retweeted_by_id")
  394. _, err := s.UnRetweet(c, id)
  395. if err != nil {
  396. return err
  397. }
  398. if len(rid) > 0 {
  399. id = rid
  400. }
  401. c.redirect(c.r.FormValue("referrer") + "#status-" + id)
  402. return nil
  403. }, CSRF, HTML)
  404. vote := handle(func(c *client) error {
  405. id, _ := mux.Vars(c.r)["id"]
  406. statusID := c.r.FormValue("status_id")
  407. choices, _ := c.r.PostForm["choices"]
  408. err := s.Vote(c, id, choices)
  409. if err != nil {
  410. return err
  411. }
  412. c.redirect(c.r.FormValue("referrer") + "#status-" + statusID)
  413. return nil
  414. }, CSRF, HTML)
  415. follow := handle(func(c *client) error {
  416. id, _ := mux.Vars(c.r)["id"]
  417. q := c.r.URL.Query()
  418. var reblogs *bool
  419. if r, ok := q["reblogs"]; ok && len(r) > 0 {
  420. reblogs = new(bool)
  421. *reblogs = r[0] == "true"
  422. }
  423. err := s.Follow(c, id, reblogs)
  424. if err != nil {
  425. return err
  426. }
  427. c.redirect(c.r.FormValue("referrer"))
  428. return nil
  429. }, CSRF, HTML)
  430. unfollow := handle(func(c *client) error {
  431. id, _ := mux.Vars(c.r)["id"]
  432. err := s.UnFollow(c, id)
  433. if err != nil {
  434. return err
  435. }
  436. c.redirect(c.r.FormValue("referrer"))
  437. return nil
  438. }, CSRF, HTML)
  439. accept := handle(func(c *client) error {
  440. id, _ := mux.Vars(c.r)["id"]
  441. err := s.Accept(c, id)
  442. if err != nil {
  443. return err
  444. }
  445. c.redirect(c.r.FormValue("referrer"))
  446. return nil
  447. }, CSRF, HTML)
  448. reject := handle(func(c *client) error {
  449. id, _ := mux.Vars(c.r)["id"]
  450. err := s.Reject(c, id)
  451. if err != nil {
  452. return err
  453. }
  454. c.redirect(c.r.FormValue("referrer"))
  455. return nil
  456. }, CSRF, HTML)
  457. mute := handle(func(c *client) error {
  458. id, _ := mux.Vars(c.r)["id"]
  459. notifications, _ := strconv.ParseBool(c.r.FormValue("notifications"))
  460. duration, _ := strconv.Atoi(c.r.FormValue("duration"))
  461. err := s.Mute(c, id, notifications, duration)
  462. if err != nil {
  463. return err
  464. }
  465. c.redirect("/user/" + id)
  466. return nil
  467. }, CSRF, HTML)
  468. unMute := handle(func(c *client) error {
  469. id, _ := mux.Vars(c.r)["id"]
  470. err := s.UnMute(c, id)
  471. if err != nil {
  472. return err
  473. }
  474. c.redirect(c.r.FormValue("referrer"))
  475. return nil
  476. }, CSRF, HTML)
  477. block := handle(func(c *client) error {
  478. id, _ := mux.Vars(c.r)["id"]
  479. err := s.Block(c, id)
  480. if err != nil {
  481. return err
  482. }
  483. c.redirect(c.r.FormValue("referrer"))
  484. return nil
  485. }, CSRF, HTML)
  486. unBlock := handle(func(c *client) error {
  487. id, _ := mux.Vars(c.r)["id"]
  488. err := s.UnBlock(c, id)
  489. if err != nil {
  490. return err
  491. }
  492. c.redirect(c.r.FormValue("referrer"))
  493. return nil
  494. }, CSRF, HTML)
  495. subscribe := handle(func(c *client) error {
  496. id, _ := mux.Vars(c.r)["id"]
  497. err := s.Subscribe(c, id)
  498. if err != nil {
  499. return err
  500. }
  501. c.redirect(c.r.FormValue("referrer"))
  502. return nil
  503. }, CSRF, HTML)
  504. unSubscribe := handle(func(c *client) error {
  505. id, _ := mux.Vars(c.r)["id"]
  506. err := s.UnSubscribe(c, id)
  507. if err != nil {
  508. return err
  509. }
  510. c.redirect(c.r.FormValue("referrer"))
  511. return nil
  512. }, CSRF, HTML)
  513. settings := handle(func(c *client) error {
  514. visibility := c.r.FormValue("visibility")
  515. format := c.r.FormValue("format")
  516. copyScope := c.r.FormValue("copy_scope") == "true"
  517. threadInNewTab := c.r.FormValue("thread_in_new_tab") == "true"
  518. hideAttachments := c.r.FormValue("hide_attachments") == "true"
  519. maskNSFW := c.r.FormValue("mask_nsfw") == "true"
  520. ni, _ := strconv.Atoi(c.r.FormValue("notification_interval"))
  521. fluorideMode := c.r.FormValue("fluoride_mode") == "true"
  522. darkMode := c.r.FormValue("dark_mode") == "true"
  523. antiDopamineMode := c.r.FormValue("anti_dopamine_mode") == "true"
  524. hideUnsupportedNotifs := c.r.FormValue("hide_unsupported_notifs") == "true"
  525. instanceEmojiFilter := c.r.FormValue("instance-emoji-filter")
  526. addReactionsFilter := c.r.FormValue("pleroma-reactions-filter")
  527. css := c.r.FormValue("css")
  528. settings := &model.Settings{
  529. DefaultVisibility: visibility,
  530. DefaultFormat: format,
  531. CopyScope: copyScope,
  532. ThreadInNewTab: threadInNewTab,
  533. HideAttachments: hideAttachments,
  534. MaskNSFW: maskNSFW,
  535. NotificationInterval: ni,
  536. FluorideMode: fluorideMode,
  537. DarkMode: darkMode,
  538. AntiDopamineMode: antiDopamineMode,
  539. HideUnsupportedNotifs: hideUnsupportedNotifs,
  540. InstanceEmojiFilter: instanceEmojiFilter,
  541. AddReactionsFilter: addReactionsFilter,
  542. CSS: css,
  543. }
  544. err := s.SaveSettings(c, settings)
  545. if err != nil {
  546. return err
  547. }
  548. c.redirect("/")
  549. return nil
  550. }, CSRF, HTML)
  551. muteConversation := handle(func(c *client) error {
  552. id, _ := mux.Vars(c.r)["id"]
  553. err := s.MuteConversation(c, id)
  554. if err != nil {
  555. return err
  556. }
  557. c.redirect(c.r.FormValue("referrer"))
  558. return nil
  559. }, CSRF, HTML)
  560. unMuteConversation := handle(func(c *client) error {
  561. id, _ := mux.Vars(c.r)["id"]
  562. err := s.UnMuteConversation(c, id)
  563. if err != nil {
  564. return err
  565. }
  566. c.redirect(c.r.FormValue("referrer"))
  567. return nil
  568. }, CSRF, HTML)
  569. pin := handle(func(c *client) error {
  570. id, _ := mux.Vars(c.r)["id"]
  571. err := s.Pin(c, id)
  572. if err != nil {
  573. return err
  574. }
  575. c.redirect(c.r.FormValue("referrer"))
  576. return nil
  577. }, CSRF, HTML)
  578. unpin := handle(func(c *client) error {
  579. id, _ := mux.Vars(c.r)["id"]
  580. err := s.UnPin(c, id)
  581. if err != nil {
  582. return err
  583. }
  584. c.redirect(c.r.FormValue("referrer"))
  585. return nil
  586. }, CSRF, HTML)
  587. delete := handle(func(c *client) error {
  588. id, _ := mux.Vars(c.r)["id"]
  589. err := s.Delete(c, id)
  590. if err != nil {
  591. return err
  592. }
  593. c.redirect(c.r.FormValue("referrer"))
  594. return nil
  595. }, CSRF, HTML)
  596. readNotifications := handle(func(c *client) error {
  597. q := c.r.URL.Query()
  598. maxID := q.Get("max_id")
  599. err := s.ReadNotifications(c, maxID)
  600. if err != nil {
  601. return err
  602. }
  603. c.redirect(c.r.FormValue("referrer"))
  604. return nil
  605. }, CSRF, HTML)
  606. bookmark := handle(func(c *client) error {
  607. id, _ := mux.Vars(c.r)["id"]
  608. rid := c.r.FormValue("retweeted_by_id")
  609. err := s.Bookmark(c, id)
  610. if err != nil {
  611. return err
  612. }
  613. if len(rid) > 0 {
  614. id = rid
  615. }
  616. c.redirect(c.r.FormValue("referrer") + "#status-" + id)
  617. return nil
  618. }, CSRF, HTML)
  619. unBookmark := handle(func(c *client) error {
  620. id, _ := mux.Vars(c.r)["id"]
  621. rid := c.r.FormValue("retweeted_by_id")
  622. err := s.UnBookmark(c, id)
  623. if err != nil {
  624. return err
  625. }
  626. if len(rid) > 0 {
  627. id = rid
  628. }
  629. c.redirect(c.r.FormValue("referrer") + "#status-" + id)
  630. return nil
  631. }, CSRF, HTML)
  632. filter := handle(func(c *client) error {
  633. phrase := c.r.FormValue("phrase")
  634. wholeWord := c.r.FormValue("whole_word") == "true"
  635. err := s.Filter(c, phrase, wholeWord)
  636. if err != nil {
  637. return err
  638. }
  639. c.redirect(c.r.FormValue("referrer"))
  640. return nil
  641. }, CSRF, HTML)
  642. unFilter := handle(func(c *client) error {
  643. id, _ := mux.Vars(c.r)["id"]
  644. err := s.UnFilter(c, id)
  645. if err != nil {
  646. return err
  647. }
  648. c.redirect(c.r.FormValue("referrer"))
  649. return nil
  650. }, CSRF, HTML)
  651. listsPage := handle(func(c *client) error {
  652. return s.ListsPage(c)
  653. }, SESSION, HTML)
  654. addList := handle(func(c *client) error {
  655. title := c.r.FormValue("title")
  656. err := s.AddList(c, title)
  657. if err != nil {
  658. return err
  659. }
  660. c.redirect(c.r.FormValue("referrer"))
  661. return nil
  662. }, CSRF, HTML)
  663. removeList := handle(func(c *client) error {
  664. id, _ := mux.Vars(c.r)["id"]
  665. err := s.RemoveList(c, id)
  666. if err != nil {
  667. return err
  668. }
  669. c.redirect(c.r.FormValue("referrer"))
  670. return nil
  671. }, CSRF, HTML)
  672. renameList := handle(func(c *client) error {
  673. id, _ := mux.Vars(c.r)["id"]
  674. title := c.r.FormValue("title")
  675. err := s.RenameList(c, id, title)
  676. if err != nil {
  677. return err
  678. }
  679. c.redirect(c.r.FormValue("referrer"))
  680. return nil
  681. }, CSRF, HTML)
  682. listPage := handle(func(c *client) error {
  683. id, _ := mux.Vars(c.r)["id"]
  684. q := c.r.URL.Query()
  685. sq := q.Get("q")
  686. return s.ListPage(c, id, sq)
  687. }, SESSION, HTML)
  688. listAddUser := handle(func(c *client) error {
  689. id, _ := mux.Vars(c.r)["id"]
  690. q := c.r.URL.Query()
  691. uid := q.Get("uid")
  692. if uid == "" {
  693. uid = c.r.FormValue("uid")
  694. }
  695. err := s.ListAddUser(c, id, uid)
  696. if err != nil {
  697. return err
  698. }
  699. c.redirect(c.r.FormValue("referrer"))
  700. return nil
  701. }, CSRF, HTML)
  702. listRemoveUser := handle(func(c *client) error {
  703. id, _ := mux.Vars(c.r)["id"]
  704. q := c.r.URL.Query()
  705. uid := q.Get("uid")
  706. err := s.ListRemoveUser(c, id, uid)
  707. if err != nil {
  708. return err
  709. }
  710. c.redirect(c.r.FormValue("referrer"))
  711. return nil
  712. }, CSRF, HTML)
  713. signout := handle(func(c *client) error {
  714. err := s.Signout(c)
  715. if err != nil {
  716. return err
  717. }
  718. c.unsetSession()
  719. c.redirect("/")
  720. return nil
  721. }, CSRF, HTML)
  722. fLike := handle(func(c *client) error {
  723. id, _ := mux.Vars(c.r)["id"]
  724. count, err := s.Like(c, id)
  725. if err != nil {
  726. return err
  727. }
  728. return c.writeJson(count)
  729. }, CSRF, JSON)
  730. fUnlike := handle(func(c *client) error {
  731. id, _ := mux.Vars(c.r)["id"]
  732. count, err := s.UnLike(c, id)
  733. if err != nil {
  734. return err
  735. }
  736. return c.writeJson(count)
  737. }, CSRF, JSON)
  738. fRetweet := handle(func(c *client) error {
  739. id, _ := mux.Vars(c.r)["id"]
  740. visibility := c.r.FormValue("retweet_visibility")
  741. count, err := s.Retweet(c, id, visibility)
  742. if err != nil {
  743. return err
  744. }
  745. return c.writeJson(count)
  746. }, CSRF, JSON)
  747. fUnretweet := handle(func(c *client) error {
  748. id, _ := mux.Vars(c.r)["id"]
  749. count, err := s.UnRetweet(c, id)
  750. if err != nil {
  751. return err
  752. }
  753. return c.writeJson(count)
  754. }, CSRF, JSON)
  755. r.HandleFunc("/", rootPage).Methods(http.MethodGet)
  756. r.HandleFunc("/nav", navPage).Methods(http.MethodGet)
  757. r.HandleFunc("/signin", signinPage).Methods(http.MethodGet)
  758. r.HandleFunc("/timeline/{type}", timelinePage).Methods(http.MethodGet)
  759. r.HandleFunc("/timeline", defaultTimelinePage).Methods(http.MethodGet)
  760. r.HandleFunc("/thread/{id}", threadPage).Methods(http.MethodGet)
  761. r.HandleFunc("/quickreply/{id}", quickReplyPage).Methods(http.MethodGet)
  762. r.HandleFunc("/likedby/{id}", likedByPage).Methods(http.MethodGet)
  763. r.HandleFunc("/reactionspage/{id}", reactedByPage).Methods(http.MethodGet)
  764. r.HandleFunc("/retweetedby/{id}", retweetedByPage).Methods(http.MethodGet)
  765. r.HandleFunc("/notifications", notificationsPage).Methods(http.MethodGet)
  766. r.HandleFunc("/user/{id}", userPage).Methods(http.MethodGet)
  767. r.HandleFunc("/user/{id}/{type}", userPage).Methods(http.MethodGet)
  768. r.HandleFunc("/usersearch/{id}", userSearchPage).Methods(http.MethodGet)
  769. r.HandleFunc("/mute/{id}", mutePage).Methods(http.MethodGet)
  770. r.HandleFunc("/about", aboutPage).Methods(http.MethodGet)
  771. r.HandleFunc("/aboutinstance", aboutInstance).Methods(http.MethodGet)
  772. r.HandleFunc("/emojis", emojisPage).Methods(http.MethodGet)
  773. r.HandleFunc("/search", searchPage).Methods(http.MethodGet)
  774. r.HandleFunc("/settings", settingsPage).Methods(http.MethodGet)
  775. r.HandleFunc("/filters", filtersPage).Methods(http.MethodGet)
  776. r.HandleFunc("/profile", profilePage).Methods(http.MethodGet)
  777. r.HandleFunc("/profile", profileUpdate).Methods(http.MethodPost)
  778. r.HandleFunc("/profile/delavatar", profileDelAvatar).Methods(http.MethodPost)
  779. r.HandleFunc("/profile/delbanner", profileDelBanner).Methods(http.MethodPost)
  780. r.HandleFunc("/signin", signin).Methods(http.MethodPost)
  781. r.HandleFunc("/oauth_callback", oauthCallback).Methods(http.MethodGet)
  782. r.HandleFunc("/post", post).Methods(http.MethodPost)
  783. r.HandleFunc("/like/{id}", like).Methods(http.MethodPost)
  784. r.HandleFunc("/unlike/{id}", unlike).Methods(http.MethodPost)
  785. r.HandleFunc("/react-with/{id}", putreact).Methods(http.MethodPost)
  786. r.HandleFunc("/unreact-with/{id}", unreact).Methods(http.MethodPost)
  787. r.HandleFunc("/retweet/{id}", retweet).Methods(http.MethodPost)
  788. r.HandleFunc("/unretweet/{id}", unretweet).Methods(http.MethodPost)
  789. r.HandleFunc("/vote/{id}", vote).Methods(http.MethodPost)
  790. r.HandleFunc("/follow/{id}", follow).Methods(http.MethodPost)
  791. r.HandleFunc("/unfollow/{id}", unfollow).Methods(http.MethodPost)
  792. r.HandleFunc("/accept/{id}", accept).Methods(http.MethodPost)
  793. r.HandleFunc("/reject/{id}", reject).Methods(http.MethodPost)
  794. r.HandleFunc("/mute/{id}", mute).Methods(http.MethodPost)
  795. r.HandleFunc("/unmute/{id}", unMute).Methods(http.MethodPost)
  796. r.HandleFunc("/block/{id}", block).Methods(http.MethodPost)
  797. r.HandleFunc("/unblock/{id}", unBlock).Methods(http.MethodPost)
  798. r.HandleFunc("/subscribe/{id}", subscribe).Methods(http.MethodPost)
  799. r.HandleFunc("/unsubscribe/{id}", unSubscribe).Methods(http.MethodPost)
  800. r.HandleFunc("/settings", settings).Methods(http.MethodPost)
  801. r.HandleFunc("/muteconv/{id}", muteConversation).Methods(http.MethodPost)
  802. r.HandleFunc("/unmuteconv/{id}", unMuteConversation).Methods(http.MethodPost)
  803. r.HandleFunc("/pin/{id}", pin).Methods(http.MethodPost)
  804. r.HandleFunc("/unpin/{id}", unpin).Methods(http.MethodPost)
  805. r.HandleFunc("/delete/{id}", delete).Methods(http.MethodPost)
  806. r.HandleFunc("/notifications/read", readNotifications).Methods(http.MethodPost)
  807. r.HandleFunc("/bookmark/{id}", bookmark).Methods(http.MethodPost)
  808. r.HandleFunc("/unbookmark/{id}", unBookmark).Methods(http.MethodPost)
  809. r.HandleFunc("/filter", filter).Methods(http.MethodPost)
  810. r.HandleFunc("/unfilter/{id}", unFilter).Methods(http.MethodPost)
  811. r.HandleFunc("/lists", listsPage).Methods(http.MethodGet)
  812. r.HandleFunc("/list", addList).Methods(http.MethodPost)
  813. r.HandleFunc("/list/{id}", listPage).Methods(http.MethodGet)
  814. r.HandleFunc("/list/{id}/remove", removeList).Methods(http.MethodPost)
  815. r.HandleFunc("/list/{id}/rename", renameList).Methods(http.MethodPost)
  816. r.HandleFunc("/list/{id}/adduser", listAddUser).Methods(http.MethodPost)
  817. r.HandleFunc("/list/{id}/removeuser", listRemoveUser).Methods(http.MethodPost)
  818. r.HandleFunc("/signout", signout).Methods(http.MethodPost)
  819. r.HandleFunc("/fluoride/like/{id}", fLike).Methods(http.MethodPost)
  820. r.HandleFunc("/fluoride/unlike/{id}", fUnlike).Methods(http.MethodPost)
  821. r.HandleFunc("/fluoride/retweet/{id}", fRetweet).Methods(http.MethodPost)
  822. r.HandleFunc("/fluoride/unretweet/{id}", fUnretweet).Methods(http.MethodPost)
  823. r.PathPrefix("/static").Handler(http.StripPrefix("/static",
  824. http.FileServer(http.Dir(staticDir))))
  825. return r
  826. }