Command line client for Write.as https://write.as/apps/cli
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

412 rindas
9.7 KiB

  1. package commands
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "text/tabwriter"
  7. "github.com/howeyc/gopass"
  8. "github.com/writeas/writeas-cli/api"
  9. "github.com/writeas/writeas-cli/config"
  10. "github.com/writeas/writeas-cli/executable"
  11. "github.com/writeas/writeas-cli/log"
  12. cli "gopkg.in/urfave/cli.v1"
  13. )
  14. func CmdPost(c *cli.Context) error {
  15. if config.IsTor(c) {
  16. log.Info(c, "Publishing via hidden service...")
  17. } else {
  18. log.Info(c, "Publishing...")
  19. }
  20. _, err := api.DoPost(c, api.ReadStdIn(), c.String("font"), false, c.Bool("code"))
  21. if err != nil {
  22. return cli.NewExitError(err.Error(), 1)
  23. }
  24. return nil
  25. }
  26. func CmdNew(c *cli.Context) error {
  27. fname, p := api.ComposeNewPost()
  28. if p == nil {
  29. // Assume composeNewPost already told us what the error was. Abort now.
  30. os.Exit(1)
  31. }
  32. // Ensure we have something to post
  33. if len(*p) == 0 {
  34. // Clean up temporary post
  35. if fname != "" {
  36. os.Remove(fname)
  37. }
  38. log.InfolnQuit("Empty post. Bye!")
  39. }
  40. if config.IsTor(c) {
  41. log.Info(c, "Publishing via hidden service...")
  42. } else {
  43. log.Info(c, "Publishing...")
  44. }
  45. _, err := api.DoPost(c, *p, c.String("font"), false, c.Bool("code"))
  46. if err != nil {
  47. log.Errorln("Error posting: %s\n%s", err, config.MessageRetryCompose(fname))
  48. return cli.NewExitError("", 1)
  49. }
  50. // Clean up temporary post
  51. if fname != "" {
  52. os.Remove(fname)
  53. }
  54. return nil
  55. }
  56. func CmdPublish(c *cli.Context) error {
  57. filename := c.Args().Get(0)
  58. if filename == "" {
  59. return cli.NewExitError("usage: "+executable.Name()+" publish <filename>", 1)
  60. }
  61. content, err := ioutil.ReadFile(filename)
  62. if err != nil {
  63. return err
  64. }
  65. if config.IsTor(c) {
  66. log.Info(c, "Publishing via hidden service...")
  67. } else {
  68. log.Info(c, "Publishing...")
  69. }
  70. _, err = api.DoPost(c, content, c.String("font"), false, c.Bool("code"))
  71. if err != nil {
  72. return cli.NewExitError(err.Error(), 1)
  73. }
  74. // TODO: write local file if directory is set
  75. return nil
  76. }
  77. func CmdDelete(c *cli.Context) error {
  78. friendlyID := c.Args().Get(0)
  79. token := c.Args().Get(1)
  80. if friendlyID == "" {
  81. return cli.NewExitError("usage: "+executable.Name()+" delete <postId> [<token>]", 1)
  82. }
  83. u, _ := config.LoadUser(c)
  84. if token == "" {
  85. // Search for the token locally
  86. token = api.TokenFromID(c, friendlyID)
  87. if token == "" && u == nil {
  88. log.Errorln("Couldn't find an edit token locally. Did you create this post here?")
  89. log.ErrorlnQuit("If you have an edit token, use: "+executable.Name()+" delete %s <token>", friendlyID)
  90. }
  91. }
  92. if config.IsTor(c) {
  93. log.Info(c, "Deleting via hidden service...")
  94. } else {
  95. log.Info(c, "Deleting...")
  96. }
  97. err := api.DoDelete(c, friendlyID, token)
  98. if err != nil {
  99. return cli.NewExitError(fmt.Sprintf("Couldn't delete post: %v", err), 1)
  100. }
  101. // TODO: Delete local file, if necessary
  102. return nil
  103. }
  104. func CmdUpdate(c *cli.Context) error {
  105. friendlyID := c.Args().Get(0)
  106. token := c.Args().Get(1)
  107. if friendlyID == "" {
  108. return cli.NewExitError("usage: "+executable.Name()+" update <postId> [<token>]", 1)
  109. }
  110. u, _ := config.LoadUser(c)
  111. if token == "" {
  112. // Search for the token locally
  113. token = api.TokenFromID(c, friendlyID)
  114. if token == "" && u == nil {
  115. log.Errorln("Couldn't find an edit token locally. Did you create this post here?")
  116. log.ErrorlnQuit("If you have an edit token, use: "+executable.Name()+" update %s <token>", friendlyID)
  117. }
  118. }
  119. // Read post body
  120. fullPost := api.ReadStdIn()
  121. if config.IsTor(c) {
  122. log.Info(c, "Updating via hidden service...")
  123. } else {
  124. log.Info(c, "Updating...")
  125. }
  126. err := api.DoUpdate(c, fullPost, friendlyID, token, c.String("font"), c.Bool("code"))
  127. if err != nil {
  128. return cli.NewExitError(fmt.Sprintf("%v", err), 1)
  129. }
  130. return nil
  131. }
  132. func CmdGet(c *cli.Context) error {
  133. friendlyID := c.Args().Get(0)
  134. if friendlyID == "" {
  135. return cli.NewExitError("usage: "+executable.Name()+" get <postId>", 1)
  136. }
  137. if config.IsTor(c) {
  138. log.Info(c, "Getting via hidden service...")
  139. } else {
  140. log.Info(c, "Getting...")
  141. }
  142. err := api.DoFetch(c, friendlyID)
  143. if err != nil {
  144. return cli.NewExitError(fmt.Sprintf("%v", err), 1)
  145. }
  146. return nil
  147. }
  148. func CmdAdd(c *cli.Context) error {
  149. friendlyID := c.Args().Get(0)
  150. token := c.Args().Get(1)
  151. if friendlyID == "" || token == "" {
  152. return cli.NewExitError("usage: "+executable.Name()+" add <postId> <token>", 1)
  153. }
  154. err := api.AddPost(c, friendlyID, token)
  155. if err != nil {
  156. return cli.NewExitError(fmt.Sprintf("%v", err), 1)
  157. }
  158. return nil
  159. }
  160. func CmdListPosts(c *cli.Context) error {
  161. urls := c.Bool("url")
  162. ids := c.Bool("id")
  163. details := c.Bool("v")
  164. posts := api.GetPosts(c)
  165. u, _ := config.LoadUser(c)
  166. if u != nil {
  167. if config.IsTor(c) {
  168. log.Info(c, "Getting posts via hidden service...")
  169. } else {
  170. log.Info(c, "Getting posts...")
  171. }
  172. remotePosts, err := api.GetUserPosts(c, true)
  173. if err != nil {
  174. return cli.NewExitError(fmt.Sprintf("error getting posts: %v", err), 1)
  175. }
  176. if len(remotePosts) > 0 {
  177. fmt.Println("Anonymous Posts")
  178. if details {
  179. identifier := "URL"
  180. if ids || !urls {
  181. identifier = "ID"
  182. }
  183. fmt.Println(identifier)
  184. }
  185. }
  186. for _, p := range remotePosts {
  187. identifier := getPostURL(c, p.ID)
  188. if ids || !urls {
  189. identifier = p.ID
  190. }
  191. fmt.Println(identifier)
  192. }
  193. if len(*posts) > 0 {
  194. fmt.Printf("\nUnclaimed Posts\n")
  195. }
  196. }
  197. if details {
  198. var p api.Post
  199. tw := tabwriter.NewWriter(os.Stdout, 10, 0, 2, ' ', tabwriter.TabIndent)
  200. numPosts := len(*posts)
  201. if ids || !urls && numPosts != 0 {
  202. fmt.Fprintf(tw, "%s\t%s\t\n", "ID", "Token")
  203. } else if numPosts != 0 {
  204. fmt.Fprintf(tw, "%s\t%s\t\n", "URL", "Token")
  205. } else {
  206. fmt.Fprintf(tw, "No local posts found\n")
  207. }
  208. for i := range *posts {
  209. p = (*posts)[numPosts-1-i]
  210. if ids || !urls {
  211. fmt.Fprintf(tw, "%s\t%s\t\n", p.ID, p.EditToken)
  212. } else {
  213. fmt.Fprintf(tw, "%s\t%s\t\n", getPostURL(c, p.ID), p.EditToken)
  214. }
  215. }
  216. return tw.Flush()
  217. }
  218. for _, p := range *posts {
  219. if ids || !urls {
  220. fmt.Printf("%s\n", p.ID)
  221. } else {
  222. fmt.Printf("%s\n", getPostURL(c, p.ID))
  223. }
  224. }
  225. return nil
  226. }
  227. func getPostURL(c *cli.Context, slug string) string {
  228. base := config.WriteasBaseURL
  229. if config.IsDev() {
  230. base = config.DevBaseURL
  231. }
  232. ext := ""
  233. // Output URL in requested format
  234. if c.Bool("md") {
  235. ext = ".md"
  236. }
  237. return fmt.Sprintf("%s/%s%s", base, slug, ext)
  238. }
  239. func CmdCollections(c *cli.Context) error {
  240. u, err := config.LoadUser(c)
  241. if err != nil {
  242. return cli.NewExitError(fmt.Sprintf("couldn't load config: %v", err), 1)
  243. }
  244. if u == nil {
  245. return cli.NewExitError("You must be authenticated to view collections.\nLog in first with: "+executable.Name()+" auth <username>", 1)
  246. }
  247. if config.IsTor(c) {
  248. log.Info(c, "Getting blogs via hidden service...")
  249. } else {
  250. log.Info(c, "Getting blogs...")
  251. }
  252. colls, err := api.DoFetchCollections(c)
  253. if err != nil {
  254. return cli.NewExitError(fmt.Sprintf("Couldn't get collections for user %s: %v", u.User.Username, err), 1)
  255. }
  256. urls := c.Bool("url")
  257. tw := tabwriter.NewWriter(os.Stdout, 8, 0, 2, ' ', tabwriter.TabIndent)
  258. detail := "Title"
  259. if urls {
  260. detail = "URL"
  261. }
  262. fmt.Fprintf(tw, "%s\t%s\t\n", "Alias", detail)
  263. for _, c := range colls {
  264. dData := c.Title
  265. if urls {
  266. dData = c.URL
  267. }
  268. fmt.Fprintf(tw, "%s\t%s\t\n", c.Alias, dData)
  269. }
  270. tw.Flush()
  271. return nil
  272. }
  273. func CmdClaim(c *cli.Context) error {
  274. u, err := config.LoadUser(c)
  275. if err != nil {
  276. return cli.NewExitError(fmt.Sprintf("couldn't load config: %v", err), 1)
  277. }
  278. if u == nil {
  279. return cli.NewExitError("You must be authenticated to claim local posts.\nLog in first with: "+executable.Name()+" auth <username>", 1)
  280. }
  281. localPosts := api.GetPosts(c)
  282. if len(*localPosts) == 0 {
  283. return nil
  284. }
  285. if config.IsTor(c) {
  286. log.Info(c, "Claiming %d post(s) for %s via hidden service...", len(*localPosts), u.User.Username)
  287. } else {
  288. log.Info(c, "Claiming %d post(s) for %s...", len(*localPosts), u.User.Username)
  289. }
  290. results, err := api.ClaimPosts(c, localPosts)
  291. if err != nil {
  292. return cli.NewExitError(fmt.Sprintf("Failed to claim posts: %v", err), 1)
  293. }
  294. var okCount, errCount int
  295. for _, r := range *results {
  296. id := r.ID
  297. if id == "" {
  298. // No top-level ID, so the claim was successful
  299. id = r.Post.ID
  300. }
  301. status := fmt.Sprintf("Post %s...", id)
  302. if r.ErrorMessage != "" {
  303. log.Errorln("%serror: %v", status, r.ErrorMessage)
  304. errCount++
  305. } else {
  306. log.Info(c, "%sOK", status)
  307. okCount++
  308. // only delete local if successful
  309. api.RemovePost(c, id)
  310. }
  311. }
  312. log.Info(c, "%d claimed, %d failed", okCount, errCount)
  313. return nil
  314. }
  315. func CmdAuth(c *cli.Context) error {
  316. username := c.Args().Get(0)
  317. // Check configuration
  318. u, err := config.LoadUser(c)
  319. if err != nil {
  320. return cli.NewExitError(fmt.Sprintf("couldn't load config: %v", err), 1)
  321. }
  322. if u != nil && u.AccessToken != "" && username == u.User.Username {
  323. return cli.NewExitError("You're already authenticated as "+u.User.Username, 1)
  324. }
  325. // Validate arguments and get password
  326. // TODO: after global config, check for default user
  327. if username == "" {
  328. return cli.NewExitError("usage: "+executable.Name()+" auth <username>", 1)
  329. }
  330. fmt.Print("Password: ")
  331. pass, err := gopass.GetPasswdMasked()
  332. if err != nil {
  333. return cli.NewExitError(fmt.Sprintf("error reading password: %v", err), 1)
  334. }
  335. // Validate password
  336. if len(pass) == 0 {
  337. return cli.NewExitError("Please enter your password.", 1)
  338. }
  339. if config.IsTor(c) {
  340. log.Info(c, "Logging in via hidden service...")
  341. } else {
  342. log.Info(c, "Logging in...")
  343. }
  344. err = api.DoLogIn(c, username, string(pass))
  345. if err != nil {
  346. return cli.NewExitError(fmt.Sprintf("error logging in: %v", err), 1)
  347. }
  348. return nil
  349. }
  350. func CmdLogOut(c *cli.Context) error {
  351. if config.IsTor(c) {
  352. log.Info(c, "Logging out via hidden service...")
  353. } else {
  354. log.Info(c, "Logging out...")
  355. }
  356. err := api.DoLogOut(c)
  357. if err != nil {
  358. return cli.NewExitError(fmt.Sprintf("error logging out: %v", err), 1)
  359. }
  360. return nil
  361. }