Advertisement
Guest User

Untitled

a guest
Sep 22nd, 2024
462
0
13 days
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.48 KB | None | 0 0
  1. main.go
  2.  
  3. ```go
  4. package main
  5.  
  6. import (
  7. "flag"
  8. "fmt"
  9. "os"
  10. "strings"
  11.  
  12. "github.com/DataDog/datadog-go/statsd"
  13. "github.com/nbd-wtf/go-nostr/nip19"
  14. log "github.com/sirupsen/logrus"
  15. )
  16.  
  17. const patience = 4
  18.  
  19. type Bot struct {
  20. PublicKey string
  21. Npub string
  22. ImpersonationPublicKey string
  23. ImpersonationNpub string
  24. SecretKey string
  25. WaitSeconds int
  26. Nip05Domain string
  27. }
  28.  
  29. func main() {
  30. npubFile := flag.String("npub-file", "", "File with line-delimited npubs to impersonate, these will also receive spam")
  31. relayFile := flag.String("relay-file", "", "File with line-delimited relay URLs")
  32. logFormat := flag.String("log-format", "json", "Log format ('json' or 'text')")
  33. logLevel := flag.String("log-level", "info", "Log level ('debug', 'info', 'warn', 'error')")
  34. nip05Domain := flag.String("nip-05-domain", "", "Domain used for NIP-05 identification (e.g., spamstr.lol)")
  35. flag.Usage = func() {
  36. fmt.Fprintf(os.Stderr, "Usage: %s -npub-file <file> -relay-file <file> [options]\n\n", os.Args[0])
  37. fmt.Fprintf(os.Stderr, "Required:\n")
  38. fmt.Fprintf(os.Stderr, " -npub-file <file> File with line-delimited npubs to impersonate and spam\n")
  39. fmt.Fprintf(os.Stderr, " -relay-file <file> File with line-delimited relay URLs\n\n")
  40. fmt.Fprintf(os.Stderr, "Options:\n")
  41. fmt.Fprintf(os.Stderr, " -log-format <format> Log format ('json' or 'text') (default: json)\n")
  42. fmt.Fprintf(os.Stderr, " -log-level <level> Log level ('debug', 'info', 'warn', 'error') (default: info)\n")
  43. fmt.Fprintf(os.Stderr, " -nip-05-domain <domain> Domain used for NIP-05 identification\n\n")
  44. fmt.Fprintf(os.Stderr, "Example:\n")
  45. fmt.Fprintf(os.Stderr, " %s -npub-file ~/.config/nostr-spam-bot-target-npubs.txt -relay-file ~/.config/nostr-spam-bot-relays.txt -log-format json -log-level info\n", os.Args[0])
  46. }
  47. flag.Parse()
  48.  
  49. switch strings.ToLower(*logLevel) {
  50. case "debug":
  51. log.SetLevel(log.DebugLevel)
  52. case "info":
  53. log.SetLevel(log.InfoLevel)
  54. case "warn":
  55. log.SetLevel(log.WarnLevel)
  56. case "error":
  57. log.SetLevel(log.ErrorLevel)
  58. default:
  59. log.SetLevel(log.InfoLevel)
  60. }
  61.  
  62. if len(*npubFile) == 0 || len(*relayFile) == 0 {
  63. flag.Usage()
  64. os.Exit(1)
  65. }
  66.  
  67. if *logFormat == "json" {
  68. log.SetFormatter(&log.JSONFormatter{
  69. TimestampFormat: "2006-01-02 15:04:05",
  70. })
  71. } else if *logFormat == "text" {
  72. log.SetFormatter(&log.TextFormatter{
  73. TimestampFormat: "2006-01-02 15:04:05",
  74. FullTimestamp: true,
  75. })
  76. } else {
  77. fmt.Printf("Invalid log format (expected 'json' or 'text'): %s", *logFormat)
  78. os.Exit(1)
  79. }
  80.  
  81. *npubFile = expandTilde(*npubFile)
  82. *relayFile = expandTilde(*relayFile)
  83.  
  84. targetPubkeys, err := readAndDecodeNpubsFromFile(*npubFile)
  85. if err != nil {
  86. log.Fatal(err)
  87. }
  88.  
  89. relays, err := readRelaysFromFile(*relayFile)
  90. if err != nil {
  91. log.Fatal(err)
  92. }
  93.  
  94. datadog, err := statsd.New("localhost:8125")
  95. if err != nil {
  96. log.Errorf("Error initializing Datadog client: %s", err)
  97. }
  98. defer datadog.Close()
  99.  
  100. log.Printf("Spamming %d pubkey(s)", len(targetPubkeys))
  101.  
  102. // Initialize bots asynchronously
  103. botChannel := make(chan Bot, len(targetPubkeys))
  104. for i, pubkey := range targetPubkeys {
  105. go func(i int, pubkey string) {
  106. sk, pk := generateKeys()
  107. npub, _ := nip19.EncodePublicKey(pk)
  108. impersonationNpub, _ := nip19.EncodePublicKey(pubkey)
  109. bot := Bot{Npub: npub, PublicKey: pk, SecretKey: sk, ImpersonationNpub: impersonationNpub, ImpersonationPublicKey: pubkey, WaitSeconds: patience * len(targetPubkeys), Nip05Domain: *nip05Domain}
  110. log.Printf("Generated bot. npub: %s, pubkey: %s", bot.Npub, bot.PublicKey)
  111. impersonateProfile(bot, relays, datadog)
  112. botChannel <- bot
  113. }(i, pubkey)
  114. }
  115.  
  116. // Collect generated bots and run them
  117. for i := 0; i < len(targetPubkeys); i++ {
  118. bot := <-botChannel
  119. go runBot(&bot, targetPubkeys, relays, datadog)
  120. }
  121.  
  122. select {}
  123. }
  124. ```
  125.  
  126. nostr.go
  127.  
  128. ```go
  129. package main
  130.  
  131. import (
  132. "bytes"
  133. "context"
  134. "encoding/json"
  135. "fmt"
  136. "net/http"
  137. "regexp"
  138. "strings"
  139. "time"
  140.  
  141. "github.com/DataDog/datadog-go/statsd"
  142. "github.com/nbd-wtf/go-nostr"
  143. log "github.com/sirupsen/logrus"
  144. "golang.org/x/exp/rand"
  145. )
  146.  
  147. type ProfileMetadata struct {
  148. Name string `json:"name"`
  149. About string `json:"about"`
  150. Picture string `json:"picture"`
  151. Nip05 string `json:"nip05,omitempty"`
  152. }
  153.  
  154. func runBot(bot *Bot, targetPubkeys []string, relays []string, datadog *statsd.Client) {
  155. spamEventChannel := make(chan *nostr.Event, 500)
  156. targetEventChannel := make(chan *nostr.Event, 500)
  157. for _, relay := range relays {
  158. go listenForSpamEvents(bot, relay, spamEventChannel, datadog)
  159. go listenForTargetEvents(bot, relay, targetPubkeys, targetEventChannel, datadog)
  160. }
  161. // This runs forever but sleeps after every published note
  162. replayNotes(bot, relays, spamEventChannel, targetEventChannel, datadog)
  163. }
  164.  
  165. func listenForSpamEvents(bot *Bot, relay string, spamEventChannel chan<- *nostr.Event, datadog *statsd.Client) {
  166. botLogger := log.WithFields(log.Fields{
  167. "bot_npub": bot.Npub,
  168. "impersonation_npub": bot.ImpersonationNpub,
  169. "relay": relay,
  170. })
  171. tags := []string{
  172. fmt.Sprintf("relay:%s", relay),
  173. fmt.Sprintf("bot_npub:%s", bot.Npub),
  174. }
  175.  
  176. botLogger.Debug("Listening for spam notes")
  177. ctx := context.Background()
  178. conn, err := nostr.RelayConnect(ctx, relay)
  179. if err != nil {
  180. botLogger.Error(err)
  181. datadog.Incr("nostr_spam_bot.relay.connect.failure", tags, 1)
  182. return
  183. }
  184. datadog.Incr("nostr_spam_bot.relay.connect.success", tags, 1)
  185. defer conn.Close()
  186.  
  187. filters := []nostr.Filter{{
  188. Kinds: []int{nostr.KindTextNote},
  189. }}
  190.  
  191. sub, err := conn.Subscribe(ctx, filters)
  192. if err != nil {
  193. datadog.Incr("nostr_spam_bot.subscription.failure", tags, 1)
  194. botLogger.Error(err)
  195. return
  196. }
  197. datadog.Incr("nostr_spam_bot.subscription.success", tags, 1)
  198.  
  199. for event := range sub.Events {
  200. select {
  201. case spamEventChannel <- event:
  202. default:
  203. datadog.Incr("nostr_spam_bot.event.skipped", tags, 1)
  204. }
  205. }
  206. }
  207.  
  208. func listenForTargetEvents(bot *Bot, relay string, targetPubkeys []string, targetEventChannel chan<- *nostr.Event, datadog *statsd.Client) {
  209. botLogger := log.WithFields(log.Fields{
  210. "bot_npub": bot.Npub,
  211. "impersonation_npub": bot.ImpersonationNpub,
  212. "relay": relay,
  213. })
  214.  
  215. botLogger.Debug("Listening for target notes")
  216. ctx := context.Background()
  217. conn, err := nostr.RelayConnect(ctx, relay)
  218. if err != nil {
  219. botLogger.Error(err)
  220. return
  221. }
  222. defer conn.Close()
  223.  
  224. filters := []nostr.Filter{{
  225. Kinds: []int{nostr.KindTextNote},
  226. Authors: targetPubkeys,
  227. }}
  228.  
  229. sub, err := conn.Subscribe(ctx, filters)
  230. if err != nil {
  231. botLogger.Error(err)
  232. return
  233. }
  234.  
  235. for event := range sub.Events {
  236. select {
  237. case targetEventChannel <- event:
  238. default:
  239. tags := []string{
  240. fmt.Sprintf("relay:%s", relay),
  241. fmt.Sprintf("bot_npub:%s", bot.Npub),
  242. }
  243. datadog.Incr("nostr_spam_bot.event.skipped", tags, 1)
  244. }
  245. }
  246. }
  247.  
  248. func replayNotes(bot *Bot, relays []string, spamEventChannel <-chan *nostr.Event, targetEventChannel <-chan *nostr.Event, datadog *statsd.Client) {
  249. for {
  250. select {
  251. case spamEvent := <-spamEventChannel:
  252. select {
  253. case targetEvent := <-targetEventChannel:
  254. for _, relay := range relays {
  255. go func(bot *Bot, relay string, spamEvent *nostr.Event, targetEvent *nostr.Event) {
  256. botLogger := log.WithFields(log.Fields{
  257. "bot_npub": bot.Npub,
  258. "impersonation_npub": bot.ImpersonationNpub,
  259. "relay": relay,
  260. })
  261.  
  262. newEvent := nostr.Event{
  263. PubKey: bot.PublicKey,
  264. CreatedAt: nostr.Timestamp(time.Now().Unix()),
  265. Kind: nostr.KindTextNote,
  266. Tags: nostr.Tags{
  267. nostr.Tag{"e", targetEvent.ID},
  268. nostr.Tag{"p", targetEvent.PubKey},
  269. },
  270. Content: addRandomEmojis(spamEvent.Content),
  271. }
  272. newEvent.Sign(bot.SecretKey)
  273.  
  274. ctx := context.Background()
  275. conn, err := nostr.RelayConnect(context.Background(), relay)
  276. if err != nil {
  277. log.Error(err)
  278. return
  279. }
  280. defer conn.Close()
  281.  
  282. err = conn.Publish(ctx, newEvent)
  283. if err != nil {
  284. botLogger.Error(err)
  285. tags := []string{
  286. fmt.Sprintf("relay:%s", relay),
  287. fmt.Sprintf("bot_npub:%s", bot.Npub),
  288. }
  289. datadog.Incr("nostr_spam_bot.publish.failure", tags, 1)
  290. return
  291. }
  292. tags := []string{
  293. fmt.Sprintf("relay:%s", relay),
  294. fmt.Sprintf("bot_npub:%s", bot.Npub),
  295. }
  296. datadog.Incr("nostr_spam_bot.publish.success", tags, 1)
  297.  
  298. botLogger.WithFields(log.Fields{
  299. "target_note": targetEvent,
  300. "spam_note": newEvent,
  301. }).Debugf("Spammed note %s", newEvent.ID)
  302. }(bot, relay, spamEvent, targetEvent)
  303. }
  304. }
  305. }
  306.  
  307. log.Debugf("Sleeping for %d seconds", bot.WaitSeconds)
  308. time.Sleep(time.Duration(bot.WaitSeconds) * time.Second)
  309. }
  310. }
  311.  
  312. func impersonateProfile(bot Bot, relays []string, datadog *statsd.Client) {
  313. for _, relay := range relays {
  314. botLogger := log.WithFields(log.Fields{
  315. "bot_npub": bot.Npub,
  316. "impersonation_npub": bot.ImpersonationNpub,
  317. "relay": relay,
  318. })
  319.  
  320. botLogger.Printf("Connecting to relay")
  321. conn, err := nostr.RelayConnect(context.Background(), relay)
  322. if err != nil {
  323. log.Error(err)
  324. continue
  325. }
  326. defer conn.Close()
  327.  
  328. filters := []nostr.Filter{{
  329. Kinds: []int{nostr.KindProfileMetadata},
  330. Authors: []string{bot.ImpersonationPublicKey},
  331. Limit: 20,
  332. }}
  333.  
  334. ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  335. defer cancel()
  336.  
  337. botLogger.Printf("Subscribing using filters %+v", filters)
  338. sub, err := conn.Subscribe(ctx, filters)
  339. if err != nil {
  340. log.Error(err)
  341. continue
  342. }
  343.  
  344. for event := range sub.Events {
  345. if event.Kind != nostr.KindProfileMetadata {
  346. botLogger.Warnf("Event is not profile metadata despite filter: %+v", event)
  347. continue
  348. }
  349.  
  350. // Modifying metadata, this removes easy to detect false NIP-05
  351. botLogger.Debug("Modifying metadata content")
  352. var profileMetadata ProfileMetadata
  353. var newContent string
  354. err := json.Unmarshal([]byte(event.Content), &profileMetadata)
  355. if err != nil {
  356. botLogger.Errorf("Failed to unmarshal profile metadata: %v", err)
  357. newContent = event.Content
  358. } else {
  359. profileMetadata.Nip05 = ""
  360. marshalledProfile, err := json.Marshal(profileMetadata)
  361. if err != nil {
  362. botLogger.Errorf("Failed to marshal profile metadata: %v", err)
  363. newContent = event.Content
  364. } else {
  365. newContent = string(marshalledProfile)
  366. botLogger.Debugf("Successfully altered metadata: %s", newContent)
  367. }
  368. }
  369.  
  370. botLogger.Debug("Appending NIP-05 identifier")
  371. if bot.Nip05Domain != "" {
  372. contentWithNip05, err := setNip05FromMetadata(bot, profileMetadata)
  373. if err != nil {
  374. botLogger.Errorf("failed to set NIP-05 identifier: %s", err)
  375. } else {
  376. botLogger.Debugf("updated metadata with NIP-05: %s", contentWithNip05)
  377. newContent = contentWithNip05
  378. }
  379. } else {
  380. botLogger.Info("No NIP-05 domain specified")
  381. }
  382.  
  383. newEvent := nostr.Event{
  384. PubKey: bot.PublicKey,
  385. CreatedAt: nostr.Timestamp(time.Now().Unix()),
  386. Kind: event.Kind,
  387. Tags: event.Tags,
  388. Content: newContent,
  389. }
  390.  
  391. err = newEvent.Sign(bot.SecretKey)
  392. if err != nil {
  393. botLogger.Error(err)
  394. continue
  395. }
  396.  
  397. botLogger.Debugf("Publishing metadata event %s", newEvent.ID)
  398. err = conn.Publish(ctx, newEvent)
  399. tags := []string{
  400. fmt.Sprintf("relay:%s", relay),
  401. fmt.Sprintf("bot_npub:%s", bot.Npub),
  402. }
  403. if err != nil {
  404. botLogger.Error(err)
  405. datadog.Incr("nostr_spam_bot.publish.failure", tags, 1)
  406. continue
  407. }
  408. datadog.Incr("nostr_spam_bot.publish.success", tags, 1)
  409. break
  410. }
  411. }
  412. }
  413.  
  414. func generateKeys() (string, string) {
  415. sk := nostr.GeneratePrivateKey()
  416. pk, _ := nostr.GetPublicKey(sk)
  417. return sk, pk
  418. }
  419.  
  420. func setNip05FromMetadata(bot Bot, profileMetadata ProfileMetadata) (string, error) {
  421. // Remove special characters and convert to lowercase
  422. reg, err := regexp.Compile("[^a-zA-Z0-9]+")
  423. if err != nil {
  424. return "", fmt.Errorf("failed to compile regex: %v", err)
  425. }
  426. cleanName := reg.ReplaceAllString(profileMetadata.Name, "")
  427. cleanName = strings.ToLower(cleanName)
  428.  
  429. // Add random 3 digit suffix
  430. suffix := fmt.Sprintf("%03d", rand.Intn(1000))
  431. modifiedName := cleanName + suffix
  432.  
  433. // Prepare the request body
  434. requestBody := struct {
  435. Name string `json:"name"`
  436. Pubkey string `json:"pubkey"`
  437. }{
  438. Name: modifiedName,
  439. Pubkey: bot.PublicKey,
  440. }
  441.  
  442. jsonBody, err := json.Marshal(requestBody)
  443. if err != nil {
  444. return "", fmt.Errorf("failed to marshal request body: %v", err)
  445. }
  446.  
  447. // Make the PUT request
  448. url := fmt.Sprintf("https://%s/.well-known/nostr.json", bot.Nip05Domain)
  449. req, err := http.NewRequest(http.MethodPut, url, bytes.NewBuffer(jsonBody))
  450. if err != nil {
  451. return "", fmt.Errorf("failed to create request: %v", err)
  452. }
  453.  
  454. req.Header.Set("Content-Type", "application/json")
  455.  
  456. client := &http.Client{}
  457. resp, err := client.Do(req)
  458. if err != nil {
  459. return "", fmt.Errorf("failed to send request: %v", err)
  460. }
  461. defer resp.Body.Close()
  462.  
  463. if resp.StatusCode != http.StatusCreated {
  464. return "", fmt.Errorf("expected 201 CREATED but got: %d", resp.StatusCode)
  465. }
  466.  
  467. // Update the profile metadata with the new NIP-05
  468. profileMetadata.Nip05 = fmt.Sprintf("%s@%s", modifiedName, bot.Nip05Domain)
  469.  
  470. // Marshal the updated profile metadata
  471. updatedContent, err := json.Marshal(profileMetadata)
  472. if err != nil {
  473. return "", fmt.Errorf("failed to marshal updated profile metadata: %v", err)
  474. }
  475.  
  476. return string(updatedContent), nil
  477. }
  478. ```
  479.  
  480. generic.go
  481.  
  482. ```go
  483. package main
  484.  
  485. import (
  486. "bufio"
  487. "os"
  488. "path/filepath"
  489. "strings"
  490.  
  491. "github.com/nbd-wtf/go-nostr/nip19"
  492. log "github.com/sirupsen/logrus"
  493. "golang.org/x/exp/rand"
  494. )
  495.  
  496. func expandTilde(path string) string {
  497. if strings.HasPrefix(path, "~") {
  498. home, err := os.UserHomeDir()
  499. if err != nil {
  500. return path
  501. }
  502. return filepath.Join(home, path[1:])
  503. }
  504. return path
  505. }
  506.  
  507. func readAndDecodeNpubsFromFile(filename string) ([]string, error) {
  508. file, err := os.Open(filename)
  509. if err != nil {
  510. return nil, err
  511. }
  512. defer file.Close()
  513.  
  514. var lines []string
  515. scanner := bufio.NewScanner(file)
  516. for scanner.Scan() {
  517. lines = append(lines, scanner.Text())
  518. }
  519.  
  520. if err := scanner.Err(); err != nil {
  521. return nil, err
  522. }
  523.  
  524. var npubs []string
  525. for _, npub := range lines {
  526. _, pubkey, err := nip19.Decode(npub)
  527. if err != nil {
  528. log.Error(err)
  529. continue
  530. }
  531. npubs = append(npubs, pubkey.(string))
  532. }
  533.  
  534. return npubs, nil
  535. }
  536.  
  537. func readRelaysFromFile(filename string) ([]string, error) {
  538. file, err := os.Open(filename)
  539. if err != nil {
  540. return nil, err
  541. }
  542. defer file.Close()
  543.  
  544. var relays []string
  545. scanner := bufio.NewScanner(file)
  546. for scanner.Scan() {
  547. relay := strings.TrimSpace(scanner.Text())
  548. if relay != "" {
  549. relays = append(relays, relay)
  550. }
  551. }
  552.  
  553. if err := scanner.Err(); err != nil {
  554. return nil, err
  555. }
  556.  
  557. return relays, nil
  558. }
  559.  
  560. func addRandomEmojis(content string) string {
  561. emojis := []string{"😀", "😂", "🤔", "👍", "🎉", "🌈", "🔥", "💯"}
  562. words := strings.Fields(content)
  563. for i := range words {
  564. if rand.Float32() < 0.2 {
  565. words[i] += " " + emojis[rand.Intn(len(emojis))]
  566. }
  567. }
  568. return strings.Join(words, " ")
  569. }
  570. ```
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement