Skip to content

Commit

Permalink
Squashed commit of the following:
Browse files Browse the repository at this point in the history
commit d654c56
Author: smeb y <[email protected]>
Date:   Mon Oct 21 15:55:05 2024 +0800

    fix(Dockerfile): add ca-certificate (#604)

    Update Dockerfile

    Signed-off-by: smeb y <[email protected]>

commit c1e624a
Author: Ettore Di Giacinto <[email protected]>
Date:   Thu Oct 17 15:23:03 2024 +0200

    chore(docs): drop unnecessary duplicated content

    Signed-off-by: Ettore Di Giacinto <[email protected]>

commit 9529e4c
Author: Ettore Di Giacinto <[email protected]>
Date:   Thu Oct 17 15:21:07 2024 +0200

    chore(docs): update .env.example (#603)

    Signed-off-by: Ettore Di Giacinto <[email protected]>

commit 038fad6
Author: Ettore Di Giacinto <[email protected]>
Date:   Thu Oct 17 14:55:25 2024 +0200

    fix(contracts): load config from embedded (#602)

    * fix(contracts): load config from embedded

    There was still some code reading from the filesystem instead of the
    embedded files in the binary.
    Regression introduced in #523.

    Fixes: #578

    See also: #579

    Signed-off-by: mudler <[email protected]>

    * chore(tests): temporarly disable Twitter tests

    These are going to be taken care of as part of
    #573

    Signed-off-by: mudler <[email protected]>

    ---------

    Signed-off-by: mudler <[email protected]>

commit a8a77a6
Author: Brendan Playford <[email protected]>
Date:   Tue Oct 15 14:32:29 2024 -0700

    feat(twitter): Implement random sleep and improve login process (#601)

    - Add RandomSleep function to introduce variability in request timing
    - Update NewScraper to use RandomSleep before and after login attempts
    - Adjust sleep duration range to 500ms - 2s for more natural behavior
    - Improve error handling and logging in the login process

commit 01ec8c4
Author: Brendan Playford <[email protected]>
Date:   Tue Oct 15 08:59:08 2024 -0700

    chore(version): update protocol version and update twitter_cookies.example.json

commit 6f594e1
Author: Brendan Playford <[email protected]>
Date:   Tue Oct 15 08:48:01 2024 -0700

    feat(twitter): Enhanced Twitter Worker Selection Algorithm (#591)

    * Add detailed error logging and track worker update time

    Enhanced the worker manager to append specific error messages to a list for better debugging. Additionally, updated node data to track the last update time, improving data consistency and traceability.

    * Update version.go

    * refactor(twitter): remove retry functionality from scraper

    - Remove Retry function and MaxRetries constant from config.go
    - Update ScrapeFollowersForProfile, ScrapeTweetsProfile, and ScrapeTweetsByQuery
      to remove Retry wrapper
    - Adjust error handling in each function to directly return errors
    - Simplify code structure and reduce complexity
    - Maintain rate limit handling functionality

    * chore(workers): update max workers to 50

    * chore(workers): upate to 25

    * feat(pubsub): improve node sorting algorithm for Twitter reliability

    - Prioritize nodes with more recent last returned tweets
    - Maintain high importance for total returned tweet count
    - Consider time since last timeout to allow recovery from temporary issues
    - Deprioritize nodes with recent "not found" occurrences
    - Remove NotFoundCount from sorting criteria

    This change aims to better balance node performance and recent activity,
    while allowing nodes to recover quickly from temporary issues like rate limiting.

    * feat(workers): improve Twitter worker selection algorithm

    - Modify GetEligibleWorkers to use a specialized selection for Twitter workers
    - Introduce controlled randomness in Twitter worker selection
    - Balance between prioritizing high-performing Twitter workers and fair distribution
    - Maintain existing behavior for non-Twitter worker selection
    - Preserve handling of local worker and respect original worker limit

    This change enhances the worker selection algorithm for Twitter tasks to provide
    a better balance between utilizing top-performing nodes and ensuring fair work
    distribution. It introduces a dynamic pool size calculation and controlled
    randomness for Twitter workers, while maintaining the existing round-robin
    approach for other worker types.

    ---------

    Co-authored-by: Bob Stevens <[email protected]>

commit f09fb20
Author: Brendan Playford <[email protected]>
Date:   Tue Oct 8 15:38:45 2024 -0700

    Feat(workers) implement adaptive worker selection for improved task distribution (#589)

    * feat(worker-selection): Implement performance-based worker sorting

    - Add performance metrics fields to NodeData struct
    - Implement NodeSorter for flexible sorting of worker nodes
    - Create SortNodesByTwitterReliability function for Twitter workers
    - Update GetEligibleWorkerNodes to use category-specific sorting
    - Modify GetEligibleWorkers to use sorted workers and add worker limit

    This commit enhances the worker selection process by prioritizing workers
    based on their performance metrics. It introduces a flexible sorting
    mechanism that can be easily extended to other worker categories in the
    future. The changes improve reliability and efficiency in task allocation
    across the Masa Oracle network.

    * feat(worker-selection): Implement priority-based selection for Twitter work

    - Update DistributeWork to use priority selection for Twitter category
    - Maintain round-robin selection for other work categories by shuffling workers
    - Integrate new GetEligibleWorkers function with work type-specific behavior
    - Respect MaxRemoteWorkers limit for all work types
    - Add distinct logging for Twitter and non-Twitter worker selection

    This commit enhances the work distribution process by implementing
    priority-based worker selection for Twitter-related tasks while
    preserving the existing round-robin behavior for other work types.
    It leverages the newly added performance metrics to choose the most
    reliable workers for Twitter tasks, and ensures consistent behavior
    for other categories by shuffling the worker list. This hybrid approach
    improves efficiency for Twitter tasks while maintaining the expected
    behavior for all other work types.

    * Update .gitignore

    * feat(worker-selection): Implement priority-based sorting for Twitter workers

    - Add LastNotFoundTime and NotFoundCount fields to NodeData struct
    - Enhance SortNodesByTwitterReliability function with multi-criteria sorting:
      1. Prioritize nodes found more often (lower NotFoundCount)
      2. Consider recency of last not-found occurrence
      3. Sort by higher number of returned tweets
      4. Consider recency of last returned tweet
      5. Prioritize nodes with fewer timeouts
      6. Consider recency of last timeout
      7. Use PeerId for stable sorting when no performance data is available
    - Remove random shuffling from GetEligibleWorkers function

    This commit improves worker selection for Twitter tasks by implementing
    a more sophisticated sorting algorithm that takes into account node
    reliability and performance metrics. It aims to enhance the efficiency
    and reliability of task distribution in the Masa Oracle network.

    * feat(worker-selection): Update Twitter fields in NodeData and Worker Manager

    Add functions to update Twitter-related metrics in NodeData and integrate updates into Worker Manager processes. This ensures accurate tracking of tweet-related events and peer activity in the system.

    * feat(worker-selection): Add unit tests for NodeData and NodeDataTracker

    Introduce unit tests for the NodeData and NodeDataTracker functionalities, covering scenarios involving updates to Twitter-related fields. These tests ensure the correctness of the UpdateTwitterFields method in NodeData and the UpdateNodeDataTwitter method in NodeDataTracker.

    * chore(workers): update timeouts and bump version

    ---------

    Co-authored-by: Bob Stevens <[email protected]>

commit 0ef0df4
Author: Brendan Playford <[email protected]>
Date:   Tue Oct 8 14:37:01 2024 -0700

    feat(api): Add configurable API server enablement (#586)

    * feat(api): Add configurable API server enablement

    This commit introduces a new feature that allows the API server to be
    conditionally enabled or disabled based on configuration. The changes
    include:

    1. In cmd/masa-node/main.go:
       - Refactored signal handling into a separate function `handleSignals`
       - Added conditional logic to start the API server only if enabled
       - Improved logging to indicate API server status

    2. In pkg/config/app.go:
       - Added `APIEnabled` field to the `AppConfig` struct
       - Set default value for `APIEnabled` to false in `setDefaultConfig`
       - Added command-line flag for `apiEnabled` in `setCommandLineConfig`

    3. In pkg/config/constants.go:
       - Added `APIEnabled` constant for environment variable configuration

    These changes provide more flexibility in node configuration, allowing
    users to run the node with or without the API server. This can be useful
    for security purposes or in scenarios where the API is not needed.

    The API can now be enabled via:
    - Environment variable: API_ENABLED=true
    - Command-line flag: --apiEnabled
    - Configuration file: apiEnabled: true

    By default, the API server will be disabled for enhanced security.

    * chore(config): update to take api-enabled=true and update Makefile with run-api case

    * Update Makefile
  • Loading branch information
restevens402 committed Oct 22, 2024
1 parent 8692adb commit 2801335
Show file tree
Hide file tree
Showing 27 changed files with 528 additions and 192 deletions.
4 changes: 2 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ RPC_URL=https://ethereum-sepolia.publicnode.com
# Twitter Configuration
# Note: A pro-paid Twitter account is required to run a Twitter worker
TWITTER_SCRAPER=true
TWITTER_USERNAME=your pro-paid twitter username (without the '@' symbol)
TWITTER_ACCOUNTS=your pro-paid twitter username (without the '@' symbol)
TWITTER_PASSWORD=your twitter password
# Important: If your 2FA code times out, you'll need to restart your node and login by submitting a request.
# We recommend temporarily disabling 2FA to save your cookies locally to your .home or .masa directory, then re-enabling it afterwards.
Expand All @@ -46,4 +46,4 @@ TELEGRAM_APP_ID=your telegram app id
TELEGRAM_APP_HASH=your telegram app hash
# Configure your Telegram bot and add it to the channel you want to scrape
TELEGRAM_BOT_TOKEN=your telegram bot token
TELEGRAM_CHANNEL_USERNAME=username of the channel to scrape (without the '@' symbol)
TELEGRAM_CHANNEL_USERNAME=username of the channel to scrape (without the '@' symbol)
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ snippets.txt

# Build result of goreleaser
dist/
bp-todo.md
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ RUN make build
# Use the official Ubuntu 22.04 image as a base for the final image
FROM ubuntu:22.04 AS base

# Install ca-certificates to ensure TLS verification works
RUN apt-get update && apt-get install -y ca-certificates && update-ca-certificates

COPY --from=builder /app/bin/masa-node /usr/bin/masa-node
RUN chmod +x /usr/bin/masa-node

Expand All @@ -38,4 +41,4 @@ EXPOSE 4001 8080

# Set default command to start the Go application

CMD /usr/bin/masa-node --bootnodes="$BOOTNODES" --env="$ENV" --validator="$VALIDATOR" --cachePath="$CACHE_PATH"
CMD /usr/bin/masa-node --bootnodes="$BOOTNODES" --env="$ENV" --validator="$VALIDATOR" --cachePath="$CACHE_PATH"
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ install:
run: build
@./bin/masa-node

run-api-enabled: build
@./bin/masa-node --api-enabled=true

faucet: build
./bin/masa-node --faucet

Expand Down
55 changes: 29 additions & 26 deletions cmd/masa-node/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,34 +100,20 @@ func main() {
// Init cache resolver
db.InitResolverCache(masaNode, keyManager)

// Listen for SIGINT (CTRL+C)
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)

// Cancel the context when SIGINT is received
go func() {
<-c
nodeData := masaNode.NodeTracker.GetNodeData(masaNode.Host.ID().String())
if nodeData != nil {
nodeData.Left()
}
cancel()
// Call the global StopFunc to stop the Telegram background connection
cfg := config.GetInstance()
if cfg.TelegramStop != nil {
if err := cfg.TelegramStop(); err != nil {
logrus.Errorf("Error stopping the background connection: %v", err)
}
}
}()
go handleSignals(cancel, masaNode, cfg)

router := api.SetupRoutes(masaNode, workHandlerManager, pubKeySub)
go func() {
err = router.Run()
if err != nil {
logrus.Fatal(err)
}
}()
if cfg.APIEnabled {
router := api.SetupRoutes(masaNode, workHandlerManager, pubKeySub)
go func() {
if err := router.Run(); err != nil {
logrus.Fatal(err)
}
}()
logrus.Info("API server started")
} else {
logrus.Info("API server is disabled")
}

// Get the multiaddress and IP address of the node
multiAddr := masaNode.GetMultiAddrs() // Get the multiaddress
Expand All @@ -137,3 +123,20 @@ func main() {

<-ctx.Done()
}

func handleSignals(cancel context.CancelFunc, masaNode *node.OracleNode, cfg *config.AppConfig) {
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)

<-c
nodeData := masaNode.NodeTracker.GetNodeData(masaNode.Host.ID().String())
if nodeData != nil {
nodeData.Left()
}
cancel()
if cfg.TelegramStop != nil {
if err := cfg.TelegramStop(); err != nil {
logrus.Errorf("Error stopping the background connection: %v", err)
}
}
}
4 changes: 3 additions & 1 deletion docs/oracle-node/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ Create a `.env` file in the root directory with the following content:

```plaintext
# Default .env configuration
BOOTNODES=/ip4/35.223.224.220/udp/4001/quic-v1/p2p/16Uiu2HAmPxXXjR1XJEwckh6q1UStheMmGaGe8fyXdeRs3SejadSa,/ip4/34.121.111.128/udp/4001/quic-v1/p2p/16Uiu2HAmKULCxKgiQn1EcfKnq1Qam6psYLDTM99XsZFhr57wLadF
# Check bootnodes addresses in the Masa documentation https://developers.masa.ai/docs/welcome-to-masa
BOOTNODES=
API_KEY=
RPC_URL=https://ethereum-sepolia.publicnode.com
Expand Down
2 changes: 1 addition & 1 deletion internal/versioning/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ var (

// XXX: Bump this value only when there are protocol changes that makes the oracle
// incompatible between version!
ProtocolVersion = `v0.8.0`
ProtocolVersion = `v0.8.4`
)
59 changes: 21 additions & 38 deletions pkg/config/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,31 +17,6 @@ import (
"github.com/spf13/viper"
)

// AppConfig is designed as a singleton to ensure that there is only one instance
// of the configuration throughout the application. This design pattern is useful
// for managing global application settings, allowing various parts of the application
// to access configuration settings consistently without the need to pass the configuration
// object around or risk having multiple, potentially conflicting instances of the configuration.
//
// The singleton pattern is implemented using a combination of a private instance variable
// (`instance`) and a public `GetInstance` method. The `instance` variable holds the single
// instance of AppConfig, while the `GetInstance` method provides a global access point to that instance.
// Additionally, the `sync.Once` mechanism ensures that the AppConfig instance is initialized only once,
// making the initialization thread-safe.
//
// Usage:
// To access the AppConfig instance, call the GetInstance method from anywhere in your application:
//
// config := config.GetInstance()
//
// This call will return the singleton AppConfig instance. If the instance has not been initialized yet,
// `GetInstance` will initialize it by setting default values, reading configuration from files,
// environment variables, and command-line flags, and then return the instance. Subsequent calls to
// `GetInstance` will return the same instance without reinitializing it.
//
// It's important to note that since AppConfig is a singleton, any modifications to the configuration
// settings through the AppConfig instance will be reflected across the entire application.

var (
instance *AppConfig
once sync.Once
Expand Down Expand Up @@ -93,6 +68,7 @@ type AppConfig struct {
LlmServer bool `mapstructure:"llmServer"`
LLMChatUrl string `mapstructure:"llmChatUrl"`
LLMCfUrl string `mapstructure:"llmCfUrl"`
APIEnabled bool `mapstructure:"api_enabled"`

TelegramStop bg.StopFunc
}
Expand Down Expand Up @@ -125,17 +101,17 @@ func GetInstance() *AppConfig {

if err := viper.Unmarshal(instance); err != nil {
logrus.Errorf("[-] Unable to unmarshal config into struct, %v", err)
instance = nil // Ensure instance is nil if unmarshalling fails
instance = nil
}

instance.APIEnabled = viper.GetBool("api_enabled")
})
return instance
}

// setDefaultConfig sets the default configuration values for the AppConfig instance.
// It retrieves the user's home directory and sets default values for various configuration options
// such as the MasaDir, Bootnodes, RpcUrl, Environment, FilePath, Validator, and CachePath.
// It also fetches bootnode information from a remote URL based on the environment (dev, test, or main).
func (c *AppConfig) setDefaultConfig() {

usr, err := user.Current()
Expand All @@ -147,7 +123,7 @@ func (c *AppConfig) setDefaultConfig() {
viper.SetDefault(MasaDir, filepath.Join(usr.HomeDir, ".masa"))

// Set defaults
viper.SetDefault("Version", versioning.ProtocolVersion)
viper.SetDefault("version", versioning.ProtocolVersion)
viper.SetDefault(PortNbr, "4001")
viper.SetDefault(UDP, true)
viper.SetDefault(TCP, false)
Expand All @@ -157,6 +133,8 @@ func (c *AppConfig) setDefaultConfig() {
viper.SetDefault(LogLevel, "info")
viper.SetDefault(LogFilePath, "masa_node.log")
viper.SetDefault(PrivKeyFile, filepath.Join(viper.GetString(MasaDir), "masa_oracle_key"))

viper.SetDefault("api_enabled", false)
}

// setFileConfig loads configuration from a YAML file.
Expand Down Expand Up @@ -196,7 +174,7 @@ func (c *AppConfig) setCommandLineConfig() error {
pflag.StringVar(&c.StakeAmount, "stake", viper.GetString(StakeAmount), "Amount of tokens to stake")
pflag.BoolVar(&c.Debug, "debug", viper.GetBool(Debug), "Override some protections for debugging (temporary)")
pflag.StringVar(&c.Environment, "env", viper.GetString(Environment), "Environment to connect to")
pflag.StringVar(&c.Version, "version", viper.GetString("VERSION"), "application version")
pflag.StringVar(&c.Version, "version", viper.GetString("version"), "Application version")
pflag.BoolVar(&c.AllowedPeer, "allowedPeer", viper.GetBool(AllowedPeer), "Set to true to allow setting this node as the allowed peer")
pflag.StringVar(&c.PrivateKey, "privateKey", viper.GetString(PrivateKey), "The private key")
pflag.StringVar(&c.PrivateKeyFile, "privKeyFile", viper.GetString(PrivKeyFile), "The private key file")
Expand All @@ -214,26 +192,32 @@ func (c *AppConfig) setCommandLineConfig() error {
pflag.StringVar(&c.Twitter2FaCode, "twitter2FaCode", viper.GetString(Twitter2FaCode), "Twitter 2FA Code")
pflag.StringVar(&c.DiscordBotToken, "discordBotToken", viper.GetString(DiscordBotToken), "Discord Bot Token")
pflag.StringVar(&c.ClaudeApiKey, "claudeApiKey", viper.GetString(ClaudeApiKey), "Claude API Key")
pflag.StringVar(&c.ClaudeApiURL, "claudeApiUrl", viper.GetString(ClaudeApiURL), "Claude API Url")
pflag.StringVar(&c.ClaudeApiURL, "claudeApiUrl", viper.GetString(ClaudeApiURL), "Claude API URL")
pflag.StringVar(&c.ClaudeApiVersion, "claudeApiVersion", viper.GetString(ClaudeApiVersion), "Claude API Version")
pflag.StringVar(&c.GPTApiKey, "gptApiKey", viper.GetString(GPTApiKey), "OpenAI API Key")
pflag.StringVar(&c.LLMChatUrl, "llmChatUrl", viper.GetString(LlmChatUrl), "URL for support LLM Chat calls")
pflag.StringVar(&c.LLMCfUrl, "llmCfUrl", viper.GetString(LlmCfUrl), "URL for support LLM Cloudflare calls")
pflag.BoolVar(&c.TwitterScraper, "twitterScraper", viper.GetBool(TwitterScraper), "TwitterScraper")
pflag.BoolVar(&c.DiscordScraper, "discordScraper", viper.GetBool(DiscordScraper), "DiscordScraper")
pflag.BoolVar(&c.TelegramScraper, "telegramScraper", viper.GetBool(TelegramScraper), "TelegramScraper")
pflag.BoolVar(&c.WebScraper, "webScraper", viper.GetBool(WebScraper), "WebScraper")
pflag.BoolVar(&c.TwitterScraper, "twitterScraper", viper.GetBool(TwitterScraper), "Twitter Scraper")
pflag.BoolVar(&c.DiscordScraper, "discordScraper", viper.GetBool(DiscordScraper), "Discord Scraper")
pflag.BoolVar(&c.TelegramScraper, "telegramScraper", viper.GetBool(TelegramScraper), "Telegram Scraper")
pflag.BoolVar(&c.WebScraper, "webScraper", viper.GetBool(WebScraper), "Web Scraper")
pflag.BoolVar(&c.LlmServer, "llmServer", viper.GetBool(LlmServer), "Can service LLM requests")
pflag.BoolVar(&c.Faucet, "faucet", viper.GetBool(Faucet), "Faucet")
pflag.BoolVar(&c.APIEnabled, "api-enabled", viper.GetBool("api_enabled"), "Enable API server")

pflag.Parse()

// Bind command line flags to Viper (optional, if you want to use Viper for additional configuration)
// Bind command line flags to Viper
err := viper.BindPFlags(pflag.CommandLine)
if err != nil {
return err
}

// Add this line after binding flags
viper.Set("api_enabled", c.APIEnabled)

c.Bootnodes = strings.Split(bootnodes, ",")

return nil
}

Expand All @@ -243,13 +227,12 @@ func (c *AppConfig) LogConfig() {
val := reflect.ValueOf(*c)
typeOfStruct := val.Type()

// logrus.Info("Current AppConfig values:")
for i := 0; i < val.NumField(); i++ {
field := typeOfStruct.Field(i)
value := val.Field(i).Interface()

// Example of skipping sensitive fields
if field.Name == "PrivateKeyFile" || field.Name == "Signature" {
// Skipping sensitive fields
if field.Name == "PrivateKey" || field.Name == "Signature" || field.Name == "PrivateKeyFile" {
continue
}
logrus.Infof("%s: %v", field.Name, value)
Expand Down
1 change: 1 addition & 0 deletions pkg/config/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ const (
LlmServer = "LLM_SERVER"
LlmChatUrl = "LLM_CHAT_URL"
LlmCfUrl = "LLM_CF_URL"
APIEnabled = "API_ENABLED"
)

// Function to call the Cloudflare API and parse the response
Expand Down
26 changes: 26 additions & 0 deletions pkg/pubsub/node_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ type NodeData struct {
Records any `json:"records,omitempty"`
Version string `json:"version"`
WorkerTimeout time.Time `json:"workerTimeout,omitempty"`
ReturnedTweets int `json:"returnedTweets"` // a running count of the number of tweets returned
LastReturnedTweet time.Time `json:"lastReturnedTweet"`
TweetTimeout bool `json:"tweetTimeout"`
TweetTimeouts int `json:"tweetTimeouts"` // a running countthe number of times a tweet request times out
LastTweetTimeout time.Time `json:"lastTweetTimeout"`
LastNotFoundTime time.Time `json:"lastNotFoundTime"`
NotFoundCount int `json:"notFoundCount"` // a running count of the number of times a node is not found
}

// NewNodeData creates a new NodeData struct initialized with the given
Expand Down Expand Up @@ -256,3 +263,22 @@ func (n *NodeData) MergeMultiaddresses(addr multiaddr.Multiaddr) {
n.Multiaddrs = append(n.Multiaddrs, JSONMultiaddr{Multiaddr: addr})
}
}

func (nd *NodeData) UpdateTwitterFields(fields NodeData) {
if fields.ReturnedTweets != 0 {
nd.ReturnedTweets += fields.ReturnedTweets
}
if !fields.LastReturnedTweet.IsZero() {
nd.LastReturnedTweet = fields.LastReturnedTweet
}
if fields.TweetTimeout {
nd.TweetTimeout = fields.TweetTimeout
nd.TweetTimeouts += fields.TweetTimeouts
nd.LastTweetTimeout = fields.LastTweetTimeout
}
if !fields.LastNotFoundTime.IsZero() {
nd.LastNotFoundTime = fields.LastNotFoundTime
nd.NotFoundCount += fields.NotFoundCount
}
nd.LastUpdatedUnix = time.Now().Unix()
}
Loading

0 comments on commit 2801335

Please sign in to comment.