This repository has been archived on 2022-06-29. You can view files and clone it, but cannot push or open issues or pull requests.
Jon Chen 737231705f
adds vendor directory
vendors dependencies in standard `vendor` directory, managed by glide
2017-05-14 19:35:03 -07:00

250 lines
5.6 KiB
Go

package main
import (
"bufio"
"compress/gzip"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"runtime"
"strings"
"sync"
"github.com/garyburd/go-oauth/oauth"
"github.com/mattn/go-gtk/gdkpixbuf"
"github.com/mattn/go-gtk/gtk"
)
var (
alive = true
)
func readURL(url string) ([]byte, *http.Response) {
r, err := http.Get(url)
if err != nil {
return nil, nil
}
var b []byte
if b, err = ioutil.ReadAll(r.Body); err != nil {
return nil, nil
}
return b, r
}
func bytes2pixbuf(data []byte, typ string) *gdkpixbuf.Pixbuf {
var loader *gdkpixbuf.Loader
if strings.Index(typ, "jpeg") >= 0 {
loader, _ = gdkpixbuf.NewLoaderWithMimeType("image/jpeg")
} else {
loader, _ = gdkpixbuf.NewLoaderWithMimeType("image/png")
}
loader.SetSize(24, 24)
loader.Write(data)
loader.Close()
return loader.GetPixbuf()
}
type tweet struct {
Text string
Identifier string `json:"id_str"`
Source string
User struct {
Name string
ScreenName string `json:"screen_name"`
FollowersCount int `json:"followers_count"`
ProfileImageUrl string `json:"profile_image_url"`
}
Place *struct {
Id string
FullName string `json:"full_name"`
}
Entities struct {
HashTags []struct {
Indices [2]int
Text string
}
UserMentions []struct {
Indices [2]int
ScreenName string `json:"screen_name"`
} `json:"user_mentions"`
Urls []struct {
Indices [2]int
Url string
DisplayUrl string `json:"display_url"`
ExpandedUrl *string `json:"expanded_url"`
}
}
}
func stream(client *oauth.Client, cred *oauth.Credentials, f func(*tweet)) {
param := make(url.Values)
uri := "https://userstream.twitter.com/1.1/user.json"
client.SignParam(cred, "GET", uri, param)
uri = uri + "?" + param.Encode()
res, err := http.Get(uri)
if err != nil {
log.Fatal("failed to get tweets:", err)
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Fatal("failed to get tweets:", err)
}
var buf *bufio.Reader
if res.Header.Get("Content-Encoding") == "gzip" {
gr, err := gzip.NewReader(res.Body)
if err != nil {
log.Fatal("failed to make gzip decoder:", err)
}
buf = bufio.NewReader(gr)
} else {
buf = bufio.NewReader(res.Body)
}
var last []byte
for alive {
b, _, err := buf.ReadLine()
last = append(last, b...)
var t tweet
err = json.Unmarshal(last, &t)
if err != nil {
continue
}
last = []byte{}
if t.Text == "" {
continue
}
f(&t)
}
}
func post(client *oauth.Client, cred *oauth.Credentials, s string) {
param := make(url.Values)
param.Set("status", s)
uri := "https://api.twitter.com/1.1/statuses/update.json"
client.SignParam(cred, "POST", uri, param)
res, err := http.PostForm(uri, url.Values(param))
if err != nil {
log.Println("failed to post tweet:", err)
return
}
defer res.Body.Close()
if res.StatusCode != 200 {
log.Println("failed to get timeline:", err)
return
}
}
func display(t *tweet, buffer *gtk.TextBuffer, tag *gtk.TextTag) {
var iter gtk.TextIter
pixbufbytes, resp := readURL(t.User.ProfileImageUrl)
buffer.GetStartIter(&iter)
if resp != nil {
buffer.InsertPixbuf(&iter, bytes2pixbuf(pixbufbytes, resp.Header.Get("Content-Type")))
}
buffer.Insert(&iter, " ")
buffer.InsertWithTag(&iter, t.User.ScreenName, tag)
buffer.Insert(&iter, ":"+t.Text+"\n")
gtk.MainIterationDo(false)
}
func show(client *oauth.Client, cred *oauth.Credentials, f func(t *tweet)) {
param := make(url.Values)
uri := "https://api.twitter.com/1.1/statuses/home_timeline.json"
client.SignParam(cred, "GET", uri, param)
uri = uri + "?" + param.Encode()
res, err := http.Get(uri)
if err != nil {
return
}
defer res.Body.Close()
if res.StatusCode != 200 {
return
}
var tweets []tweet
json.NewDecoder(res.Body).Decode(&tweets)
for _, t := range tweets {
f(&t)
}
}
func main() {
b, err := ioutil.ReadFile("settings.json")
if err != nil {
fmt.Println(`"settings.json" not found: `, err)
return
}
var config map[string]string
err = json.Unmarshal(b, &config)
if err != nil {
fmt.Println(`can't read "settings.json": `, err)
return
}
client := &oauth.Client{Credentials: oauth.Credentials{config["ClientToken"], config["ClientSecret"]}}
cred := &oauth.Credentials{config["AccessToken"], config["AccessSecret"]}
runtime.LockOSThread()
gtk.Init(&os.Args)
window := gtk.NewWindow(gtk.WINDOW_TOPLEVEL)
window.SetTitle("Twitter!")
window.Connect("destroy", gtk.MainQuit)
vbox := gtk.NewVBox(false, 1)
scrolledwin := gtk.NewScrolledWindow(nil, nil)
textview := gtk.NewTextView()
textview.SetEditable(false)
textview.SetCursorVisible(false)
scrolledwin.Add(textview)
vbox.Add(scrolledwin)
buffer := textview.GetBuffer()
tag := buffer.CreateTag("blue", map[string]string{"foreground": "#0000FF", "weight": "700"})
hbox := gtk.NewHBox(false, 1)
vbox.PackEnd(hbox, false, true, 5)
label := gtk.NewLabel("Tweet")
hbox.PackStart(label, false, false, 5)
text := gtk.NewEntry()
hbox.PackEnd(text, true, true, 5)
text.Connect("activate", func() {
t := text.GetText()
text.SetText("")
post(client, cred, t)
})
window.Add(vbox)
window.SetSizeRequest(800, 500)
window.ShowAll()
var mutex sync.Mutex
go func() {
show(client, cred, func(t *tweet) {
mutex.Lock()
display(t, buffer, tag)
mutex.Unlock()
})
stream(client, cred, func(t *tweet) {
mutex.Lock()
display(t, buffer, tag)
var start, end gtk.TextIter
buffer.GetIterAtLine(&start, buffer.GetLineCount()-2)
buffer.GetEndIter(&end)
buffer.Delete(&start, &end)
mutex.Unlock()
})
}()
for alive {
mutex.Lock()
alive = gtk.MainIterationDo(false)
mutex.Unlock()
}
}