Twitter bot is working, code if anyone's curious
main.go (lambda handler, general glue):
Code:
package main
import (
"fmt"
"log"
"os"
"github.com/aws/aws-lambda-go/lambda"
)
func main() {
lambda.Start(Handler)
}
func Handler() {
str, err := GetS3File("tweet.txt")
if err != nil {
return
}
log.Println("Read string:", str)
tweet, err := GetNextTweet(str)
if err != nil {
log.Println("Twitter error:", err)
}
if tweet == nil {
log.Println("No tweet found, exiting")
return
}
log.Println("next oldest id: ", tweet.IdStr())
err = UpdateS3File("tweet.txt", tweet.IdStr())
if err != nil {
log.Println("Error updating file:", err)
}
var token string
cookieJson, err := GetS3File("cookies.txt")
if err != nil {
fmt.Println("no cookies, will log in")
} else {
token, err = GetS3File("token.txt")
if err != nil {
fmt.Println("no cached token, will have to grab")
}
}
threadID, ok := os.LookupEnv("FORUM_THREAD_ID")
if !ok {
fmt.Println("No thread ID found")
return
}
var client *ForumClient
if cookieJson != "" {
client, err = NewClientWithJsonCookies(cookieJson)
} else {
user, ok := os.LookupEnv("FORUM_USERNAME")
if !ok {
fmt.Println("No username found")
return
}
pass, ok := os.LookupEnv("FORUM_PASSWORD")
if !ok {
fmt.Println("No password found")
return
}
client, err = NewClientWithLogin(user, pass)
}
if err != nil {
fmt.Println("Error getting client:", err)
return
}
tokenWasEmpty := token == ""
client.Token = token
postContent := fmt.Sprintf("[tweet ]%s[/ tweet]\n%s", tweet.IdStr(), TweetLink(tweet.IdStr()))
err = client.SubmitNewPost(postContent, threadID)
if err != nil {
fmt.Println("Error submitting post:", err)
return
}
updatedJSON, err := JsonFromCookies(client.Client.Jar)
if err != nil {
fmt.Println("Error getting cookies:", err)
return
}
err = UpdateS3File("cookies.txt", updatedJSON)
if err != nil {
fmt.Println("Error updating S3 cookies:", err)
return
}
if tokenWasEmpty && client.Token != "" {
err = UpdateS3File("token.txt", client.Token)
if err != nil {
fmt.Println("Error updating token:", err)
}
}
}
aws.go (s3 access):
Code:
package main
import (
"log"
"strings"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
)
var sharedSession *session.Session
func getSession() *session.Session {
if sharedSession == nil {
sharedSession = session.New(&aws.Config{Region: aws.String("us-east-2")})
}
return sharedSession
}
func GetS3File(filename string) (string, error) {
sess := getSession()
downloader := s3manager.NewDownloader(sess)
buf := aws.NewWriteAtBuffer([]byte{})
_, err := downloader.Download(buf, &s3.GetObjectInput{
Bucket: aws.String("mybotbucket"), // TODO: parameterize
Key: aws.String(filename),
})
if err != nil {
log.Println("download err:", err)
return "", err
}
return string(buf.Bytes()), nil
}
func UpdateS3File(filename, content string) error {
sess := getSession()
uploader := s3manager.NewUploader(sess)
_, err := uploader.Upload(&s3manager.UploadInput{
Bucket: aws.String("mybotbucket"),
Key: aws.String(filename),
Body: strings.NewReader(content),
})
return err
}
twitter.go (get latest trump tweet) (some of this copypastaed from examples that I'm too lazy to update):
Code:
package main
import (
"errors"
"fmt"
"log"
"net/http"
"net/url"
"os"
"github.com/kurrik/oauth1a"
"github.com/kurrik/twittergo"
)
func LoadEnvCredentials() (*oauth1a.ClientConfig, error) {
key, ok := os.LookupEnv("TWITTER_CONSUMER_KEY")
if !ok {
return nil, errors.New("no key")
}
secret, ok := os.LookupEnv("TWITTER_CONSUMER_SECRET")
if !ok {
return nil, errors.New("no secret")
}
config := &oauth1a.ClientConfig{
ConsumerKey: key,
ConsumerSecret: secret,
}
return config, nil
}
var NoResults = errors.New("No search results")
func GetNextTweet(baseId string) (*twittergo.Tweet, error) {
config, err := LoadEnvCredentials()
if err != nil {
log.Println("Twitter config error:", err)
return nil, err
}
client := twittergo.NewClient(config, nil)
var (
req *http.Request
resp *twittergo.APIResponse
query url.Values
results *twittergo.Timeline
)
const urltmpl = "/1.1/statuses/user_timeline.json?%v"
query = url.Values{}
query.Set("count", "200")
query.Set("screen_name", "realDonaldTrump")
query.Set("tweet_mode", "extended")
query.Set("trim_user", "1")
query.Set("exclude_replies", "1")
query.Set("include_rts", "0")
if baseId != "" {
query.Set("since_id", baseId)
}
endpoint := fmt.Sprintf(urltmpl, query.Encode())
if req, err = http.NewRequest("GET", endpoint, nil); err != nil {
log.Printf("Could not parse request: %v\n", err)
os.Exit(1)
}
if resp, err = client.SendRequest(req); err != nil {
log.Printf("Could not send request: %v\n", err)
os.Exit(1)
}
results = &twittergo.Timeline{}
if err = resp.Parse(results); err != nil {
if rle, ok := err.(twittergo.RateLimitError); ok {
msg := "Rate limited. Reset at %v\n"
log.Printf(msg, rle.Reset)
} else {
log.Printf("Problem parsing response: %v\n", err)
}
return nil, err
}
batch := len(*results)
if batch == 0 {
log.Printf("No more results, end of timeline.\n")
return nil, NoResults
}
var oldest *twittergo.Tweet
for _, tweet := range *results {
if oldest == nil || tweet.CreatedAt().Before(oldest.CreatedAt()) {
oldest = &tweet
}
}
return oldest, nil
}
func TweetLink(id string) string {
return fmt.Sprintf("https://twitter.com/realDonaldTrump/status/%s", id)
}
forum.go - for posting stuff to 2+2, definitely took the most time (and borrowed a lot from what Chips' code was doing, thanks for the help WN)
Code:
package main
import (
"bytes"
"crypto/md5"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"strings"
)
type ForumClient struct {
Client *http.Client
Token string
}
// NewClientWithLogin will attempt a fresh login on the forum with the given
// username and password, seeding the returned http.Client with the cookies
// needed to make future authenticated requests
func NewClientWithLogin(username, password string) (*ForumClient, error) {
client, err := newClient()
if err != nil {
return nil, err
}
// load 2+2 to seed initial cookies
resp, err := client.Get(forumURL)
if err != nil {
fmt.Println("forum load error:", err)
return nil, err
}
// get session token
var sessionToken string
for _, cookie := range resp.Cookies() {
if cookie.Name == "bbsessionhash" {
sessionToken = cookie.Value
break
}
}
// get md5 password
hasher := md5.New()
_, err = hasher.Write([]byte(password))
if err != nil {
return nil, err
}
md5pass := hex.EncodeToString(hasher.Sum(nil))
// set up login form
vals := url.Values{
"do": {"login"},
"cookieuser": {"1"},
"securitytoken": {"guest"},
"vb_login_md5password": {md5pass},
"vb_login_md5password_utf": {md5pass},
"vb_login_password": {""},
"vb_login_username": {username},
"s": {sessionToken},
}
req, err := http.NewRequest("POST", loginURL, strings.NewReader(vals.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0")
req.Header.Set("Referer", forumURL)
_, err = client.Do(req)
if err != nil {
fmt.Println("login err:", err)
return nil, err
}
return &ForumClient{Client: client}, nil
}
func NewClientWithCookies(cookies []*http.Cookie) (*ForumClient, error) {
client, err := newClient()
if err != nil {
return nil, err
}
parsedURL, err := url.Parse(forumURL)
if err != nil {
return nil, err
}
client.Jar.SetCookies(parsedURL, cookies)
return &ForumClient{Client: client}, nil
}
func NewClientWithJsonCookies(cookieJson string) (*ForumClient, error) {
decoder := json.NewDecoder(strings.NewReader(cookieJson))
cookies := make([]*http.Cookie, 0)
err := decoder.Decode(&cookies)
if err != nil {
return nil, err
}
return NewClientWithCookies(cookies)
}
func JsonFromCookies(jar http.CookieJar) (string, error) {
parsedURL, err := url.Parse(forumURL)
if err != nil {
return "", err
}
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
err = encoder.Encode(jar.Cookies(parsedURL))
if err != nil {
return "", err
}
return buf.String(), nil
}
func (c *ForumClient) SubmitNewPost(content, threadId string) error {
destURL := forumURL + fmt.Sprintf("/newreply.php?do=postreply&t=%s", threadId)
uid, err := cookieByName(c.Client.Jar, "bbuserid")
if err != nil {
return err
}
if c.Token == "" {
token, err := getSecurityToken(c.Client)
if err != nil {
return err
}
c.Token = token
}
vals := url.Values{
"do": {"postreply"},
"emailupdate": {"0"},
"iconid": {"0"},
"loggedinuser": {uid.Value},
"message": {content},
"multiquoteempty": {},
"p": {},
"parseurl": {"1"},
"posthash": {"invalid+posthash"},
"poststarttime": {"0"},
"rating": {"0"},
"s": {},
"sbutton": {"Submit+Reply"},
"securitytoken": {c.Token},
"t": {threadId},
"title": {},
"wysiwyg": {"0"},
}
req, err := http.NewRequest("POST", destURL, strings.NewReader(vals.Encode()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0")
req.Header.Set("Referer", destURL)
_, err = c.Client.Do(req)
if err != nil {
return err
}
return nil
}
const forumURL = "https://forumserver.twoplustwo.com"
const loginURL = "https://forumserver.twoplustwo.com/login.php?do=login"
func newClient() (*http.Client, error) {
jar, err := cookiejar.New(nil)
if err != nil {
return nil, err
}
client := &http.Client{Jar: jar}
client.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }
return client, nil
}
func cookieByName(jar http.CookieJar, name string) (*http.Cookie, error) {
if jar == nil {
return nil, errors.New("nil jar")
}
parsed, err := url.Parse(forumURL)
if err != nil {
return nil, err
}
for _, cookie := range jar.Cookies(parsed) {
if cookie.Name == name {
return cookie, nil
}
}
return nil, fmt.Errorf("cookie not found: %s", name)
}
var securityRegex = regexp.MustCompile(`var SECURITYTOKEN = "([0-9a-z\-_]+)"`)
func getSecurityToken(client *http.Client) (string, error) {
resp, err := client.Get(forumURL + "/private.php")
if err != nil {
return "", err
}
buf, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
resp.Body.Close()
token := securityRegex.FindStringSubmatch(string(buf))
if token == nil || len(token) <= 1 {
return "", errors.New("token not found")
}
return token[1], nil
}
Last edited by goofyballer; 06-20-2018 at 07:19 PM.