diff --git a/.github/workflows/build-goreleaser-test.yml b/.github/workflows/build-goreleaser-test.yml new file mode 100644 index 00000000..761f013d --- /dev/null +++ b/.github/workflows/build-goreleaser-test.yml @@ -0,0 +1,25 @@ +name: goreleaser-build-test + +on: + push: + pull_request: + +jobs: + goreleaser: # Add this new job for GoReleaser + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: v2.1.0 + args: build --clean --snapshot + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml new file mode 100644 index 00000000..dccaa4f8 --- /dev/null +++ b/.github/workflows/goreleaser.yml @@ -0,0 +1,30 @@ +name: goreleaser + +on: + push: + tags: + - 'v*' # Add this line to trigger the workflow on tag pushes that match 'v*' + +permissions: + id-token: write + contents: read + +jobs: + goreleaser: # Add this new job for GoReleaser + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: 1.22 + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: v2.1.0 + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 00000000..1bc752c8 --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,70 @@ +name: static check +on: pull_request + +jobs: + imports: + name: Imports + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: check + uses: danhunsaker/golang-github-actions@v1.3.0 + with: + run: imports + token: ${{ secrets.GITHUB_TOKEN }} + + errcheck: + name: Errcheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: check + uses: danhunsaker/golang-github-actions@v1.3.0 + with: + run: errcheck + token: ${{ secrets.GITHUB_TOKEN }} + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: check + uses: danhunsaker/golang-github-actions@v1.3.0 + with: + run: lint + token: ${{ secrets.GITHUB_TOKEN }} + + shadow: + name: Shadow + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: check + uses: danhunsaker/golang-github-actions@v1.3.0 + with: + run: shadow + token: ${{ secrets.GITHUB_TOKEN }} + + staticcheck: + name: StaticCheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: check + uses: danhunsaker/golang-github-actions@v1.3.0 + with: + run: staticcheck + token: ${{ secrets.GITHUB_TOKEN }} + + sec: + name: Sec + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + - name: check + uses: danhunsaker/golang-github-actions@v1.3.0 + with: + run: sec + token: ${{ secrets.GITHUB_TOKEN }} + flags: "-exclude=G104" diff --git a/.gitignore b/.gitignore index 3fc6bb2b..eaa12583 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,6 @@ docs/api-reference.md transcription.txt snippets.txt .env copy + +# Build result of goreleaser +dist/ diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 00000000..230d00e2 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,38 @@ +# Make sure to check the documentation at http://goreleaser.com +version: 2 +builds: + - main: ./cmd/masa-node/main.go + ldflags: + - -w -s + - -X github.com/masa-finance/masa-oracle/internal.Version={{.Tag}} + - -X github.com/masa-finance/masa-oracle/internal.Commit={{.Commit}} + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - darwin + - freebsd + goarch: + - amd64 + - arm + - arm64 +source: + enabled: true + name_template: '{{ .ProjectName }}-{{ .Tag }}-source' +archives: + # Default template uses underscores instead of - + - name_template: >- + {{ .ProjectName }}-{{ .Tag }}- + {{- if eq .Os "freebsd" }}FreeBSD + {{- else }}{{- title .Os }}{{end}}- + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{end}} + {{- if .Arm }}v{{ .Arm }}{{ end }} +checksum: + name_template: '{{ .ProjectName }}-{{ .Tag }}-checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + use: github-native \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go index 28b9199e..94d35e82 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -48,6 +48,1212 @@ const docTemplate = `{ "name": "username", "in": "path", "required": true + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Masa API Support", + "url": "https://masa.ai", + "email": "support@masa.ai" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/license/mit" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "securityDefinitions": { + "Bearer": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + }, + "security": [ + { + "Bearer": [] + } + ], + "paths": { + "/peers": { + "get": { + "description": "Retrieves a list of peers connected to the node", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Peers" + ], + "summary": "Get list of peers", + "responses": { + "200": { + "description": "List of peer IDs", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/peer/addresses": { + "get": { + "description": "Retrieves a list of peer addresses connected to the node", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Peers" + ], + "summary": "Get peer addresses", + "responses": { + "200": { + "description": "List of peer addresses", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/twitter/profile/{username}": { + "get": { + "description": "Retrieves tweets from a specific Twitter profile", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Twitter" + ], + "summary": "Search Twitter Profile", + "parameters": [ + { + "type": "string", + "description": "Twitter Username", + "name": "username", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "List of tweets from the profile", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Tweet" + } + } + }, + "400": { + "description": "Invalid username or error fetching tweets", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/twitter/followers/{username}": { + "get": { + "description": "Retrieves followers from a specific Twitter profile.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Twitter" + ], + "summary": "Search Followers by Twitter Username", + "parameters": [ + { + "type": "string", + "description": "Twitter Username", + "name": "username", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Maximum number of users to return", + "name": "count", + "in": "query", + "required": false, + "default": 20 + } + ], + "responses": { + "200": { + "description": "Array of profiles a user has as followers", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Profile" + } + } + }, + "400": { + "description": "Invalid username or error fetching followers", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/data/twitter/tweets/recent": { + "post": { + "description": "Retrieves recent tweets based on query parameters", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Twitter" + ], + "summary": "Search recent tweets", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Search parameters", + "required": true, + "schema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search Query" + }, + "count": { + "type": "integer", + "description": "Number of tweets to return" + } + } + } + } + ], + "responses": { + "200": { + "description": "List of recent tweets", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Tweet" + } + } + }, + "400": { + "description": "Invalid query or error fetching tweets", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/twitter/tweets/trends": { + "get": { + "description": "Retrieves the latest Twitter trending topics", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Twitter" + ], + "summary": "Twitter Trends", + "responses": { + "200": { + "description": "List of trending topics", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Trend" + } + } + }, + "400": { + "description": "Error fetching Twitter trends", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/discord/profile/{userID}": { + "get": { + "description": "Retrieves a Discord user profile by user ID.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discord" + ], + "summary": "Search Discord Profile", + "parameters": [ + { + "name": "userID", + "in": "path", + "description": "Discord User ID", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successfully retrieved Discord user profile", + "schema": { + "$ref": "#/definitions/UserProfile" + } + }, + "400": { + "description": "Invalid user ID or error fetching profile", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/discord/channels/{channelID}/messages": { + "get": { + "description": "Retrieves messages from a specified Discord channel.", + "tags": ["Discord"], + "summary": "Get messages from a Discord channel", + "parameters": [ + { + "name": "channelID", + "in": "path", + "description": "Discord Channel ID", + "required": true, + "type": "string" + }, + { + "name": "limit", + "in": "query", + "description": "The maximum number of messages to return", + "required": false, + "type": "integer", + "format": "int32" + }, + { + "name": "before", + "in": "query", + "description": "A message ID to return messages posted before this message", + "required": false, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successfully retrieved messages from the Discord channel", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/ChannelMessage" + } + } + }, + "400": { + "description": "Invalid channel ID or error fetching messages", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/discord/guilds/{guildID}/channels": { + "get": { + "description": "Retrieves channels from a specified Discord guild.", + "tags": ["Discord"], + "summary": "Get channels from a Discord guild", + "parameters": [ + { + "name": "guildID", + "in": "path", + "description": "Discord Guild ID", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successfully retrieved channels from the Discord guild", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/GuildChannel" + } + } + }, + "400": { + "description": "Invalid guild ID or error fetching channels", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/discord/user/guilds": { + "get": { + "description": "Retrieves guilds from a specified Discord user.", + "tags": ["Discord"], + "summary": "Get guilds from a Discord user", + "parameters": [ + ], + "responses": { + "200": { + "description": "Successfully retrieved guilds from the Discord user", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/UserGuild" + } + } + }, + "400": { + "description": "Invalid user ID or error fetching guilds", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/discord/guilds/all": { + "get": { + "description": "Retrieves all guilds that all the Discord workers are apart of.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Discord" + ], + "summary": "Get all guilds", + "responses": { + "200": { + "description": "Successfully retrieved all guilds for the Discord user", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Guild" + } + } + }, + "400": { + "description": "Error fetching guilds or invalid access token", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/telegram/channel/messages": { + "post": { + "description": "Retrieves messages from a specified Telegram channel.", + "tags": ["Telegram"], + "summary": "Get Telegram Channel Messages", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Request body", + "required": true, + "schema": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "Telegram Username" + } + }, + "required": ["username"] + } + } + ], + "responses": { + "200": { + "description": "Successfully retrieved messages", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Message" + } + } + }, + "400": { + "description": "Invalid username or error fetching messages", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/data/web": { + "post": { + "description": "Retrieves data from the web", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Web" + ], + "summary": "Web Data", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Search parameters", + "required": true, + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "Url" + }, + "depth": { + "type": "integer", + "description": "Number of pages to scrape" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successfully retrieved web data", + "schema": { + "$ref": "#/definitions/WebDataResponse" + } + }, + "400": { + "description": "Invalid query or error fetching web data", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/dht": { + "get": { + "description": "Retrieves data from the DHT (Distributed Hash Table)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "DHT" + ], + "summary": "Get DHT Data", + "parameters": [ + { + "in": "query", + "name": "key", + "description": "Key to retrieve data for", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "Successfully retrieved data from DHT", + "schema": { + "$ref": "#/definitions/DHTResponse" + } + }, + "400": { + "description": "Error retrieving data from DHT", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + }, + "post": { + "description": "Adds data to the DHT (Distributed Hash Table)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "DHT" + ], + "summary": "Post to DHT", + "parameters": [ + { + "description": "Data to store in DHT", + "name": "data", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successfully added data to DHT", + "schema": { + "$ref": "#/definitions/SuccessResponse" + } + }, + "400": { + "description": "Error adding data to DHT", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/llm/models": { + "get": { + "description": "Retrieves the available LLM models", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "LLM" + ], + "summary": "Get LLM Models", + "responses": { + "200": { + "description": "Successfully retrieved LLM models", + "schema": { + "$ref": "#/definitions/LLMModelsResponse" + } + }, + "400": { + "description": "Error retrieving LLM models", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/chat": { + "post": { + "summary": "Chat with AI", + "description": "Initiates a chat session with an AI model.", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Chat request payload", + "required": true, + "schema": { + "type": "object", + "properties": { + "model": { + "type": "string", + "example": "llama3" + }, + "messages": { + "type": "array", + "items": { + "type": "object", + "properties": { + "role": { + "type": "string", + "example": "user" + }, + "content": { + "type": "string", + "example": "why is the sky blue?" + } + } + } + }, + "stream": { + "type": "boolean", + "example": false + } + }, + "required": ["model", "messages", "stream"] + } + } + ], + "responses": { + "200": { + "description": "Successfully received response from AI", + "schema": { + "$ref": "#/definitions/ChatResponse" + } + }, + "400": { + "description": "Error communicating with AI", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/node/data": { + "get": { + "description": "Retrieves data from the node", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Node" + ], + "summary": "Node Data", + "responses": { + "200": { + "description": "Successfully retrieved node data", + "schema": { + "$ref": "#/definitions/NodeDataResponse" + } + }, + "400": { + "description": "Error retrieving node data", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/node/data/{peerid}": { + "get": { + "description": "Retrieves data for a specific node identified by peer ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Node" + ], + "summary": "Get Node Data by Peer ID", + "parameters": [ + { + "type": "string", + "description": "Peer ID", + "name": "peerid", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved node data by peer ID", + "schema": { + "$ref": "#/definitions/NodeDataResponse" + } + }, + "400": { + "description": "Error retrieving node data by peer ID", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + }, + "security": [ + { + "Bearer": [] + } + ] + } + }, + "/sentiment/tweets": { + "post": { + "description": "Searches for tweets and analyzes their sentiment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Sentiment" + ], + "summary": "Analyze Sentiment of Tweets", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Sentiment analysis request body", + "required": true, + "schema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "Search Query" + }, + "count": { + "type": "integer", + "description": "Number of tweets to analyze" + }, + "model": { + "type": "string", + "description": "Sentiment analysis model to use" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successfully analyzed sentiment of tweets", + "schema": { + "$ref": "#/definitions/SentimentAnalysisResponse" + } + }, + "400": { + "description": "Error analyzing sentiment of tweets", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/sentiment/telegram": { + "post": { + "description": "Searches for Telegram messages and analyzes their sentiment", + "tags": ["Sentiment"], + "summary": "Analyze Sentiment of Telegram Messages", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "name": "query", + "in": "body", + "description": "Search Query", + "required": true, + "schema": { + "type": "object", + "properties": { + "query": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successfully analyzed sentiment of Telegram messages", + "schema": { + "$ref": "#/definitions/SentimentAnalysisResponse" + } + }, + "400": { + "description": "Error analyzing sentiment of Telegram messages", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/sentiment/discord": { + "post": { + "description": "Searches for Discord messages and analyzes their sentiment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Sentiment" + ], + "summary": "Analyze Sentiment of Discord Messages", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Sentiment analysis request body for Discord messages", + "required": true, + "schema": { + "type": "object", + "properties": { + "channelID": { + "type": "string", + "description": "Discord Channel ID" + }, + "prompt": { + "type": "string", + "description": "Prompt to enter" + }, + "model": { + "type": "string", + "description": "Sentiment analysis model to use" + } + }, + "required": ["channelID", "model"] + } + } + ], + "responses": { + "200": { + "description": "Successfully analyzed sentiment of Discord messages", + "schema": { + "$ref": "#/definitions/SentimentAnalysisResponse" + } + }, + "400": { + "description": "Error analyzing sentiment of Discord messages", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/auth": { + "get": { + "description": "Retrieves the API key for the node", + "produces": [ + "application/json" + ], + "tags": [ + "Authentication" + ], + "summary": "Get Node API Key", + "responses": { + "200": { + "description": "Successfully retrieved API key", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "500": { + "description": "Error generating API key", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/auth/telegram/start": { + "post": { + "description": "Initiates the authentication process with Telegram by sending a code to the provided phone number.", + "tags": ["Authentication"], + "summary": "Start Telegram Authentication", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "name": "phone_number", + "in": "body", + "description": "Phone Number", + "required": true, + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successfully sent authentication code", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Failed to initialize Telegram client or to start authentication", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/auth/telegram/complete": { + "post": { + "description": "Completes the authentication process with Telegram using the code sent to the phone number.", + "tags": ["Authentication"], + "summary": "Complete Telegram Authentication", + "consumes": ["application/json"], + "produces": ["application/json"], + "parameters": [ + { + "name": "phone_number", + "in": "body", + "description": "Phone Number", + "required": true, + "schema": { + "type": "object", + "properties": { + "phone_number": { + "type": "string" + }, + "code": { + "type": "string" + }, + "phone_code_hash": { + "type": "string" + }, + "password": { + "type": "string", + "description": "Optional password for two-factor authentication" + } + }, + "required": ["phone_number", "code", "phone_code_hash"] + } + } + ], + "responses": { + "200": { + "description": "Successfully authenticated", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "400": { + "description": "Invalid request body", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "401": { + "description": "Two-factor authentication is required", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + }, + "500": { + "description": "Failed to initialize Telegram client or to complete authentication", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + "/sentiment/web": { + "post": { + "description": "Searches for web content and analyzes its sentiment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Sentiment" + ], + "summary": "Analyze Sentiment of Web Content", + "parameters": [ + { + "in": "body", + "name": "body", + "description": "Sentiment analysis request body", + "required": true, + "schema": { + "type": "object", + "properties": { + "url": { + "type": "string", + "description": "URL of the web content" + }, + "depth": { + "type": "integer", + "description": "Depth of web crawling" + }, + "model": { + "type": "string", + "description": "Sentiment analysis model to use" + } + } + } + } + ], + "responses": { + "200": { + "description": "Successfully analyzed sentiment of web content", + "schema": { + "$ref": "#/definitions/SentimentAnalysisResponse" + } + }, + "400": { + "description": "Error analyzing sentiment of web content", + "schema": { + "$ref": "#/definitions/ErrorResponse" + } + } + } + } + }, + }, + "DHTResponse": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } } ], "responses": { diff --git a/docs/oracle-node/telegram-sentiment.md b/docs/oracle-node/telegram-sentiment.md index 8764ea15..5c399367 100644 --- a/docs/oracle-node/telegram-sentiment.md +++ b/docs/oracle-node/telegram-sentiment.md @@ -5,15 +5,15 @@ title: Telegram Sentiment ## Masa Node Telegram Sentiment Analysis Feature -The Masa Node introduces a powerful feature for analyzing the sentiment of telegram messages. This functionality leverages advanced language models to interpret the sentiment behind a collection of tweets, providing valuable insights into public perception and trends. +The Masa Node introduces a powerful feature for analyzing the sentiment of telegram messages. This functionality leverages advanced language models to interpret the sentiment behind a collection of Telegram messages, providing valuable insights into public perception and trends. ## Overview -The Telegram sentiment analysis feature is part of the broader capabilities of the Masa Node, designed to interact with Telegram messages data in a meaningful way. It uses state-of-the-art language models to evaluate the sentiment of tweets, categorizing them into positive, negative, or neutral sentiments. +The Telegram sentiment analysis feature is part of the broader capabilities of the Masa Node, designed to interact with Telegram messages data in a meaningful way. It uses state-of-the-art language models to evaluate the sentiment of Telegram messages, categorizing them into positive, negative, or neutral sentiments. ## How It Works -The sentiment analysis process involves fetching tweets based on specific queries, and then analyzing these tweets using selected language models. The system supports various models, including Claude and GPT variants, allowing for flexible and powerful sentiment analysis. +The sentiment analysis process involves fetching Telegram messages based on specific queries, and then analyzing these messages using selected language models. The system supports various models, including Claude and GPT variants, allowing for flexible and powerful sentiment analysis. ### Models @@ -64,7 +64,7 @@ const ( #### Masa cli or code integration -Tweets are fetched using the Twitter Scraper library, as seen in the [llmbridge](file:///Users/john/Projects/masa/masa-oracle/pkg/llmbridge/sentiment.go#) package. This process does not require Telegram API keys, making it accessible and straightforward. +Messages are fetched using the Telegram Scraper library, as seen in the [llmbridge](/masa-oracle/pkg/llmbridge/sentiment.go#) package. This process does not require Telegram API keys, making it accessible and straightforward. ```go func AnalyzeSentimentTelegram(messages []*tg.Message, model string, prompt string) (string, string, error) { @@ -72,4 +72,4 @@ func AnalyzeSentimentTelegram(messages []*tg.Message, model string, prompt strin ### Analyzing Sentiment -Once tweets are fetched, they are sent to the chosen language model for sentiment analysis. The system currently supports models prefixed with "claude-" and "gpt-", catering to a range of analysis needs. \ No newline at end of file +Once Telegram Messages are fetched, they are sent to the chosen language model for sentiment analysis. The system currently supports models prefixed with "claude-" and "gpt-", catering to a range of analysis needs. \ No newline at end of file diff --git a/docs/oracle-node/twitter-data.md b/docs/oracle-node/twitter-data.md index 4eba986d..a4c9a281 100644 --- a/docs/oracle-node/twitter-data.md +++ b/docs/oracle-node/twitter-data.md @@ -211,6 +211,18 @@ Example response: The Advanced Search feature allows users to perform more complex queries to filter tweets according to various criteria such as date ranges, specific users, hashtags, and more. Below you will find detailed information on how to construct advanced search queries. +### Exact Search +Search for tweets containing specific hashtags. + +**Syntax:** `"\"searchterm\"` + +**Example:** + +```bash +curl -X POST http://localhost:8080/api/v1/data/twitter/tweets/recent \ +-H "Content-Type: application/json" \ +-d '{"query": "\"masa\", "count": 10}' +``` ### Hashtag Search Search for tweets containing specific hashtags. diff --git a/docs/worker-node/telegram-worker.md b/docs/worker-node/telegram-worker.md index c9badcc4..323ac686 100644 --- a/docs/worker-node/telegram-worker.md +++ b/docs/worker-node/telegram-worker.md @@ -58,6 +58,8 @@ TELEGRAM_SCRAPER=true 1. Call the `/api/v1/auth/telegram/complete` endpoint with the code you received on your phone, your phone number, and the `phone_code_hash` from the previous step. +*Note* - If you have 2FA turned on, you will need to pass your 2FA password in the API call. + ### Verifying Node Configuration Ensure your node is correctly configured to handle Twitter data requests by checkint the initialization message: diff --git a/go.mod b/go.mod index 8dfc41bb..bec464b7 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/chyeh/pubip v0.0.0-20170203095919-b7e679cf541c github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/dgraph-io/badger v1.6.2 - github.com/ethereum/go-ethereum v1.14.7 + github.com/ethereum/go-ethereum v1.14.8 github.com/fatih/color v1.17.0 github.com/gdamore/tcell/v2 v2.7.4 github.com/gin-contrib/cors v1.7.2 @@ -53,7 +53,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.13.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect - github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect @@ -114,7 +114,7 @@ require ( github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect - github.com/holiman/uint256 v1.3.0 // indirect + github.com/holiman/uint256 v1.3.1 // indirect github.com/huin/goupnp v1.3.0 // indirect github.com/ipfs/boxo v0.18.0 // indirect github.com/ipfs/go-log v1.0.5 // indirect diff --git a/go.sum b/go.sum index 29f6c9a1..646335e2 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/bits-and-blooms/bitset v1.13.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6 github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= -github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= -github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ= +github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0 h1:59Kx4K6lzOW5w6nFlA0v5+lk/6sjybR934QNHSJZPTQ= github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= @@ -149,14 +149,12 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.14.7 h1:EHpv3dE8evQmpVEQ/Ne2ahB06n2mQptdwqaMNhAT29g= -github.com/ethereum/go-ethereum v1.14.7/go.mod h1:Mq0biU2jbdmKSZoqOj29017ygFrMnB5/Rifwp980W4o= +github.com/ethereum/go-ethereum v1.14.8 h1:NgOWvXS+lauK+zFukEvi85UmmsS/OkV0N23UZ1VTIig= +github.com/ethereum/go-ethereum v1.14.8/go.mod h1:TJhyuDq0JDppAkFXgqjwpdlQApywnu/m10kFPxh8vvs= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4= github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= -github.com/fjl/memsize v0.0.2 h1:27txuSD9or+NZlnOWdKUxeBzTAUkWCVh+4Gf2dWFOzA= -github.com/fjl/memsize v0.0.2/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= @@ -332,8 +330,8 @@ github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6w github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.3.0 h1:4wdcm/tnd0xXdu7iS3ruNvxkWwrb4aeBQv19ayYn8F4= -github.com/holiman/uint256 v1.3.0/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= +github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= diff --git a/pkg/api/handlers_data.go b/pkg/api/handlers_data.go index 4e7e7504..c7c3950e 100644 --- a/pkg/api/handlers_data.go +++ b/pkg/api/handlers_data.go @@ -887,13 +887,14 @@ func (api *API) CompleteAuth() gin.HandlerFunc { PhoneNumber string `json:"phone_number"` Code string `json:"code"` PhoneCodeHash string `json:"phone_code_hash"` + Password string `json:"password"` } if err := c.ShouldBindJSON(&reqBody); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"}) return } - auth, err := telegram.CompleteAuthentication(context.Background(), reqBody.PhoneNumber, reqBody.Code, reqBody.PhoneCodeHash) + auth, err := telegram.CompleteAuthentication(context.Background(), reqBody.PhoneNumber, reqBody.Code, reqBody.PhoneCodeHash, reqBody.Password) if err != nil { // Check if 2FA is required if err.Error() == "2FA required" { diff --git a/pkg/scrapers/telegram/telegram_client.go b/pkg/scrapers/telegram/telegram_client.go index 8bc42c8a..836d2ea3 100644 --- a/pkg/scrapers/telegram/telegram_client.go +++ b/pkg/scrapers/telegram/telegram_client.go @@ -8,6 +8,7 @@ import ( "os" "path/filepath" "strconv" + "strings" "sync" "github.com/gotd/contrib/bg" @@ -113,12 +114,13 @@ func StartAuthentication(ctx context.Context, phoneNumber string) (string, error } // CompleteAuthentication uses the provided code to authenticate with Telegram. -func CompleteAuthentication(ctx context.Context, phoneNumber, code, phoneCodeHash string) (*tg.AuthAuthorization, error) { +// CompleteAuthentication uses the provided code to authenticate with Telegram. +func CompleteAuthentication(ctx context.Context, phoneNumber, code, phoneCodeHash, password string) (*tg.AuthAuthorization, error) { // Initialize the Telegram client (if not already initialized) client, err := GetClient() if err != nil { logrus.Printf("Failed to initialize Telegram client: %v", err) - return nil, err // Edit: Added nil as the first return value + return nil, err } // Define a variable to hold the authentication result @@ -127,9 +129,18 @@ func CompleteAuthentication(ctx context.Context, phoneNumber, code, phoneCodeHas err = client.Run(ctx, func(ctx context.Context) error { // Use the provided code and phoneCodeHash to authenticate auth, err := client.Auth().SignIn(ctx, phoneNumber, code, phoneCodeHash) + if err != nil { - log.Printf("Error during SignIn: %v", err) - return err + if strings.Contains(err.Error(), "2FA required") { + auth, err = client.Auth().Password(ctx, password) + if err != nil { + log.Printf("Error during 2FA SignIn: %v", err) + return err + } + } else { + log.Printf("Error during SignIn: %v", err) + return err + } } // At this point, authentication was successful, and you have the user's Telegram auth data.