250 lines
5.6 KiB
Go
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()
|
|
}
|
|
}
|