Page MenuHomeMusing Studio

No OneTemporary

diff --git a/writeas/cli.go b/writeas/cli.go
index 0fb8b92..badc84d 100644
--- a/writeas/cli.go
+++ b/writeas/cli.go
@@ -1,444 +1,478 @@
package main
import (
"bufio"
"bytes"
"fmt"
"github.com/codegangsta/cli"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strconv"
"strings"
)
const (
apiUrl = "http://i.write.as"
hiddenApiUrl = "http://writeas7pm7rcdqg.onion"
readApiUrl = "http://i.write.as"
VERSION = "0.2"
)
func main() {
initialize()
// Run the app
app := cli.NewApp()
app.Name = "writeas"
app.Version = VERSION
app.Usage = "Simple text pasting and publishing"
app.Authors = []cli.Author{
{
Name: "Write.as",
Email: "hello@write.as",
},
}
app.Action = cmdPost
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "tor, t",
Usage: "Perform action on Tor hidden service",
},
cli.IntFlag{
Name: "tor-port",
Usage: "Use a different port to connect to Tor",
Value: 9150,
},
}
app.Commands = []cli.Command{
{
Name: "post",
Usage: "Alias for default action: create post from stdin",
Action: cmdPost,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "tor, t",
Usage: "Post via Tor hidden service",
},
cli.IntFlag{
Name: "tor-port",
Usage: "Use a different port to connect to Tor",
Value: 9150,
},
},
},
+ {
+ Name: "new",
+ Usage: "Create a new post with your default text editor and publish",
+ Action: cmdNew,
+ Flags: []cli.Flag{
+ cli.BoolFlag{
+ Name: "tor, t",
+ Usage: "Post via Tor hidden service",
+ },
+ cli.IntFlag{
+ Name: "tor-port",
+ Usage: "Use a different port to connect to Tor",
+ Value: 9150,
+ },
+ },
+ },
{
Name: "delete",
Usage: "Delete a post",
Action: cmdDelete,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "tor, t",
Usage: "Delete via Tor hidden service",
},
cli.IntFlag{
Name: "tor-port",
Usage: "Use a different port to connect to Tor",
Value: 9150,
},
},
},
{
Name: "update",
Usage: "Update (overwrite) a post",
Action: cmdUpdate,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "tor, t",
Usage: "Update via Tor hidden service",
},
cli.IntFlag{
Name: "tor-port",
Usage: "Use a different port to connect to Tor",
Value: 9150,
},
},
},
{
Name: "get",
Usage: "Read a raw post",
Action: cmdGet,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "tor, t",
Usage: "Get from Tor hidden service",
},
cli.IntFlag{
Name: "tor-port",
Usage: "Use a different port to connect to Tor",
Value: 9150,
},
},
},
{
Name: "add",
Usage: "Add a post locally",
Action: cmdAdd,
},
{
Name: "list",
Usage: "List local posts",
Action: cmdList,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "id",
Usage: "Show list with post IDs (default)",
},
cli.BoolFlag{
Name: "url",
Usage: "Show list with URLs",
},
},
},
}
app.Run(os.Args)
}
func initialize() {
// Ensure we have a data directory to use
if !dataDirExists() {
createDataDir()
}
}
func readStdIn() []byte {
numBytes, numChunks := int64(0), int64(0)
r := bufio.NewReader(os.Stdin)
fullPost := []byte{}
buf := make([]byte, 0, 1024)
for {
n, err := r.Read(buf[:cap(buf)])
buf = buf[:n]
if n == 0 {
if err == nil {
continue
}
if err == io.EOF {
break
}
log.Fatal(err)
}
numChunks++
numBytes += int64(len(buf))
fullPost = append(fullPost, buf...)
if err != nil && err != io.EOF {
log.Fatal(err)
}
}
return fullPost
}
func check(err error) {
if err != nil {
fmt.Printf("%s\n", err)
os.Exit(1)
}
}
-func cmdPost(c *cli.Context) {
- fullPost := readStdIn()
-
+func handlePost(fullPost []byte, c *cli.Context) {
tor := c.Bool("tor") || c.Bool("t")
if c.Int("tor-port") != 0 {
torPort = c.Int("tor-port")
}
if tor {
fmt.Println("Posting to hidden service...")
} else {
fmt.Println("Posting...")
}
DoPost(fullPost, false, tor)
}
+func cmdPost(c *cli.Context) {
+ handlePost(readStdIn(), c)
+}
+
+func cmdNew(c *cli.Context) {
+ p := composeNewPost()
+ if p == nil {
+ // Assume composeNewPost already told us what the error was. Abort now.
+ os.Exit(1)
+ }
+
+ // Ensure we have something to post
+ if len(*p) == 0 {
+ fmt.Println("Empty post. Bye!")
+ os.Exit(0)
+ }
+
+ handlePost(*p, c)
+}
+
func cmdDelete(c *cli.Context) {
friendlyId := c.Args().Get(0)
token := c.Args().Get(1)
if friendlyId == "" {
fmt.Println("usage: writeas delete <postId> [<token>]")
os.Exit(1)
}
if token == "" {
// Search for the token locally
token = tokenFromID(friendlyId)
if token == "" {
fmt.Println("Couldn't find an edit token locally. Did you create this post here?")
fmt.Printf("If you have an edit token, use: writeas delete %s <token>\n", friendlyId)
os.Exit(1)
}
}
tor := c.Bool("tor") || c.Bool("t")
if c.Int("tor-port") != 0 {
torPort = c.Int("tor-port")
}
DoDelete(friendlyId, token, tor)
}
func cmdUpdate(c *cli.Context) {
friendlyId := c.Args().Get(0)
token := c.Args().Get(1)
if friendlyId == "" {
fmt.Println("usage: writeas update <postId> [<token>]")
os.Exit(1)
}
if token == "" {
// Search for the token locally
token = tokenFromID(friendlyId)
if token == "" {
fmt.Println("Couldn't find an edit token locally. Did you create this post here?")
fmt.Printf("If you have an edit token, use: writeas update %s <token>\n", friendlyId)
os.Exit(1)
}
}
// Read post body
fullPost := readStdIn()
tor := c.Bool("tor") || c.Bool("t")
if c.Int("tor-port") != 0 {
torPort = c.Int("tor-port")
}
DoUpdate(fullPost, friendlyId, token, tor)
}
func cmdGet(c *cli.Context) {
friendlyId := c.Args().Get(0)
if friendlyId == "" {
fmt.Println("usage: writeas get <postId>")
os.Exit(1)
}
tor := c.Bool("tor") || c.Bool("t")
if c.Int("tor-port") != 0 {
torPort = c.Int("tor-port")
}
DoFetch(friendlyId, tor)
}
func cmdAdd(c *cli.Context) {
friendlyId := c.Args().Get(0)
token := c.Args().Get(1)
if friendlyId == "" || token == "" {
fmt.Println("usage: writeas add <postId> <token>")
os.Exit(1)
}
addPost(friendlyId, token)
}
func cmdList(c *cli.Context) {
urls := c.Bool("url")
ids := c.Bool("id")
var p Post
posts := getPosts()
for i := range *posts {
p = (*posts)[len(*posts)-1-i]
if ids || !urls {
fmt.Printf("%s ", p.ID)
}
if urls {
fmt.Printf("https://write.as/%s ", p.ID)
}
fmt.Print("\n")
}
}
func client(read, tor bool, path, query string) (string, *http.Client) {
var u *url.URL
var client *http.Client
if tor {
u, _ = url.ParseRequestURI(hiddenApiUrl)
if len(path) != 12 {
// Handle alpha phase HTML-based URLs
path += ".txt"
}
if read {
u.Path = "/" + path
} else {
u.Path = "/api"
}
client = torClient()
} else {
u, _ = url.ParseRequestURI(apiUrl)
u.Path = "/" + path
client = &http.Client{}
}
if query != "" {
u.RawQuery = query
}
urlStr := fmt.Sprintf("%v", u)
return urlStr, client
}
func DoFetch(friendlyId string, tor bool) {
path := friendlyId
urlStr, client := client(true, tor, path, "")
r, _ := http.NewRequest("GET", urlStr, nil)
r.Header.Add("User-Agent", "writeas-cli v"+VERSION)
resp, err := client.Do(r)
check(err)
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
content, err := ioutil.ReadAll(resp.Body)
check(err)
fmt.Printf("%s\n", string(content))
} else if resp.StatusCode == http.StatusNotFound {
fmt.Printf("Post not found.\n")
} else {
fmt.Printf("Problem getting post: %s\n", resp.Status)
}
}
func DoPost(post []byte, encrypt, tor bool) {
data := url.Values{}
data.Set("w", string(post))
if encrypt {
data.Add("e", "")
}
data.Add("font", "mono")
urlStr, client := client(false, tor, "", "")
r, _ := http.NewRequest("POST", urlStr, bytes.NewBufferString(data.Encode()))
r.Header.Add("User-Agent", "writeas-cli v"+VERSION)
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
resp, err := client.Do(r)
check(err)
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
content, err := ioutil.ReadAll(resp.Body)
check(err)
nlPos := strings.Index(string(content), "\n")
url := content[:nlPos]
idPos := strings.LastIndex(string(url), "/") + 1
id := string(url[idPos:])
token := string(content[nlPos+1 : len(content)-1])
addPost(id, token)
fmt.Printf("%s\n", url)
} else {
fmt.Printf("Unable to post: %s\n", resp.Status)
}
}
func DoUpdate(post []byte, friendlyId, token string, tor bool) {
urlStr, client := client(false, tor, "", fmt.Sprintf("id=%s&t=%s", friendlyId, token))
data := url.Values{}
data.Set("w", string(post))
r, _ := http.NewRequest("POST", urlStr, bytes.NewBufferString(data.Encode()))
r.Header.Add("User-Agent", "writeas-cli v"+VERSION)
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
r.Header.Add("Content-Length", strconv.Itoa(len(data.Encode())))
resp, err := client.Do(r)
check(err)
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
if tor {
fmt.Println("Post updated via hidden service.")
} else {
fmt.Println("Post updated.")
}
} else {
if DEBUG {
fmt.Printf("Problem updating: %s\n", resp.Status)
} else {
fmt.Printf("Post doesn't exist, or bad edit token given.\n")
}
}
}
func DoDelete(friendlyId, token string, tor bool) {
urlStr, client := client(false, tor, "", fmt.Sprintf("id=%s&t=%s", friendlyId, token))
r, _ := http.NewRequest("DELETE", urlStr, nil)
r.Header.Add("User-Agent", "writeas-cli v"+VERSION)
r.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := client.Do(r)
check(err)
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
if tor {
fmt.Println("Post deleted from hidden service.")
} else {
fmt.Println("Post deleted.")
}
removePost(friendlyId)
} else {
if DEBUG {
fmt.Printf("Problem deleting: %s\n", resp.Status)
} else {
fmt.Printf("Post doesn't exist, or bad edit token given.\n")
}
}
}
diff --git a/writeas/posts.go b/writeas/posts.go
index 52dd2ec..f3b6748 100644
--- a/writeas/posts.go
+++ b/writeas/posts.go
@@ -1,99 +1,148 @@
package main
import (
"fmt"
"github.com/writeas/writeas-cli/utils"
+ "io/ioutil"
"os"
"path/filepath"
"strings"
)
const (
POSTS_FILE = "posts.psv"
SEPARATOR = `|`
)
type Post struct {
ID string
EditToken string
}
func userDataDir() string {
return filepath.Join(parentDataDir(), DATA_DIR_NAME)
}
func dataDirExists() bool {
return fileutils.Exists(userDataDir())
}
func createDataDir() {
err := os.Mkdir(userDataDir(), 0700)
if err != nil {
if DEBUG {
panic(err)
} else {
fmt.Printf("Error creating data directory: %s\n", err)
return
}
}
}
func addPost(id, token string) {
f, err := os.OpenFile(filepath.Join(userDataDir(), POSTS_FILE), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0600)
if err != nil {
if DEBUG {
panic(err)
} else {
fmt.Printf("Error creating local posts list: %s\n", err)
return
}
}
defer f.Close()
l := fmt.Sprintf("%s%s%s\n", id, SEPARATOR, token)
if _, err = f.WriteString(l); err != nil {
if DEBUG {
panic(err)
} else {
fmt.Printf("Error writing to local posts list: %s\n", err)
return
}
}
}
func tokenFromID(id string) string {
post := fileutils.FindLine(filepath.Join(userDataDir(), POSTS_FILE), id)
if post == "" {
return ""
}
parts := strings.Split(post, SEPARATOR)
if len(parts) < 2 {
return ""
}
return parts[1]
}
func removePost(id string) {
fileutils.RemoveLine(filepath.Join(userDataDir(), POSTS_FILE), id)
}
func getPosts() *[]Post {
lines := fileutils.ReadData(filepath.Join(userDataDir(), POSTS_FILE))
posts := []Post{}
parts := make([]string, 2)
for _, l := range *lines {
parts = strings.Split(l, SEPARATOR)
if len(parts) < 2 {
continue
}
posts = append(posts, Post{ID: parts[0], EditToken: parts[1]})
}
return &posts
+}
+
+func composeNewPost() *[]byte {
+ f, err := ioutil.TempFile(os.TempDir(), "WApost")
+ if err != nil {
+ if DEBUG {
+ panic(err)
+ } else {
+ fmt.Printf("Error creating temp file: %s\n", err)
+ return nil
+ }
+ }
+ defer os.Remove(f.Name())
+
+ cmd := editPostCmd(f.Name())
+ if cmd == nil {
+ fmt.Println(NO_EDITOR_ERR)
+ return nil
+ }
+ cmd.Stdin = os.Stdin
+ cmd.Stdout = os.Stdout
+ cmd.Stderr = os.Stderr
+ if err := cmd.Start(); err != nil {
+ if DEBUG {
+ panic(err)
+ } else {
+ fmt.Printf("Error starting editor: %s\n", err)
+ return nil
+ }
+ }
+ if err := cmd.Wait(); err != nil {
+ if DEBUG {
+ panic(err)
+ } else {
+ fmt.Printf("Editor finished with error: %s\n", err)
+ return nil
+ }
+ }
+ post, err := ioutil.ReadFile(f.Name())
+ if err != nil {
+ if DEBUG {
+ panic(err)
+ } else {
+ fmt.Printf("Error reading post: %s\n", err)
+ return nil
+ }
+ }
+ return &post
}
diff --git a/writeas/posts_nix.go b/writeas/posts_nix.go
index fb1fabc..9e38aa3 100644
--- a/writeas/posts_nix.go
+++ b/writeas/posts_nix.go
@@ -1,18 +1,40 @@
// +build !windows
package main
import (
+ "fmt"
"github.com/mitchellh/go-homedir"
+ "os"
+ "os/exec"
)
-const DATA_DIR_NAME = ".writeas"
+const (
+ DATA_DIR_NAME = ".writeas"
+ NO_EDITOR_ERR = "Couldn't find default editor. Try setting $EDITOR environment variable in ~/.profile"
+)
func parentDataDir() string {
dir, err := homedir.Dir()
if err != nil {
panic(err)
}
return dir
}
+
+func editPostCmd(fname string) *exec.Cmd {
+ editor := os.Getenv("EDITOR")
+ if editor == "" {
+ // Fall back to default editor
+ path, err := exec.LookPath("vim")
+ if err != nil {
+ path, err = exec.LookPath("nano")
+ if err != nil {
+ return nil
+ }
+ }
+ editor = path
+ }
+ return exec.Command(editor, fname)
+}
diff --git a/writeas/posts_win.go b/writeas/posts_win.go
index 3166372..d7b5663 100644
--- a/writeas/posts_win.go
+++ b/writeas/posts_win.go
@@ -1,13 +1,21 @@
// +build windows
package main
import (
"os"
+ "os/exec"
)
-const DATA_DIR_NAME = "Write.as"
+const (
+ DATA_DIR_NAME = "Write.as"
+ NO_EDITOR_ERR = "Error getting default editor. You shouldn't see this, so let us know you did: hello@write.as"
+)
func parentDataDir() string {
return os.Getenv("APPDATA")
}
+
+func editPostCmd(fname string) *exec.Cmd {
+ return exec.Command(fname)
+}

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 17, 7:52 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3241180

Event Timeline