├── INSTALL ├── LICENSE ├── Makefile ├── README.rst ├── api.go ├── app ├── Makefile └── app.go ├── doc ├── go-twitter.htm └── go-twitter.txt ├── examples ├── Makefile ├── parallel-bench ├── parallel.go └── random-crawler.go ├── http_auth.go ├── rate_limit.go ├── search.go ├── status.go ├── twitter_test.go ├── user.go └── util.go /INSTALL: -------------------------------------------------------------------------------- 1 | 2 | 1. make install 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright 2009 Bill Casarin 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http:www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | include $(GOROOT)/src/Make.inc 3 | 4 | TARG=twitter 5 | GOFILES=\ 6 | api.go\ 7 | status.go\ 8 | user.go\ 9 | search.go\ 10 | util.go\ 11 | rate_limit.go\ 12 | http_auth.go 13 | 14 | include $(GOROOT)/src/Make.pkg 15 | 16 | .PHONY: doc 17 | 18 | doc: 19 | godoc -html=true twitter \ 20 | | sed -e 's/\/src\/pkg\/twitter\//\.\.\//g' > doc/go-twitter.htm 21 | godoc -html=false twitter > doc/go-twitter.txt 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | go-twitter 3 | ============ 4 | 5 | go-twitter is a Twitter library package for Go. The interface is similar to 6 | python-twitter. 7 | 8 | 9 | Installation 10 | ============ 11 | 12 | #. Make sure you have Go installed and your environment is set up 13 | correctly: $GOROOT, $GOARCH, $GOBIN, etc. 14 | 15 | #. Checkout the code from the repository or extract the source code. 16 | 17 | #. cd go-twitter && make install 18 | 19 | 20 | Quick Start 21 | =========== 22 | 23 | :: 24 | 25 | import ( 26 | "go-twitter" 27 | "fmt" 28 | 29 | ) 30 | // Prints the public timeline 31 | func main() { 32 | api := twitter.NewApi(); 33 | pubTimeline := <-api.GetPublicTimeline(); 34 | 35 | for i, status := range pubTimeline { 36 | fmt.Printf("#%d %s: %s\n", i, 37 | status.GetUser().GetScreenName(), 38 | status.GetText()); 39 | } 40 | } 41 | 42 | 43 | Documentation 44 | ============= 45 | 46 | doc/ - godoc generated files, site coming soon 47 | 48 | -------------------------------------------------------------------------------- /api.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2009 Bill Casarin 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | package twitter 17 | 18 | import ( 19 | "fmt" 20 | "os" 21 | "encoding/json" 22 | "strconv" 23 | "time" 24 | "regexp" 25 | "net/url" 26 | ) 27 | 28 | const ( 29 | kDefaultClient = "go-twitter" 30 | kDefaultClientURL = "http://jb55.github.com/go-twitter" 31 | kDefaultClientVersion = "0.1" 32 | kDefaultUserAgent = "go-twitter" 33 | kErr = "GoTwitter Error: " 34 | kWarn = "GoTwitter Warning: " 35 | kDefaultTimelineAlloc = 20 36 | 37 | _QUERY_GETSTATUS = "http://www.twitter.com/statuses/show/%d.json" 38 | _QUERY_UPDATESTATUS = "http://www.twitter.com/statuses/update/update.json" 39 | _QUERY_PUBLICTIMELINE = "http://www.twitter.com/statuses/public_timeline.json" 40 | _QUERY_USERTIMELINE = "http://www.twitter.com/statuses/user_timeline.json" 41 | _QUERY_REPLIES = "http://www.twitter.com/statuses/mentions.json" 42 | _QUERY_FRIENDSTIMELINE = "http://www.twitter.com/statuses/friends_timeline.json" 43 | _QUERY_USER_NAME = "http://www.twitter.com/%s.json?screen_name=%s" 44 | _QUERY_USER_ID = "http://www.twitter.com/%s.json?user_id=%d" 45 | _QUERY_USER_DEFAULT = "http://www.twitter.com/%s.json" 46 | _QUERY_SEARCH = "http://search.twitter.com/search.json" 47 | _QUERY_RATELIMIT = "http://twitter.com/account/rate_limit_status.json" 48 | ) 49 | 50 | const ( 51 | _STATUS = iota 52 | _SLICESTATUS 53 | _SLICESEARCH 54 | _USER 55 | _SLICEUSER 56 | _BOOL 57 | _ERROR 58 | _RATELIMIT 59 | ) 60 | 61 | type TwitterError struct { 62 | error string 63 | } 64 | 65 | type Api struct { 66 | user string 67 | pass string 68 | errors chan error 69 | lastError error 70 | client string 71 | clientURL string 72 | clientVersion string 73 | userAgent string 74 | receiveChannel interface{} 75 | } 76 | 77 | // type that satisfies the os.Error interface 78 | func (self TwitterError) Error() string { return self.error } 79 | 80 | // Creates and initializes new Api objec 81 | func NewApi() *Api { 82 | api := new(Api) 83 | api.init() 84 | return api 85 | } 86 | 87 | func (self *Api) isAuthed() bool { 88 | // TODO: validate user and pass 89 | return self.user != "" && self.pass != "" 90 | } 91 | 92 | // Returns the last error sent to the error channel. 93 | // Calling this function pops the last error, subsequent calls will be nil 94 | // unless another error has occured. 95 | func (self *Api) GetLastError() error { 96 | last := self.lastError 97 | self.lastError = nil 98 | return last 99 | } 100 | 101 | // Gets the followers for a given user represented by a slice 102 | // of twitter.User instances 103 | // 104 | // user: 105 | // A user id or name to fetch the followers from. If this argument 106 | // is nil, then the followers are fetched from the authenticated user. 107 | // This paramater must be an int, int64, or string. 108 | // 109 | // page: 110 | // Not yet implemented 111 | func (self *Api) GetFollowers(user interface{}, page int) <-chan []User { 112 | return self.getUsersByType(user, page, "statuses/followers") 113 | } 114 | 115 | // Gets the friends for a given user represented by a slice 116 | // of twitter.User instances 117 | // 118 | // user: 119 | // A user id or name to fetch the friends from. If this argument 120 | // is nil, then the friends are fetched from the authenticated user. 121 | // This paramater must be an int, int64, or string. 122 | // 123 | // page: 124 | // Not yet implemented 125 | func (self *Api) GetFriends(user interface{}, page int) <-chan []User { 126 | return self.getUsersByType(user, page, "statuses/friends") 127 | } 128 | 129 | func (self *Api) getUsersByType(user interface{}, page int, typ string) <-chan []User { 130 | var url_ string 131 | var ok bool 132 | responseChannel := self.buildRespChannel(_SLICEUSER).(chan []User) 133 | 134 | if url_, ok = self.buildUserUrl(typ, user, page); !ok { 135 | responseChannel <- nil 136 | return responseChannel 137 | } 138 | 139 | go self.goGetUsers(url_, responseChannel) 140 | return responseChannel 141 | } 142 | 143 | // Performs a simple Twitter search. Returns a slice of twitter.SearchResult 144 | // instances 145 | // 146 | // query: 147 | // The string of text to search for. This is URL encoded automatically. 148 | func (self *Api) SearchSimple(query string) <-chan []SearchResult { 149 | return self.Search(query, 0, 0, 0, "", "") 150 | } 151 | 152 | // Performs a Twitter search. Returns a slice of twitter.SearchResult instances 153 | // string fields are automatically URL Encoded 154 | // query: 155 | // The string of text to search for. 156 | // page: 157 | // The page of results to return. Set to 0 to use the default value. 158 | // perPage: 159 | // The number of results per page. Set to 0 to use the default value. 160 | // sinceId: 161 | // Return tweets with status ids greater than the given id. Set to 0 162 | // to use the default value. 163 | // locale: 164 | // Specify the language of the query you are sending (only ja is currently 165 | // effective). This is intended for language-specific clients and the default 166 | // should work in the majority of cases. Set to an empty string to use 167 | // the default value. 168 | // lang: 169 | // Restricts tweets to the given language, given by an ISO 639-1 code. 170 | // Set to an empty string to use the default value. 171 | func (self *Api) Search(query string, page int, perPage int, sinceId int, locale string, lang string) <-chan []SearchResult { 172 | variables := make(map[string]string) 173 | url_ := _QUERY_SEARCH 174 | responseChannel := self.buildRespChannel(_SLICESEARCH).(chan []SearchResult) 175 | 176 | variables["q"] = query 177 | 178 | if page >= 2 { 179 | variables["page"] = strconv.Itoa(page) 180 | } 181 | 182 | if perPage > 0 { 183 | variables["rpp"] = strconv.Itoa(perPage) 184 | } 185 | 186 | if sinceId > 0 { 187 | variables["since_id"] = strconv.Itoa(sinceId) 188 | } 189 | 190 | if locale != "" { 191 | variables["locale"] = locale 192 | } 193 | 194 | if lang != "" { 195 | variables["lang"] = lang 196 | } 197 | 198 | url_ = addQueryVariables(url_, variables) 199 | go self.goGetSearchResults(url_, responseChannel) 200 | 201 | return responseChannel 202 | } 203 | 204 | // Returns a channel which receives a twitter.User instance for the given 205 | // username. 206 | // 207 | // id: 208 | // A twiter user id 209 | func (self *Api) GetUserById(id int64) <-chan User { 210 | var url_ string 211 | var ok bool 212 | responseChannel := self.buildRespChannel(_USER).(chan User) 213 | 214 | if url_, ok = self.buildUserUrl("users/show", id, 0); !ok { 215 | responseChannel <- nil 216 | return responseChannel 217 | } 218 | 219 | go self.goGetUser(url_, responseChannel) 220 | return responseChannel 221 | } 222 | 223 | // Returns a channel which receives a twitter.User instance for the given 224 | // username. 225 | // 226 | // name: 227 | // The screenname of the user 228 | func (self *Api) GetUser(name string) <-chan User { 229 | var url_ string 230 | var ok bool 231 | responseChannel := self.buildRespChannel(_USER).(chan User) 232 | 233 | if url_, ok = self.buildUserUrl("users/show", name, 0); !ok { 234 | responseChannel <- nil 235 | return responseChannel 236 | } 237 | 238 | go self.goGetUser(url_, responseChannel) 239 | return responseChannel 240 | } 241 | 242 | // Checks to see if there are any errors in the error channel 243 | func (self *Api) HasErrors() bool { return len(self.errors) > 0 } 244 | 245 | // Retrieves the public timeline as a slice of Status objects 246 | func (self *Api) GetPublicTimeline() <-chan []Status { 247 | responseChannel := self.buildRespChannel(_SLICESTATUS).(chan []Status) 248 | go self.goGetStatuses(_QUERY_PUBLICTIMELINE, responseChannel) 249 | return responseChannel 250 | } 251 | 252 | // Retrieves the currently authorized user's 253 | // timeline as a slice of Status objects 254 | func (self *Api) GetUserTimeline() <-chan []Status { 255 | responseChannel := self.buildRespChannel(_SLICESTATUS).(chan []Status) 256 | go self.goGetStatuses(_QUERY_USERTIMELINE, responseChannel) 257 | return responseChannel 258 | } 259 | 260 | // Returns the 20 most recent statuses posted by the authenticating user and 261 | // that user's friends. This is the equivalent of /timeline/home on the Web. 262 | // Returns the statuses as a slice of Status objects 263 | func (self *Api) GetFriendsTimeline() <-chan []Status { 264 | responseChannel := self.buildRespChannel(_SLICESTATUS).(chan []Status) 265 | go self.goGetStatuses(_QUERY_FRIENDSTIMELINE, responseChannel) 266 | return responseChannel 267 | } 268 | 269 | // Returns the 20 most recent mentions for the authenticated user 270 | // Returns the statuses as a slice of Status objects 271 | func (self *Api) GetReplies() <-chan []Status { 272 | responseChannel := self.buildRespChannel(_SLICESTATUS).(chan []Status) 273 | go self.goGetStatuses(_QUERY_REPLIES, responseChannel) 274 | return responseChannel 275 | } 276 | 277 | // Returns rate limiting information 278 | func (self *Api) GetRateLimitInfo() <-chan RateLimit { 279 | responseChannel := self.buildRespChannel(_RATELIMIT).(chan RateLimit) 280 | go self.goGetRateLimit(_QUERY_RATELIMIT, responseChannel) 281 | return responseChannel 282 | } 283 | 284 | // Set the X-Twitter HTTP headers that will be sent to the server. 285 | // 286 | // client: 287 | // The client name as a string. Will be sent to the server as 288 | // the 'X-Twitter-Client' header. 289 | // url: 290 | // The URL of the meta.xml as a string. Will be sent to the server 291 | // as the 'X-Twitter-Client-URL' header. 292 | // version: 293 | // The client version as a string. Will be sent to the server 294 | // as the 'X-Twitter-Client-Version' header. 295 | func (self *Api) SetXTwitterHeaders(client, url_, version string) { 296 | self.client = client 297 | self.clientURL = url_ 298 | self.clientVersion = version 299 | } 300 | 301 | // Builds a response channel for async function calls 302 | func (self *Api) buildRespChannel(channelType int) interface{} { 303 | const size = 1 304 | 305 | // TODO: I think it's time to learn the reflect package... 306 | // this switch statement is to protect the client from 307 | // using a wrong receive channel 308 | if self.receiveChannel != nil { 309 | switch channelType { 310 | case _STATUS: 311 | if _, ok := self.receiveChannel.(chan Status); ok { 312 | return self.receiveChannel 313 | } 314 | break 315 | case _SLICESTATUS: 316 | if _, ok := self.receiveChannel.(chan []Status); ok { 317 | return self.receiveChannel 318 | } 319 | break 320 | case _SLICESEARCH: 321 | if _, ok := self.receiveChannel.(chan []SearchResult); ok { 322 | return self.receiveChannel 323 | } 324 | break 325 | case _USER: 326 | if _, ok := self.receiveChannel.(chan User); ok { 327 | return self.receiveChannel 328 | } 329 | break 330 | case _RATELIMIT: 331 | if _, ok := self.receiveChannel.(chan RateLimit); ok { 332 | return self.receiveChannel 333 | } 334 | break 335 | case _SLICEUSER: 336 | if _, ok := self.receiveChannel.(chan []User); ok { 337 | return self.receiveChannel 338 | } 339 | break 340 | case _BOOL: 341 | if _, ok := self.receiveChannel.(chan bool); ok { 342 | return self.receiveChannel 343 | } 344 | break 345 | case _ERROR: 346 | if _, ok := self.receiveChannel.(chan error); ok { 347 | return self.receiveChannel 348 | } 349 | } 350 | } 351 | 352 | switch channelType { 353 | case _STATUS: 354 | return make(chan Status, size) 355 | case _SLICESTATUS: 356 | return make(chan []Status, size) 357 | case _SLICESEARCH: 358 | return make(chan []SearchResult, size) 359 | case _USER: 360 | return make(chan User, size) 361 | case _RATELIMIT: 362 | return make(chan RateLimit, size) 363 | case _SLICEUSER: 364 | return make(chan []User, size) 365 | case _BOOL: 366 | return make(chan bool, size) 367 | case _ERROR: 368 | return make(chan error, size) 369 | } 370 | 371 | self.reportError("Invalid channel type") 372 | return nil 373 | } 374 | 375 | func (self *Api) goGetStatuses(url_ string, responseChannel chan []Status) { 376 | responseChannel <- self.getStatuses(url_) 377 | } 378 | 379 | func (self *Api) goGetUsers(url_ string, responseChannel chan []User) { 380 | responseChannel <- self.getUsers(url_) 381 | } 382 | 383 | func (self *Api) goGetRateLimit(url_ string, responseChannel chan RateLimit) { 384 | var rateLimitDummy tTwitterRateLimitDummy 385 | jsonString := self.getJsonFromUrl(url_) 386 | json.Unmarshal([]uint8(jsonString), &rateLimitDummy) 387 | 388 | rateLimit := &(rateLimitDummy.Object) 389 | 390 | responseChannel <- rateLimit 391 | } 392 | 393 | func (self *Api) goGetSearchResults(url_ string, responseChannel chan []SearchResult) { 394 | var searchDummy tTwitterSearchDummy 395 | var results []SearchResult 396 | 397 | jsonString := self.getJsonFromUrl(url_) 398 | json.Unmarshal([]uint8(jsonString), &searchDummy) 399 | 400 | dummyLen := len(searchDummy.Object.Results) 401 | results = make([]SearchResult, dummyLen) 402 | 403 | for i := 0; i < dummyLen; i++ { 404 | result := &searchDummy.Object.Results[i] 405 | results[i] = result 406 | if err := result.GetError(); err != "" { 407 | self.reportError(err) 408 | } 409 | } 410 | 411 | responseChannel <- results 412 | } 413 | 414 | func (self *Api) getStatuses(url_ string) []Status { 415 | var timelineDummy tTwitterTimelineDummy 416 | var timeline []Status 417 | 418 | jsonString := self.getJsonFromUrl(url_) 419 | json.Unmarshal([]uint8(jsonString), &timelineDummy) 420 | 421 | dummyLen := len(timelineDummy.Object) 422 | timeline = make([]Status, dummyLen) 423 | 424 | for i := 0; i < dummyLen; i++ { 425 | status := &timelineDummy.Object[i] 426 | timeline[i] = status 427 | if err := status.GetError(); err != "" { 428 | self.reportError(err) 429 | } else { 430 | } 431 | } 432 | 433 | return timeline 434 | } 435 | 436 | func parseTwitterDate(date string) *time.Time { 437 | r, err := regexp.Compile("\\+0000") 438 | 439 | if err != nil { 440 | fmt.Fprintf(os.Stderr, err.Error()+"\n") 441 | } 442 | 443 | newStr := r.ReplaceAllString(date, "-0000") 444 | parsedTime, err := time.Parse(time.RubyDate, newStr) 445 | 446 | if err != nil { 447 | fmt.Fprintf(os.Stderr, err.Error()+"\n") 448 | t :=time.Now() 449 | return &t 450 | } 451 | 452 | return &parsedTime 453 | } 454 | 455 | // TODO: consolidate getStatuses/getUsers when we get generics or when someone 456 | // submits a patch of reflect wizardry which I can't seem to wrap my head 457 | // around 458 | func (self *Api) getUsers(url_ string) []User { 459 | var usersDummy tTwitterUserListDummy 460 | var users []User 461 | 462 | jsonString := self.getJsonFromUrl(url_) 463 | json.Unmarshal([]uint8(jsonString), &usersDummy) 464 | 465 | dummyLen := len(usersDummy.Object) 466 | users = make([]User, dummyLen) 467 | 468 | for i := 0; i < dummyLen; i++ { 469 | user := &usersDummy.Object[i] 470 | users[i] = user 471 | if err := user.GetError(); err != "" { 472 | self.reportError(err) 473 | } 474 | } 475 | 476 | return users 477 | } 478 | 479 | // Sets the Twitter client header, aka the X-Twitter-Client http header on 480 | // all POST operations 481 | func (self *Api) SetClientString(client string) { 482 | self.client = client 483 | } 484 | 485 | // Initializes a new Api object, called by NewApi() 486 | func (self *Api) init() { 487 | self.errors = make(chan error, 16) 488 | self.receiveChannel = nil 489 | self.client = kDefaultClient 490 | self.clientURL = kDefaultClientURL 491 | self.clientVersion = kDefaultClientVersion 492 | self.userAgent = kDefaultUserAgent 493 | } 494 | 495 | // Overrides the default user agent (go-twitter) 496 | func (self *Api) SetUserAgent(agent string) { self.userAgent = agent } 497 | 498 | // Sets the username and password string for all subsequent authorized 499 | // HTTP requests 500 | func (self *Api) SetCredentials(username, password string) { 501 | self.user = username 502 | self.pass = password 503 | } 504 | 505 | // Disable Twitter authentication, subsequent REST calls will not use 506 | // Authentication 507 | func (self *Api) ClearCredentials() { 508 | self.user = "" 509 | self.pass = "" 510 | } 511 | 512 | // Returns a channel which receives API errors. Can be used for logging 513 | // errors. 514 | // 515 | // monitorErrors - listens to api errors and logs them 516 | // 517 | // func monitorErrors(quit chan bool, errors <-chan os.Error) { 518 | // for ;; { 519 | // select { 520 | // case err := <-errors: 521 | // fmt.Fprintf(os.Stderr, err.String()); 522 | // break; 523 | // case <-quit: 524 | // return; 525 | // } 526 | // } 527 | // } 528 | // 529 | func (self *Api) GetErrorChannel() <-chan error { 530 | return self.errors 531 | } 532 | 533 | // Post a Twitter status message to the authenticated user 534 | // 535 | // The twitter.Api instance must be authenticated 536 | func (self *Api) PostUpdate(status string, inReplyToId int64) <-chan bool { 537 | responseChannel := self.buildRespChannel(_BOOL).(chan bool) 538 | 539 | go self.goPostUpdate(status, inReplyToId, responseChannel) 540 | return responseChannel 541 | } 542 | 543 | func (self *Api) goPostUpdate(status string, inReplyToId int64, response chan bool) { 544 | url_ := _QUERY_UPDATESTATUS 545 | var data string 546 | 547 | data = "status=" + url.QueryEscape(status) 548 | if inReplyToId != 0 { 549 | reply_data := fmt.Sprintf("&in_reply_to_status_id=%d", inReplyToId) 550 | data += reply_data 551 | } 552 | 553 | _, err := httpPost(url_, self.user, self.pass, self.client, self.clientURL, 554 | self.clientVersion, self.userAgent, data) 555 | if err != nil { 556 | self.reportError(kErr + err.Error()) 557 | response <- false 558 | } 559 | 560 | response <- true 561 | } 562 | 563 | // Gets a Twitter status given a status id 564 | // 565 | // The twitter.Api instance must be authenticated if the status message 566 | // is private 567 | // 568 | // Returns: a channel which receives a twitter.Status object when 569 | // the request is completed 570 | func (self *Api) GetStatus(id int64) <-chan Status { 571 | responseChannel := self.buildRespChannel(_STATUS).(chan Status) 572 | 573 | go self.goGetStatus(id, responseChannel) 574 | return responseChannel 575 | } 576 | 577 | func (self *Api) SetReceiveChannel(receiveChannel interface{}) { 578 | self.receiveChannel = receiveChannel 579 | } 580 | 581 | func (self *Api) goGetUser(url_ string, response chan User) { 582 | var user tTwitterUserDummy 583 | jsonString := self.getJsonFromUrl(url_) 584 | json.Unmarshal([]uint8(jsonString), &user) 585 | 586 | u := &(user.Object) 587 | if err := u.GetError(); err != "" { 588 | self.reportError(err) 589 | } 590 | 591 | response <- u 592 | } 593 | 594 | func (self *Api) goGetStatus(id int64, response chan Status) { 595 | url_ := fmt.Sprintf(_QUERY_GETSTATUS, id) 596 | var status tTwitterStatusDummy 597 | jsonString := self.getJsonFromUrl(url_) 598 | json.Unmarshal([]uint8(jsonString), &status) 599 | 600 | s := &(status.Object) 601 | if err := s.GetError(); err != "" { 602 | self.reportError(err) 603 | } 604 | 605 | response <- s 606 | } 607 | 608 | func (self *Api) reportError(error string) { 609 | err := &TwitterError{error} 610 | self.lastError = err 611 | select { 612 | case self.errors <- err: // do nothing 613 | default: 614 | // The error buffer is full, make room for one 615 | <-self.errors 616 | select { 617 | case self.errors <- err: // do nothing 618 | default: 619 | // Yo dawg 620 | fmt.Fprintf(os.Stderr, "Error adding error to error buffer\n") 621 | } 622 | } 623 | } 624 | 625 | func (self *Api) getJsonFromUrl(url_ string) string { 626 | r, error := httpGet(url_, self.user, self.pass) 627 | if error != nil { 628 | self.reportError(kErr + error.Error()) 629 | return "" 630 | } 631 | 632 | data, err := parseResponse(r) 633 | data = fixBrokenJson(data) 634 | if err != nil { 635 | self.reportError(kErr + err.Error()) 636 | return "" 637 | } 638 | 639 | return data 640 | } 641 | 642 | func (self *Api) buildUserUrl(typ string, user interface{}, page int) (string, bool) { 643 | var url_ string 644 | 645 | if user == nil { 646 | url_ = fmt.Sprintf(_QUERY_USER_DEFAULT, typ) 647 | return url_, true 648 | } 649 | 650 | switch user.(type) { 651 | case string: 652 | url_ = fmt.Sprintf(_QUERY_USER_NAME, typ, user.(string)) 653 | break 654 | case int64: 655 | url_ = fmt.Sprintf(_QUERY_USER_ID, typ, user.(int64)) 656 | break 657 | case int: 658 | url_ = fmt.Sprintf(_QUERY_USER_ID, typ, user.(int)) 659 | break 660 | default: 661 | self.reportError("User parameter must be a string, int, or int64") 662 | return "", false 663 | } 664 | 665 | return url_, true 666 | } 667 | -------------------------------------------------------------------------------- /app/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | all: app 4 | 5 | clean: 6 | rm -f app app.$O 7 | 8 | app: app.$O 9 | $(LD) -o $@ $^ 10 | 11 | app.$O: app.go 12 | $(GC) -o $@ $^ 13 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2009 Bill Casarin 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | package main 17 | 18 | import "twitter" 19 | import "fmt" 20 | 21 | func main() { 22 | const nIds = 10; 23 | 24 | api := twitter.NewApi(); 25 | errors := api.GetErrorChannel(); 26 | 27 | //showSearch(api, "@jb55") 28 | rateLimitInfo := <-api.GetRateLimitInfo() 29 | 30 | fmt.Printf("Remaining hits this hour: %d/%d\n", 31 | rateLimitInfo.GetRemainingHits(), 32 | rateLimitInfo.GetHourlyLimit()) 33 | 34 | fmt.Printf("other: %d, %s\n", 35 | rateLimitInfo.GetResetTimeInSeconds(), 36 | rateLimitInfo.GetResetTime()) 37 | 38 | for i := 0; api.HasErrors(); i++ { 39 | fmt.Printf("Error #%d: %s\n", i, <-errors); 40 | } 41 | 42 | status := <-api.GetStatus(7696223837); 43 | fmt.Printf("status created at seconds: %d\n", status.GetCreatedAtInSeconds()); 44 | 45 | //api.PostUpdate("Testing my Go twitter library", 0); 46 | } 47 | 48 | func showFollowers(api *twitter.Api, user interface{}) { 49 | followers := <-api.GetFollowers(user, 0); 50 | 51 | for _, follower := range followers { 52 | fmt.Printf("%v\n", follower.GetName()); 53 | } 54 | } 55 | 56 | func showFriends(api *twitter.Api, user interface{}) { 57 | friends := <-api.GetFriends(user, 0); 58 | 59 | for _, friend := range friends { 60 | fmt.Printf("%v\n", friend.GetName()); 61 | } 62 | } 63 | 64 | func showSearch(api *twitter.Api, query string) { 65 | results := <-api.SearchSimple(query); 66 | 67 | for _, result := range results { 68 | fmt.Printf("%s: %s\n", result.GetFromUser(), result.GetText()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /doc/go-twitter.htm: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 |

import "twitter"

9 |

10 | Copyright 2009 Bill Casarin <billcasarin@gmail.com> 11 |

12 |

13 | Licensed under the Apache License, Version 2.0 (the "License"); 14 | you may not use this file except in compliance with the License. 15 | You may obtain a copy of the License at 16 |

17 |
http://www.apache.org/licenses/LICENSE-2.0
 18 | 
19 |

20 | Unless required by applicable law or agreed to in writing, software 21 | distributed under the License is distributed on an "AS IS" BASIS, 22 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23 | See the License for the specific language governing permissions and 24 | limitations under the License. 25 |

26 | 27 |

28 |

Package files

29 | 30 | api.go 31 | cache.go 32 | hacks.go 33 | http_auth.go 34 | rate_limit.go 35 | search.go 36 | status.go 37 | user.go 38 | util.go 39 | 40 |

41 |

type Api

42 | 43 |

type Api struct {
 44 |     // contains unexported fields
 45 | }

46 |

func NewApi

47 |

func NewApi() *Api

48 |

49 | Creates and initializes new Api objec 50 |

51 | 52 |

func (*Api) ClearCredentials

53 |

func (self *Api) ClearCredentials()

54 |

55 | Disable Twitter authentication, subsequent REST calls will not use 56 | Authentication 57 |

58 | 59 |

func (*Api) GetErrorChannel

60 |

func (self *Api) GetErrorChannel() <-chan os.Error

61 |

62 | Returns a channel which receives API errors. Can be used for logging 63 | errors. 64 |

65 |
monitorErrors - listens to api errors and logs them
 66 | 
 67 | func monitorErrors(quit chan bool, errors <-chan os.Error) {
 68 |   for ;; {
 69 |     select {
 70 |     case err := <-errors:
 71 |       fmt.Fprintf(os.Stderr, err.String());
 72 |       break;
 73 |     case <-quit:
 74 |       return;
 75 |     }
 76 |   }
 77 | }
 78 | 
79 | 80 |

func (*Api) GetFollowers

81 |

func (self *Api) GetFollowers(user interface{}, page int) <-chan []User

82 |

83 | Gets the followers for a given user represented by a slice 84 | of twitter.User instances 85 |

86 |

87 | user: 88 |

89 |
A user id or name to fetch the followers from. If this argument
 90 | is nil, then the followers are fetched from the authenticated user.
 91 | This paramater must be an int, int64, or string.
 92 | 
93 |

94 | page: 95 |

96 |
Not yet implemented
 97 | 
98 | 99 |

func (*Api) GetFriends

100 |

func (self *Api) GetFriends(user interface{}, page int) <-chan []User

101 |

102 | Gets the friends for a given user represented by a slice 103 | of twitter.User instances 104 |

105 |

106 | user: 107 |

108 |
A user id or name to fetch the friends from. If this argument
109 | is nil, then the friends are fetched from the authenticated user.
110 | This paramater must be an int, int64, or string.
111 | 
112 |

113 | page: 114 |

115 |
Not yet implemented
116 | 
117 | 118 |

func (*Api) GetFriendsTimeline

119 |

func (self *Api) GetFriendsTimeline() <-chan []Status

120 |

121 | Returns the 20 most recent statuses posted by the authenticating user and 122 | that user's friends. This is the equivalent of /timeline/home on the Web. 123 | Returns the statuses as a slice of Status objects 124 |

125 | 126 |

func (*Api) GetLastError

127 |

func (self *Api) GetLastError() os.Error

128 |

129 | Returns the last error sent to the error channel. 130 | Calling this function pops the last error, subsequent calls will be nil 131 | unless another error has occured. 132 |

133 | 134 |

func (*Api) GetPublicTimeline

135 |

func (self *Api) GetPublicTimeline() <-chan []Status

136 |

137 | Retrieves the public timeline as a slice of Status objects 138 |

139 | 140 |

func (*Api) GetRateLimitInfo

141 |

func (self *Api) GetRateLimitInfo() <-chan RateLimit

142 |

143 | Returns rate limiting information 144 |

145 | 146 |

func (*Api) GetReplies

147 |

func (self *Api) GetReplies() <-chan []Status

148 |

149 | Returns the 20 most recent mentions for the authenticated user 150 | Returns the statuses as a slice of Status objects 151 |

152 | 153 |

func (*Api) GetStatus

154 |

func (self *Api) GetStatus(id int64) <-chan Status

155 |

156 | Gets a Twitter status given a status id 157 |

158 |

159 | The twitter.Api instance must be authenticated if the status message 160 | is private 161 |

162 |

163 | Returns: a channel which receives a twitter.Status object when 164 |

165 |
the request is completed
166 | 
167 | 168 |

func (*Api) GetUser

169 |

func (self *Api) GetUser(name string) <-chan User

170 |

171 | Returns a channel which receives a twitter.User instance for the given 172 | username. 173 |

174 |

175 | name: 176 |

177 |
The screenname of the user
178 | 
179 | 180 |

func (*Api) GetUserById

181 |

func (self *Api) GetUserById(id int64) <-chan User

182 |

183 | Returns a channel which receives a twitter.User instance for the given 184 | username. 185 |

186 |

187 | id: 188 |

189 |
A twiter user id
190 | 
191 | 192 |

func (*Api) GetUserTimeline

193 |

func (self *Api) GetUserTimeline() <-chan []Status

194 |

195 | Retrieves the currently authorized user's 196 | timeline as a slice of Status objects 197 |

198 | 199 |

func (*Api) HasErrors

200 |

func (self *Api) HasErrors() bool

201 |

202 | Checks to see if there are any errors in the error channel 203 |

204 | 205 |

func (*Api) PostUpdate

206 |

func (self *Api) PostUpdate(status string, inReplyToId int64) <-chan bool

207 |

208 | Post a Twitter status message to the authenticated user 209 |

210 |

211 | The twitter.Api instance must be authenticated 212 |

213 | 214 |

func (*Api) Search

215 |

func (self *Api) Search(query string, page int, perPage int, sinceId int, locale string, lang string) <-chan []SearchResult

216 |

217 | Performs a Twitter search. Returns a slice of twitter.SearchResult instances 218 | string fields are automatically URL Encoded 219 | query: 220 |

221 |
The string of text to search for.
222 | 
223 |

224 | page: 225 |

226 |
The page of results to return. Set to 0 to use the default value.
227 | 
228 |

229 | perPage: 230 |

231 |
The number of results per page. Set to 0 to use the default value.
232 | 
233 |

234 | sinceId: 235 |

236 |
Return tweets with status ids greater than the given id. Set to 0
237 | to use the default value.
238 | 
239 |

240 | locale: 241 |

242 |
Specify the language of the query you are sending (only ja is currently
243 | effective). This is intended for language-specific clients and the default
244 | should work in the majority of cases. Set to an empty string to use
245 | the default value.
246 | 
247 |

248 | lang: 249 |

250 |
Restricts tweets to the given language, given by an ISO 639-1 code.
251 | Set to an empty string to use the default value.
252 | 
253 | 254 |

func (*Api) SearchSimple

255 |

func (self *Api) SearchSimple(query string) <-chan []SearchResult

256 |

257 | Performs a simple Twitter search. Returns a slice of twitter.SearchResult 258 | instances 259 |

260 |

261 | query: 262 |

263 |
The string of text to search for. This is URL encoded automatically.
264 | 
265 | 266 |

func (*Api) SetCache

267 |

func (self *Api) SetCache(backend *CacheBackend)

268 | 269 |

func (*Api) SetClientString

270 |

func (self *Api) SetClientString(client string)

271 |

272 | Sets the Twitter client header, aka the X-Twitter-Client http header on 273 | all POST operations 274 |

275 | 276 |

func (*Api) SetCredentials

277 |

func (self *Api) SetCredentials(username, password string)

278 |

279 | Sets the username and password string for all subsequent authorized 280 | HTTP requests 281 |

282 | 283 |

func (*Api) SetReceiveChannel

284 |

func (self *Api) SetReceiveChannel(receiveChannel interface{})

285 | 286 |

func (*Api) SetUserAgent

287 |

func (self *Api) SetUserAgent(agent string)

288 |

289 | Overrides the default user agent (go-twitter) 290 |

291 | 292 |

func (*Api) SetXTwitterHeaders

293 |

func (self *Api) SetXTwitterHeaders(client, url, version string)

294 |

295 | Set the X-Twitter HTTP headers that will be sent to the server. 296 |

297 |

298 | client: 299 |

300 |
The client name as a string.  Will be sent to the server as
301 | the 'X-Twitter-Client' header.
302 | 
303 |

304 | url: 305 |

306 |
The URL of the meta.xml as a string.  Will be sent to the server
307 | as the 'X-Twitter-Client-URL' header.
308 | 
309 |

310 | version: 311 |

312 |
The client version as a string.  Will be sent to the server
313 | as the 'X-Twitter-Client-Version' header.
314 | 
315 | 316 |

type Cache

317 | 318 |

type Cache interface {
319 |     // Stores a value in the database, the key is determined
320 |     // by the GetId() function in the IdProvider interface
321 |     Store(data IdProvider)
322 | 
323 |     // Checks to see if the cache contains a given key
324 |     HasId(id int64) bool
325 | 
326 |     // Gets a value from the cache
327 |     Get(id int64) IdProvider
328 | 
329 |     // Gets the time a given key was stored
330 |     GetTimeStored(id int64) int64
331 | }

332 |

type CacheBackend

333 | 334 |

type CacheBackend struct {
335 |     // contains unexported fields
336 | }

337 |

func NewCacheBackend

338 |

func NewCacheBackend(user Cache, status Cache, expireTime int64) *CacheBackend

339 |

340 | Creates a custom cache backend 341 |

342 | 343 |

func (*CacheBackend) GetStatus

344 |

func (self *CacheBackend) GetStatus(id int64) Status

345 | 346 |

func (*CacheBackend) GetUser

347 |

func (self *CacheBackend) GetUser(id int64) User

348 | 349 |

func (*CacheBackend) HasStatusExpired

350 |

func (self *CacheBackend) HasStatusExpired(id int64) bool

351 | 352 |

func (*CacheBackend) HasUserExpired

353 |

func (self *CacheBackend) HasUserExpired(id int64) bool

354 | 355 |

func (*CacheBackend) StoreStatus

356 |

func (self *CacheBackend) StoreStatus(status Status)

357 | 358 |

func (*CacheBackend) StoreUser

359 |

func (self *CacheBackend) StoreUser(user User)

360 | 361 |

type IdProvider

362 | 363 |

type IdProvider interface {
364 |     GetId() int64
365 | }

366 |

type MemoryCache

367 | 368 |

type MemoryCache struct {
369 |     // contains unexported fields
370 | }

371 |

func NewMemoryCache

372 |

func NewMemoryCache() *MemoryCache

373 | 374 |

func (*MemoryCache) Get

375 |

func (self *MemoryCache) Get(id int64) IdProvider

376 | 377 |

func (*MemoryCache) GetTimeStored

378 |

func (self *MemoryCache) GetTimeStored(id int64) int64

379 | 380 |

func (*MemoryCache) HasId

381 |

func (self *MemoryCache) HasId(id int64) bool

382 | 383 |

func (*MemoryCache) Store

384 |

func (self *MemoryCache) Store(data IdProvider)

385 | 386 |

type RateLimit

387 | 388 |

type RateLimit interface {
389 |     GetRemainingHits() int
390 |     GetHourlyLimit() int
391 |     GetResetTimeInSeconds() int64
392 |     GetResetTime() string
393 | }

394 |

type SearchResult

395 | 396 |

type SearchResult interface {
397 |     GetCreatedAt() string
398 |     GetFromUser() string
399 |     GetToUserId() int64
400 |     GetText() string
401 |     GetId() int64
402 |     GetFromUserId() int64
403 |     GetGeo() string
404 |     GetIsoLanguageCode() string
405 |     GetSource() string
406 | }

407 |

type Status

408 | 409 |

type Status interface {
410 |     GetCreatedAt() string
411 |     GetCreatedAtInSeconds() int
412 |     GetFavorited() bool
413 |     GetId() int64
414 |     GetText() string
415 |     GetInReplyToScreenName() string
416 |     GetInReplyToStatusId() int64
417 |     GetInReplyToUserId() int64
418 |     GetNow() int
419 |     GetUser() User
420 |     // contains unexported methods
421 | }

422 |

type TwitterError

423 | 424 |

type TwitterError struct {
425 |     // contains unexported fields
426 | }

427 |

func (TwitterError) String

428 |

func (self TwitterError) String() string

429 |

430 | type that satisfies the os.Error interface 431 |

432 | 433 |

type User

434 | 435 |

type User interface {
436 |     GetId() int64
437 |     GetName() string
438 |     GetScreenName() string
439 |     GetLocation() string
440 |     GetDescription() string
441 |     GetProfileImageUrl() string
442 |     GetProfileBackgroundTitle() bool
443 |     GetProfileBackgroundImageUrl() string
444 |     GetProfileSidebarFillColor() string
445 |     GetProfileLinkColor() string
446 |     GetProfileTextColor() string
447 |     GetProtected() bool
448 |     GetUtcOffset() int
449 |     GetTimeZone() string
450 |     GetURL() string
451 |     GetStatus() Status
452 | 
453 |     GetStatusesCount() int
454 |     GetFollowersCount() int
455 |     GetFriendsCount() int
456 |     GetFavoritesCount() int
457 |     // contains unexported methods
458 | }

459 | -------------------------------------------------------------------------------- /doc/go-twitter.txt: -------------------------------------------------------------------------------- 1 | PACKAGE 2 | 3 | package twitter 4 | import "twitter" 5 | 6 | Copyright 2009 Bill Casarin 7 | 8 | Licensed under the Apache License, Version 2.0 (the "License"); 9 | you may not use this file except in compliance with the License. 10 | You may obtain a copy of the License at 11 | 12 | http://www.apache.org/licenses/LICENSE-2.0 13 | 14 | Unless required by applicable law or agreed to in writing, software 15 | distributed under the License is distributed on an "AS IS" BASIS, 16 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | See the License for the specific language governing permissions and 18 | limitations under the License. 19 | 20 | 21 | TYPES 22 | 23 | type Api struct { 24 | // contains unexported fields 25 | } 26 | 27 | func NewApi() *Api 28 | Creates and initializes new Api objec 29 | 30 | func (self *Api) ClearCredentials() 31 | Disable Twitter authentication, subsequent REST calls will not use 32 | Authentication 33 | 34 | func (self *Api) GetErrorChannel() <-chan os.Error 35 | Returns a channel which receives API errors. Can be used for logging 36 | errors. 37 | 38 | monitorErrors - listens to api errors and logs them 39 | 40 | func monitorErrors(quit chan bool, errors <-chan os.Error) { 41 | for ;; { 42 | select { 43 | case err := <-errors: 44 | fmt.Fprintf(os.Stderr, err.String()); 45 | break; 46 | case <-quit: 47 | return; 48 | } 49 | } 50 | } 51 | 52 | func (self *Api) GetFollowers(user interface{}, page int) <-chan []User 53 | Gets the followers for a given user represented by a slice 54 | of twitter.User instances 55 | 56 | user: 57 | A user id or name to fetch the followers from. If this argument 58 | is nil, then the followers are fetched from the authenticated user. 59 | This paramater must be an int, int64, or string. 60 | 61 | page: 62 | Not yet implemented 63 | 64 | func (self *Api) GetFriends(user interface{}, page int) <-chan []User 65 | Gets the friends for a given user represented by a slice 66 | of twitter.User instances 67 | 68 | user: 69 | A user id or name to fetch the friends from. If this argument 70 | is nil, then the friends are fetched from the authenticated user. 71 | This paramater must be an int, int64, or string. 72 | 73 | page: 74 | Not yet implemented 75 | 76 | func (self *Api) GetFriendsTimeline() <-chan []Status 77 | Returns the 20 most recent statuses posted by the authenticating user and 78 | that user's friends. This is the equivalent of /timeline/home on the Web. 79 | Returns the statuses as a slice of Status objects 80 | 81 | func (self *Api) GetLastError() os.Error 82 | Returns the last error sent to the error channel. 83 | Calling this function pops the last error, subsequent calls will be nil 84 | unless another error has occured. 85 | 86 | func (self *Api) GetPublicTimeline() <-chan []Status 87 | Retrieves the public timeline as a slice of Status objects 88 | 89 | func (self *Api) GetRateLimitInfo() <-chan RateLimit 90 | Returns rate limiting information 91 | 92 | func (self *Api) GetReplies() <-chan []Status 93 | Returns the 20 most recent mentions for the authenticated user 94 | Returns the statuses as a slice of Status objects 95 | 96 | func (self *Api) GetStatus(id int64) <-chan Status 97 | Gets a Twitter status given a status id 98 | 99 | The twitter.Api instance must be authenticated if the status message 100 | is private 101 | 102 | Returns: a channel which receives a twitter.Status object when 103 | the request is completed 104 | 105 | func (self *Api) GetUser(name string) <-chan User 106 | Returns a channel which receives a twitter.User instance for the given 107 | username. 108 | 109 | name: 110 | The screenname of the user 111 | 112 | func (self *Api) GetUserById(id int64) <-chan User 113 | Returns a channel which receives a twitter.User instance for the given 114 | username. 115 | 116 | id: 117 | A twiter user id 118 | 119 | func (self *Api) GetUserTimeline() <-chan []Status 120 | Retrieves the currently authorized user's 121 | timeline as a slice of Status objects 122 | 123 | func (self *Api) HasErrors() bool 124 | Checks to see if there are any errors in the error channel 125 | 126 | func (self *Api) PostUpdate(status string, inReplyToId int64) <-chan bool 127 | Post a Twitter status message to the authenticated user 128 | 129 | The twitter.Api instance must be authenticated 130 | 131 | func (self *Api) Search(query string, page int, perPage int, sinceId int, locale string, lang string) <-chan []SearchResult 132 | Performs a Twitter search. Returns a slice of twitter.SearchResult instances 133 | string fields are automatically URL Encoded 134 | query: 135 | The string of text to search for. 136 | page: 137 | The page of results to return. Set to 0 to use the default value. 138 | perPage: 139 | The number of results per page. Set to 0 to use the default value. 140 | sinceId: 141 | Return tweets with status ids greater than the given id. Set to 0 142 | to use the default value. 143 | locale: 144 | Specify the language of the query you are sending (only ja is currently 145 | effective). This is intended for language-specific clients and the default 146 | should work in the majority of cases. Set to an empty string to use 147 | the default value. 148 | lang: 149 | Restricts tweets to the given language, given by an ISO 639-1 code. 150 | Set to an empty string to use the default value. 151 | 152 | func (self *Api) SearchSimple(query string) <-chan []SearchResult 153 | Performs a simple Twitter search. Returns a slice of twitter.SearchResult 154 | instances 155 | 156 | query: 157 | The string of text to search for. This is URL encoded automatically. 158 | 159 | func (self *Api) SetCache(backend *CacheBackend) 160 | 161 | func (self *Api) SetClientString(client string) 162 | Sets the Twitter client header, aka the X-Twitter-Client http header on 163 | all POST operations 164 | 165 | func (self *Api) SetCredentials(username, password string) 166 | Sets the username and password string for all subsequent authorized 167 | HTTP requests 168 | 169 | func (self *Api) SetReceiveChannel(receiveChannel interface{}) 170 | 171 | func (self *Api) SetUserAgent(agent string) 172 | Overrides the default user agent (go-twitter) 173 | 174 | func (self *Api) SetXTwitterHeaders(client, url, version string) 175 | Set the X-Twitter HTTP headers that will be sent to the server. 176 | 177 | client: 178 | The client name as a string. Will be sent to the server as 179 | the 'X-Twitter-Client' header. 180 | url: 181 | The URL of the meta.xml as a string. Will be sent to the server 182 | as the 'X-Twitter-Client-URL' header. 183 | version: 184 | The client version as a string. Will be sent to the server 185 | as the 'X-Twitter-Client-Version' header. 186 | 187 | type Cache interface { 188 | // Stores a value in the database, the key is determined 189 | // by the GetId() function in the IdProvider interface 190 | Store(data IdProvider) 191 | 192 | // Checks to see if the cache contains a given key 193 | HasId(id int64) bool 194 | 195 | // Gets a value from the cache 196 | Get(id int64) IdProvider 197 | 198 | // Gets the time a given key was stored 199 | GetTimeStored(id int64) int64 200 | } 201 | 202 | type CacheBackend struct { 203 | // contains unexported fields 204 | } 205 | 206 | func NewCacheBackend(user Cache, status Cache, expireTime int64) *CacheBackend 207 | Creates a custom cache backend 208 | 209 | func (self *CacheBackend) GetStatus(id int64) Status 210 | 211 | func (self *CacheBackend) GetUser(id int64) User 212 | 213 | func (self *CacheBackend) HasStatusExpired(id int64) bool 214 | 215 | func (self *CacheBackend) HasUserExpired(id int64) bool 216 | 217 | func (self *CacheBackend) StoreStatus(status Status) 218 | 219 | func (self *CacheBackend) StoreUser(user User) 220 | 221 | type IdProvider interface { 222 | GetId() int64 223 | } 224 | 225 | type MemoryCache struct { 226 | // contains unexported fields 227 | } 228 | 229 | func NewMemoryCache() *MemoryCache 230 | 231 | func (self *MemoryCache) Get(id int64) IdProvider 232 | 233 | func (self *MemoryCache) GetTimeStored(id int64) int64 234 | 235 | func (self *MemoryCache) HasId(id int64) bool 236 | 237 | func (self *MemoryCache) Store(data IdProvider) 238 | 239 | type RateLimit interface { 240 | GetRemainingHits() int 241 | GetHourlyLimit() int 242 | GetResetTimeInSeconds() int64 243 | GetResetTime() string 244 | } 245 | 246 | type SearchResult interface { 247 | GetCreatedAt() string 248 | GetFromUser() string 249 | GetToUserId() int64 250 | GetText() string 251 | GetId() int64 252 | GetFromUserId() int64 253 | GetGeo() string 254 | GetIsoLanguageCode() string 255 | GetSource() string 256 | } 257 | 258 | type Status interface { 259 | GetCreatedAt() string 260 | GetCreatedAtInSeconds() int 261 | GetFavorited() bool 262 | GetId() int64 263 | GetText() string 264 | GetInReplyToScreenName() string 265 | GetInReplyToStatusId() int64 266 | GetInReplyToUserId() int64 267 | GetNow() int 268 | GetUser() User 269 | // contains unexported methods 270 | } 271 | 272 | type TwitterError struct { 273 | // contains unexported fields 274 | } 275 | 276 | func (self TwitterError) String() string 277 | type that satisfies the os.Error interface 278 | 279 | type User interface { 280 | GetId() int64 281 | GetName() string 282 | GetScreenName() string 283 | GetLocation() string 284 | GetDescription() string 285 | GetProfileImageUrl() string 286 | GetProfileBackgroundTitle() bool 287 | GetProfileBackgroundImageUrl() string 288 | GetProfileSidebarFillColor() string 289 | GetProfileLinkColor() string 290 | GetProfileTextColor() string 291 | GetProtected() bool 292 | GetUtcOffset() int 293 | GetTimeZone() string 294 | GetURL() string 295 | GetStatus() Status 296 | 297 | GetStatusesCount() int 298 | GetFollowersCount() int 299 | GetFriendsCount() int 300 | GetFavoritesCount() int 301 | // contains unexported methods 302 | } 303 | 304 | -------------------------------------------------------------------------------- /examples/Makefile: -------------------------------------------------------------------------------- 1 | include $(GOROOT)/src/Make.inc 2 | 3 | all: random-crawler parallel 4 | 5 | clean: 6 | rm -f random-crawler random-crawler.$O 7 | 8 | random-crawler: random-crawler.$O 9 | $(LD) -o $@ $^ 10 | 11 | random-crawler.$O: random-crawler.go 12 | $(GC) -o $@ $^ 13 | 14 | parallel: parallel.$O 15 | $(LD) -o $@ $^ 16 | 17 | parallel.$O: parallel.go 18 | $(GC) -o $@ $^ 19 | 20 | -------------------------------------------------------------------------------- /examples/parallel-bench: -------------------------------------------------------------------------------- 1 | Not Parallel... (-p for parallel) 2 | 3 | real 0m4.317s 4 | user 0m0.070s 5 | sys 0m0.010s 6 | 7 | Parallel... 8 | 9 | real 0m1.511s 10 | user 0m0.040s 11 | sys 0m0.000s 12 | -------------------------------------------------------------------------------- /examples/parallel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "twitter" 4 | import "flag" 5 | import "fmt" 6 | 7 | func main() { 8 | api := twitter.NewApi() 9 | isParallel := flag.Bool("p", false, "parallel downloads with go channels") 10 | flag.Parse() 11 | 12 | if (isParallel != nil && *isParallel) { 13 | fmt.Printf("Parallel...\n"); 14 | 15 | pub_timeline_chan := api.GetPublicTimeline() 16 | search_chan := api.SearchSimple("@jb55") 17 | search_chan2 := api.SearchSimple("Google") 18 | search_chan3 := api.SearchSimple("Hi there") 19 | 20 | <-pub_timeline_chan 21 | <-search_chan 22 | <-search_chan2 23 | <-search_chan3 24 | 25 | } else { 26 | fmt.Printf("Not Parallel... (-p for parallel)\n"); 27 | 28 | <-api.GetPublicTimeline() 29 | <-api.SearchSimple("@jb55") 30 | <-api.SearchSimple("Google") 31 | <-api.SearchSimple("Hi there") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/random-crawler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "twitter" 4 | import "fmt" 5 | import "time" 6 | import "rand" 7 | 8 | const kMaxDepth = 25 9 | const kStart = "jb55" 10 | 11 | var api *twitter.Api 12 | var r *rand.Rand 13 | var done chan bool 14 | 15 | func main() { 16 | api = twitter.NewApi() 17 | done = make(chan bool) 18 | r = rand.New(rand.NewSource(time.Seconds())) 19 | crawl(kStart, 0) 20 | <-done 21 | } 22 | 23 | func crawl(userName string, level int) { 24 | // Get the user's status 25 | text := (<-api.GetUser(userName)).GetStatus().GetText() 26 | 27 | for i := 0; i < level; i++ { 28 | fmt.Printf(" ") 29 | } 30 | 31 | fmt.Printf("%s: %s\n", userName, text) 32 | 33 | level++ 34 | if level > kMaxDepth { 35 | done <- true 36 | return 37 | } 38 | 39 | // Get the user's friends 40 | friends := <-api.GetFriends(userName, 1) 41 | length := len(friends) 42 | 43 | if length == 0 { 44 | done <- true 45 | return 46 | } 47 | 48 | rVal := r.Intn(length - 1) 49 | // Choose a random friend for the next user 50 | nextUser := friends[rVal].GetScreenName() 51 | 52 | go crawl(nextUser, level) 53 | } 54 | -------------------------------------------------------------------------------- /http_auth.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2009 Bill Casarin 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | // From gotweet - a command line twitter client by Dmitry Chestnykh 17 | // modified by Bill Casarin 18 | // 19 | 20 | package twitter 21 | 22 | import ( 23 | "net/http" 24 | "encoding/base64" 25 | "io" 26 | "strings" 27 | "net" 28 | "bufio" 29 | "fmt" 30 | "bytes" 31 | "net/url" 32 | ) 33 | 34 | type readClose struct { 35 | io.Reader 36 | io.Closer 37 | } 38 | 39 | type badStringError struct { 40 | what string 41 | str string 42 | } 43 | 44 | func (e *badStringError) Error() string { return fmt.Sprintf("%s %q", e.what, e.str) } 45 | 46 | // Given a string of the form "host", "host:port", or "[ipv6::address]:port", 47 | // return true if the string includes a port. 48 | func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } 49 | 50 | func send(req *http.Request) (resp *http.Response, err error) { 51 | addr := req.URL.Host 52 | if !hasPort(addr) { 53 | addr += ":http" 54 | } 55 | conn, err := net.Dial("tcp", addr) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | err = req.Write(conn) 61 | if err != nil { 62 | conn.Close() 63 | return nil, err 64 | } 65 | 66 | reader := bufio.NewReader(conn) 67 | resp, err = http.ReadResponse(reader, req) 68 | if err != nil { 69 | conn.Close() 70 | return nil, err 71 | } 72 | 73 | r := io.Reader(reader) 74 | if n := resp.ContentLength; n != -1 { 75 | r = io.LimitReader(r, n) 76 | } 77 | resp.Body = readClose{r, conn} 78 | 79 | return 80 | } 81 | 82 | func encodedUsernameAndPassword(user, pwd string) string { 83 | bb := &bytes.Buffer{} 84 | encoder := base64.NewEncoder(base64.StdEncoding, bb) 85 | encoder.Write([]byte(user + ":" + pwd)) 86 | encoder.Close() 87 | return bb.String() 88 | } 89 | 90 | func authGet(url_, user, pwd string) (r *http.Response, err error) { 91 | var req http.Request 92 | h := make(http.Header) 93 | h.Add("Authorization", "Basic "+ 94 | encodedUsernameAndPassword(user, pwd)) 95 | req.Header = h 96 | if req.URL, err = url.Parse(url_); err != nil { 97 | return 98 | } 99 | if r, err = send(&req); err != nil { 100 | return 101 | } 102 | return 103 | } 104 | 105 | // Post issues a POST to the specified URL. 106 | // 107 | // Caller should close r.Body when done reading it. 108 | func authPost(url_, user, pwd, client, clientURL, version, agent, bodyType string, 109 | body io.Reader) (r *http.Response, err error) { 110 | var req http.Request 111 | req.Method = "POST" 112 | req.Body = body.(io.ReadCloser) 113 | 114 | h := make(http.Header) 115 | h.Add("Content-Type", bodyType) 116 | h.Add("Transfer-Encoding", "chunked") 117 | h.Add("User-Agent", agent) 118 | h.Add("X-Twitter-Client", client) 119 | h.Add("X-Twitter-Client-URL", clientURL) 120 | h.Add("X-Twitter-Version", version) 121 | h.Add("Authorization", "Basic "+encodedUsernameAndPassword(user, pwd)) 122 | req.Header = h 123 | 124 | req.URL, err = url.Parse(url_) 125 | if err != nil { 126 | return nil, err 127 | } 128 | 129 | return send(&req) 130 | } 131 | 132 | // Do an authenticated Get if we've called Authenticated, otherwise 133 | // just Get it without authentication 134 | func httpGet(url_, user, pass string) (*http.Response, error) { 135 | var r *http.Response 136 | var err error 137 | 138 | if user != "" && pass != "" { 139 | r, err = authGet(url_, user, pass) 140 | } else { 141 | r, err = http.Get(url_) 142 | } 143 | 144 | return r, err 145 | } 146 | 147 | // Do an authenticated Post if we've called Authenticated, otherwise 148 | // just Post it without authentication 149 | func httpPost(url_, user, pass, client, clientURL, version, agent, 150 | data string) (*http.Response, error) { 151 | var r *http.Response 152 | var err error 153 | 154 | body := bytes.NewBufferString(data) 155 | bodyType := "application/x-www-form-urlencoded" 156 | 157 | if user != "" && pass != "" { 158 | r, err = authPost(url_, user, pass, client, clientURL, 159 | version, agent, bodyType, body) 160 | } else { 161 | r, err = http.Post(url_, bodyType, body) 162 | } 163 | 164 | return r, err 165 | } 166 | -------------------------------------------------------------------------------- /rate_limit.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | type RateLimit interface { 4 | GetRemainingHits() int 5 | GetHourlyLimit() int 6 | GetResetTimeInSeconds() int64 7 | GetResetTime() string 8 | } 9 | 10 | type tTwitterRateLimit struct { 11 | Remaining_hits int 12 | Hourly_limit int 13 | Reset_time_in_seconds int64 14 | Reset_time string 15 | } 16 | 17 | type tTwitterRateLimitDummy struct { 18 | Object tTwitterRateLimit 19 | } 20 | 21 | func (self *tTwitterRateLimit) GetRemainingHits() int { 22 | return self.Remaining_hits 23 | } 24 | 25 | func (self *tTwitterRateLimit) GetHourlyLimit() int { 26 | return self.Hourly_limit 27 | } 28 | 29 | func (self *tTwitterRateLimit) GetResetTimeInSeconds() int64 { 30 | return self.Reset_time_in_seconds 31 | } 32 | 33 | func (self *tTwitterRateLimit) GetResetTime() string { 34 | return self.Reset_time 35 | } 36 | -------------------------------------------------------------------------------- /search.go: -------------------------------------------------------------------------------- 1 | package twitter 2 | 3 | type SearchResult interface { 4 | GetCreatedAt() string 5 | GetFromUser() string 6 | GetToUserId() int64 7 | GetText() string 8 | GetId() int64 9 | GetFromUserId() int64 10 | GetGeo() string 11 | GetIsoLanguageCode() string 12 | GetSource() string 13 | } 14 | 15 | type tTwitterSearch struct { 16 | Results []tTwitterSearchResult 17 | } 18 | 19 | type tTwitterSearchResult struct { 20 | Profile_image_url string 21 | Created_at string 22 | From_user string 23 | To_user_id int64 24 | Text string 25 | Id int64 26 | From_user_id int64 27 | Geo string 28 | Iso_language_code string 29 | Source string 30 | Error string 31 | } 32 | 33 | type tTwitterSearchDummy struct { 34 | Object tTwitterSearch 35 | } 36 | 37 | func (self *tTwitterSearchResult) GetError() string { 38 | return self.Error 39 | } 40 | 41 | func (self *tTwitterSearchResult) GetCreatedAt() string { 42 | return self.Created_at 43 | } 44 | 45 | func (self *tTwitterSearchResult) GetFromUser() string { 46 | return self.From_user 47 | } 48 | 49 | func (self *tTwitterSearchResult) GetToUserId() int64 { 50 | return self.To_user_id 51 | } 52 | 53 | func (self *tTwitterSearchResult) GetText() string { 54 | return self.Text 55 | } 56 | 57 | func (self *tTwitterSearchResult) GetId() int64 { 58 | return self.Id 59 | } 60 | 61 | func (self *tTwitterSearchResult) GetFromUserId() int64 { 62 | return self.From_user_id 63 | } 64 | 65 | func (self *tTwitterSearchResult) GetGeo() string { 66 | return self.Geo 67 | } 68 | 69 | func (self *tTwitterSearchResult) GetIsoLanguageCode() string { 70 | return self.Geo 71 | } 72 | 73 | func (self *tTwitterSearchResult) GetSource() string { 74 | return self.Source 75 | } 76 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2009 Bill Casarin 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | package twitter 17 | 18 | type Status interface { 19 | GetCreatedAt() string 20 | GetCreatedAtInSeconds() int64 21 | GetFavorited() bool 22 | GetId() int64 23 | GetText() string 24 | GetInReplyToScreenName() string 25 | GetInReplyToStatusId() int64 26 | GetInReplyToUserId() int64 27 | GetNow() int 28 | GetUser() User 29 | setUser(user User) 30 | } 31 | 32 | type errorSource interface { 33 | GetError() string 34 | } 35 | 36 | // Our internal status struct 37 | // the naming is odd so that 38 | // json.Unmarshal can do its thing properly 39 | type tTwitterStatus struct { 40 | Text string 41 | Created_at string 42 | Favorited bool 43 | Id int64 44 | In_reply_to_screen_name string 45 | In_reply_to_status_id int64 46 | In_reply_to_user_id int64 47 | Error string 48 | User *tTwitterUser 49 | now int 50 | createdAtSeconds int64 51 | } 52 | 53 | type tTwitterStatusDummy struct { 54 | Object tTwitterStatus 55 | } 56 | 57 | type tTwitterTimelineDummy struct { 58 | Object []tTwitterStatus 59 | } 60 | 61 | func newEmptyTwitterStatus() *tTwitterStatus { return new(tTwitterStatus) } 62 | 63 | func (self *tTwitterStatus) GetError() string { return self.Error } 64 | 65 | func (self *tTwitterStatus) GetCreatedAt() string { 66 | return self.Created_at 67 | } 68 | 69 | func (self *tTwitterStatus) GetUser() User { 70 | if self.User == nil { 71 | self.User = newEmptyTwitterUser() 72 | } 73 | self.User.setStatus(self) 74 | return self.User 75 | } 76 | 77 | func (self *tTwitterStatus) setUser(user User) { 78 | self.User = user.(*tTwitterUser) 79 | } 80 | 81 | func (self *tTwitterStatus) GetCreatedAtInSeconds() int64 { 82 | if self.createdAtSeconds == 0 { 83 | self.createdAtSeconds = int64(parseTwitterDate(self.Created_at).Second()) 84 | } 85 | return self.createdAtSeconds; 86 | } 87 | 88 | func (self *tTwitterStatus) GetFavorited() bool { 89 | return self.Favorited 90 | } 91 | 92 | func (self *tTwitterStatus) GetId() int64 { return self.Id } 93 | 94 | func (self *tTwitterStatus) GetInReplyToScreenName() string { 95 | return self.In_reply_to_screen_name 96 | } 97 | 98 | func (self *tTwitterStatus) GetText() string { return self.Text } 99 | 100 | func (self *tTwitterStatus) GetInReplyToStatusId() int64 { 101 | return self.In_reply_to_status_id 102 | } 103 | 104 | func (self *tTwitterStatus) GetInReplyToUserId() int64 { 105 | return self.In_reply_to_user_id 106 | } 107 | 108 | func (self *tTwitterStatus) GetNow() int { return self.now } 109 | -------------------------------------------------------------------------------- /twitter_test.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2009 Bill Casarin 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | package twitter 17 | 18 | import "testing" 19 | 20 | import "fmt" 21 | 22 | // dont change this 23 | const kId = 5641609144 24 | 25 | func TestValidStatus(t *testing.T) { 26 | api := NewApi() 27 | errors := api.GetErrorChannel() 28 | 29 | fmt.Printf("<-api.GetStatus() ...\n") 30 | status := (<-api.GetUser("jb55")).GetStatus() 31 | 32 | verifyValidStatus(status, t) 33 | verifyValidUser(status.GetUser(), t) 34 | getAllApiErrors(errors, t) 35 | } 36 | 37 | func TestValidUser(t *testing.T) { 38 | api := NewApi() 39 | errors := api.GetErrorChannel() 40 | 41 | fmt.Printf("<-api.GetUserById() ...\n") 42 | user := <-api.GetUserById(9918032) 43 | 44 | verifyValidUser(user, t) 45 | verifyValidStatus(user.GetStatus(), t) 46 | getAllApiErrors(errors, t) 47 | } 48 | 49 | func TestValidFollowerList(t *testing.T) { 50 | api := NewApi() 51 | errors := api.GetErrorChannel() 52 | fmt.Printf("<-api.GetFollowers() ...\n") 53 | users := <-api.GetFollowers("jb55", 0) 54 | length := len(users) 55 | 56 | if length <= 1 { 57 | t.Errorf("len(GetFollowers()) <= 1, got %d expected > 1", length) 58 | } 59 | 60 | for _, user := range users { 61 | verifyValidUser(user, t) 62 | if user.GetStatus().GetId() != 0 { 63 | verifyValidStatus(user.GetStatus(), t) 64 | } 65 | } 66 | 67 | getAllApiErrors(errors, t) 68 | } 69 | 70 | func TestValidFriendsList(t *testing.T) { 71 | api := NewApi() 72 | errors := api.GetErrorChannel() 73 | fmt.Printf("<-api.GetFriends() ...\n") 74 | users := <-api.GetFriends("jb55", 0) 75 | length := len(users) 76 | 77 | if length <= 1 { 78 | t.Errorf("len(GetFriends()) <= 1, got %d expected > 1", length) 79 | } 80 | 81 | for _, user := range users { 82 | verifyValidUser(user, t) 83 | if user.GetStatus().GetId() != 0 { 84 | verifyValidStatus(user.GetStatus(), t) 85 | } 86 | } 87 | 88 | getAllApiErrors(errors, t) 89 | } 90 | 91 | func TestValidSearchResults(t *testing.T) { 92 | api := NewApi() 93 | errors := api.GetErrorChannel() 94 | fmt.Printf("<-api.SearchSimple() ...\n") 95 | results := <-api.SearchSimple("#ff") 96 | length := len(results) 97 | 98 | if length <= 1 { 99 | t.Errorf("len(SearchSimple()) <= 1, got %d expected > 1", length) 100 | } 101 | 102 | for _, result := range results { 103 | verifyValidSearchResult(result, t) 104 | } 105 | 106 | getAllApiErrors(errors, t) 107 | } 108 | 109 | func TestValidPublicTimeLine(t *testing.T) { 110 | api := NewApi() 111 | errors := api.GetErrorChannel() 112 | fmt.Printf("<-api.GetPublicTimeline() ...\n") 113 | statuses := <-api.GetPublicTimeline() 114 | length := len(statuses) 115 | 116 | if length <= 1 { 117 | t.Errorf("len(GetPublicTimeline()) <= 1, got %d expected > 1", length) 118 | } 119 | 120 | if StatusEqual(statuses[0], statuses[1]) { 121 | t.Errorf("GetPublicTimeline()[0] == GetPublicTimeline()[1], expected different") 122 | } 123 | 124 | t.Logf("Number of Statuses retrieved: %d", length) 125 | for _, status := range statuses { 126 | verifyValidStatus(status, t) 127 | } 128 | 129 | getAllApiErrors(errors, t) 130 | } 131 | 132 | // Authfile: .twitterauth 133 | // Format: single line, two words 134 | // username password 135 | func authFromFile() { return } 136 | 137 | func verifyValidUser(u User, t *testing.T) { 138 | assertGreaterThanZero(u.GetId(), "GetId", t) 139 | assertNotEmpty(u.GetScreenName(), "GetScreenName", t) 140 | assertNotEmpty(u.GetName(), "GetName", t) 141 | assertNotNil(u.GetStatus(), "GetStatus", t) 142 | assertNotEmpty(u.GetScreenName(), "GetScreenName", t) 143 | } 144 | 145 | func verifyValidStatus(s Status, t *testing.T) { 146 | assertGreaterThanZero(s.GetId(), "GetId", t) 147 | assertGreaterThanZero(s.GetCreatedAtInSeconds(), "GetCreatedAtInSeconds", t) 148 | assertNotEmpty(s.GetText(), "GetText", t) 149 | assertNotNil(s.GetUser(), "GetUser", t) 150 | } 151 | 152 | func verifyValidSearchResult(r SearchResult, t *testing.T) { 153 | assertGreaterThanZero(r.GetId(), "GetId", t) 154 | assertNotEmpty(r.GetText(), "GetText", t) 155 | } 156 | 157 | func getAllApiErrors(errors <-chan error, t *testing.T) { 158 | if len(errors) == 0 { 159 | return 160 | } 161 | t.Log("--- Errors generated by GoTwitter START ----") 162 | for err := range errors { 163 | t.Log(err.Error()) 164 | } 165 | t.Error("--- Errors generated by GoTwitter END ----") 166 | } 167 | 168 | func IsEmpty(s string) bool { return len(s) == 0 } 169 | 170 | func assertGreaterThanZero(i int64, fn string, t *testing.T) { 171 | if i <= 0 { 172 | t.Errorf("%s is <= 0, got %d expected > 0", fn, i) 173 | } 174 | } 175 | 176 | func assertNotEmpty(s, fn string, t *testing.T) { 177 | if IsEmpty(s) { 178 | t.Errorf("%s is empty, expected not empty", fn) 179 | } 180 | } 181 | 182 | func assertNotNil(i interface{}, fn string, t *testing.T) { 183 | if i == nil { 184 | t.Errorf("%s is nil, expected not nil", fn) 185 | } 186 | } 187 | 188 | func StatusEqual(a, b Status) bool { 189 | return a.GetCreatedAt() == b.GetCreatedAt() && 190 | a.GetCreatedAtInSeconds() == b.GetCreatedAtInSeconds() && 191 | a.GetFavorited() == b.GetFavorited() && 192 | a.GetId() == b.GetId() && 193 | a.GetInReplyToScreenName() == b.GetInReplyToScreenName() && 194 | a.GetInReplyToStatusId() == b.GetInReplyToStatusId() && 195 | a.GetInReplyToUserId() == b.GetInReplyToUserId() && 196 | a.GetNow() == b.GetNow() 197 | } 198 | -------------------------------------------------------------------------------- /user.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2009 Bill Casarin 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | package twitter 17 | 18 | type User interface { 19 | GetId() int64 20 | GetName() string 21 | GetScreenName() string 22 | GetLocation() string 23 | GetDescription() string 24 | GetProfileImageUrl() string 25 | GetProfileBackgroundTitle() bool 26 | GetProfileBackgroundImageUrl() string 27 | GetProfileSidebarFillColor() string 28 | GetProfileLinkColor() string 29 | GetProfileTextColor() string 30 | GetProtected() bool 31 | GetUtcOffset() int 32 | GetTimeZone() string 33 | GetURL() string 34 | GetStatus() Status 35 | setStatus(status Status) 36 | GetStatusesCount() int 37 | GetFollowersCount() int 38 | GetFriendsCount() int 39 | GetFavoritesCount() int 40 | } 41 | 42 | type tTwitterUser struct { 43 | Id int64 44 | Name string 45 | Screen_name string 46 | Location string 47 | Description string 48 | Profile_image_url string 49 | Profile_background_title bool 50 | Profile_background_image_url string 51 | Profile_sidebar_fill_color string 52 | Profile_link_color string 53 | Profile_text_color string 54 | Protected bool 55 | Utc_offset int 56 | Url string 57 | Timezone string 58 | Status *tTwitterStatus 59 | Statuses_count int 60 | Followers_count int 61 | Friends_count int 62 | Favorites_count int 63 | Error string 64 | } 65 | 66 | type tTwitterUserDummy struct { 67 | Object tTwitterUser 68 | } 69 | 70 | type tTwitterUserListDummy struct { 71 | Object []tTwitterUser 72 | } 73 | 74 | func newEmptyTwitterUser() *tTwitterUser { return new(tTwitterUser) } 75 | 76 | func (self *tTwitterUser) GetError() string { return self.Error } 77 | 78 | func (self *tTwitterUser) GetId() int64 { return self.Id } 79 | 80 | func (self *tTwitterUser) GetName() string { return self.Name } 81 | 82 | func (self *tTwitterUser) GetScreenName() string { 83 | return self.Screen_name 84 | } 85 | 86 | func (self *tTwitterUser) GetLocation() string { 87 | return self.Location 88 | } 89 | 90 | func (self *tTwitterUser) GetDescription() string { 91 | return self.Description 92 | } 93 | 94 | func (self *tTwitterUser) GetProfileImageUrl() string { 95 | return self.Profile_image_url 96 | } 97 | 98 | func (self *tTwitterUser) GetProfileBackgroundTitle() bool { 99 | return self.Profile_background_title 100 | } 101 | 102 | func (self *tTwitterUser) GetProfileSidebarFillColor() string { 103 | return self.Profile_sidebar_fill_color 104 | } 105 | 106 | func (self *tTwitterUser) GetProfileBackgroundImageUrl() string { 107 | return self.Profile_background_image_url 108 | } 109 | 110 | func (self *tTwitterUser) GetProfileLinkColor() string { 111 | return self.Profile_link_color 112 | } 113 | 114 | func (self *tTwitterUser) GetProfileTextColor() string { 115 | return self.Profile_text_color 116 | } 117 | 118 | func (self *tTwitterUser) GetProtected() bool { return self.Protected } 119 | 120 | func (self *tTwitterUser) GetUtcOffset() int { return self.Utc_offset } 121 | 122 | func (self *tTwitterUser) GetTimeZone() string { 123 | return self.Timezone 124 | } 125 | 126 | func (self *tTwitterUser) GetURL() string { return self.Url } 127 | 128 | func (self *tTwitterUser) GetStatus() Status { 129 | if self.Status == nil { 130 | self.Status = newEmptyTwitterStatus() 131 | } 132 | self.Status.setUser(self) 133 | return self.Status 134 | } 135 | 136 | func (self *tTwitterUser) setStatus(status Status) { 137 | self.Status = status.(*tTwitterStatus) 138 | } 139 | 140 | func (self *tTwitterUser) GetStatusesCount() int { 141 | return self.Statuses_count 142 | } 143 | 144 | func (self *tTwitterUser) GetFollowersCount() int { 145 | return self.Followers_count 146 | } 147 | 148 | func (self *tTwitterUser) GetFriendsCount() int { 149 | return self.Friends_count 150 | } 151 | 152 | func (self *tTwitterUser) GetFavoritesCount() int { 153 | return self.Favorites_count 154 | } 155 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2009 Bill Casarin 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | package twitter 17 | 18 | import ( 19 | "io/ioutil" 20 | "net/http" 21 | "fmt" 22 | "net/url" 23 | ) 24 | 25 | func fixBrokenJson(j string) string { return `{"object":` + j + "}" } 26 | 27 | func parseResponse(response *http.Response) (string, error) { 28 | var b []byte 29 | b, _ = ioutil.ReadAll(response.Body) 30 | response.Body.Close() 31 | bStr := string(b) 32 | 33 | return bStr, nil 34 | } 35 | 36 | func addQueryVariables(url_ string, variables map[string]string) string { 37 | var addition string 38 | newUrl := url_ 39 | 40 | i := 0 41 | for key, value := range variables { 42 | if i == 0 { 43 | addition = fmt.Sprintf("?%s=%s", key, url.QueryEscape(value)) 44 | } else { 45 | addition = fmt.Sprintf("&%s=%s", key, url.QueryEscape(value)) 46 | } 47 | newUrl += addition 48 | i++ 49 | } 50 | 51 | return newUrl 52 | } 53 | --------------------------------------------------------------------------------