@ -0,0 +1,32 @@ |
|||||
|
# 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 |
||||
|
|
||||
|
|
||||
|
botsConfig.json |
||||
|
build/botsConfig.json |
||||
|
repliesConfig.json |
||||
|
keywordsConfig.json |
||||
|
temp |
||||
|
text.txt |
@ -1,2 +1,40 @@ |
|||||
# echo-botnet |
|
||||
|
# echo-botnet [![Go Report Card](https://goreportcard.com/badge/github.com/arnaucode/echo-botnet)](https://goreportcard.com/report/github.com/arnaucode/echo-botnet) |
||||
A twitter botnet with autonomous bots replying tweets with pre-configured replies |
A twitter botnet with autonomous bots replying tweets with pre-configured replies |
||||
|
|
||||
|
|
||||
|
Echo (Ēkhō): https://en.wikipedia.org/wiki/Echo_(mythology)# |
||||
|
|
||||
|
|
||||
|
Needs the files: |
||||
|
``` |
||||
|
botsConfig.json --> contains the tokens of the bots |
||||
|
keywordsConfig.json --> contains the keywords to track from Twitter API |
||||
|
repliesConfig.json --> contains the replies |
||||
|
``` |
||||
|
|
||||
|
configuration file example (botsConfig.json): |
||||
|
``` |
||||
|
[{ |
||||
|
"title": "account1", |
||||
|
"consumer_key": "xxxxxxxxxxxxx", |
||||
|
"consumer_secret": "xxxxxxxxxxxxx", |
||||
|
"access_token_key": "xxxxxxxxxxxxx", |
||||
|
"access_token_secret": "xxxxxxxxxxxxx" |
||||
|
}, |
||||
|
{ |
||||
|
"title": "account2", |
||||
|
"consumer_key": "xxxxxxxxxxxxx", |
||||
|
"consumer_secret": "xxxxxxxxxxxxx", |
||||
|
"access_token_key": "xxxxxxxxxxxxx", |
||||
|
"access_token_secret": "xxxxxxxxxxxxx" |
||||
|
}, |
||||
|
{ |
||||
|
"title": "account3", |
||||
|
"consumer_key": "xxxxxxxxxxxxx", |
||||
|
"consumer_secret": "xxxxxxxxxxxxx", |
||||
|
"access_token_key": "xxxxxxxxxxxxx", |
||||
|
"access_token_secret": "xxxxxxxxxxxxx" |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
``` |
@ -0,0 +1,22 @@ |
|||||
|
[{ |
||||
|
"title": "account1", |
||||
|
"consumer_key": "xxxxxxxxxxxxx", |
||||
|
"consumer_secret": "xxxxxxxxxxxxx", |
||||
|
"access_token_key": "xxxxxxxxxxxxx", |
||||
|
"access_token_secret": "xxxxxxxxxxxxx" |
||||
|
}, |
||||
|
{ |
||||
|
"title": "account2", |
||||
|
"consumer_key": "xxxxxxxxxxxxx", |
||||
|
"consumer_secret": "xxxxxxxxxxxxx", |
||||
|
"access_token_key": "xxxxxxxxxxxxx", |
||||
|
"access_token_secret": "xxxxxxxxxxxxx" |
||||
|
}, |
||||
|
{ |
||||
|
"title": "account3", |
||||
|
"consumer_key": "xxxxxxxxxxxxx", |
||||
|
"consumer_secret": "xxxxxxxxxxxxx", |
||||
|
"access_token_key": "xxxxxxxxxxxxx", |
||||
|
"access_token_secret": "xxxxxxxxxxxxx" |
||||
|
} |
||||
|
] |
@ -0,0 +1,6 @@ |
|||||
|
[ |
||||
|
"word1", |
||||
|
"word2", |
||||
|
"wordToTrack3", |
||||
|
"word4" |
||||
|
] |
@ -0,0 +1,7 @@ |
|||||
|
[ |
||||
|
"Reply message 1", |
||||
|
"Reply message 2", |
||||
|
"Reply message 3", |
||||
|
"Hey wassup friend!", |
||||
|
"You like the botnet?" |
||||
|
] |
@ -0,0 +1,57 @@ |
|||||
|
package main |
||||
|
|
||||
|
import "fmt" |
||||
|
|
||||
|
//Color struct, defines the color
|
||||
|
type Color struct{} |
||||
|
|
||||
|
var c Color |
||||
|
|
||||
|
//DarkGray color
|
||||
|
func (c Color) DarkGray(t string) { |
||||
|
fmt.Print("\x1b[30;1m") //dark gray
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Red color
|
||||
|
func (c Color) Red(t string) { |
||||
|
fmt.Print("\x1b[31;1m") //red
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Green color
|
||||
|
func (c Color) Green(t string) { |
||||
|
fmt.Print("\x1b[32;1m") //green
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Yellow color
|
||||
|
func (c Color) Yellow(t string) { |
||||
|
fmt.Print("\x1b[33;1m") //yellow
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Blue color
|
||||
|
func (c Color) Blue(t string) { |
||||
|
fmt.Print("\x1b[34;1m") //blue
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Purple color
|
||||
|
func (c Color) Purple(t string) { |
||||
|
fmt.Print("\x1b[35;1m") //purple
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
||||
|
|
||||
|
//Cyan color
|
||||
|
func (c Color) Cyan(t string) { |
||||
|
fmt.Print("\x1b[36;1m") //cyan
|
||||
|
fmt.Println(t) |
||||
|
fmt.Print("\x1b[0m") //defaultColor
|
||||
|
} |
@ -0,0 +1,28 @@ |
|||||
|
package main |
||||
|
|
||||
|
import "fmt" |
||||
|
|
||||
|
const version = "0.1-dev" |
||||
|
|
||||
|
func main() { |
||||
|
c.Yellow("echo-botnet") |
||||
|
fmt.Println("---------------") |
||||
|
c.Cyan("echo-botnet initialized") |
||||
|
c.Purple("https://github.com/arnaucode/echo-botnet") |
||||
|
fmt.Println("version " + version) |
||||
|
|
||||
|
keywords := readKeywordsConfig() |
||||
|
c.Cyan("keywords configured: ") |
||||
|
fmt.Println(keywords) |
||||
|
replies := readRepliesConfig() |
||||
|
c.Cyan("replies configured: ") |
||||
|
fmt.Println(replies) |
||||
|
|
||||
|
fmt.Println("Reading botsConfig.json file") |
||||
|
botnet := readConfigTokensAndConnect() |
||||
|
c.Cyan("[list of configured bots]:") |
||||
|
for _, v := range botnet.ScreenNames { |
||||
|
c.Cyan(v) |
||||
|
} |
||||
|
streamTweets(botnet, keywords, replies) |
||||
|
} |
@ -0,0 +1,54 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"io/ioutil" |
||||
|
|
||||
|
"github.com/dghubble/go-twitter/twitter" |
||||
|
"github.com/dghubble/oauth1" |
||||
|
) |
||||
|
|
||||
|
//Config stores the data from json botsConfig.json file
|
||||
|
type Config struct { |
||||
|
Title string `json:"title"` |
||||
|
Consumer_key string `json:"consumer_key"` |
||||
|
Consumer_secret string `json:"consumer_secret"` |
||||
|
Access_token_key string `json:"access_token_key"` |
||||
|
Access_token_secret string `json:"access_token_secret"` |
||||
|
} |
||||
|
|
||||
|
//Botnet stores each bot configured
|
||||
|
type Botnet struct { |
||||
|
ScreenNames []string |
||||
|
Clients []*twitter.Client |
||||
|
} |
||||
|
|
||||
|
func readConfigTokensAndConnect() (botnet Botnet) { |
||||
|
var config []Config |
||||
|
var clients []*twitter.Client |
||||
|
|
||||
|
file, e := ioutil.ReadFile("botsConfig.json") |
||||
|
if e != nil { |
||||
|
fmt.Println("error:", e) |
||||
|
} |
||||
|
content := string(file) |
||||
|
json.Unmarshal([]byte(content), &config) |
||||
|
fmt.Println("botnetConfig.json read comlete") |
||||
|
|
||||
|
fmt.Print("connecting to twitter api --> ") |
||||
|
for i := 0; i < len(config); i++ { |
||||
|
configu := oauth1.NewConfig(config[i].Consumer_key, config[i].Consumer_secret) |
||||
|
token := oauth1.NewToken(config[i].Access_token_key, config[i].Access_token_secret) |
||||
|
httpClient := configu.Client(oauth1.NoContext, token) |
||||
|
// twitter client
|
||||
|
client := twitter.NewClient(httpClient) |
||||
|
clients = append(clients, client) |
||||
|
botnet.ScreenNames = append(botnet.ScreenNames, config[i].Title) |
||||
|
} |
||||
|
botnet.Clients = clients |
||||
|
|
||||
|
fmt.Println("connection successfull") |
||||
|
|
||||
|
return botnet |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"io/ioutil" |
||||
|
) |
||||
|
|
||||
|
func readKeywordsConfig() []string { |
||||
|
var keywords []string |
||||
|
file, e := ioutil.ReadFile("keywordsConfig.json") |
||||
|
if e != nil { |
||||
|
fmt.Println("error:", e) |
||||
|
} |
||||
|
content := string(file) |
||||
|
json.Unmarshal([]byte(content), &keywords) |
||||
|
|
||||
|
return keywords |
||||
|
} |
@ -0,0 +1,19 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"encoding/json" |
||||
|
"fmt" |
||||
|
"io/ioutil" |
||||
|
) |
||||
|
|
||||
|
func readRepliesConfig() []string { |
||||
|
var replies []string |
||||
|
file, e := ioutil.ReadFile("repliesConfig.json") |
||||
|
if e != nil { |
||||
|
fmt.Println("error:", e) |
||||
|
} |
||||
|
content := string(file) |
||||
|
json.Unmarshal([]byte(content), &replies) |
||||
|
|
||||
|
return replies |
||||
|
} |
@ -0,0 +1,108 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"log" |
||||
|
"math/rand" |
||||
|
"strconv" |
||||
|
"strings" |
||||
|
"time" |
||||
|
|
||||
|
"github.com/dghubble/go-twitter/twitter" |
||||
|
) |
||||
|
|
||||
|
func isRT(tweet *twitter.Tweet) bool { |
||||
|
tweetWords := strings.Split(tweet.Text, " ") |
||||
|
for i := 0; i < len(tweetWords); i++ { |
||||
|
if tweetWords[i] == "RT" { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
func isFromOwnBot(flock Botnet, tweet *twitter.Tweet) bool { |
||||
|
for i := 0; i < len(flock.ScreenNames); i++ { |
||||
|
if flock.ScreenNames[i] == tweet.User.ScreenName { |
||||
|
return true |
||||
|
} |
||||
|
} |
||||
|
return false |
||||
|
} |
||||
|
|
||||
|
func getRandomReplyFromReplies(replies []string) string { |
||||
|
rand.Seed(time.Now().UnixNano()) |
||||
|
random := rand.Intn(len(replies)) |
||||
|
return replies[random] |
||||
|
} |
||||
|
|
||||
|
func replyTweet(client *twitter.Client, text string, inReplyToStatusID int64) { |
||||
|
tweet, httpResp, err := client.Statuses.Update(text, &twitter.StatusUpdateParams{ |
||||
|
InReplyToStatusID: inReplyToStatusID, |
||||
|
}) |
||||
|
if err != nil { |
||||
|
fmt.Println(err) |
||||
|
} |
||||
|
if httpResp.Status != "200 OK" { |
||||
|
c.Red("error: " + httpResp.Status) |
||||
|
c.Purple("maybe twitter has blocked the account, CTRL+C, wait 15 minutes and try again") |
||||
|
} |
||||
|
fmt.Print("tweet posted: ") |
||||
|
c.Green(tweet.Text) |
||||
|
} |
||||
|
|
||||
|
func startStreaming(botnet Botnet, bot *twitter.Client, botScreenName string, keywords []string, replies []string) { |
||||
|
// Convenience Demux demultiplexed stream messages
|
||||
|
demux := twitter.NewSwitchDemux() |
||||
|
demux.Tweet = func(tweet *twitter.Tweet) { |
||||
|
if isRT(tweet) == false && isFromOwnBot(botnet, tweet) == false { |
||||
|
//processTweet(botnetUser, botScreenName, keywords, tweet)
|
||||
|
fmt.Println("[bot @" + botScreenName + "] - New tweet detected:") |
||||
|
c.Yellow(tweet.Text) |
||||
|
reply := getRandomReplyFromReplies(replies) |
||||
|
fmt.Print("reply: ") |
||||
|
c.Green(reply) |
||||
|
fmt.Println(tweet.User.ScreenName) |
||||
|
fmt.Println(tweet.ID) |
||||
|
replyTweet(bot, "@"+tweet.User.ScreenName+" "+reply, tweet.ID) |
||||
|
waitMinutes(1) |
||||
|
} |
||||
|
} |
||||
|
demux.DM = func(dm *twitter.DirectMessage) { |
||||
|
fmt.Println(dm.SenderID) |
||||
|
} |
||||
|
demux.Event = func(event *twitter.Event) { |
||||
|
fmt.Printf("%#v\n", event) |
||||
|
} |
||||
|
|
||||
|
fmt.Println("Starting Stream...") |
||||
|
// FILTER
|
||||
|
filterParams := &twitter.StreamFilterParams{ |
||||
|
Track: keywords, |
||||
|
StallWarnings: twitter.Bool(true), |
||||
|
} |
||||
|
stream, err := bot.Streams.Filter(filterParams) |
||||
|
if err != nil { |
||||
|
log.Fatal(err) |
||||
|
} |
||||
|
// Receive messages until stopped or stream quits
|
||||
|
demux.HandleChan(stream.Messages) |
||||
|
|
||||
|
// Wait for SIGINT and SIGTERM (HIT CTRL-C)
|
||||
|
/*ch := make(chan os.Signal) |
||||
|
signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM) |
||||
|
log.Println(<-ch) |
||||
|
|
||||
|
fmt.Println("Stopping Stream...") |
||||
|
stream.Stop()*/ |
||||
|
} |
||||
|
func streamTweets(botnet Botnet, keywords []string, replies []string) { |
||||
|
fmt.Println("total keywords: " + strconv.Itoa(len(keywords))) |
||||
|
c.Purple("keywords to follow: ") |
||||
|
fmt.Println(keywords) |
||||
|
c.Green("Starting to stream tweets") |
||||
|
for i := 0; i < len(botnet.Clients); i++ { |
||||
|
go startStreaming(botnet, botnet.Clients[i], botnet.ScreenNames[i], keywords, replies) |
||||
|
//wait 35 seconds to start the next bot
|
||||
|
waitSeconds(35) |
||||
|
} |
||||
|
} |
@ -0,0 +1,22 @@ |
|||||
|
package main |
||||
|
|
||||
|
import ( |
||||
|
"fmt" |
||||
|
"strconv" |
||||
|
"time" |
||||
|
) |
||||
|
|
||||
|
func waitMinutes(minutes int) { |
||||
|
//wait to avoid the twitter api limitation
|
||||
|
timeToSleep := time.Duration(minutes) * time.Minute |
||||
|
fmt.Println("waiting " + strconv.Itoa(minutes) + " min to avoid twitter api limitation") |
||||
|
fmt.Println(time.Now().Local()) |
||||
|
time.Sleep(timeToSleep) |
||||
|
} |
||||
|
func waitSeconds(seconds int) { |
||||
|
//wait to avoid the twitter api limitation
|
||||
|
timeToSleep := time.Duration(seconds) * time.Second |
||||
|
fmt.Println("waiting " + strconv.Itoa(seconds) + " seconds to avoid twitter api limitation") |
||||
|
fmt.Println(time.Now().Local()) |
||||
|
time.Sleep(timeToSleep) |
||||
|
} |