|
package main |
|
|
|
import ( |
|
"encoding/json" |
|
"fmt" |
|
"io/ioutil" |
|
"net/http" |
|
"sort" |
|
"strings" |
|
"syscall" |
|
"time" |
|
"unsafe" |
|
) |
|
|
|
// Replace socialno.de with twitter.com if it suits you better |
|
// You can also get user_timeline.json?screen_name=USERNAMEHERE |
|
// Where USERNAMEHERE is, of course, the username of the person |
|
var Config = Configuration{ |
|
FetchURL: "http://socialno.de/api/statuses/public_timeline.json", |
|
RefreshInterval: 60, // In seconds |
|
} |
|
|
|
type Feed struct { |
|
Posts Posts |
|
} |
|
|
|
type Post struct { |
|
CreatedAt string `json:"created_at"` |
|
ID int `json:"id"` |
|
StatusnetConversationID int `json:"statusnet_conversation_id"` |
|
Text string `json:"text"` |
|
User User `json:"user"` |
|
Truncated bool `json:"truncated"` |
|
} |
|
|
|
type User struct { |
|
Name string `json:"name"` |
|
ID int `json:"id"` |
|
} |
|
|
|
type Configuration struct { |
|
FetchURL string |
|
RefreshInterval int |
|
} |
|
|
|
type winsize struct { |
|
Row uint16 |
|
Col uint16 |
|
Xpixel uint16 |
|
Ypixel uint16 |
|
} |
|
|
|
type Posts []*Post |
|
|
|
func (s Posts) Len() int { return len(s) } |
|
func (s Posts) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |
|
|
|
type ByID struct{ Posts } |
|
|
|
func (s ByID) Less(i, j int) bool { return s.Posts[i].ID < s.Posts[j].ID } |
|
func init() { |
|
fmt.Print("\033[?25l") // Hide the cursor |
|
} |
|
func main() { |
|
var lastID int |
|
for { |
|
|
|
Connect: |
|
response, err := http.Get(Config.FetchURL) |
|
if err != nil { |
|
fmt.Println("Error connecting to", Config.FetchURL, ", trying again") |
|
goto Connect |
|
} |
|
|
|
bits, err := ioutil.ReadAll(response.Body) |
|
response.Body.Close() |
|
feed := &Feed{} |
|
feedData := feed.Posts |
|
err = json.Unmarshal(bits, &feedData) |
|
if err != nil { |
|
fmt.Println("Error reading from", Config.FetchURL, ", re-connecting") |
|
goto Connect |
|
} |
|
|
|
sort.Sort(ByID{feedData}) |
|
termSize, err := GetWinsize() |
|
if err != nil { |
|
fmt.Println(err) |
|
} |
|
for _, post := range feedData { |
|
if lastID < post.ID { |
|
indent := len(post.User.Name) + 2 |
|
maxLen := int(termSize.Col - uint16(indent) - 1) |
|
words := strings.Split(post.Text, " ") |
|
|
|
fmt.Print(post.User.Name + ": ") |
|
|
|
for wordCount, charsLeft := 0, |
|
maxLen; wordCount < len(words); wordCount++ { |
|
word := words[wordCount] |
|
if charsLeft > len(word)+1 { |
|
fmt.Print(word + " ") |
|
charsLeft -= (len(word) + 1) |
|
|
|
} else if charsLeft == len(word) { |
|
fmt.Print(word + "\n") |
|
fmt.Print(strings.Repeat(" ", indent)) |
|
charsLeft = maxLen |
|
|
|
} else { |
|
fmt.Print("\n") |
|
fmt.Print(strings.Repeat(" ", indent)) |
|
fmt.Print(word + " ") |
|
charsLeft = maxLen - (len(word) + 1) |
|
} |
|
|
|
} |
|
fmt.Print("\n\n") |
|
lastID = post.ID |
|
} |
|
} |
|
|
|
// Spinning cursor while we wait |
|
chars := [4]byte{'|', '/', '-', '\\'} |
|
for i := 4; i <= Config.RefreshInterval+4; i++ { |
|
fmt.Print("\b" + string(chars[i%4])) |
|
time.Sleep(1 * time.Second) |
|
} |
|
fmt.Print("\b") |
|
} |
|
} |
|
|
|
func GetWinsize() (*winsize, error) { |
|
ws := new(winsize) |
|
|
|
r1, _, errno := syscall.Syscall(syscall.SYS_IOCTL, |
|
uintptr(syscall.Stdin), |
|
uintptr(syscall.TIOCGWINSZ), |
|
uintptr(unsafe.Pointer(ws)), |
|
) |
|
|
|
if int(r1) == -1 { |
|
return nil, fmt.Errorf("GetWinsize", int(errno)) |
|
} |
|
return ws, nil |
|
} |
Did it. Works like a charm. Thanks, inhies
Submitted to Pinboard: https://pinboard.in/u:gwenbell/b:296b87d44b61