Page Menu
Home
Musing Studio
Search
Configure Global Search
Log In
Files
F10671121
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
14 KB
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, May 17, 7:52 AM (1 d, 9 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3241180
Attached To
rWCLI writeas-cli
Event Timeline
Log In to Comment