Initial commit

Implemented getters for:
    * Snapchat
    * Twitter
    * Instagram

Also implemented config and database modules.
This commit is contained in:
Andreas Mieke 2017-01-17 23:38:35 +01:00
commit 0a69d4a9f2
14 changed files with 1013 additions and 0 deletions

61
.gitignore vendored Normal file
View file

@ -0,0 +1,61 @@
# Created by https://www.gitignore.io/api/go,osx
### Go ###
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# External packages folder
vendor/
### OSX ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
# End of https://www.gitignore.io/api/go,osx

64
config/config.go Normal file
View file

@ -0,0 +1,64 @@
package config
import (
"bytes"
"io/ioutil"
"github.com/BurntSushi/toml"
)
type Config struct {
DatabaseConnection string
BindAddress string
AssetsDirectory string
TemplatesDirectory string
ContentDirectory string
ContentWebDirectory string
Snapchat Snapchat
Twitter Twitter
Instagram Instagram
}
type Instagram struct {
Tag string
}
type Twitter struct {
ConsumerKey string
ConsumerSecret string
OAuthToken string
OAuthTokenSecret string
Filter []string
}
type Snapchat struct {
ApiBase string
UserAgent string
UserName string
GetConversations SnapchatEndpoint
GetBlob SnapchatEndpoint
MarkAsSeen SnapchatEndpoint
}
type SnapchatEndpoint struct {
Uuid string
ClientAuthToken string
RequestToken string
Timestamp string
}
var C Config
func LoadConfig(path string) error {
_, e := toml.DecodeFile(path, &C)
return e
}
func WriteConfig(path string) error {
buf := new(bytes.Buffer)
err := toml.NewEncoder(buf).Encode(C)
if err != nil {
return err
}
err = ioutil.WriteFile(path, buf.Bytes(), 0644)
return err
}

67
database/main.go Normal file
View file

@ -0,0 +1,67 @@
package database
import (
"log"
"time"
"git.1750studios.com/AniNite/SocialDragon/config"
"github.com/jinzhu/gorm"
_ "github.com/lib/pq"
)
type Service uint
const (
Snapchat Service = iota
Twitter
Instagram
)
type State uint
const (
Inbox State = iota
Approved
Rejected
)
type Item struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
UserID uint `sql:"index"`
Service Service
State State
IsVideo bool
Path string
OriginalID string
}
type User struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
Name string
DisplayName string
Service Service
Blocked bool
}
var Db *gorm.DB
func InitDb() {
var err error
Db, err = gorm.Open("postgres", config.C.DatabaseConnection)
if err != nil {
log.Fatalf("Database error: %+v", err)
}
Db.LogMode(false)
Db.SingularTable(true)
Db.Model(&Item{}).AddForeignKey("user_id", "user(id)", "RESTRICT", "RESTRICT")
Db.AutoMigrate(&Item{}, &User{})
}

149
instagram/api.go Normal file
View file

@ -0,0 +1,149 @@
package instagram
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io"
"io/ioutil"
"log"
"os"
"github.com/disintegration/imaging"
"git.1750studios.com/AniNite/SocialDragon/config"
)
type InstagramListResponse struct {
Tag struct {
Media struct {
Nodes []struct {
ID string `json:"id"`
Caption string `json:"caption"`
DisplaySrc string `json:"display_src"`
IsVideo bool `json:"is_video"`
Owner struct {
ID string `json:"id"`
} `json:"owner"`
ThumbnailSrc string `json:"thumbnail_src"`
Code string `json:"code"`
Date int `json:"date"`
} `json:"nodes"`
PageInfo struct {
EndCursor string `json:"end_cursor"`
HasNextPage bool `json:"has_next_page"`
StartCursor string `json:"start_cursor"`
HasPreviousPage bool `json:"has_previous_page"`
} `json:"page_info"`
Count int `json:"count"`
} `json:"media"`
} `json:"tag"`
}
type InstagramPostResponse struct {
Media struct {
Owner struct {
ProfilePicURL string `json:"profile_pic_url"`
HasBlockedViewer bool `json:"has_blocked_viewer"`
Username string `json:"username"`
FullName string `json:"full_name"`
RequestedByViewer bool `json:"requested_by_viewer"`
FollowedByViewer bool `json:"followed_by_viewer"`
IsPrivate bool `json:"is_private"`
ID string `json:"id"`
BlockedByViewer bool `json:"blocked_by_viewer"`
IsUnpublished bool `json:"is_unpublished"`
} `json:"owner"`
CaptionIsEdited bool `json:"caption_is_edited"`
VideoURL string `json:"video_url"`
DisplaySrc string `json:"display_src"`
CommentsDisabled bool `json:"comments_disabled"`
Code string `json:"code"`
IsAd bool `json:"is_ad"`
IsVideo bool `json:"is_video"`
Caption string `json:"caption"`
ID string `json:"id"`
Date int `json:"date"`
VideoViews int `json:"video_views"`
} `json:"media"`
}
func LoadList() (*InstagramListResponse, error) {
var listResponse InstagramListResponse
res, err := GetHTTPResource("https://www.instagram.com/explore/tags/" + config.C.Instagram.Tag + "/?__a=1")
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
jerr := json.Unmarshal(body, &listResponse)
if jerr != nil {
return nil, jerr
}
return &listResponse, nil
}
func LoadPost(code string) (*InstagramPostResponse, error) {
var postResponse InstagramPostResponse
res, err := GetHTTPResource("https://www.instagram.com/p/" + code + "/?__a=1")
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
jerr := json.Unmarshal(body, &postResponse)
if jerr != nil {
return nil, jerr
}
return &postResponse, nil
}
func ImageNameGenerator(seed string) (string, string) {
seedBytes := []byte(seed)
sha256Bytes := sha256.Sum256(seedBytes)
hash := hex.EncodeToString(sha256Bytes[:])
folders := config.C.ContentDirectory + "/" + hash[0:2] + "/" + hash[0:4] + "/"
urls := config.C.ContentWebDirectory + "/" + hash[0:2] + "/" + hash[0:4] + "/"
if err := os.MkdirAll(folders, 0775); err != nil {
log.Fatalf("FAT Could not create ContentDirectory, error: %+v", err)
}
finalPath := folders + hash
finalUrl := urls + hash
return finalPath, finalUrl
}
func DownloadMedia(data io.Reader, path string, video bool) (string, error) {
if video {
ext := ".mp4"
blob, err := ioutil.ReadAll(data)
if err != nil {
return "", err
}
if len(blob) == 0 {
return "", errors.New("Empty response")
}
err = ioutil.WriteFile(path+ext, blob, 0644)
if err != nil {
return "", err
}
return ext, nil
} else {
ext := ".jpg"
image, err := imaging.Decode(data)
if err != nil {
return "", err
}
err = imaging.Save(image, path+ext)
if err != nil {
return "", err
}
return ext, nil
}
}

33
instagram/http.go Normal file
View file

@ -0,0 +1,33 @@
package instagram
import (
"errors"
"log"
"net/http"
"strconv"
)
var sem = make(chan byte, 2)
var client = &http.Client{}
func GetHTTPResource(u string) (*http.Response, error) {
sem <- 1
req, err := http.NewRequest("GET", u, nil)
if err != nil {
log.Fatalf("FAT HTTP - Failed to create new Request: %+v", err)
<-sem
return nil, err
}
res, err := client.Do(req)
if err != nil {
<-sem
return nil, err
}
if res.StatusCode != 200 {
res.Body.Close()
<-sem
return nil, errors.New(strconv.Itoa(res.StatusCode))
}
<-sem
return res, nil
}

94
instagram/main.go Normal file
View file

@ -0,0 +1,94 @@
package instagram
import (
"log"
"git.1750studios.com/AniNite/SocialDragon/database"
)
func LoadNewInstas() {
log.Printf("Loading new Instas...")
list, err := LoadList()
if err != nil {
log.Printf("Can't load Instagram feed: %+v", err)
return
}
for _, image := range list.Tag.Media.Nodes {
var count int
if database.Db.Model(database.Item{}).Where("original_id = ?", image.Code).Count(&count); count > 0 {
continue
}
post, err := LoadPost(image.Code)
if err != nil {
log.Printf("Can't load Instagram post %s: %+v", image.Code, err)
continue
}
if post.Media.IsAd {
continue
}
var US database.User
if database.Db.Model(database.User{}).Where("name = ? AND service = ?", post.Media.Owner.Username, database.Instagram).First(&US).Count(&count); count == 0 {
US.DisplayName = post.Media.Owner.FullName
US.Name = post.Media.Owner.Username
US.Service = database.Instagram
US.Blocked = false
database.Db.Create(&US)
}
if post.Media.IsVideo {
log.Printf("Found video %s from %s", post.Media.Code, post.Media.Owner.Username)
name, uname := ImageNameGenerator(post.Media.Code)
res, err := GetHTTPResource(post.Media.VideoURL)
if err != nil {
log.Printf("Can't load video %s: %+v", post.Media.Code, err)
continue
}
defer res.Body.Close()
ext, err := DownloadMedia(res.Body, name, true)
if err != nil {
log.Printf("Can't load video %s: %+v", post.Media.Code, err)
continue
}
log.Printf("Loaded video %s, location %s!", post.Media.Code, uname+ext)
var IT database.Item
IT.UserID = US.ID
IT.Service = database.Instagram
if US.Blocked {
IT.State = database.Rejected
} else {
IT.State = database.Inbox
}
IT.IsVideo = true
IT.Path = uname + ext
IT.OriginalID = post.Media.Code
database.Db.Create(&IT)
} else {
log.Printf("Found picture %s from %s", post.Media.Code, post.Media.Owner.Username)
name, uname := ImageNameGenerator(post.Media.Code)
res, err := GetHTTPResource(post.Media.DisplaySrc)
if err != nil {
log.Printf("Can't load picture %s: %+v", post.Media.Code, err)
continue
}
defer res.Body.Close()
ext, err := DownloadMedia(res.Body, name, false)
if err != nil {
log.Printf("Can't load picture %s: %+v", post.Media.Code, err)
continue
}
log.Printf("Loaded picture %s, location %s!", post.Media.Code, uname+ext)
var IT database.Item
IT.UserID = US.ID
IT.Service = database.Instagram
if US.Blocked {
IT.State = database.Rejected
} else {
IT.State = database.Inbox
}
IT.IsVideo = false
IT.Path = uname + ext
IT.OriginalID = post.Media.Code
database.Db.Create(&IT)
}
}
log.Printf("Finished looking for new Instas.")
}

163
snapchat/api.go Normal file
View file

@ -0,0 +1,163 @@
package snapchat
import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"io"
"io/ioutil"
"log"
"os"
"github.com/disintegration/imaging"
"git.1750studios.com/AniNite/SocialDragon/config"
)
type Conversations struct {
ConversationsResponse []ConversationsResponse `json:"conversations_response"`
}
type ConversationsResponse struct {
PendingReceivedSnaps []Snap `json:"pending_received_snaps"`
}
type Snap struct {
Id string `json:"id"`
Username string `json:"sn"`
Movie int `json:"m"`
Rotation int `json:"mo"`
}
func GetConversations() (*Conversations, error) {
var conversations Conversations
headers := map[string]string{
"X-Snapchat-UUID": config.C.Snapchat.GetConversations.Uuid,
"X-Snapchat-Client-Auth-Token": config.C.Snapchat.GetConversations.ClientAuthToken,
}
data := map[string]string{
"checksum_dict": "{}",
"friends_request": "{\"friends_sync_token\":\"3\"}",
"req_token": config.C.Snapchat.GetConversations.RequestToken,
"timestamp": config.C.Snapchat.GetConversations.Timestamp,
"username": config.C.Snapchat.UserName,
}
res, err := GetHTTPResource("/loq/conversations", headers, data)
if err != nil {
return nil, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
jerr := json.Unmarshal(body, &conversations)
if jerr != nil {
return nil, jerr
}
return &conversations, nil
}
func GetBlob(snap Snap) (string, error) {
headers := map[string]string{
"X-Snapchat-UUID": config.C.Snapchat.GetBlob.Uuid,
"X-Snapchat-Client-Auth-Token": config.C.Snapchat.GetBlob.ClientAuthToken,
}
data := map[string]string{
"id": snap.Id,
"req_token": config.C.Snapchat.GetBlob.RequestToken,
"timestamp": config.C.Snapchat.GetBlob.Timestamp,
"username": config.C.Snapchat.UserName,
}
res, err := GetHTTPResource("/bq/blob", headers, data)
if err != nil {
return "", err
}
name, uname := ImageNameGenerator(snap.Id)
defer res.Body.Close()
ext, err := RotateImage(snap, res.Body, name)
if err != nil {
return "", err
}
return uname + ext, nil
}
func MarkAsSeen(snap Snap, screenshotted bool) error {
headers := map[string]string{
"X-Snapchat-UUID": config.C.Snapchat.MarkAsSeen.Uuid,
"X-Snapchat-Client-Auth-Token": config.C.Snapchat.MarkAsSeen.ClientAuthToken,
}
data := map[string]string{
"req_token": config.C.Snapchat.MarkAsSeen.RequestToken,
"timestamp": config.C.Snapchat.MarkAsSeen.Timestamp,
"username": config.C.Snapchat.UserName,
}
if screenshotted {
data["json"] = "{\"" + snap.Id + "\":{\"t\":1467469712.44128,\"replayed\":0,\"c\":1,\"stack_id\":\"E437851F-2AA8-4C6C-AE11-5FB49FBF93C0\",\"sv\":0,\"es_id\":\"cssek-0::TgcQVwLCU3kHIEj+o6s2CQ==:QTm2VyfemhN5owtHmVuMpik4hwFNz4gSpUQ9D9zlWxatYA==\"}}"
} else {
data["json"] = "{\"" + snap.Id + "\":{\"t\":1467469712.44128,\"replayed\":0,\"stack_id\":\"E437851F-2AA8-4C6C-AE11-5FB49FBF93C0\",\"sv\":0,\"es_id\":\"cssek-0::TgcQVwLCU3kHIEj+o6s2CQ==:QTm2VyfemhN5owtHmVuMpik4hwFNz4gSpUQ9D9zlWxatYA==\"}}"
}
res, err := GetHTTPResource("/bq/update_snaps", headers, data)
if err != nil {
return err
}
defer res.Body.Close()
return nil
}
func ImageNameGenerator(seed string) (string, string) {
seedBytes := []byte(seed)
sha256Bytes := sha256.Sum256(seedBytes)
hash := hex.EncodeToString(sha256Bytes[:])
folders := config.C.ContentDirectory + "/" + hash[0:2] + "/" + hash[0:4] + "/"
urls := config.C.ContentWebDirectory + "/" + hash[0:2] + "/" + hash[0:4] + "/"
if err := os.MkdirAll(folders, 0775); err != nil {
log.Fatalf("FAT Could not create ContentDirectory, error: %+v", err)
}
finalPath := folders + hash
finalUrl := urls + hash
return finalPath, finalUrl
}
func RotateImage(snap Snap, data io.Reader, path string) (string, error) {
if snap.Movie == 1 {
ext := ".mp4"
blob, err := ioutil.ReadAll(data)
if err != nil {
return "", err
}
if len(blob) == 0 {
return "", errors.New("Empty response")
}
err = ioutil.WriteFile(path+ext, blob, 0644)
if err != nil {
return "", err
}
return ext, nil
} else {
ext := ".jpg"
image, err := imaging.Decode(data)
if err != nil {
return "", err
}
switch snap.Rotation {
case 1:
image = imaging.Rotate180(image)
break
case 2:
image = imaging.Rotate90(image)
break
case 3:
image = imaging.Rotate270(image)
break
default:
break
}
err = imaging.Save(image, path+ext)
if err != nil {
return "", err
}
return ext, nil
}
}

48
snapchat/http.go Normal file
View file

@ -0,0 +1,48 @@
package snapchat
import (
"errors"
"log"
"net/http"
"net/url"
"strconv"
"strings"
"git.1750studios.com/AniNite/SocialDragon/config"
)
var sem = make(chan byte, 2)
var client = &http.Client{}
func GetHTTPResource(u string, headers map[string]string, data map[string]string) (*http.Response, error) {
sem <- 1
form := url.Values{}
for key, value := range data {
form.Set(key, value)
}
req, err := http.NewRequest("POST", config.C.Snapchat.ApiBase+u, strings.NewReader(form.Encode()))
if err != nil {
log.Fatalf("FAT HTTP - Failed to create new Request: %+v", err)
<-sem
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept-Locale", "de-DE")
req.Header.Set("User-Agent", config.C.Snapchat.UserAgent)
req.Header.Set("Accept-Language", "de;q=1, en;q=0.9, fr;q=0.8, zh-Hans;q=0.7, zh-Hant;q=0.6, ja;q=0.5")
for key, value := range headers {
req.Header.Set(key, value)
}
res, err := client.Do(req)
if err != nil {
<-sem
return nil, err
}
if res.StatusCode != 200 {
res.Body.Close()
<-sem
return nil, errors.New(strconv.Itoa(res.StatusCode))
}
<-sem
return res, nil
}

57
snapchat/main.go Normal file
View file

@ -0,0 +1,57 @@
package snapchat
import (
"log"
"git.1750studios.com/AniNite/SocialDragon/database"
)
func LoadNewSnaps() {
log.Print("Loading new Snaps...")
cons, err := GetConversations()
if err != nil {
log.Printf("Can't load snaps: %+v", err)
return
}
for _, con := range cons.ConversationsResponse {
for _, snap := range con.PendingReceivedSnaps {
var count int
if database.Db.Model(database.Item{}).Where("original_id = ?", snap.Id).Count(&count); count > 0 {
break
}
var US database.User
if database.Db.Model(database.User{}).Where("name = ? AND service = ?", snap.Username, database.Snapchat).First(&US).Count(&count); count == 0 {
US.DisplayName = snap.Username
US.Name = snap.Username
US.Service = database.Snapchat
US.Blocked = false
database.Db.Create(&US)
}
log.Printf("Found new Snap %s from %s", snap.Id, snap.Username)
uname, err := GetBlob(snap)
if err != nil {
log.Printf("Can't load snap %s: %+v", snap.Id, err)
return
}
log.Printf("Loaded snap %s, location %s!", snap.Id, uname)
var IT database.Item
IT.UserID = US.ID
IT.IsVideo = snap.Movie != 0
IT.OriginalID = snap.Id
IT.Path = uname
IT.Service = database.Snapchat
if US.Blocked {
IT.State = database.Rejected
} else {
IT.State = database.Inbox
}
database.Db.Create(&IT)
err = MarkAsSeen(snap, false)
if err != nil {
log.Printf("Could not mark snap %s as seen: %+v", snap.Id, err)
return
}
}
}
log.Printf("Finished looking for new snaps.")
}

33
socialdragon/main.go Normal file
View file

@ -0,0 +1,33 @@
package main
import (
"os"
"os/signal"
"syscall"
"github.com/robfig/cron"
"git.1750studios.com/AniNite/SocialDragon/config"
"git.1750studios.com/AniNite/SocialDragon/database"
"git.1750studios.com/AniNite/SocialDragon/instagram"
"git.1750studios.com/AniNite/SocialDragon/snapchat"
"git.1750studios.com/AniNite/SocialDragon/twitter"
)
func main() {
config.LoadConfig(os.Getenv("HOME") + "/.socialdragon.toml")
database.InitDb()
c := cron.New()
c.AddFunc("@every 30s", snapchat.LoadNewSnaps)
c.AddFunc("@every 30s", instagram.LoadNewInstas)
c.Start()
go twitter.LoadNewTweets()
ch := make(chan os.Signal)
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM)
<-ch
twitter.Stop()
}

33
socialdragon/test.toml Normal file
View file

@ -0,0 +1,33 @@
DatabaseConnection = ""
BindAddress = ""
AssetsDirectory = ""
TemplatesDirectory = ""
ContentDirectory = ""
ContentWebDirectory = ""
[Snapchat]
ApiBase = ""
UserAgent = ""
UserName = ""
[Snapchat.GetConversations]
Uuid = ""
ClientAuthToken = ""
RequestToken = ""
Timestamp = ""
[Snapchat.GetBlob]
Uuid = ""
ClientAuthToken = ""
RequestToken = ""
Timestamp = ""
[Snapchat.MarkAsSeen]
Uuid = ""
ClientAuthToken = ""
RequestToken = ""
Timestamp = ""
[Twitter]
ConsumerKey = ""
ConsumerSecret = ""
OAuthToken = ""
OAuthTokenSecret = ""
Filter = ""

58
twitter/downloader.go Normal file
View file

@ -0,0 +1,58 @@
package twitter
import (
"crypto/sha256"
"encoding/hex"
"errors"
"io"
"io/ioutil"
"log"
"os"
"github.com/disintegration/imaging"
"git.1750studios.com/AniNite/SocialDragon/config"
)
func MediaNameGenerator(seed string) (string, string) {
seedBytes := []byte(seed)
sha256Bytes := sha256.Sum256(seedBytes)
hash := hex.EncodeToString(sha256Bytes[:])
folders := config.C.ContentDirectory + "/" + hash[0:2] + "/" + hash[0:4] + "/"
urls := config.C.ContentWebDirectory + "/" + hash[0:2] + "/" + hash[0:4] + "/"
if err := os.MkdirAll(folders, 0775); err != nil {
log.Fatalf("FAT Could not create ContentDirectory, error: %+v", err)
}
finalPath := folders + hash
finalUrl := urls + hash
return finalPath, finalUrl
}
func DownloadMedia(data io.Reader, path string, video bool) (string, error) {
if video {
ext := ".mp4"
blob, err := ioutil.ReadAll(data)
if err != nil {
return "", err
}
if len(blob) == 0 {
return "", errors.New("Empty response")
}
err = ioutil.WriteFile(path+ext, blob, 0644)
if err != nil {
return "", err
}
return ext, nil
} else {
ext := ".jpg"
image, err := imaging.Decode(data)
if err != nil {
return "", err
}
err = imaging.Save(image, path+ext)
if err != nil {
return "", err
}
return ext, nil
}
}

33
twitter/http.go Normal file
View file

@ -0,0 +1,33 @@
package twitter
import (
"errors"
"log"
"net/http"
"strconv"
)
var sem = make(chan byte, 2)
var client = &http.Client{}
func GetHTTPResource(u string) (*http.Response, error) {
sem <- 1
req, err := http.NewRequest("GET", u, nil)
if err != nil {
log.Fatalf("FAT HTTP - Failed to create new Request: %+v", err)
<-sem
return nil, err
}
res, err := client.Do(req)
if err != nil {
<-sem
return nil, err
}
if res.StatusCode != 200 {
res.Body.Close()
<-sem
return nil, errors.New(strconv.Itoa(res.StatusCode))
}
<-sem
return res, nil
}

120
twitter/main.go Normal file
View file

@ -0,0 +1,120 @@
package twitter
import (
"log"
"git.1750studios.com/AniNite/SocialDragon/config"
"git.1750studios.com/AniNite/SocialDragon/database"
"github.com/dghubble/go-twitter/twitter"
"github.com/dghubble/oauth1"
)
var stream *twitter.Stream
func LoadNewTweets() error {
log.Printf("Loading new tweets...")
conf := oauth1.NewConfig(config.C.Twitter.ConsumerKey, config.C.Twitter.ConsumerSecret)
token := oauth1.NewToken(config.C.Twitter.OAuthToken, config.C.Twitter.OAuthTokenSecret)
httpClient := conf.Client(oauth1.NoContext, token)
client := twitter.NewClient(httpClient)
params := &twitter.StreamFilterParams{
Track: config.C.Twitter.Filter,
StallWarnings: twitter.Bool(false),
}
var err error
stream, err = client.Streams.Filter(params)
if err != nil {
return err
}
demux := twitter.NewSwitchDemux()
demux.Tweet = func(tweet *twitter.Tweet) {
if tweet.ExtendedEntities == nil || tweet.RetweetedStatus != nil {
return
}
var count int
var US database.User
if database.Db.Model(database.User{}).Where("name = ? AND service = ?", tweet.User.ScreenName, database.Twitter).First(&US).Count(&count); count == 0 {
US.DisplayName = tweet.User.Name
US.Name = tweet.User.ScreenName
US.Service = database.Twitter
US.Blocked = false
database.Db.Create(&US)
}
for _, media := range tweet.ExtendedEntities.Media {
if media.Type == "video" {
bitrate := 0
index := 0
for i, variant := range media.VideoInfo.Variants {
if variant.Bitrate > bitrate {
index = i
bitrate = variant.Bitrate
}
}
log.Printf("Found video in tweet %s from %s", tweet.IDStr, tweet.User.ScreenName)
name, uname := MediaNameGenerator(media.VideoInfo.Variants[index].URL)
res, err := GetHTTPResource(media.VideoInfo.Variants[index].URL)
if err != nil {
log.Printf("Can't load video from tweet %s: %+v", tweet.IDStr, err)
return
}
defer res.Body.Close()
ext, err := DownloadMedia(res.Body, name, true)
if err != nil {
log.Printf("Can't load video from tweet %s: %+v", tweet.IDStr, err)
return
}
log.Printf("Loaded tweet %s, location %s!", tweet.IDStr, uname+ext)
var IT database.Item
IT.UserID = US.ID
IT.Service = database.Twitter
if US.Blocked {
IT.State = database.Rejected
} else {
IT.State = database.Inbox
}
IT.IsVideo = true
IT.Path = uname + ext
IT.OriginalID = tweet.IDStr
database.Db.Create(&IT)
} else {
log.Printf("Found picture(s) in tweet %s from %s", tweet.IDStr, tweet.User.ScreenName)
name, uname := MediaNameGenerator(media.MediaURLHttps)
res, err := GetHTTPResource(media.MediaURLHttps)
if err != nil {
log.Printf("Can't load picture(s) from tweet %s: %+v", tweet.IDStr, err)
return
}
defer res.Body.Close()
ext, err := DownloadMedia(res.Body, name, false)
if err != nil {
log.Printf("Can't load picture(s) from tweet %s: %+v", tweet.IDStr, err)
return
}
log.Printf("Loaded tweet %s, location %s!", tweet.IDStr, uname+ext)
var IT database.Item
IT.UserID = US.ID
IT.Service = database.Twitter
if US.Blocked {
IT.State = database.Rejected
} else {
IT.State = database.Inbox
}
IT.IsVideo = false
IT.Path = uname + ext
IT.OriginalID = tweet.IDStr
database.Db.Create(&IT)
}
}
}
demux.HandleChan(stream.Messages)
log.Printf("Finished looking for new tweets.")
return nil
}
func Stop() {
log.Printf("Stopping twitter stream...")
stream.Stop()
log.Printf("Stopped twitter stream.")
}