strideutils 0.8.4__tar.gz → 0.8.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {strideutils-0.8.4 → strideutils-0.8.6}/.bumpversion.cfg +1 -1
- {strideutils-0.8.4 → strideutils-0.8.6}/PKG-INFO +1 -1
- {strideutils-0.8.4 → strideutils-0.8.6}/pyproject.toml +1 -1
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/exec_tx.py +3 -3
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/exec_tx.pyi +1 -1
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/sheets_connector.py +35 -2
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/sheets_connector.pyi +2 -0
- strideutils-0.8.6/strideutils/slack_connector.py +183 -0
- strideutils-0.8.6/strideutils/slack_connector.pyi +29 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/stride_config.py +61 -78
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/stride_config.pyi +4 -2
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/exec_tx.pyi +1 -1
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/sheets_connector.pyi +3 -0
- strideutils-0.8.6/strideutils-stubs/slack_connector.pyi +37 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/stride_config.pyi +4 -2
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils.egg-info/PKG-INFO +1 -1
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_config.py +4 -4
- strideutils-0.8.4/strideutils/slack_connector.py +0 -147
- strideutils-0.8.4/strideutils/slack_connector.pyi +0 -13
- strideutils-0.8.4/strideutils-stubs/slack_connector.pyi +0 -24
- {strideutils-0.8.4 → strideutils-0.8.6}/.github/workflows/lint.yml +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/.github/workflows/publish.yml +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/.github/workflows/test.yml +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/.github/workflows/version.yaml +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/.gitignore +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/LICENSE +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/MANIFEST.in +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/Makefile +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/README.md +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/example-config.yaml +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/setup.cfg +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/Makefile +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/__init__.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/__init__.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/coingecko.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/coingecko.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/config_examples/env.local.example +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/config_examples/strideutils_config.yaml.example +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/cryptography.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/cryptography.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/py.typed +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/redis_connector.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/redis_connector.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/run_safely.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/run_safely.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/stride_alerts.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/stride_alerts.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/stride_requests.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/stride_requests.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/twilio_connector.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/twilio_connector.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/types.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils/types.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/__init__.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/coingecko.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/cryptography.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/redis_connector.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/run_safely.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/stride_alerts.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/stride_requests.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/twilio_connector.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils-stubs/types.pyi +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils.egg-info/SOURCES.txt +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils.egg-info/dependency_links.txt +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils.egg-info/requires.txt +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/strideutils.egg-info/top_level.txt +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_coingecko.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_encryption.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_redis_connector.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_sheets_connector.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_slack_connector.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_stride_requests.py +0 -0
- {strideutils-0.8.4 → strideutils-0.8.6}/tests/test_twilio_connector.py +0 -0
|
@@ -8,7 +8,7 @@ from typing import Optional
|
|
|
8
8
|
from strideutils import stride_requests
|
|
9
9
|
from strideutils.stride_config import config
|
|
10
10
|
|
|
11
|
-
WALLET_SETUP_COMMAND = "echo {mnemonic} | {binary} keys add {
|
|
11
|
+
WALLET_SETUP_COMMAND = "echo {mnemonic} | {binary} keys add {account_name} --recover --keyring-backend=test"
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@dataclass
|
|
@@ -22,7 +22,7 @@ class TxResponse:
|
|
|
22
22
|
raw_log: Optional[str] = None
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
def restore_wallet(
|
|
25
|
+
def restore_wallet(account_name: str = "wallet", mnemonic_var: str = "MNEMONIC", binary: str = "strided"):
|
|
26
26
|
"""
|
|
27
27
|
Restores a wallet in the keyring, using the mnemonic under the specified environment variable
|
|
28
28
|
"""
|
|
@@ -30,7 +30,7 @@ def restore_wallet(wallet_name: str = "wallet", mnemonic_var: str = "MNEMONIC",
|
|
|
30
30
|
if not mnemonic:
|
|
31
31
|
raise EnvironmentError(f"{mnemonic_var} must be set")
|
|
32
32
|
|
|
33
|
-
os.system(WALLET_SETUP_COMMAND.format(mnemonic=mnemonic, binary=binary,
|
|
33
|
+
os.system(WALLET_SETUP_COMMAND.format(mnemonic=mnemonic, binary=binary, account_name=account_name))
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
def execute_tx(command: str, api_endpoint: str = config.stride.api_endpoint) -> TxResponse:
|
|
@@ -14,5 +14,5 @@ class TxResponse:
|
|
|
14
14
|
tx_response: dict | None = ...
|
|
15
15
|
raw_log: str | None = ...
|
|
16
16
|
|
|
17
|
-
def restore_wallet(
|
|
17
|
+
def restore_wallet(account_name: str = 'wallet', mnemonic_var: str = 'MNEMONIC', binary: str = 'strided'): ...
|
|
18
18
|
def execute_tx(command: str, api_endpoint: str = ...) -> TxResponse: ...
|
|
@@ -12,7 +12,7 @@ publicsheets@stride-nodes.iam.gserviceaccount.com can access will be readable by
|
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
14
|
import warnings
|
|
15
|
-
from typing import Dict, List, Tuple
|
|
15
|
+
from typing import Any, Dict, List, Tuple
|
|
16
16
|
|
|
17
17
|
import gspread # type: ignore
|
|
18
18
|
import pandas as pd
|
|
@@ -139,7 +139,40 @@ class GoogleSheetsClient:
|
|
|
139
139
|
# Write the new values with update (which will override an existing sheet).
|
|
140
140
|
with warnings.catch_warnings():
|
|
141
141
|
warnings.filterwarnings("ignore")
|
|
142
|
-
worksheet.update(values=columns + rows, range_name=range_name)
|
|
142
|
+
worksheet.update(values=columns + rows, range_name=range_name) # type: ignore
|
|
143
|
+
|
|
144
|
+
def write_cell(
|
|
145
|
+
self,
|
|
146
|
+
value: List[List[Any]],
|
|
147
|
+
range_name: str,
|
|
148
|
+
sheet_id: str,
|
|
149
|
+
sheet_name: str,
|
|
150
|
+
) -> None:
|
|
151
|
+
"""
|
|
152
|
+
Writes a value to a specified Google Sheet.
|
|
153
|
+
|
|
154
|
+
Please note: value must be a list of lists, where each inner list represents a row.
|
|
155
|
+
If you're only passing in a value for one cell, then `value=[[your_value]]`.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
value: The value to write in the sheet. This is a List of Lists.
|
|
159
|
+
range_name: The range to write the value to, e.g. "B3:C4"
|
|
160
|
+
sheet_id: The ID of the Sheet (found in the URL)
|
|
161
|
+
sheet_name: The name of the worksheet to write to
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
gspread.exceptions.APIError: If there's an error accessing the sheet
|
|
165
|
+
"""
|
|
166
|
+
# Fetch the worksheet if it exists, otherwise create a new worksheet.
|
|
167
|
+
spreadsheet = self.client.open_by_key(sheet_id)
|
|
168
|
+
try:
|
|
169
|
+
worksheet = spreadsheet.worksheet(sheet_name)
|
|
170
|
+
except WorksheetNotFound:
|
|
171
|
+
worksheet = spreadsheet.add_worksheet(title=sheet_name, rows=1, cols=1)
|
|
172
|
+
|
|
173
|
+
with warnings.catch_warnings():
|
|
174
|
+
warnings.filterwarnings("ignore")
|
|
175
|
+
worksheet.update(values=value, range_name=range_name)
|
|
143
176
|
|
|
144
177
|
def reorder_sheet(
|
|
145
178
|
self,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import gspread
|
|
2
2
|
import pandas as pd
|
|
3
3
|
from strideutils.stride_config import get_env_or_raise as get_env_or_raise
|
|
4
|
+
from typing import Any
|
|
4
5
|
|
|
5
6
|
SCOPES: tuple[str, str, str]
|
|
6
7
|
|
|
@@ -11,5 +12,6 @@ class GoogleSheetsClient:
|
|
|
11
12
|
def __init__(self, cache_enabled: bool = False) -> None: ...
|
|
12
13
|
def grab_sheet(self, sheet_id: str, sheet_name: str, columns_labeled: bool = True) -> pd.DataFrame: ...
|
|
13
14
|
def write_sheet(self, df: pd.DataFrame, sheet_id: str, sheet_name: str) -> None: ...
|
|
15
|
+
def write_cell(self, value: list[list[Any]], range_name: str, sheet_id: str, sheet_name: str) -> None: ...
|
|
14
16
|
def reorder_sheet(self, sheet_id: str, sheet_name: str, new_location: int = 0) -> None: ...
|
|
15
17
|
def get_sheet_names(self, sheet_id: str) -> list[str]: ...
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import os
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Callable, List, Optional, Union
|
|
5
|
+
|
|
6
|
+
from slack_sdk import WebClient
|
|
7
|
+
from slack_sdk.socket_mode import SocketModeClient
|
|
8
|
+
from slack_sdk.socket_mode.client import BaseSocketModeClient
|
|
9
|
+
from slack_sdk.socket_mode.request import SocketModeRequest
|
|
10
|
+
from slack_sdk.socket_mode.response import SocketModeResponse
|
|
11
|
+
|
|
12
|
+
from strideutils.stride_config import Environment as e
|
|
13
|
+
from strideutils.stride_config import get_env_or_raise
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class SlackMessage:
|
|
18
|
+
timestamp: datetime.datetime
|
|
19
|
+
timestamp_id: str
|
|
20
|
+
thread_ts: Optional[str]
|
|
21
|
+
sender: str
|
|
22
|
+
text: str
|
|
23
|
+
|
|
24
|
+
@classmethod
|
|
25
|
+
def from_dict(cls, message: dict):
|
|
26
|
+
thread_ts = message.get("thread_ts", None)
|
|
27
|
+
return cls(
|
|
28
|
+
timestamp=datetime.datetime.fromtimestamp(float(message["ts"])),
|
|
29
|
+
timestamp_id=message["ts"],
|
|
30
|
+
thread_ts=thread_ts,
|
|
31
|
+
sender=message["user"],
|
|
32
|
+
text=message["text"],
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SlackClient:
|
|
37
|
+
"""
|
|
38
|
+
Singleton client used to post messages to slack
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
_instance = None
|
|
42
|
+
|
|
43
|
+
def __new__(cls):
|
|
44
|
+
# Creates a new instance if one does not already exist
|
|
45
|
+
if cls._instance is None:
|
|
46
|
+
cls._instance = super(SlackClient, cls).__new__(cls)
|
|
47
|
+
return cls._instance
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
# Prevent re-initialization if a client has already been created
|
|
51
|
+
if hasattr(self, "client"):
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
self.api_token = get_env_or_raise(e.STRIDEBOT_API_TOKEN)
|
|
55
|
+
self.client = WebClient(token=self.api_token)
|
|
56
|
+
self.socket_mode_client: SocketModeClient | None = None
|
|
57
|
+
|
|
58
|
+
def setup_for_listening(self):
|
|
59
|
+
"""
|
|
60
|
+
Sets up the client for listening to slack messages using SocketMode.
|
|
61
|
+
Without calling this function, a Socket Mode client will not be created.
|
|
62
|
+
|
|
63
|
+
We don't create the client in the constructor to avoid unnecessary overhead,
|
|
64
|
+
as not all processes will want to initiate socket connections.
|
|
65
|
+
"""
|
|
66
|
+
self.app_token = get_env_or_raise(e.STRIDEBOT_APP_TOKEN)
|
|
67
|
+
socket_mode_client = SocketModeClient(app_token=self.app_token, web_client=self.client)
|
|
68
|
+
print("Socket mode client created")
|
|
69
|
+
self.socket_mode_client = socket_mode_client
|
|
70
|
+
|
|
71
|
+
def post_message(
|
|
72
|
+
self,
|
|
73
|
+
message: Union[str, List[str]],
|
|
74
|
+
channel: str,
|
|
75
|
+
botname: Optional[str] = None,
|
|
76
|
+
thread_ts: Optional[str] = None,
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
Posts a slack message to the given channel
|
|
80
|
+
If the message is an array, this will post each element of the array as a thread
|
|
81
|
+
|
|
82
|
+
Returns the thread_ts to allow chaining messages
|
|
83
|
+
"""
|
|
84
|
+
# Allow optional channel overrides via an environment variable
|
|
85
|
+
channel = os.environ.get(e.SLACK_CHANNEL_OVERRIDE, default=channel)
|
|
86
|
+
|
|
87
|
+
# Listify
|
|
88
|
+
messages = [message] if type(message) is str else message
|
|
89
|
+
|
|
90
|
+
# Post each message, threading with the previous
|
|
91
|
+
thread_ts = None or thread_ts
|
|
92
|
+
for msg in messages:
|
|
93
|
+
response = self.client.chat_postMessage(
|
|
94
|
+
channel=channel,
|
|
95
|
+
text=msg,
|
|
96
|
+
thread_ts=thread_ts,
|
|
97
|
+
username=botname,
|
|
98
|
+
)
|
|
99
|
+
if thread_ts is None: # API advises to use parent IDs instead of child's
|
|
100
|
+
thread_ts = response["ts"]
|
|
101
|
+
|
|
102
|
+
return thread_ts
|
|
103
|
+
|
|
104
|
+
def upload_file(self, file_name: str, content: str) -> str:
|
|
105
|
+
"""
|
|
106
|
+
This uploads a file and returns a link that can be used to embed a file in slack
|
|
107
|
+
Ex:
|
|
108
|
+
url = upload_file("test.txt", "Hello World")
|
|
109
|
+
post_msg(f"<{url}|This is a file>", channel="#alerts-debug")
|
|
110
|
+
"""
|
|
111
|
+
slack_file = self.client.files_upload_v2(filename=file_name, content=content)
|
|
112
|
+
file_link = slack_file["file"]["permalink"]
|
|
113
|
+
return file_link
|
|
114
|
+
|
|
115
|
+
def listen_to_channel(
|
|
116
|
+
self, message_callback: Callable[[BaseSocketModeClient, dict], None], target_channel: str
|
|
117
|
+
) -> None:
|
|
118
|
+
"""
|
|
119
|
+
Starts a listener for slack messages in the specified channel using SocketMode.
|
|
120
|
+
Calls `message_callback` with the event data when a message is received.
|
|
121
|
+
"""
|
|
122
|
+
if not self.socket_mode_client:
|
|
123
|
+
self.setup_for_listening()
|
|
124
|
+
assert self.socket_mode_client is not None, "Failed to initialize socket mode client"
|
|
125
|
+
|
|
126
|
+
def handle_message(client: BaseSocketModeClient, req: SocketModeRequest):
|
|
127
|
+
if req.type == "events_api":
|
|
128
|
+
# ack the request immediately
|
|
129
|
+
response = SocketModeResponse(envelope_id=req.envelope_id)
|
|
130
|
+
if req.payload["event"]["channel"] == target_channel:
|
|
131
|
+
client.send_socket_mode_response(response)
|
|
132
|
+
message_callback(client, req.payload["event"])
|
|
133
|
+
|
|
134
|
+
self.socket_mode_client.socket_mode_request_listeners.append(handle_message)
|
|
135
|
+
self.socket_mode_client.connect()
|
|
136
|
+
|
|
137
|
+
def get_prior_messages_in_channel(self, channel: str, limit: int = 200, max_age=-1) -> List[List[SlackMessage]]:
|
|
138
|
+
"""
|
|
139
|
+
Fetch the most recent messages from a Slack channel.
|
|
140
|
+
Args:
|
|
141
|
+
channel (str): ID of the Slack channel.
|
|
142
|
+
limit (int): Maximum number of messages to fetch (default: 100).
|
|
143
|
+
max_age (int): Maximum age in seconds of messages to fetch (default: -1, fetch all messages).
|
|
144
|
+
Returns:
|
|
145
|
+
List[List[SlackMessages]]: A list of list of message objects.
|
|
146
|
+
Each element in the array is a list of messages in that thread.
|
|
147
|
+
"""
|
|
148
|
+
# fetch message history
|
|
149
|
+
response = self.client.conversations_history(channel=channel, limit=limit)
|
|
150
|
+
|
|
151
|
+
raw_messages = response.get("messages", [])
|
|
152
|
+
channel_messages: list[list[SlackMessage]] = []
|
|
153
|
+
now_time = datetime.datetime.now()
|
|
154
|
+
for raw_message in raw_messages:
|
|
155
|
+
# check if message is too old
|
|
156
|
+
if max_age > 0:
|
|
157
|
+
message_time = datetime.datetime.fromtimestamp(float(raw_message["ts"]))
|
|
158
|
+
if (now_time - message_time).total_seconds() > max_age:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
if 'thread_ts' in raw_message:
|
|
162
|
+
# make sure we haven't processed this thread already.
|
|
163
|
+
# This happens when multiple messages from the same thread are returned in `response`.
|
|
164
|
+
# After we process the first message, because we call `conversations_replies` below,
|
|
165
|
+
# we will have already processed the whole thread.
|
|
166
|
+
thread_ts = raw_message['thread_ts']
|
|
167
|
+
if thread_ts in [msg[0].thread_ts for msg in channel_messages]:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# this is a thread, fetch the full thread
|
|
171
|
+
thread_replies = self.client.conversations_replies(channel=channel, ts=thread_ts)
|
|
172
|
+
thread_msgs = []
|
|
173
|
+
for reply in thread_replies["messages"]:
|
|
174
|
+
thread_msg = SlackMessage.from_dict(reply)
|
|
175
|
+
thread_msgs.append(thread_msg)
|
|
176
|
+
channel_messages.append(thread_msgs)
|
|
177
|
+
|
|
178
|
+
else:
|
|
179
|
+
# this is not a thread
|
|
180
|
+
slack_msg = SlackMessage.from_dict(raw_message)
|
|
181
|
+
channel_messages.append([slack_msg])
|
|
182
|
+
|
|
183
|
+
return channel_messages
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from _typeshed import Incomplete
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from slack_sdk.socket_mode.client import BaseSocketModeClient
|
|
5
|
+
from strideutils.stride_config import get_env_or_raise as get_env_or_raise
|
|
6
|
+
from typing import Callable
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class SlackMessage:
|
|
10
|
+
timestamp: datetime.datetime
|
|
11
|
+
timestamp_id: str
|
|
12
|
+
thread_ts: str | None
|
|
13
|
+
sender: str
|
|
14
|
+
text: str
|
|
15
|
+
@classmethod
|
|
16
|
+
def from_dict(cls, message: dict): ...
|
|
17
|
+
|
|
18
|
+
class SlackClient:
|
|
19
|
+
def __new__(cls): ...
|
|
20
|
+
api_token: Incomplete
|
|
21
|
+
client: Incomplete
|
|
22
|
+
socket_mode_client: Incomplete
|
|
23
|
+
def __init__(self) -> None: ...
|
|
24
|
+
app_token: Incomplete
|
|
25
|
+
def setup_for_listening(self) -> None: ...
|
|
26
|
+
def post_message(self, message: str | list[str], channel: str, botname: str | None = None, thread_ts: str | None = None): ...
|
|
27
|
+
def upload_file(self, file_name: str, content: str) -> str: ...
|
|
28
|
+
def listen_to_channel(self, message_callback: Callable[[BaseSocketModeClient, dict], None], target_channel: str) -> None: ...
|
|
29
|
+
def get_prior_messages_in_channel(self, channel: str, limit: int = 200, max_age: int = -1) -> list[list[SlackMessage]]: ...
|
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
This file loads and provide an object `config` to access all secrets, configs, and chain info
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import itertools
|
|
6
5
|
import os
|
|
7
6
|
from dataclasses import dataclass, field, fields
|
|
8
|
-
from typing import Any, Dict, List, Optional,
|
|
7
|
+
from typing import Any, Dict, List, Optional, cast
|
|
9
8
|
|
|
10
9
|
import yaml
|
|
11
10
|
from dotenv import load_dotenv
|
|
@@ -33,6 +32,7 @@ class Environment:
|
|
|
33
32
|
TWILIO_API_TOKEN = "TWILIO_API_TOKEN"
|
|
34
33
|
|
|
35
34
|
STRIDEBOT_API_TOKEN = "STRIDEBOT_API_TOKEN"
|
|
35
|
+
STRIDEBOT_APP_TOKEN = "STRIDEBOT_APP_TOKEN"
|
|
36
36
|
SLACK_CHANNEL_OVERRIDE = "SLACK_CHANNEL_OVERRIDE"
|
|
37
37
|
|
|
38
38
|
PUBLICSHEETS_AUTH = "PUBLICSHEETS_AUTH"
|
|
@@ -191,6 +191,10 @@ class Config(ConfigObj):
|
|
|
191
191
|
def app_chains(self) -> List[ChainConfig]:
|
|
192
192
|
return [app_chain for app_chain in self._app_chains.values()]
|
|
193
193
|
|
|
194
|
+
@property
|
|
195
|
+
def chains(self) -> List[ChainConfig]:
|
|
196
|
+
return self.host_zones + self.app_chains
|
|
197
|
+
|
|
194
198
|
@property
|
|
195
199
|
def host_zone_names(self) -> List[str]:
|
|
196
200
|
return [host_zone for host_zone in self._host_zones.keys()]
|
|
@@ -199,55 +203,33 @@ class Config(ConfigObj):
|
|
|
199
203
|
def app_chain_names(self) -> List[str]:
|
|
200
204
|
return [app_chain for app_chain in self._app_chains.keys()]
|
|
201
205
|
|
|
202
|
-
def
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
except AttributeError:
|
|
230
|
-
pass
|
|
231
|
-
|
|
232
|
-
try:
|
|
233
|
-
denom_to_zone[chain_info.denom] = chain_info
|
|
234
|
-
except AttributeError:
|
|
235
|
-
pass
|
|
236
|
-
|
|
237
|
-
if isinstance(chain_info, HostChainConfig):
|
|
238
|
-
try:
|
|
239
|
-
denom_on_stride_to_zone[chain_info.denom_on_stride] = chain_info
|
|
240
|
-
except AttributeError:
|
|
241
|
-
pass
|
|
242
|
-
|
|
243
|
-
self._name_to_zone = name_to_zone
|
|
244
|
-
self._id_to_zone = id_to_zone
|
|
245
|
-
self._ticker_to_zone = ticker_to_zone
|
|
246
|
-
self._denom_to_zone = denom_to_zone
|
|
247
|
-
self._denom_on_stride_to_zone = denom_on_stride_to_zone
|
|
248
|
-
|
|
249
|
-
def __post_init__(self):
|
|
250
|
-
self._create_mappings()
|
|
206
|
+
def _evaluate_chain_match(
|
|
207
|
+
self,
|
|
208
|
+
chain: ChainConfig,
|
|
209
|
+
name: Optional[str],
|
|
210
|
+
id: Optional[str],
|
|
211
|
+
ticker: Optional[str],
|
|
212
|
+
denom: Optional[str],
|
|
213
|
+
denom_on_stride: Optional[str],
|
|
214
|
+
) -> bool:
|
|
215
|
+
if name is not None:
|
|
216
|
+
return chain.name == name
|
|
217
|
+
|
|
218
|
+
if id is not None:
|
|
219
|
+
return chain.id == id
|
|
220
|
+
|
|
221
|
+
if ticker is not None:
|
|
222
|
+
chain_tickers = [chain.ticker, chain.ticker.upper(), chain.ticker.lower(), chain.denom]
|
|
223
|
+
return ticker in chain_tickers
|
|
224
|
+
|
|
225
|
+
if denom is not None:
|
|
226
|
+
return chain.denom == denom
|
|
227
|
+
|
|
228
|
+
if denom_on_stride is not None:
|
|
229
|
+
if isinstance(chain, HostChainConfig):
|
|
230
|
+
return chain.denom_on_stride == denom_on_stride
|
|
231
|
+
|
|
232
|
+
return False
|
|
251
233
|
|
|
252
234
|
def get_chain(
|
|
253
235
|
self,
|
|
@@ -256,7 +238,7 @@ class Config(ConfigObj):
|
|
|
256
238
|
ticker: Optional[str] = None,
|
|
257
239
|
denom: Optional[str] = None,
|
|
258
240
|
denom_on_stride: Optional[str] = None,
|
|
259
|
-
) ->
|
|
241
|
+
) -> ChainConfig:
|
|
260
242
|
"""
|
|
261
243
|
Fetch info about a host zone by name, id, ticker, denom, or token hash.
|
|
262
244
|
|
|
@@ -274,22 +256,18 @@ class Config(ConfigObj):
|
|
|
274
256
|
):
|
|
275
257
|
return self.stride
|
|
276
258
|
|
|
277
|
-
|
|
278
|
-
if
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
raise KeyError("No chain found for the given parameter")
|
|
290
|
-
|
|
291
|
-
# This line should never be reached due to the initial check and the try-except block.
|
|
292
|
-
raise KeyError("Unexpected error in get_chain method")
|
|
259
|
+
for chain in self.chains:
|
|
260
|
+
if self._evaluate_chain_match(
|
|
261
|
+
chain=chain,
|
|
262
|
+
name=name,
|
|
263
|
+
id=id,
|
|
264
|
+
ticker=ticker,
|
|
265
|
+
denom=denom,
|
|
266
|
+
denom_on_stride=denom_on_stride,
|
|
267
|
+
):
|
|
268
|
+
return chain
|
|
269
|
+
|
|
270
|
+
raise KeyError("No chain found for the given parameter")
|
|
293
271
|
|
|
294
272
|
def get_host_chain(
|
|
295
273
|
self,
|
|
@@ -306,16 +284,21 @@ class Config(ConfigObj):
|
|
|
306
284
|
KeyError if a valid host chain doesn't exist or if multiple parameters are specified
|
|
307
285
|
TypeError if the found chain is not a host chain
|
|
308
286
|
"""
|
|
309
|
-
|
|
310
|
-
name
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
287
|
+
if sum(map(bool, [name, id, ticker, denom, denom_on_stride])) != 1:
|
|
288
|
+
raise KeyError("Exactly one of name, id, ticker, denom, or token hash must be specified.")
|
|
289
|
+
|
|
290
|
+
for chain in self.host_zones:
|
|
291
|
+
if self._evaluate_chain_match(
|
|
292
|
+
chain=chain,
|
|
293
|
+
name=name,
|
|
294
|
+
id=id,
|
|
295
|
+
ticker=ticker,
|
|
296
|
+
denom=denom,
|
|
297
|
+
denom_on_stride=denom_on_stride,
|
|
298
|
+
):
|
|
299
|
+
return chain
|
|
300
|
+
|
|
301
|
+
raise KeyError("No host chain found for the given parameter")
|
|
319
302
|
|
|
320
303
|
|
|
321
304
|
def _load_raw_config() -> dict[str, Any]:
|
|
@@ -14,6 +14,7 @@ class Environment:
|
|
|
14
14
|
TWILIO_ACCOUNT_ID = ...
|
|
15
15
|
TWILIO_API_TOKEN = ...
|
|
16
16
|
STRIDEBOT_API_TOKEN = ...
|
|
17
|
+
STRIDEBOT_APP_TOKEN = ...
|
|
17
18
|
SLACK_CHANNEL_OVERRIDE = ...
|
|
18
19
|
PUBLICSHEETS_AUTH = ...
|
|
19
20
|
COINGECKO_API_TOKEN = ...
|
|
@@ -80,11 +81,12 @@ class Config(ConfigObj):
|
|
|
80
81
|
@property
|
|
81
82
|
def app_chains(self) -> list[ChainConfig]: ...
|
|
82
83
|
@property
|
|
84
|
+
def chains(self) -> list[ChainConfig]: ...
|
|
85
|
+
@property
|
|
83
86
|
def host_zone_names(self) -> list[str]: ...
|
|
84
87
|
@property
|
|
85
88
|
def app_chain_names(self) -> list[str]: ...
|
|
86
|
-
def
|
|
87
|
-
def get_chain(self, name: str | None = None, id: str | None = None, ticker: str | None = None, denom: str | None = None, denom_on_stride: str | None = None) -> ChainConfig | StrideChainConfig | HostChainConfig: ...
|
|
89
|
+
def get_chain(self, name: str | None = None, id: str | None = None, ticker: str | None = None, denom: str | None = None, denom_on_stride: str | None = None) -> ChainConfig: ...
|
|
88
90
|
def get_host_chain(self, name: str | None = None, id: str | None = None, ticker: str | None = None, denom: str | None = None, denom_on_stride: str | None = None) -> HostChainConfig: ...
|
|
89
91
|
|
|
90
92
|
config: Config
|
|
@@ -15,5 +15,5 @@ class TxResponse:
|
|
|
15
15
|
tx_response: dict | None = ...
|
|
16
16
|
raw_log: str | None = ...
|
|
17
17
|
|
|
18
|
-
def restore_wallet(
|
|
18
|
+
def restore_wallet(account_name: str = 'wallet', mnemonic_var: str = 'MNEMONIC', binary: str = 'strided'): ...
|
|
19
19
|
def execute_tx(command: str, api_endpoint: str = ...) -> TxResponse: ...
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
1
3
|
import gspread
|
|
2
4
|
import pandas as pd
|
|
3
5
|
|
|
@@ -12,5 +14,6 @@ class GoogleSheetsClient:
|
|
|
12
14
|
def __init__(self, cache_enabled: bool = False) -> None: ...
|
|
13
15
|
def grab_sheet(self, sheet_id: str, sheet_name: str, columns_labeled: bool = True) -> pd.DataFrame: ...
|
|
14
16
|
def write_sheet(self, df: pd.DataFrame, sheet_id: str, sheet_name: str) -> None: ...
|
|
17
|
+
def write_cell(self, value: list[list[Any]], range_name: str, sheet_id: str, sheet_name: str) -> None: ...
|
|
15
18
|
def reorder_sheet(self, sheet_id: str, sheet_name: str, new_location: int = 0) -> None: ...
|
|
16
19
|
def get_sheet_names(self, sheet_id: str) -> list[str]: ...
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
from _typeshed import Incomplete
|
|
6
|
+
from slack_sdk.socket_mode.client import BaseSocketModeClient
|
|
7
|
+
|
|
8
|
+
from strideutils.stride_config import get_env_or_raise as get_env_or_raise
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class SlackMessage:
|
|
12
|
+
timestamp: datetime.datetime
|
|
13
|
+
timestamp_id: str
|
|
14
|
+
thread_ts: str | None
|
|
15
|
+
sender: str
|
|
16
|
+
text: str
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_dict(cls, message: dict): ...
|
|
19
|
+
|
|
20
|
+
class SlackClient:
|
|
21
|
+
def __new__(cls): ...
|
|
22
|
+
api_token: Incomplete
|
|
23
|
+
client: Incomplete
|
|
24
|
+
socket_mode_client: Incomplete
|
|
25
|
+
def __init__(self) -> None: ...
|
|
26
|
+
app_token: Incomplete
|
|
27
|
+
def setup_for_listening(self) -> None: ...
|
|
28
|
+
def post_message(
|
|
29
|
+
self, message: str | list[str], channel: str, botname: str | None = None, thread_ts: str | None = None
|
|
30
|
+
): ...
|
|
31
|
+
def upload_file(self, file_name: str, content: str) -> str: ...
|
|
32
|
+
def listen_to_channel(
|
|
33
|
+
self, message_callback: Callable[[BaseSocketModeClient, dict], None], target_channel: str
|
|
34
|
+
) -> None: ...
|
|
35
|
+
def get_prior_messages_in_channel(
|
|
36
|
+
self, channel: str, limit: int = 200, max_age: int = -1
|
|
37
|
+
) -> list[list[SlackMessage]]: ...
|
|
@@ -14,6 +14,7 @@ class Environment:
|
|
|
14
14
|
TWILIO_ACCOUNT_ID = ...
|
|
15
15
|
TWILIO_API_TOKEN = ...
|
|
16
16
|
STRIDEBOT_API_TOKEN = ...
|
|
17
|
+
STRIDEBOT_APP_TOKEN = ...
|
|
17
18
|
SLACK_CHANNEL_OVERRIDE = ...
|
|
18
19
|
PUBLICSHEETS_AUTH = ...
|
|
19
20
|
COINGECKO_API_TOKEN = ...
|
|
@@ -79,10 +80,11 @@ class Config(ConfigObj):
|
|
|
79
80
|
@property
|
|
80
81
|
def app_chains(self) -> list[ChainConfig]: ...
|
|
81
82
|
@property
|
|
83
|
+
def chains(self) -> list[ChainConfig]: ...
|
|
84
|
+
@property
|
|
82
85
|
def host_zone_names(self) -> list[str]: ...
|
|
83
86
|
@property
|
|
84
87
|
def app_chain_names(self) -> list[str]: ...
|
|
85
|
-
def __post_init__(self) -> None: ...
|
|
86
88
|
def get_chain(
|
|
87
89
|
self,
|
|
88
90
|
name: str | None = None,
|
|
@@ -90,7 +92,7 @@ class Config(ConfigObj):
|
|
|
90
92
|
ticker: str | None = None,
|
|
91
93
|
denom: str | None = None,
|
|
92
94
|
denom_on_stride: str | None = None,
|
|
93
|
-
) -> ChainConfig
|
|
95
|
+
) -> ChainConfig: ...
|
|
94
96
|
def get_host_chain(
|
|
95
97
|
self,
|
|
96
98
|
name: str | None = None,
|
|
@@ -222,13 +222,13 @@ class TestParseConfig:
|
|
|
222
222
|
assert isinstance(host_chain, HostChainConfig)
|
|
223
223
|
|
|
224
224
|
# Test looking up stride (should fail)
|
|
225
|
-
with pytest.raises(
|
|
225
|
+
with pytest.raises(KeyError, match="No host chain found for the given parameter"):
|
|
226
226
|
config.get_host_chain(name="stride")
|
|
227
227
|
|
|
228
228
|
# Test looking up an app chain (should fail)
|
|
229
|
-
with pytest.raises(
|
|
229
|
+
with pytest.raises(KeyError, match="No host chain found for the given parameter"):
|
|
230
230
|
config.get_host_chain(name="neutron")
|
|
231
231
|
|
|
232
232
|
# Test looking up non-existent chain
|
|
233
|
-
with pytest.raises(KeyError, match="No chain found for the given parameter"):
|
|
234
|
-
config.get_host_chain(name="
|
|
233
|
+
with pytest.raises(KeyError, match="No host chain found for the given parameter"):
|
|
234
|
+
config.get_host_chain(name="no-exist")
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import traceback
|
|
3
|
-
from typing import List, Optional, Union
|
|
4
|
-
|
|
5
|
-
from slack_sdk import WebClient
|
|
6
|
-
from slack_sdk.errors import SlackApiError
|
|
7
|
-
|
|
8
|
-
from strideutils.stride_config import Environment as e
|
|
9
|
-
from strideutils.stride_config import config, get_env_or_raise
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class SlackClient:
|
|
13
|
-
"""
|
|
14
|
-
Singleton client used to post messages to slack
|
|
15
|
-
"""
|
|
16
|
-
|
|
17
|
-
_instance = None
|
|
18
|
-
|
|
19
|
-
def __new__(cls):
|
|
20
|
-
# Creates a new instance if one does not already exist
|
|
21
|
-
if cls._instance is None:
|
|
22
|
-
cls._instance = super(SlackClient, cls).__new__(cls)
|
|
23
|
-
return cls._instance
|
|
24
|
-
|
|
25
|
-
def __init__(self):
|
|
26
|
-
# Prevent re-initialization if a client has already been created
|
|
27
|
-
if hasattr(self, "client"):
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
self.api_token = get_env_or_raise(e.STRIDEBOT_API_TOKEN)
|
|
31
|
-
self.client = WebClient(token=self.api_token)
|
|
32
|
-
|
|
33
|
-
def post_message(
|
|
34
|
-
self,
|
|
35
|
-
message: Union[str, List[str]],
|
|
36
|
-
channel: str,
|
|
37
|
-
botname: Optional[str] = None,
|
|
38
|
-
thread_ts: Optional[str] = None,
|
|
39
|
-
):
|
|
40
|
-
"""
|
|
41
|
-
Posts a slack message to the given channel
|
|
42
|
-
If the message is an array, this will post each element of the array as a thread
|
|
43
|
-
|
|
44
|
-
Returns the thread_ts to allow chaining messages
|
|
45
|
-
"""
|
|
46
|
-
# Allow optional channel overrides via an environment variable
|
|
47
|
-
channel = os.environ.get(e.SLACK_CHANNEL_OVERRIDE, default=channel)
|
|
48
|
-
|
|
49
|
-
# Listify
|
|
50
|
-
messages = [message] if type(message) is str else message
|
|
51
|
-
|
|
52
|
-
# Post each message, threading with the previous
|
|
53
|
-
thread_ts = None or thread_ts
|
|
54
|
-
for msg in messages:
|
|
55
|
-
response = self.client.chat_postMessage(
|
|
56
|
-
channel=channel,
|
|
57
|
-
text=msg,
|
|
58
|
-
thread_ts=thread_ts,
|
|
59
|
-
username=botname,
|
|
60
|
-
)
|
|
61
|
-
if thread_ts is None: # API advises to use parent IDs instead of child's
|
|
62
|
-
thread_ts = response["ts"]
|
|
63
|
-
|
|
64
|
-
return thread_ts
|
|
65
|
-
|
|
66
|
-
def upload_file(self, file_name: str, content: str) -> str:
|
|
67
|
-
"""
|
|
68
|
-
This uploads a file and returns a link that can be used to embed a file in slack
|
|
69
|
-
Ex:
|
|
70
|
-
url = upload_file("test.txt", "Hello World")
|
|
71
|
-
post_msg(f"<{url}|This is a file>", channel="#alerts-debug")
|
|
72
|
-
"""
|
|
73
|
-
slack_file = self.client.files_upload_v2(filename=file_name, content=content)
|
|
74
|
-
file_link = slack_file["file"]["permalink"]
|
|
75
|
-
return file_link
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
# TODO: Remove after all launchpad instances are removed
|
|
79
|
-
def post_msg(
|
|
80
|
-
txt: Union[str, List[str]],
|
|
81
|
-
channel: str,
|
|
82
|
-
botname: Optional[str] = None,
|
|
83
|
-
unfurl_links: bool = False,
|
|
84
|
-
unfurl_media: bool = False,
|
|
85
|
-
thread_ts: Optional[str] = None,
|
|
86
|
-
):
|
|
87
|
-
"""
|
|
88
|
-
DEPRECATED: Use slack client instead
|
|
89
|
-
|
|
90
|
-
Posts a slack message to the given channel, e.g.
|
|
91
|
-
|
|
92
|
-
post_msg('hello world', 'general')
|
|
93
|
-
|
|
94
|
-
if txt is an array, this will post each element of the array as a thread
|
|
95
|
-
|
|
96
|
-
Args:
|
|
97
|
-
username: Rename stridebot
|
|
98
|
-
"""
|
|
99
|
-
token = get_env_or_raise(e.STRIDEBOT_API_TOKEN)
|
|
100
|
-
client = WebClient(token=token)
|
|
101
|
-
channel = config.slack_channels.get(channel.replace('#', ''), channel)
|
|
102
|
-
channel = os.environ.get('SLACK_CHANNEL_OVERRIDE', default=channel)
|
|
103
|
-
|
|
104
|
-
# Listify
|
|
105
|
-
messages = [txt] if type(txt) is str else txt
|
|
106
|
-
|
|
107
|
-
# thread ts keeps track of the latest thread ID so we can keep replying in-thread
|
|
108
|
-
thread_ts = None or thread_ts
|
|
109
|
-
for msg in messages:
|
|
110
|
-
try:
|
|
111
|
-
response = client.chat_postMessage(
|
|
112
|
-
channel=channel,
|
|
113
|
-
text=msg,
|
|
114
|
-
thread_ts=thread_ts,
|
|
115
|
-
username=botname,
|
|
116
|
-
unfurl_links=unfurl_links,
|
|
117
|
-
unfurl_media=unfurl_media,
|
|
118
|
-
)
|
|
119
|
-
if thread_ts is None: # API advises to use parent IDs instead of child's
|
|
120
|
-
thread_ts = response['ts']
|
|
121
|
-
except SlackApiError as error:
|
|
122
|
-
print("Error sending message: ", error)
|
|
123
|
-
raise
|
|
124
|
-
return thread_ts
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def upload_file(file_name: str, content: str) -> str:
|
|
128
|
-
"""
|
|
129
|
-
DEPRECATED: Use slack client instead
|
|
130
|
-
|
|
131
|
-
This uploads a file called "file_name" with the string contents "content"
|
|
132
|
-
|
|
133
|
-
This will return a link that can be used to embed a file in slack.
|
|
134
|
-
For example:
|
|
135
|
-
url = upload_file("test.txt", "Hello World")
|
|
136
|
-
post_msg(f"<{url}|This is a file>", channel="#alerts-debug")
|
|
137
|
-
"""
|
|
138
|
-
token = get_env_or_raise(e.STRIDEBOT_API_TOKEN)
|
|
139
|
-
client = WebClient(token=token)
|
|
140
|
-
try:
|
|
141
|
-
slack_file = client.files_upload_v2(filename=file_name, content=content)
|
|
142
|
-
except Exception as error:
|
|
143
|
-
print(f"Error uploading file to slack: {error}")
|
|
144
|
-
traceback.print_exc()
|
|
145
|
-
raise ValueError(f"Error uploading file to slack: {error}")
|
|
146
|
-
file_link = slack_file['file']['permalink']
|
|
147
|
-
return file_link
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from _typeshed import Incomplete
|
|
2
|
-
from strideutils.stride_config import config as config, get_env_or_raise as get_env_or_raise
|
|
3
|
-
|
|
4
|
-
class SlackClient:
|
|
5
|
-
def __new__(cls): ...
|
|
6
|
-
api_token: Incomplete
|
|
7
|
-
client: Incomplete
|
|
8
|
-
def __init__(self) -> None: ...
|
|
9
|
-
def post_message(self, message: str | list[str], channel: str, botname: str | None = None, thread_ts: str | None = None): ...
|
|
10
|
-
def upload_file(self, file_name: str, content: str) -> str: ...
|
|
11
|
-
|
|
12
|
-
def post_msg(txt: str | list[str], channel: str, botname: str | None = None, unfurl_links: bool = False, unfurl_media: bool = False, thread_ts: str | None = None): ...
|
|
13
|
-
def upload_file(file_name: str, content: str) -> str: ...
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from _typeshed import Incomplete
|
|
2
|
-
|
|
3
|
-
from strideutils.stride_config import config as config
|
|
4
|
-
from strideutils.stride_config import get_env_or_raise as get_env_or_raise
|
|
5
|
-
|
|
6
|
-
class SlackClient:
|
|
7
|
-
def __new__(cls): ...
|
|
8
|
-
api_token: Incomplete
|
|
9
|
-
client: Incomplete
|
|
10
|
-
def __init__(self) -> None: ...
|
|
11
|
-
def post_message(
|
|
12
|
-
self, message: str | list[str], channel: str, botname: str | None = None, thread_ts: str | None = None
|
|
13
|
-
): ...
|
|
14
|
-
def upload_file(self, file_name: str, content: str) -> str: ...
|
|
15
|
-
|
|
16
|
-
def post_msg(
|
|
17
|
-
txt: str | list[str],
|
|
18
|
-
channel: str,
|
|
19
|
-
botname: str | None = None,
|
|
20
|
-
unfurl_links: bool = False,
|
|
21
|
-
unfurl_media: bool = False,
|
|
22
|
-
thread_ts: str | None = None,
|
|
23
|
-
): ...
|
|
24
|
-
def upload_file(file_name: str, content: str) -> str: ...
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{strideutils-0.8.4 → strideutils-0.8.6}/strideutils/config_examples/strideutils_config.yaml.example
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|