@ -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 |
|||
|
|||
|
|||
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) |
|||
} |