Add triage tests, prepare-export, and UI fixes for Levkin migration.
CI / skip-ci-check (push) Successful in 4s
CI / secret-scan (push) Failing after 2s
CI / python-ci (push) Successful in 25s

Cherry-pick heuristics, manifest merge, global file search, scrollbar styling, and pytest/Playwright coverage unblock review and filtered export for mmetl.
This commit is contained in:
2026-06-03 15:42:48 -04:00
parent 88b7082f4f
commit 4b034d19f6
48 changed files with 6904 additions and 126 deletions
+69
View File
@@ -0,0 +1,69 @@
# Tests
## Fixture (seeded from your export)
`tests/fixtures/mini-export/` is a small slice of the real Levkin export:
- `#medical`, `#zoey`, `#153niagara` (first 4 day-files each, ~54 messages)
- Regenerate after export changes:
```bash
make fixture
# or
python3 scripts/build-test-fixture.py "/path/to/export"
```
## Python (unit + API)
```bash
make test-unit
```
Covers:
- `slack_export.py` — parse, search, file types
- `scripts/auto-cherry-pick.py` — drafts, data-only rules, return channels
- `server.py` — HTTP API against mini-export
Uses `.venv` automatically.
## Playwright (UI)
```bash
make test-e2e
```
Covers:
- Channel list, filters (**All** / **Has picks**)
- Cherry-pick checkbox on/off
- Detail panel **Skip**
- **Only selected** filter
- Global **Files** search
Runs server on port **18765** with `tests/fixtures/test-manifest.json`.
## All
```bash
make test
```
## Coverage
```bash
make test-unit # includes --cov report
```
Current targets (~**98%** Python, **11** Playwright tests):
| Module | Coverage | Notes |
|--------|----------|-------|
| `slack_export.py` | ~98% | canvases index, HTTP 401, search limits |
| `server.py` | ~98% | logging when not quiet, empty DOCX body |
| `scripts/auto-cherry-pick.py` | via `test_auto_cherry_pick_main.py` | full `main()` on fixture |
| `static/app.js` | Playwright e2e | bulk select, text search, save |
**100** pytest cases + **11** e2e. Manifest is copied per session and reset before each API test so POST cases do not leak state.
Server runs **in-process** during pytest; Playwright uses port **18765**.
+138
View File
@@ -0,0 +1,138 @@
"""Pytest fixtures — mini-export seeded from real Levkin Slack data."""
from __future__ import annotations
import importlib.util
import json
import shutil
import socket
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[1]
FIXTURE_EXPORT = REPO_ROOT / "tests" / "fixtures" / "mini-export"
TEST_MANIFEST = REPO_ROOT / "tests" / "fixtures" / "test-manifest.json"
def _load_cherry_pick_module():
path = REPO_ROOT / "scripts" / "auto-cherry-pick.py"
spec = importlib.util.spec_from_file_location("auto_cherry_pick", path)
mod = importlib.util.module_from_spec(spec)
assert spec.loader
spec.loader.exec_module(mod)
return mod
@pytest.fixture(scope="session")
def cherry_pick():
return _load_cherry_pick_module()
@pytest.fixture(scope="session")
def fixture_export_path() -> Path:
assert FIXTURE_EXPORT.is_dir(), (
f"Run: python3 scripts/build-test-fixture.py ({FIXTURE_EXPORT})"
)
return FIXTURE_EXPORT
@pytest.fixture
def export(fixture_export_path):
sys.path.insert(0, str(REPO_ROOT))
from slack_export import SlackExport
return SlackExport(fixture_export_path)
@pytest.fixture
def empty_manifest(fixture_export_path, tmp_path):
data = {
"version": 1,
"export_path": str(fixture_export_path),
"updated_at": None,
"global_notes": "",
"channels": {},
"users": {},
}
path = tmp_path / "manifest.json"
path.write_text(json.dumps(data, indent=2) + "\n", encoding="utf-8")
return path, data
def _free_port() -> int:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]
def _wait_health(url: str, timeout: float = 8.0) -> None:
deadline = time.time() + timeout
while time.time() < deadline:
try:
with urllib.request.urlopen(url, timeout=1) as resp:
if resp.status == 200:
return
except (urllib.error.URLError, TimeoutError):
time.sleep(0.15)
raise RuntimeError(f"Server did not start: {url}")
@pytest.fixture(scope="session")
def review_server(tmp_path_factory):
"""Start review server in-process (for coverage) with isolated manifest copy."""
import os
import threading
from http.server import ThreadingHTTPServer
sys.path.insert(0, str(REPO_ROOT))
from server import ReviewHandler
from slack_export import SlackExport
port = _free_port()
manifest = tmp_path_factory.mktemp("manifest") / "manifest.json"
shutil.copy(TEST_MANIFEST, manifest)
export = SlackExport(FIXTURE_EXPORT)
handler_cls = type(
"BoundReviewHandler",
(ReviewHandler,),
{"export": export, "manifest_path": manifest},
)
os.environ["SLACK_IMPORT_QUIET"] = "1"
httpd = ThreadingHTTPServer(("127.0.0.1", port), handler_cls)
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
thread.start()
base = f"http://127.0.0.1:{port}"
try:
_wait_health(f"{base}/api/health")
yield {"base": base, "port": port, "httpd": httpd, "manifest_path": manifest}
finally:
httpd.shutdown()
@pytest.fixture
def api(review_server):
# Reset manifest so POST tests cannot leak state across cases.
shutil.copy(TEST_MANIFEST, review_server["manifest_path"])
base = review_server["base"]
def get(path: str) -> dict:
with urllib.request.urlopen(f"{base}{path}", timeout=5) as resp:
return json.loads(resp.read().decode())
def post(path: str, body: dict) -> dict:
data = json.dumps(body).encode("utf-8")
req = urllib.request.Request(
f"{base}{path}",
data=data,
headers={"Content-Type": "application/json"},
method="POST",
)
with urllib.request.urlopen(req, timeout=5) as resp:
return json.loads(resp.read().decode())
return {"get": get, "post": post, "base": base}
+139
View File
@@ -0,0 +1,139 @@
import { test, expect } from "@playwright/test";
const BASE = process.env.SLACK_REVIEW_URL || "http://127.0.0.1:18765";
async function waitForAppReady(page: import("@playwright/test").Page) {
await page.goto(BASE);
await expect(page.locator("#channel-title")).not.toHaveText("Select a channel", {
timeout: 15_000,
});
}
async function showAllChannels(page: import("@playwright/test").Page) {
await page.locator("#filter-status").selectOption("all");
await expect(page.locator("#sidebar-list .list-item").first()).toBeVisible();
}
async function openChannel(page: import("@playwright/test").Page, name: string) {
await showAllChannels(page);
const btn = page.locator("#sidebar-list button").filter({ hasText: name });
await btn.first().click();
}
test.describe("Review UI", () => {
test.beforeAll(async ({ request }) => {
const res = await request.get(`${BASE}/api/health`);
expect(res.ok()).toBeTruthy();
});
test("loads channel list and stats", async ({ page }) => {
await waitForAppReady(page);
await expect(page.locator("h1")).toHaveText("Slack import review");
await expect(page.locator("#stats")).not.toBeEmpty();
await expect(page.locator("#sidebar-list .list-item").first()).toBeVisible({
timeout: 10_000,
});
});
test("opens medical channel and shows messages", async ({ page }) => {
await waitForAppReady(page);
await openChannel(page, "medical");
await expect(page.locator("#channel-title")).toHaveText("#medical");
await expect(page.locator(".message").first()).toBeVisible({ timeout: 10_000 });
});
test("cherry-pick checkbox toggles on and off", async ({ page }) => {
await waitForAppReady(page);
await openChannel(page, "medical");
await page.waitForSelector(".message", { timeout: 10_000 });
const row = page.locator(".message").first();
const checkbox = row.locator("input[data-msg-check]");
await checkbox.check();
await expect(row).toHaveClass(/sel-import/);
await checkbox.uncheck();
await expect(row).not.toHaveClass(/sel-import/);
});
test("detail panel skip clears selection", async ({ page }) => {
await waitForAppReady(page);
await openChannel(page, "medical");
await page.waitForSelector(".message", { timeout: 10_000 });
const row = page.locator(".message").first();
await row.click();
await expect(page.locator("#message-detail")).toBeVisible();
await page.locator("#msg-action").selectOption("import");
await expect(row).toHaveClass(/sel-import/);
await page.locator("#msg-action").selectOption("skip");
await expect(row).not.toHaveClass(/sel-import/);
});
test("filter only selected hides unchecked", async ({ page }) => {
await waitForAppReady(page);
await openChannel(page, "zoey");
await page.waitForSelector(".message", { timeout: 10_000 });
const before = await page.locator(".message").count();
const checkbox = page.locator(".message").first().locator("input[data-msg-check]");
await checkbox.check();
await page.locator("#filter-only-selected").check();
const after = await page.locator(".message").count();
expect(after).toBeLessThanOrEqual(before);
expect(after).toBeGreaterThanOrEqual(1);
});
test("global file search mode returns hits", async ({ page }) => {
await waitForAppReady(page);
await page.locator("#global-search-mode").selectOption("files");
await page.locator("#global-file-type").selectOption("image");
await page.waitForTimeout(400);
const results = page.locator("#global-search-results .search-hit");
const count = await results.count();
expect(count).toBeGreaterThanOrEqual(0);
});
test("sidebar Has picks filter", async ({ page }) => {
await waitForAppReady(page);
await page.locator("#filter-status").selectOption("partial");
const items = page.locator("#sidebar-list .list-item");
await expect(items.first()).toBeVisible();
const count = await items.count();
expect(count).toBeGreaterThanOrEqual(1);
});
test("import mode pick checkbox enables cherry-pick", async ({ page }) => {
await waitForAppReady(page);
await openChannel(page, "medical");
await page.locator("#import-mode-pick").check();
await expect(page.locator("#import-mode-pick")).toBeChecked();
});
test("save button shows confirmation", async ({ page }) => {
await waitForAppReady(page);
await page.locator("#btn-save").click();
await expect(page.locator("#save-status")).toContainText("Saved", { timeout: 3000 });
});
test("global text search finds channel", async ({ page }) => {
await waitForAppReady(page);
await page.locator("#global-search-mode").selectOption("text");
await page.locator("#global-search").fill("niagara");
await page.waitForTimeout(400);
await expect(page.locator("#global-search-results .search-hit").first()).toBeVisible();
});
test("select files in view toggles", async ({ page }) => {
await waitForAppReady(page);
await openChannel(page, "153niagara");
await page.waitForSelector(".message", { timeout: 10_000 });
await page.locator("#filter-attachment-type").selectOption("any");
const btn = page.locator("#btn-select-files");
await btn.click();
await expect(btn).toContainText("Clear", { timeout: 3000 });
await btn.click();
await expect(btn).toContainText("Select", { timeout: 3000 });
});
});
+17
View File
@@ -0,0 +1,17 @@
[
{
"subtype": "channel_join",
"user": "U2HHNN1HQ",
"text": "<@U2HHNN1HQ> has joined the channel",
"type": "message",
"ts": "1475689651.000002"
},
{
"subtype": "channel_join",
"user": "U2J30FCLW",
"text": "<@U2J30FCLW> has joined the channel",
"inviter": "U2HHNN1HQ",
"type": "message",
"ts": "1475689652.000003"
}
]
+198
View File
@@ -0,0 +1,198 @@
[
{
"text": "<@U2HHNN1HQ|jedi_jew> shared a file: <https:\/\/levkine.slack.com\/files\/jedi_jew\/F2N37HDQA\/Proof_read|Proof read>",
"files": [
{
"id": "F049MQBLJBF",
"created": 1476210524,
"timestamp": 1476210524,
"mimetype": "application\/vnd.slack-docs",
"filetype": "quip",
"pretty_type": "Canvas",
"user": "U2HHNN1HQ",
"user_team": "T2HHNN1FU",
"editable": true,
"size": 1039,
"mode": "quip",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "Proof_read",
"title": "Proof read",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049MQBLJBF\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049MQBLJBF\/download\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"permalink": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-F049MQBLJBF-8ae09c2c9a",
"url_static_preview": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF\/mobile",
"quip_thread_id": "EYc9AAYAZZK",
"updated": 1476210573,
"update_notification": null,
"canvas_readtime": 0.84945804897631,
"is_starred": false,
"skipped_shares": true,
"is_restricted_sharing_enabled": false,
"has_rich_preview": false,
"file_access": "visible",
"access": "owner",
"org_or_workspace_access": "read",
"canvas_creator_id": "U2HHNN1HQ",
"team_pref_version_history_enabled": true,
"canvas_printing_enabled": true,
"is_ai_suggested": false
}
],
"upload": false,
"user": "U2HHNN1HQ",
"display_as_bot": false,
"blocks": [
{
"type": "rich_text",
"block_id": "2ktK",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "user",
"user_id": "U2HHNN1HQ"
},
{
"type": "text",
"text": " shared a file: "
},
{
"type": "link",
"url": "https:\/\/levkine.slack.com\/files\/jedi_jew\/F2N37HDQA\/Proof_read",
"text": "Proof read"
}
]
}
]
}
],
"type": "message",
"ts": "1476210563.000002"
},
{
"subtype": "file_comment",
"text": "<@U2HHNN1HQ> commented on <@U2HHNN1HQ>’s file <https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF|https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF>: You can proof read in the post",
"comment": {
"id": "Fc2N2M6J5A",
"created": 1476210563,
"timestamp": 1476210563,
"user": "U2HHNN1HQ",
"is_intro": false,
"comment": "You can proof read in the post"
},
"is_intro": false,
"file": {
"id": "F049MQBLJBF",
"created": 1476210524,
"timestamp": 1476210524,
"mimetype": "application\/vnd.slack-docs",
"filetype": "quip",
"pretty_type": "Canvas",
"user": "U2HHNN1HQ",
"user_team": "T2HHNN1FU",
"editable": true,
"size": 1039,
"mode": "quip",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "Proof_read",
"title": "Proof read",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049MQBLJBF\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049MQBLJBF\/download\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"permalink": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-F049MQBLJBF-8ae09c2c9a",
"url_static_preview": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF\/mobile",
"quip_thread_id": "EYc9AAYAZZK",
"updated": 1476210573,
"update_notification": null,
"canvas_readtime": 0.84945804897631,
"is_starred": false,
"skipped_shares": true,
"is_restricted_sharing_enabled": false,
"has_rich_preview": false,
"file_access": "visible",
"access": "owner",
"org_or_workspace_access": "read",
"canvas_creator_id": "U2HHNN1HQ",
"team_pref_version_history_enabled": true,
"canvas_printing_enabled": true,
"is_ai_suggested": false
},
"files": [
{
"id": "F049MQBLJBF",
"created": 1476210524,
"timestamp": 1476210524,
"mimetype": "application\/vnd.slack-docs",
"filetype": "quip",
"pretty_type": "Canvas",
"user": "U2HHNN1HQ",
"user_team": "T2HHNN1FU",
"editable": true,
"size": 1039,
"mode": "quip",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "Proof_read",
"title": "Proof read",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049MQBLJBF\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049MQBLJBF\/download\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"permalink": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-F049MQBLJBF-8ae09c2c9a",
"url_static_preview": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF\/mobile",
"quip_thread_id": "EYc9AAYAZZK",
"updated": 1476210573,
"update_notification": null,
"canvas_readtime": 0.84945804897631,
"is_starred": false,
"skipped_shares": true,
"is_restricted_sharing_enabled": false,
"has_rich_preview": false,
"file_access": "visible",
"access": "owner",
"org_or_workspace_access": "read",
"canvas_creator_id": "U2HHNN1HQ",
"team_pref_version_history_enabled": true,
"canvas_printing_enabled": true,
"is_ai_suggested": false
}
],
"team": "T00",
"blocks": [
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "<@U2HHNN1HQ> commented on <@U2HHNN1HQ>’s file <https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF|https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049MQBLJBF>: You can proof read in the post"
}
]
}
]
}
],
"type": "message",
"ts": "1476210563.000003"
}
]
+309
View File
@@ -0,0 +1,309 @@
[
{
"text": "<@U2J30FCLW|ira_feeds> shared a file: <https:\/\/levkine.slack.com\/files\/ira_feeds\/F2R3Y6L9L\/draft_-_hot_plate|draft - hot plate>",
"files": [
{
"id": "F049QLYJZSN",
"created": 1476833849,
"timestamp": 1476833849,
"mimetype": "application\/vnd.slack-docs",
"filetype": "quip",
"pretty_type": "Canvas",
"user": "U2J30FCLW",
"user_team": "T2HHNN1FU",
"editable": true,
"size": 483,
"mode": "quip",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "draft_-_hot_plate",
"title": "draft - hot plate",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049QLYJZSN\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049QLYJZSN\/download\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"permalink": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-F049QLYJZSN-1ded605162",
"url_static_preview": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN\/mobile",
"quip_thread_id": "QeY9AAYD2SZ",
"updated": 1476833915,
"update_notification": null,
"canvas_readtime": 0.40626254516259,
"is_starred": false,
"skipped_shares": true,
"is_restricted_sharing_enabled": false,
"has_rich_preview": false,
"file_access": "visible",
"access": "write",
"org_or_workspace_access": "write",
"canvas_creator_id": "U2J30FCLW",
"team_pref_version_history_enabled": true,
"canvas_printing_enabled": true,
"is_ai_suggested": false
}
],
"upload": false,
"user": "U2J30FCLW",
"display_as_bot": false,
"blocks": [
{
"type": "rich_text",
"block_id": "NzVx",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "user",
"user_id": "U2J30FCLW"
},
{
"type": "text",
"text": " shared a file: "
},
{
"type": "link",
"url": "https:\/\/levkine.slack.com\/files\/ira_feeds\/F2R3Y6L9L\/draft_-_hot_plate",
"text": "draft - hot plate"
}
]
}
]
}
],
"type": "message",
"ts": "1476833882.000005"
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476833895.000006",
"text": "boom chikadee",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "93iAJ",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "boom chikadee"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476833908.000007",
"text": "i dont have their emails on me, do you want to send it out?",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "H8ho",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "i dont have their emails on me, do you want to send it out?"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1476834078.000008",
"text": "sure",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "SbFFK",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "sure"
}
]
}
]
}
]
},
{
"subtype": "file_comment",
"text": "<@U2HHNN1HQ> commented on <@U2J30FCLW>’s file <https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN|https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN>: perfect",
"comment": {
"id": "Fc2R4FEFRQ",
"created": 1476834088,
"timestamp": 1476834088,
"user": "U2HHNN1HQ",
"is_intro": false,
"comment": "perfect"
},
"is_intro": false,
"file": {
"id": "F049QLYJZSN",
"created": 1476833849,
"timestamp": 1476833849,
"mimetype": "application\/vnd.slack-docs",
"filetype": "quip",
"pretty_type": "Canvas",
"user": "U2J30FCLW",
"user_team": "T2HHNN1FU",
"editable": true,
"size": 483,
"mode": "quip",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "draft_-_hot_plate",
"title": "draft - hot plate",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049QLYJZSN\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049QLYJZSN\/download\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"permalink": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-F049QLYJZSN-1ded605162",
"url_static_preview": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN\/mobile",
"quip_thread_id": "QeY9AAYD2SZ",
"updated": 1476833915,
"update_notification": null,
"canvas_readtime": 0.40626254516259,
"is_starred": false,
"skipped_shares": true,
"is_restricted_sharing_enabled": false,
"has_rich_preview": false,
"file_access": "visible",
"access": "write",
"org_or_workspace_access": "write",
"canvas_creator_id": "U2J30FCLW",
"team_pref_version_history_enabled": true,
"canvas_printing_enabled": true,
"is_ai_suggested": false
},
"files": [
{
"id": "F049QLYJZSN",
"created": 1476833849,
"timestamp": 1476833849,
"mimetype": "application\/vnd.slack-docs",
"filetype": "quip",
"pretty_type": "Canvas",
"user": "U2J30FCLW",
"user_team": "T2HHNN1FU",
"editable": true,
"size": 483,
"mode": "quip",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "draft_-_hot_plate",
"title": "draft - hot plate",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049QLYJZSN\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F049QLYJZSN\/download\/canvas?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"permalink": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-F049QLYJZSN-1ded605162",
"url_static_preview": "https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN\/mobile",
"quip_thread_id": "QeY9AAYD2SZ",
"updated": 1476833915,
"update_notification": null,
"canvas_readtime": 0.40626254516259,
"is_starred": false,
"skipped_shares": true,
"is_restricted_sharing_enabled": false,
"has_rich_preview": false,
"file_access": "visible",
"access": "write",
"org_or_workspace_access": "write",
"canvas_creator_id": "U2J30FCLW",
"team_pref_version_history_enabled": true,
"canvas_printing_enabled": true,
"is_ai_suggested": false
}
],
"team": "T00",
"blocks": [
{
"type": "rich_text",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "<@U2HHNN1HQ> commented on <@U2J30FCLW>’s file <https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN|https:\/\/levkine.slack.com\/docs\/T2HHNN1FU\/F049QLYJZSN>: perfect"
}
]
}
]
}
],
"type": "message",
"ts": "1476834088.000009"
}
]
+298
View File
@@ -0,0 +1,298 @@
[
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1477341477.000002",
"text": "someone from easyhome just came by for kerry",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "KaUr\/",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "someone from easyhome just came by for kerry"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1477341924.000003",
"text": "and?",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "cbcz",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "and?"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1477342010.000004",
"text": "he didnt say anything. i mentioned she has aliases, thought maybe if we give them to easyhome, they can track her down and it might be easier for us to collect on what she owes",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "31r",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "he didnt say anything. i mentioned she has aliases, thought maybe if we give them to easyhome, they can track her down and it might be easier for us to collect on what she owes"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1477342047.000005",
"text": "i emailed the collection agency today",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "uUcSc",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "i emailed the collection agency today"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1477342051.000006",
"text": "did they ask about the bed?",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "KyL8",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "did they ask about the bed?"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1477342059.000007",
"text": "nope, and i didnt say anything",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "Z+r2R",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "nope, and i didnt say anything"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1477342065.000008",
"text": "good",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "kRTUp",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "good"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1477342068.000009",
"text": "i really want to keep it",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "F2F9",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "i really want to keep it"
}
]
}
]
}
]
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

+1
View File
@@ -0,0 +1 @@
fixture preview text
+68
View File
@@ -0,0 +1,68 @@
[
{
"id": "C0121TJUSUD",
"name": "zoey",
"created": 1587214815,
"creator": "U2HHNN1HQ",
"is_archived": false,
"is_general": false,
"members": [
"U2HHNN1HQ",
"U2J30FCLW"
],
"topic": {
"value": "",
"creator": "",
"last_set": 0
},
"purpose": {
"value": "",
"creator": "",
"last_set": 0
}
},
{
"id": "C2KPK5G8N",
"name": "153niagara",
"created": 1475689652,
"creator": "U2HHNN1HQ",
"is_archived": false,
"is_general": false,
"members": [
"U2HHNN1HQ",
"U2J30FCLW"
],
"topic": {
"value": "",
"creator": "",
"last_set": 0
},
"purpose": {
"value": "",
"creator": "",
"last_set": 0
}
},
{
"id": "C2KQHKAV8",
"name": "medical",
"created": 1475691337,
"creator": "U2HHNN1HQ",
"is_archived": false,
"is_general": false,
"members": [
"U2HHNN1HQ",
"U2J30FCLW"
],
"topic": {
"value": "",
"creator": "",
"last_set": 0
},
"purpose": {
"value": "",
"creator": "",
"last_set": 0
}
}
]
+17
View File
@@ -0,0 +1,17 @@
[
{
"subtype": "channel_join",
"user": "U2HHNN1HQ",
"text": "<@U2HHNN1HQ> has joined the channel",
"type": "message",
"ts": "1475691337.000002"
},
{
"subtype": "channel_join",
"user": "U2J30FCLW",
"text": "<@U2J30FCLW> has joined the channel",
"inviter": "U2HHNN1HQ",
"type": "message",
"ts": "1475691337.000003"
}
]
+261
View File
@@ -0,0 +1,261 @@
[
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476122672.000002",
"text": "subclinical hypothyroidism d\/t autoimmunity = hashimoto's thyroiditis.",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "xcov",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "subclinical hypothyroidism d\/t autoimmunity = hashimoto's thyroiditis."
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476122755.000003",
"text": "alterations in thyroid functioning, in this case hypothyroidism manifest as reduced appetite, low energy, chronic infections (hello - persistent GI infections), weight loss, skin issues, CONSTIPATION, and literally all of my other symptoms",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "ME3ih",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "alterations in thyroid functioning, in this case hypothyroidism manifest as reduced appetite, low energy, chronic infections (hello - persistent GI infections), weight loss, skin issues, CONSTIPATION, and literally all of my other symptoms"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1476122785.000004",
"text": "So how do we fix it?",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "Ojdhx",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "So how do we fix it?"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476122816.000005",
"text": "its a chicken and egg thing - dont know if the hashimoto's or the GI issues came first so need to fix both at same time",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "9+Oie",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "its a chicken and egg thing - dont know if the hashimoto's or the GI issues came first so need to fix both at same time"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476122850.000006",
"text": "synthetic thyroid (already on it) and fixing leaky gut (may need to do new food sensitivity testing)",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "OlG",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "synthetic thyroid (already on it) and fixing leaky gut (may need to do new food sensitivity testing)"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476122864.000007",
"text": "the synthroid may be too low a dose",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "+6S",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "the synthroid may be too low a dose"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1476122903.000008",
"text": "but we'll see",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "oZKHf",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "but we'll see"
}
]
}
]
}
]
}
]
+241
View File
@@ -0,0 +1,241 @@
[
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1477621049.000002",
"text": "<https:\/\/www.tweed.com\/collections\/available\/products\/ocean-view-leafs-by-snoop>",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"attachments": [
{
"service_name": "Tweed",
"title": "OCEAN VIEW (Leafs by Snoop)",
"title_link": "https:\/\/www.tweed.com\/collections\/available\/products\/ocean-view-leafs-by-snoop",
"text": "THC: 15.7\/ CBD: &lt;0.05 ↓Also of Note THC and CBD amounts listed for this strain have been tested by Tweed. This product has not been cold-pasteurized. To lear",
"fallback": "Tweed: OCEAN VIEW (Leafs by Snoop)",
"thumb_url": "http:\/\/cdn.shopify.com\/s\/files\/1\/0266\/0901\/products\/Untitled-4_grande.png?v=1477513626",
"thumb_width": 41,
"thumb_height": 40,
"fields": [
{
"title": "Price",
"value": "From $9.60 CAD",
"short": true
},
{
"title": "Brand",
"value": "Leafs by Snoop",
"short": true
}
],
"from_url": "https:\/\/www.tweed.com\/collections\/available\/products\/ocean-view-leafs-by-snoop",
"service_icon": "http:\/\/cdn.shopify.com\/s\/files\/1\/0266\/0901\/t\/16\/assets\/favicon.png?17894041713191168692",
"id": 1
}
],
"blocks": [
{
"type": "rich_text",
"block_id": "KpkG",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "link",
"url": "https:\/\/www.tweed.com\/collections\/available\/products\/ocean-view-leafs-by-snoop"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1477621066.000004",
"text": "nice",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "Il6oK",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "nice"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1477621108.000005",
"text": "<https:\/\/www.tweed.com\/collections\/available\/products\/palm-tree-cbd-leafs-by-snoop>",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"attachments": [
{
"service_name": "Tweed",
"title": "PALM TREE CBD (Leafs By Snoop)",
"title_link": "https:\/\/www.tweed.com\/collections\/available\/products\/palm-tree-cbd-leafs-by-snoop",
"text": "THC: 8.71\/ CBD: 9.76 ↓Also of Note THC and CBD amounts listed for this strain have been tested by Tweed. This product has not been cold-pasteurized. To learn mo",
"fallback": "Tweed: PALM TREE CBD (Leafs By Snoop)",
"thumb_url": "http:\/\/cdn.shopify.com\/s\/files\/1\/0266\/0901\/products\/Untitled-44_grande.png?v=1477513710",
"thumb_width": 41,
"thumb_height": 40,
"fields": [
{
"title": "Price",
"value": "From $7.20 CAD",
"short": true
},
{
"title": "Brand",
"value": "Leafs by Snoop",
"short": true
}
],
"from_url": "https:\/\/www.tweed.com\/collections\/available\/products\/palm-tree-cbd-leafs-by-snoop",
"service_icon": "http:\/\/cdn.shopify.com\/s\/files\/1\/0266\/0901\/t\/16\/assets\/favicon.png?17894041713191168692",
"id": 1
}
],
"blocks": [
{
"type": "rich_text",
"block_id": "ueWRO",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "link",
"url": "https:\/\/www.tweed.com\/collections\/available\/products\/palm-tree-cbd-leafs-by-snoop"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1477621115.000007",
"text": "that one is similar to Devon",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "FwcA",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "that one is similar to Devon"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1477621164.000008",
"text": "the breakdown is a bit strange though - its supposedly focusing and sleepy at the same time",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "thl",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "the breakdown is a bit strange though - its supposedly focusing and sleepy at the same time"
}
]
}
]
}
]
}
]
+84
View File
@@ -0,0 +1,84 @@
[
{
"text": "",
"files": [
{
"id": "FCT24D5B7",
"created": 1536844410,
"timestamp": 1536844410,
"mimetype": "application\/pdf",
"filetype": "pdf",
"pretty_type": "PDF",
"user": "U2HHNN1HQ",
"user_team": "T2HHNN1FU",
"editable": false,
"size": 8190867,
"mode": "hosted",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "img20180913_09120499.pdf",
"title": "img20180913_09120499.pdf",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-FCT24D5B7\/img20180913_09120499.pdf?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-FCT24D5B7\/download\/img20180913_09120499.pdf?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"media_display_type": "unknown",
"thumb_pdf": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-FCT24D5B7-45e94cde7b\/img20180913_09120499_thumb_pdf.png?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_pdf_w": 930,
"thumb_pdf_h": 1196,
"permalink": "https:\/\/levkine.slack.com\/files\/U2HHNN1HQ\/FCT24D5B7\/img20180913_09120499.pdf",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-FCT24D5B7-e76617f641",
"is_starred": false,
"skipped_shares": true,
"has_rich_preview": false,
"file_access": "visible"
}
],
"upload": true,
"user": "U2HHNN1HQ",
"display_as_bot": false,
"type": "message",
"ts": "1536844412.000100"
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1536844629.000100",
"client_msg_id": "a0e588b0-0bf3-442a-bf02-edfbea2bec31",
"text": "Can you email so I can forward? ",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "Bv=9",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Can you email so I can forward?"
}
]
}
]
}
]
}
]
+933
View File
@@ -0,0 +1,933 @@
[
{
"id": "U2HHNN1HQ",
"team_id": "T2HHNN1FU",
"name": "jedi_jew",
"deleted": false,
"color": "9f69e7",
"real_name": "Ilia Dobkin",
"tz": "America/New_York",
"tz_label": "Eastern Daylight Time",
"tz_offset": -14400,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Ilia Dobkin",
"real_name_normalized": "Ilia Dobkin",
"display_name": "Ilia",
"display_name_normalized": "Ilia",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "gde41167d309",
"huddle_state": "default_unset",
"huddle_state_expiration_ts": 0,
"first_name": "Ilia",
"last_name": "Dobkin",
"image_24": "https://secure.gravatar.com/avatar/ede41167d309b977e5c67645d9a1a699.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-24.png",
"image_32": "https://secure.gravatar.com/avatar/ede41167d309b977e5c67645d9a1a699.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-32.png",
"image_48": "https://secure.gravatar.com/avatar/ede41167d309b977e5c67645d9a1a699.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-48.png",
"image_72": "https://secure.gravatar.com/avatar/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"image_192": "https://secure.gravatar.com/avatar/ede41167d309b977e5c67645d9a1a699.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-192.png",
"image_512": "https://secure.gravatar.com/avatar/ede41167d309b977e5c67645d9a1a699.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": true,
"is_owner": true,
"is_primary_owner": true,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": false,
"is_app_user": false,
"updated": 1747338096,
"is_email_confirmed": true,
"who_can_share_contact_card": "EVERYONE"
},
{
"id": "U2J30FCLW",
"team_id": "T2HHNN1FU",
"name": "ira_feeds",
"deleted": false,
"color": "4bbe2e",
"real_name": "Irina",
"tz": "America/New_York",
"tz_label": "Eastern Daylight Time",
"tz_offset": -14400,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Irina",
"real_name_normalized": "Irina",
"display_name": "ira_feeds",
"display_name_normalized": "ira_feeds",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "gc49a37283df",
"first_name": "Irina",
"last_name": "",
"image_24": "https://secure.gravatar.com/avatar/bc49a37283dfa139af6916bb32030146.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-24.png",
"image_32": "https://secure.gravatar.com/avatar/bc49a37283dfa139af6916bb32030146.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-32.png",
"image_48": "https://secure.gravatar.com/avatar/bc49a37283dfa139af6916bb32030146.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-48.png",
"image_72": "https://secure.gravatar.com/avatar/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"image_192": "https://secure.gravatar.com/avatar/bc49a37283dfa139af6916bb32030146.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-192.png",
"image_512": "https://secure.gravatar.com/avatar/bc49a37283dfa139af6916bb32030146.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": true,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": false,
"is_app_user": false,
"updated": 1626449137,
"is_email_confirmed": true,
"who_can_share_contact_card": "EVERYONE"
},
{
"id": "U2KQS74Q0",
"team_id": "T2HHNN1FU",
"name": "humblebot",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Humblebot",
"real_name_normalized": "Humblebot",
"display_name": "humblebot",
"display_name_normalized": "humblebot",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "b11cb3deda79",
"api_app_id": "A0H11TLMD",
"image_original": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_original.png",
"is_custom_image": true,
"bot_id": "B2KR3BLG1",
"first_name": "Humblebot",
"image_24": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_24.png",
"image_32": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_32.png",
"image_48": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_48.png",
"image_72": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_72.png",
"image_192": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_192.png",
"image_512": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_512.png",
"image_1024": "https://avatars.slack-edge.com/2016-10-05/87859422434_b11cb3deda792d278311_512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": true,
"is_app_user": false,
"updated": 1742354811
},
{
"id": "U3WCAAHV4",
"team_id": "T2HHNN1FU",
"name": "deactivateduser196138",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Deactivated User",
"real_name_normalized": "Deactivated User",
"display_name": "deactivateduser",
"display_name_normalized": "deactivateduser",
"fields": null,
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "g849cc56ed76",
"huddle_state": "default_unset",
"first_name": "Deactivated",
"last_name": "User",
"image_24": "https://secure.gravatar.com/avatar/512b06e3a7177de41d7c382e05055887.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0024-24.png",
"image_32": "https://secure.gravatar.com/avatar/512b06e3a7177de41d7c382e05055887.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0024-32.png",
"image_48": "https://secure.gravatar.com/avatar/512b06e3a7177de41d7c382e05055887.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0024-48.png",
"image_72": "https://secure.gravatar.com/avatar/512b06e3a7177de41d7c382e05055887.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0024-72.png",
"image_192": "https://secure.gravatar.com/avatar/512b06e3a7177de41d7c382e05055887.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0024-192.png",
"image_512": "https://secure.gravatar.com/avatar/512b06e3a7177de41d7c382e05055887.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0024-512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": false,
"is_app_user": false,
"updated": 1748398757,
"is_forgotten": true
},
{
"id": "UPE67TA20",
"team_id": "T2HHNN1FU",
"name": "google_calendar",
"deleted": false,
"color": "df3dc0",
"real_name": "Google Calendar",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Google Calendar",
"real_name_normalized": "Google Calendar",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "379b3e470f6f",
"api_app_id": "ADZ494LHY",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_original.png",
"is_custom_image": true,
"bot_id": "BPE67T9PW",
"image_24": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_24.png",
"image_32": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_32.png",
"image_48": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_48.png",
"image_72": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_72.png",
"image_192": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_192.png",
"image_512": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_512.png",
"image_1024": "https://avatars.slack-edge.com/2026-02-18/10537829174518_379b3e470f6fb8f55e0d_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": false,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": true,
"is_app_user": false,
"updated": 1771487052,
"is_email_confirmed": false,
"who_can_share_contact_card": "EVERYONE"
},
{
"id": "U06GCU7DDHU",
"team_id": "T2HHNN1FU",
"name": "aiintern",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "AI Intern",
"real_name_normalized": "AI Intern",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "7eedd5bf639b",
"api_app_id": "A04KKT0TQ2W",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_original.jpg",
"is_custom_image": true,
"bot_id": "B06H8B5JHR6",
"first_name": "AI",
"last_name": "Intern",
"image_24": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_24.jpg",
"image_32": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_32.jpg",
"image_48": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_48.jpg",
"image_72": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_72.jpg",
"image_192": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_192.jpg",
"image_512": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_512.jpg",
"image_1024": "https://avatars.slack-edge.com/2024-01-31/6576243776113_7eedd5bf639b86048f0c_1024.jpg",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": true,
"is_app_user": false,
"updated": 1742354804
},
{
"id": "U08JDB604N6",
"team_id": "T2HHNN1FU",
"name": "mural-bot",
"deleted": false,
"color": "e85d72",
"real_name": "MURAL",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "MURAL",
"real_name_normalized": "MURAL",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "e36bc8361c8f",
"api_app_id": "A4LV0TKR9",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_original.jpg",
"is_custom_image": true,
"bot_id": "B08JDB604LS",
"first_name": "MURAL",
"last_name": "",
"image_24": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_24.jpg",
"image_32": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_32.jpg",
"image_48": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_48.jpg",
"image_72": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_72.jpg",
"image_192": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_192.jpg",
"image_512": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_512.jpg",
"image_1024": "https://avatars.slack-edge.com/2025-03-18/8645220738464_e36bc8361c8f486870cf_1024.jpg",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": false,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": true,
"is_app_user": false,
"updated": 1742354230,
"is_email_confirmed": false,
"who_can_share_contact_card": "EVERYONE"
},
{
"id": "U2KQP4VUZ",
"team_id": "T2HHNN1FU",
"name": "meekan",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Meekan Scheduling",
"real_name_normalized": "Meekan Scheduling",
"display_name": "meekan",
"display_name_normalized": "meekan",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "7d4b8d227280",
"api_app_id": "A0G51AT60",
"image_original": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_original.png",
"is_custom_image": true,
"bot_id": "B2KQCB85C",
"first_name": "Meekan",
"last_name": "Scheduling",
"image_24": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_24.png",
"image_32": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_32.png",
"image_48": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_48.png",
"image_72": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_72.png",
"image_192": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_192.png",
"image_512": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_512.png",
"image_1024": "https://avatars.slack-edge.com/2017-11-27/278874366151_7d4b8d227280e8130276_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": true,
"is_app_user": false,
"updated": 1742354835
},
{
"id": "U2KR6LA3B",
"team_id": "T2HHNN1FU",
"name": "howdy",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Howdy",
"real_name_normalized": "Howdy",
"display_name": "howdy",
"display_name_normalized": "howdy",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "26e5aa1363cf",
"api_app_id": "A09RDP4AW",
"image_original": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_original.png",
"is_custom_image": true,
"bot_id": "B2KQVTFUN",
"first_name": "Howdy",
"image_24": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_24.png",
"image_32": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_32.png",
"image_48": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_48.png",
"image_72": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_72.png",
"image_192": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_192.png",
"image_512": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_512.png",
"image_1024": "https://avatars.slack-edge.com/2016-10-05/87845548640_26e5aa1363cf295e9a90_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": true,
"is_app_user": false,
"updated": 1504348811
},
{
"id": "U35CW23UZ",
"team_id": "T2HHNN1FU",
"name": "leo_officevibe_bot",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Leo (Officevibe Bot)",
"real_name_normalized": "Leo (Officevibe Bot)",
"display_name": "leo_officevibe_bot",
"display_name_normalized": "leo_officevibe_bot",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "65c14a601f21",
"api_app_id": "A0GU27WR1",
"always_active": false,
"image_original": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_original.png",
"is_custom_image": true,
"bot_id": "B35BYVB7A",
"first_name": "Leo (Officevibe Bot)",
"image_24": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_24.png",
"image_32": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_32.png",
"image_48": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_48.png",
"image_72": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_72.png",
"image_192": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_192.png",
"image_512": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_512.png",
"image_1024": "https://avatars.slack-edge.com/2016-11-22/108800284919_65c14a601f2106b2af3b_512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": true,
"is_app_user": false,
"updated": 1504349194
},
{
"id": "U3660DH2R",
"team_id": "T2HHNN1FU",
"name": "jewbot",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "jewbot",
"real_name_normalized": "jewbot",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "7ab64cd294c2",
"api_app_id": "",
"always_active": false,
"image_original": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_original.png",
"is_custom_image": true,
"bot_id": "B365PU67N",
"image_24": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_24.png",
"image_32": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_32.png",
"image_48": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_48.png",
"image_72": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_72.png",
"image_192": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_192.png",
"image_512": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_512.png",
"image_1024": "https://avatars.slack-edge.com/2016-11-22/108792815526_7ab64cd294c2adc03cc4_512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": true,
"is_app_user": false,
"updated": 1742354829
},
{
"id": "UBMJ5M7CZ",
"team_id": "T2HHNN1FU",
"name": "googledrive",
"deleted": false,
"color": "5b89d5",
"real_name": "Google Drive",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Google Drive",
"real_name_normalized": "Google Drive",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "a74495570a68",
"api_app_id": "A6NL8MJ6Q",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_original.png",
"is_custom_image": true,
"bot_id": "BBMT728UU",
"first_name": "Google",
"last_name": "Drive",
"image_24": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_24.png",
"image_32": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_32.png",
"image_48": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_48.png",
"image_72": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_72.png",
"image_192": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_192.png",
"image_512": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_512.png",
"image_1024": "https://avatars.slack-edge.com/2020-10-15/1438649512884_a74495570a688b6aa1b5_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": false,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": true,
"is_app_user": false,
"updated": 1602813021,
"is_email_confirmed": false,
"who_can_share_contact_card": "EVERYONE"
},
{
"id": "ULBNL5U3X",
"team_id": "T2HHNN1FU",
"name": "deactivateduser536011",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Deactivated User",
"real_name_normalized": "Deactivated User",
"display_name": "deactivateduser",
"display_name_normalized": "deactivateduser",
"fields": null,
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "g849cc56ed76",
"huddle_state": "default_unset",
"first_name": "Deactivated",
"last_name": "User",
"image_24": "https://secure.gravatar.com/avatar/6964489e1811fcd5a8604d2ec7c72abf.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0007-24.png",
"image_32": "https://secure.gravatar.com/avatar/6964489e1811fcd5a8604d2ec7c72abf.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0007-32.png",
"image_48": "https://secure.gravatar.com/avatar/6964489e1811fcd5a8604d2ec7c72abf.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0007-48.png",
"image_72": "https://secure.gravatar.com/avatar/6964489e1811fcd5a8604d2ec7c72abf.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0007-72.png",
"image_192": "https://secure.gravatar.com/avatar/6964489e1811fcd5a8604d2ec7c72abf.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0007-192.png",
"image_512": "https://secure.gravatar.com/avatar/6964489e1811fcd5a8604d2ec7c72abf.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0007-512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": false,
"is_app_user": false,
"updated": 1748398744,
"is_forgotten": true
},
{
"id": "UP862QZ2M",
"team_id": "T2HHNN1FU",
"name": "deactivateduser867192",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Deactivated User",
"real_name_normalized": "Deactivated User",
"display_name": "deactivateduser",
"display_name_normalized": "deactivateduser",
"fields": null,
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "g849cc56ed76",
"huddle_state": "default_unset",
"first_name": "Deactivated",
"last_name": "User",
"image_24": "https://secure.gravatar.com/avatar/51ac16be6dc728a09d4f2b3b0833f0ec.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0020-24.png",
"image_32": "https://secure.gravatar.com/avatar/51ac16be6dc728a09d4f2b3b0833f0ec.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0020-32.png",
"image_48": "https://secure.gravatar.com/avatar/51ac16be6dc728a09d4f2b3b0833f0ec.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0020-48.png",
"image_72": "https://secure.gravatar.com/avatar/51ac16be6dc728a09d4f2b3b0833f0ec.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0020-72.png",
"image_192": "https://secure.gravatar.com/avatar/51ac16be6dc728a09d4f2b3b0833f0ec.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0020-192.png",
"image_512": "https://secure.gravatar.com/avatar/51ac16be6dc728a09d4f2b3b0833f0ec.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0020-512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": false,
"is_app_user": false,
"updated": 1748398741,
"is_forgotten": true
},
{
"id": "UP8BUNWAV",
"team_id": "T2HHNN1FU",
"name": "deactivateduser876224",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Deactivated User",
"real_name_normalized": "Deactivated User",
"display_name": "deactivateduser",
"display_name_normalized": "deactivateduser",
"fields": null,
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "g849cc56ed76",
"huddle_state": "default_unset",
"first_name": "Deactivated",
"last_name": "User",
"image_24": "https://secure.gravatar.com/avatar/35499da5ddf5214ad21f33c672280a42.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-24.png",
"image_32": "https://secure.gravatar.com/avatar/35499da5ddf5214ad21f33c672280a42.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-32.png",
"image_48": "https://secure.gravatar.com/avatar/35499da5ddf5214ad21f33c672280a42.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-48.png",
"image_72": "https://secure.gravatar.com/avatar/35499da5ddf5214ad21f33c672280a42.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-72.png",
"image_192": "https://secure.gravatar.com/avatar/35499da5ddf5214ad21f33c672280a42.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-192.png",
"image_512": "https://secure.gravatar.com/avatar/35499da5ddf5214ad21f33c672280a42.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0013-512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": false,
"is_app_user": false,
"updated": 1748398749,
"is_forgotten": true
},
{
"id": "UQF26AT1R",
"team_id": "T2HHNN1FU",
"name": "deactivateduser911684",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Deactivated User",
"real_name_normalized": "Deactivated User",
"display_name": "deactivateduser",
"display_name_normalized": "deactivateduser",
"fields": null,
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "g849cc56ed76",
"huddle_state": "default_unset",
"first_name": "Deactivated",
"last_name": "User",
"image_24": "https://secure.gravatar.com/avatar/a2c76eb32226f873bb3ab77542778325.jpg?s=24&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0021-24.png",
"image_32": "https://secure.gravatar.com/avatar/a2c76eb32226f873bb3ab77542778325.jpg?s=32&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0021-32.png",
"image_48": "https://secure.gravatar.com/avatar/a2c76eb32226f873bb3ab77542778325.jpg?s=48&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0021-48.png",
"image_72": "https://secure.gravatar.com/avatar/a2c76eb32226f873bb3ab77542778325.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0021-72.png",
"image_192": "https://secure.gravatar.com/avatar/a2c76eb32226f873bb3ab77542778325.jpg?s=192&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0021-192.png",
"image_512": "https://secure.gravatar.com/avatar/a2c76eb32226f873bb3ab77542778325.jpg?s=512&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0021-512.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": false,
"is_app_user": false,
"updated": 1748398765,
"is_forgotten": true
},
{
"id": "U04ARJ3A7LM",
"team_id": "T2HHNN1FU",
"name": "asana",
"deleted": false,
"color": "d58247",
"real_name": "Asana",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Asana",
"real_name_normalized": "Asana",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "3b8995f549dc",
"api_app_id": "A01734836JY",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_original.png",
"is_custom_image": true,
"bot_id": "B04AUGF0ZUJ",
"first_name": "Asana",
"last_name": "",
"image_24": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_24.png",
"image_32": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_32.png",
"image_48": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_48.png",
"image_72": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_72.png",
"image_192": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_192.png",
"image_512": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_512.png",
"image_1024": "https://avatars.slack-edge.com/2024-09-14/7727304057861_3b8995f549dc5f9ca5f6_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": false,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": true,
"is_app_user": false,
"updated": 1726350133,
"is_email_confirmed": false,
"who_can_share_contact_card": "EVERYONE"
},
{
"id": "U08HX1HNW31",
"team_id": "T2HHNN1FU",
"name": "otter",
"deleted": false,
"color": "827327",
"real_name": "Otter.ai",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Otter.ai",
"real_name_normalized": "Otter.ai",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "c1628076c3b3",
"api_app_id": "A04ULDS8UFN",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_original.png",
"is_custom_image": true,
"bot_id": "B08HX1HNPST",
"first_name": "Otter.ai",
"last_name": "",
"image_24": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_24.png",
"image_32": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_32.png",
"image_48": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_48.png",
"image_72": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_72.png",
"image_192": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_192.png",
"image_512": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_512.png",
"image_1024": "https://avatars.slack-edge.com/2025-03-18/8633921706705_c1628076c3b3d6440777_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": false,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": true,
"is_app_user": false,
"updated": 1742353383,
"is_email_confirmed": false,
"who_can_share_contact_card": "EVERYONE"
},
{
"id": "U08JDP2SQRZ",
"team_id": "T2HHNN1FU",
"name": "miro",
"deleted": true,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Miro",
"real_name_normalized": "Miro",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "c8beb7d87ed3",
"api_app_id": "A8VK125AS",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_original.png",
"is_custom_image": true,
"bot_id": "B08JDP2SDFD",
"first_name": "Miro",
"last_name": "",
"image_24": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_24.png",
"image_32": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_32.png",
"image_48": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_48.png",
"image_72": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_72.png",
"image_192": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_192.png",
"image_512": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_512.png",
"image_1024": "https://avatars.slack-edge.com/2025-03-18/8645198828528_c8beb7d87ed3cae76b84_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_bot": true,
"is_app_user": false,
"updated": 1772212905
},
{
"id": "U08SZSAHH5F",
"team_id": "T2HHNN1FU",
"name": "wf_bot_a08szs6b62v",
"deleted": false,
"color": "902d59",
"real_name": "Item status notification",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Item status notification",
"real_name_normalized": "Item status notification",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "85aa14fc1250",
"api_app_id": "A08SZS6B62V",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_original.png",
"is_custom_image": true,
"bot_id": "B08SZSAHERF",
"first_name": "Item",
"last_name": "status notification",
"image_24": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_24.png",
"image_32": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_32.png",
"image_48": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_48.png",
"image_72": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_72.png",
"image_192": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_192.png",
"image_512": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_512.png",
"image_1024": "https://avatars.slack-edge.com/2025-05-15/8906772541186_85aa14fc1250307356f0_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": false,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": true,
"is_app_user": false,
"updated": 1747337592,
"is_email_confirmed": false,
"who_can_share_contact_card": "EVERYONE",
"is_workflow_bot": true
},
{
"id": "U08SZSC6VH7",
"team_id": "T2HHNN1FU",
"name": "wf_bot_a08szs62zgr",
"deleted": false,
"color": "bd9336",
"real_name": "Welcome message",
"tz": "America/Los_Angeles",
"tz_label": "Pacific Daylight Time",
"tz_offset": -25200,
"profile": {
"title": "",
"phone": "",
"skype": "",
"real_name": "Welcome message",
"real_name_normalized": "Welcome message",
"display_name": "",
"display_name_normalized": "",
"fields": {},
"status_text": "",
"status_emoji": "",
"status_emoji_display_info": [],
"status_expiration": 0,
"status_clear_on_focus_end": false,
"avatar_hash": "ab5acb82d517",
"api_app_id": "A08SZS62ZGR",
"always_active": true,
"image_original": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_original.png",
"is_custom_image": true,
"bot_id": "B08SZSC2V7T",
"first_name": "Welcome",
"last_name": "message",
"image_24": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_24.png",
"image_32": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_32.png",
"image_48": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_48.png",
"image_72": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_72.png",
"image_192": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_192.png",
"image_512": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_512.png",
"image_1024": "https://avatars.slack-edge.com/2025-05-15/8927514171840_ab5acb82d5173fa35a95_1024.png",
"status_text_canonical": "",
"team": "T2HHNN1FU"
},
"is_admin": false,
"is_owner": false,
"is_primary_owner": false,
"is_restricted": false,
"is_ultra_restricted": false,
"is_bot": true,
"is_app_user": false,
"updated": 1747337594,
"is_email_confirmed": false,
"who_can_share_contact_card": "EVERYONE",
"is_workflow_bot": true
}
]
+159
View File
@@ -0,0 +1,159 @@
[
{
"subtype": "channel_join",
"user": "U2HHNN1HQ",
"text": "<@U2HHNN1HQ> has joined the channel",
"type": "message",
"ts": "1587214815.000200"
},
{
"subtype": "channel_join",
"user": "U2J30FCLW",
"text": "<@U2J30FCLW> has joined the channel",
"inviter": "U2HHNN1HQ",
"type": "message",
"ts": "1587214815.000400"
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1587214908.000600",
"client_msg_id": "7719b88e-a403-427f-8cd1-2665e5f6d342",
"text": "baby!",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "7Zca",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "baby!"
}
]
}
]
}
]
},
{
"text": "",
"files": [
{
"id": "F011NH76RUP",
"created": 1587214950,
"timestamp": 1587214950,
"mimetype": "image\/jpeg",
"filetype": "jpg",
"pretty_type": "JPEG",
"user": "U2HHNN1HQ",
"user_team": "T2HHNN1FU",
"editable": false,
"size": 391288,
"mode": "hosted",
"is_external": false,
"external_type": "",
"is_public": true,
"public_url_shared": false,
"display_as_bot": false,
"username": "",
"name": "IMG_20200418_090219.jpg",
"title": "IMG_20200418_090219.jpg",
"is_modified_by_ai": false,
"url_private": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F011NH76RUP\/img_20200418_090219.jpg?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"url_private_download": "https:\/\/files.slack.com\/files-pri\/T2HHNN1FU-F011NH76RUP\/download\/img_20200418_090219.jpg?token=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"media_display_type": "unknown",
"thumb_64": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_64.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_80": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_80.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_360": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_360.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_360_w": 360,
"thumb_360_h": 270,
"thumb_480": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_480.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_480_w": 480,
"thumb_480_h": 360,
"thumb_160": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_160.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_720": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_720.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_720_w": 720,
"thumb_720_h": 540,
"thumb_800": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_800.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_800_w": 800,
"thumb_800_h": 600,
"thumb_960": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_960.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_960_w": 960,
"thumb_960_h": 720,
"thumb_1024": "https:\/\/files.slack.com\/files-tmb\/T2HHNN1FU-F011NH76RUP-165eb608df\/img_20200418_090219_1024.jpg?t=xoxe-85600749538-11214969224067-11215699816469-13b604e355c6cb0f8b9d8a5c2dd39dd1",
"thumb_1024_w": 1024,
"thumb_1024_h": 768,
"original_w": 4032,
"original_h": 3024,
"thumb_tiny": "AwAkADB\/2iQHq1NN1J\/eNRc5IzjPWnBMrnt70APN1IB1bPuKb9sl\/vfpUZJBG75h6U824ZC8bZHv2oAX7bID1B\/Ck+3t6A1W2MzYA5p0qqE+VcEdTnNAEy\/M2B1NSIwCsAMt2BqOIAygE4q35KleRye9AFMBnJ4AHfFOWQ7QqgADnnvU5tyF4Vfriq8YO4gk8dPagCxGpCkkAs5zkCoZYgEYZ59KnaZVQAbmIqpMXZST8q+lAAn+tX61dclQCO9Uk\/1q\/Wrkv3FoAiaVwfvVCGJc5wfwpzdajH3jQAssrghQcD24qu0jH0\/KpJvvfhUNAH\/\/2Q==",
"permalink": "https:\/\/levkine.slack.com\/files\/U2HHNN1HQ\/F011NH76RUP\/img_20200418_090219.jpg",
"permalink_public": "https:\/\/slack-files.com\/T2HHNN1FU-F011NH76RUP-993ff9b75a",
"is_starred": false,
"skipped_shares": true,
"has_rich_preview": false,
"file_access": "visible"
}
],
"upload": true,
"user": "U2HHNN1HQ",
"display_as_bot": false,
"type": "message",
"ts": "1587214956.000700",
"client_msg_id": "e9473154-7c74-4e16-a1ea-aa5855d8648f"
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1587215471.001100",
"client_msg_id": "1837b1b9-15d4-41bb-bd82-c4be4d24ce03",
"text": "another bublik!",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"blocks": [
{
"type": "rich_text",
"block_id": "beU",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "another bublik!"
}
]
}
]
}
]
}
]
+277
View File
@@ -0,0 +1,277 @@
[
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587651542.000200",
"client_msg_id": "5e8e082d-4605-4875-a2d6-86607e98e112",
"text": "Baby girl names",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"reply_count": 45,
"reply_users_count": 2,
"latest_reply": "1728352618.235309",
"reply_users": [
"U2HHNN1HQ",
"U2J30FCLW"
],
"replies": [
{
"user": "U2HHNN1HQ",
"ts": "1587651713.000300"
},
{
"user": "U2J30FCLW",
"ts": "1587842605.000600"
},
{
"user": "U2HHNN1HQ",
"ts": "1587844690.001400"
},
{
"user": "U2HHNN1HQ",
"ts": "1587844880.001600"
},
{
"user": "U2HHNN1HQ",
"ts": "1587847411.003900"
},
{
"user": "U2HHNN1HQ",
"ts": "1587849379.004800"
},
{
"user": "U2J30FCLW",
"ts": "1587849408.005000"
},
{
"user": "U2HHNN1HQ",
"ts": "1587849458.005200"
},
{
"user": "U2J30FCLW",
"ts": "1587851865.005400"
},
{
"user": "U2J30FCLW",
"ts": "1590795033.000100"
},
{
"user": "U2HHNN1HQ",
"ts": "1595716889.000100"
},
{
"user": "U2HHNN1HQ",
"ts": "1595828802.000600"
},
{
"user": "U2HHNN1HQ",
"ts": "1644007596.214229"
},
{
"user": "U2J30FCLW",
"ts": "1644007715.586559"
},
{
"user": "U2HHNN1HQ",
"ts": "1645042618.388199"
},
{
"user": "U2HHNN1HQ",
"ts": "1645804390.278629"
},
{
"user": "U2HHNN1HQ",
"ts": "1647010205.674939"
},
{
"user": "U2HHNN1HQ",
"ts": "1655498003.694859"
},
{
"user": "U2J30FCLW",
"ts": "1655498021.086939"
},
{
"user": "U2J30FCLW",
"ts": "1655498026.842429"
},
{
"user": "U2J30FCLW",
"ts": "1655498036.462279"
},
{
"user": "U2HHNN1HQ",
"ts": "1655498116.144589"
},
{
"user": "U2HHNN1HQ",
"ts": "1656524709.035089"
},
{
"user": "U2J30FCLW",
"ts": "1656525039.656139"
},
{
"user": "U2HHNN1HQ",
"ts": "1656547897.691709"
},
{
"user": "U2HHNN1HQ",
"ts": "1656967670.835969"
},
{
"user": "U2HHNN1HQ",
"ts": "1665714316.436109"
},
{
"user": "U2HHNN1HQ",
"ts": "1665714329.861469"
},
{
"user": "U2HHNN1HQ",
"ts": "1665714395.843509"
},
{
"user": "U2HHNN1HQ",
"ts": "1665714406.505149"
},
{
"user": "U2HHNN1HQ",
"ts": "1666017144.309979"
},
{
"user": "U2J30FCLW",
"ts": "1666018571.116679"
},
{
"user": "U2HHNN1HQ",
"ts": "1666283311.665639"
},
{
"user": "U2HHNN1HQ",
"ts": "1666548905.238439"
},
{
"user": "U2HHNN1HQ",
"ts": "1666549084.108529"
},
{
"user": "U2HHNN1HQ",
"ts": "1666710283.949469"
},
{
"user": "U2J30FCLW",
"ts": "1667570827.120389"
},
{
"user": "U2HHNN1HQ",
"ts": "1668113332.283759"
},
{
"user": "U2HHNN1HQ",
"ts": "1668990306.602409"
},
{
"user": "U2HHNN1HQ",
"ts": "1669219368.659929"
},
{
"user": "U2HHNN1HQ",
"ts": "1677535858.373999"
},
{
"user": "U2HHNN1HQ",
"ts": "1679322325.329009"
},
{
"user": "U2J30FCLW",
"ts": "1680209837.675889"
},
{
"user": "U2J30FCLW",
"ts": "1680209876.614319"
},
{
"user": "U2HHNN1HQ",
"ts": "1728352618.235309"
}
],
"is_locked": false,
"subscribed": true,
"last_read": "1728352618.235309",
"blocks": [
{
"type": "rich_text",
"block_id": "jjC",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Baby girl names"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587651713.000300",
"edited": {
"user": "U2HHNN1HQ",
"ts": "1665714375.000000"
},
"client_msg_id": "c9bd3422-21c0-423d-830e-030c97e91b02",
"text": "Ada\nAlia\nAndi\nAria\/Arya \nDara\nElli\nErin\nEsther\nFadi[e]\nFaye\nFran\nJada\nJane\nJoni\nKira\nMara\nNina\nNora\nRaya\nShai\nYara\nZara\nZoey (Zoya)",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "LQG",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Ada\nAlia\nAndi\nAria\/Arya \nDara\nElli\nErin\nEsther\nFadi[e]\nFaye\nFran\nJada\nJane\nJoni\nKira\nMara\nNina\nNora\nRaya\nShai\nYara\nZara\nZoey (Zoya)"
}
]
}
]
}
]
}
]
+665
View File
@@ -0,0 +1,665 @@
[
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1587842605.000600",
"edited": {
"user": "U2J30FCLW",
"ts": "1587842643.000000"
},
"client_msg_id": "614c8f69-b8dc-483e-b559-e26c490e57b4",
"text": "Alia\nAndi\nEllie\nErin\nEsther\nFaye\nKira\nMara\nNora\nRaya\nRivka\nShai\nZara\nZoey (Zoya)",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "R4c",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Alia\nAndi\nEllie\nErin\nEsther\nFaye\nKira\nMara\nNora\nRaya\nRivka\nShai\nZara\nZoey (Zoya)"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587842804.001100",
"client_msg_id": "a48e6885-3191-4df9-ae39-cc6e31939fad",
"text": "Baby boy names",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587842804.001100",
"reply_count": 30,
"reply_users_count": 2,
"latest_reply": "1729275913.097429",
"reply_users": [
"U2HHNN1HQ",
"U2J30FCLW"
],
"replies": [
{
"user": "U2HHNN1HQ",
"ts": "1587844686.001200"
},
{
"user": "U2HHNN1HQ",
"ts": "1587847806.004200"
},
{
"user": "U2HHNN1HQ",
"ts": "1587847856.004400"
},
{
"user": "U2HHNN1HQ",
"ts": "1587848964.004600"
},
{
"user": "U2J30FCLW",
"ts": "1593312084.002600"
},
{
"user": "U2HHNN1HQ",
"ts": "1655497970.128139"
},
{
"user": "U2HHNN1HQ",
"ts": "1656550206.616869"
},
{
"user": "U2HHNN1HQ",
"ts": "1657383160.915509"
},
{
"user": "U2HHNN1HQ",
"ts": "1665713679.801149"
},
{
"user": "U2HHNN1HQ",
"ts": "1665713775.915449"
},
{
"user": "U2HHNN1HQ",
"ts": "1665713968.370649"
},
{
"user": "U2HHNN1HQ",
"ts": "1665714073.390289"
},
{
"user": "U2HHNN1HQ",
"ts": "1665772316.640129"
},
{
"user": "U2HHNN1HQ",
"ts": "1669042577.074349"
},
{
"user": "U2HHNN1HQ",
"ts": "1669134587.534359"
},
{
"user": "U2HHNN1HQ",
"ts": "1669817715.842749"
},
{
"user": "U2HHNN1HQ",
"ts": "1677534276.857389"
},
{
"user": "U2HHNN1HQ",
"ts": "1677534932.624229"
},
{
"user": "U2J30FCLW",
"ts": "1677537182.790029"
},
{
"user": "U2J30FCLW",
"ts": "1677537194.748409"
},
{
"user": "U2HHNN1HQ",
"ts": "1681397953.547769"
},
{
"user": "U2HHNN1HQ",
"ts": "1681573698.769379"
},
{
"user": "U2HHNN1HQ",
"ts": "1681573897.480289"
},
{
"user": "U2HHNN1HQ",
"ts": "1681673462.733159"
},
{
"user": "U2J30FCLW",
"ts": "1681673481.251839"
},
{
"user": "U2HHNN1HQ",
"ts": "1681673491.808809"
},
{
"user": "U2HHNN1HQ",
"ts": "1682375712.953539"
},
{
"user": "U2HHNN1HQ",
"ts": "1726097016.108129"
},
{
"user": "U2HHNN1HQ",
"ts": "1728352950.566759"
},
{
"user": "U2J30FCLW",
"ts": "1729275913.097429"
}
],
"is_locked": false,
"subscribed": true,
"last_read": "1729275913.097429",
"blocks": [
{
"type": "rich_text",
"block_id": "0Mn",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Baby boy names"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587844686.001200",
"edited": {
"user": "U2HHNN1HQ",
"ts": "1587847558.000000"
},
"client_msg_id": "36244a45-572c-4d55-887e-759be38af09a",
"text": "Noah\nAbir\nAxel\nAzra\nJonah\nJoni\nSeth\nOzzy\nZach\nUriah\nShai",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587842804.001100",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "i4KW",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Noah\nAbir\nAxel\nAzra\nJonah\nJoni\nSeth\nOzzy\nZach\nUriah\nShai"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587844690.001400",
"client_msg_id": "6eb582dc-ea1f-420e-9264-9a7a71baeeb6",
"text": "Adie\nJean",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "gAvpY",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Adie\nJean"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587844880.001600",
"client_msg_id": "112d0d54-1cd8-4812-a5d9-59f6af293603",
"text": "Sara",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "OHsu",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Sara"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587847411.003900",
"client_msg_id": "c10be03a-516a-4d31-9820-1263811fdfaf",
"text": "Alvi",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "SsyoV",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Alvi"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587847806.004200",
"client_msg_id": "886ee1b0-2ae7-4e3b-b59c-d155dc54808e",
"text": "Zain",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587842804.001100",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "dgP",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Zain"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587847856.004400",
"edited": {
"user": "U2HHNN1HQ",
"ts": "1669817790.000000"
},
"client_msg_id": "b6c4a1b6-2a19-4b65-be54-0eff9061b8c1",
"text": "Enoch (Nucky)\nEnok",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587842804.001100",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "9uj3M",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Enoch (Nucky)\nEnok"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587848964.004600",
"client_msg_id": "7cf7fc44-924f-4dad-ac6a-3e909d26b94c",
"text": "Noel",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587842804.001100",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "dpq",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Noel"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587849379.004800",
"client_msg_id": "3581817c-3d7a-4708-bc2e-0ece2f173539",
"text": "Lucy\nSadie\nNoa",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "qD4V",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Lucy\nSadie\nNoa"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1587849408.005000",
"client_msg_id": "44d5139e-033f-4a83-bc6b-ffc5bde8edae",
"text": "Not a fan of noa. But I do like Lucy and Sadie",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "EAVP",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Not a fan of noa. But I do like Lucy and Sadie"
}
]
}
]
}
]
},
{
"user": "U2HHNN1HQ",
"type": "message",
"ts": "1587849458.005200",
"client_msg_id": "2a778fe4-88aa-4b81-a3e6-9515ada836ce",
"text": "Alvi?",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gde41167d309",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/ede41167d309b977e5c67645d9a1a699.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0001-72.png",
"first_name": "Ilia",
"real_name": "Ilia Dobkin",
"display_name": "Ilia",
"team": "T2HHNN1FU",
"name": "jedi_jew",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "PdB",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Alvi?"
}
]
}
]
}
]
},
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1587851865.005400",
"client_msg_id": "9a9a0cf0-962f-443d-9458-4af3c299575e",
"text": "Not a fan",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "dpYcf",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Not a fan"
}
]
}
]
}
]
}
]
+42
View File
@@ -0,0 +1,42 @@
[
{
"user": "U2J30FCLW",
"type": "message",
"ts": "1590795033.000100",
"client_msg_id": "c1405527-49a1-49f6-9a14-5e7797adee00",
"text": "Ellie\nErin\nEsther\nMara\nShai\nSara\nZoey\nLuct\nSadie",
"team": "T2HHNN1FU",
"user_team": "T2HHNN1FU",
"source_team": "T2HHNN1FU",
"user_profile": {
"avatar_hash": "gc49a37283df",
"image_72": "https:\/\/secure.gravatar.com\/avatar\/bc49a37283dfa139af6916bb32030146.jpg?s=72&d=https%3A%2F%2Fa.slack-edge.com%2Fdf10d%2Fimg%2Favatars%2Fava_0017-72.png",
"first_name": "Irina",
"real_name": "Irina",
"display_name": "ira_feeds",
"team": "T2HHNN1FU",
"name": "ira_feeds",
"is_restricted": false,
"is_ultra_restricted": false
},
"thread_ts": "1587651542.000200",
"parent_user_id": "U2HHNN1HQ",
"blocks": [
{
"type": "rich_text",
"block_id": "dYo",
"elements": [
{
"type": "rich_text_section",
"elements": [
{
"type": "text",
"text": "Ellie\nErin\nEsther\nMara\nShai\nSara\nZoey\nLuct\nSadie"
}
]
}
]
}
]
}
]
+167
View File
@@ -0,0 +1,167 @@
{
"version": 1,
"export_path": "/Users/macilia/Documents/code/slack-sieve/tests/fixtures/mini-export",
"updated_at": "2026-06-03T16:36:33.190656+00:00",
"global_notes": "pytest was here",
"channels": {
"medical": {
"import": true,
"status": "partial",
"reviewed": true,
"import_mode": "pick",
"notes": "",
"mattermost_channel": "",
"mattermost_team": "",
"messages": {
"medical:test:1": {
"action": "import",
"checked": true,
"files": {},
"notes": "",
"merge_target": ""
}
}
},
"29wilson": {
"import": false,
"status": "skip",
"reviewed": true,
"import_mode": "all",
"notes": "Returned — not importing",
"mattermost_channel": "",
"mattermost_team": "",
"messages": {}
},
"zoey": {
"import": true,
"status": "partial",
"reviewed": true,
"import_mode": "pick",
"notes": "",
"mattermost_channel": "",
"mattermost_team": "",
"messages": {
"zoey:test:2": {
"action": "import",
"checked": true,
"files": {},
"notes": "",
"merge_target": ""
}
}
},
"153niagara": {
"import": false,
"status": "undecided",
"reviewed": false,
"import_mode": "all",
"notes": "",
"mattermost_channel": "",
"mattermost_team": "",
"messages": {
"153niagara:1476210563.000002": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1476210563.000003": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1476833882.000005": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1476833895.000006": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1476833908.000007": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1476834078.000008": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1476834088.000009": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477341477.000002": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477341924.000003": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477342010.000004": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477342047.000005": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477342051.000006": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477342059.000007": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477342065.000008": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
},
"153niagara:1477342068.000009": {
"action": "inherit",
"merge_target": "",
"notes": "",
"files": {}
}
}
}
},
"users": {
"UTEST": {
"import": true,
"notes": "mapped",
"mattermost_username": "ilia"
},
"U2HHNN1HQ": {
"import": true,
"notes": "mapped",
"mattermost_username": "testuser"
}
}
}
+49
View File
@@ -0,0 +1,49 @@
"""Integration test for auto-cherry-pick main() on mini-export."""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[1]
@pytest.fixture
def cherry_pick_module():
import importlib.util
path = REPO / "scripts" / "auto-cherry-pick.py"
spec = importlib.util.spec_from_file_location("auto_cherry_pick_main", path)
mod = importlib.util.module_from_spec(spec)
assert spec.loader
spec.loader.exec_module(mod)
return mod
def test_main_dry_run_on_fixture(cherry_pick_module, fixture_export_path, tmp_path, monkeypatch):
manifest = tmp_path / "manifest.json"
monkeypatch.setattr(cherry_pick_module, "MANIFEST_PATH", manifest)
monkeypatch.setattr(cherry_pick_module, "DEFAULT_EXPORT", fixture_export_path)
monkeypatch.setattr(sys, "argv", ["auto-cherry-pick.py", "--dry-run"])
assert cherry_pick_module.main() == 0
def test_main_writes_pick_manifest(cherry_pick_module, fixture_export_path, tmp_path, monkeypatch):
manifest = tmp_path / "manifest.json"
monkeypatch.setattr(cherry_pick_module, "MANIFEST_PATH", manifest)
monkeypatch.setattr(cherry_pick_module, "DEFAULT_EXPORT", fixture_export_path)
monkeypatch.setattr(sys, "argv", ["auto-cherry-pick.py"])
assert cherry_pick_module.main() == 0
data = json.loads(manifest.read_text())
picks = sum(
1
for ch in data["channels"].values()
for m in ch.get("messages", {}).values()
if m.get("action") == "import"
)
assert picks > 0
assert "medical" in data["channels"]
assert data["channels"]["medical"]["import_mode"] == "pick"
+82
View File
@@ -0,0 +1,82 @@
"""Unit tests for scripts/auto-cherry-pick.py heuristics."""
from __future__ import annotations
def test_return_channels_skipped(cherry_pick, empty_manifest):
_, manifest = empty_manifest
manifest["channels"]["29wilson"] = {
"status": "partial",
"messages": {"x": {"action": "import"}},
}
cherry_pick.reset_all_picks(manifest)
entry = manifest["channels"]["29wilson"]
assert entry["status"] == "skip"
assert entry["messages"] == {}
def test_conversation_not_imported(cherry_pick):
assert cherry_pick.looks_conversational("should we go with option A?")
assert cherry_pick.looks_conversational("sounds good!")
assert not cherry_pick.text_is_factual("should we book for Tuesday?")
assert cherry_pick.text_is_factual("Call Dr. Smith at 416-555-1234")
def test_name_list_detected(cherry_pick):
text = "Baby girl names\n• Maya\n• Sarah\n• Leah\n• Naomi"
assert cherry_pick.is_name_list_message(text)
assert not cherry_pick.is_name_list_message("Zoey had fun at camp today")
def test_latest_draft_only_one_winner(cherry_pick, export):
msgs, files_map = cherry_pick.build_latest_draft_winners(export, "153niagara")
# Multiple draft versions of hot_plate → one message id wins
hot = [mid for mid in msgs if "153niagara" in mid]
assert len(hot) >= 1
for mid, fids in files_map.items():
assert mid in msgs
assert fids
def test_should_import_doc_not_chat(cherry_pick, export):
latest, _ = cherry_pick.build_latest_draft_winners(export, "153niagara")
picked_docs = 0
picked_chat = 0
for msg in export.iter_channel_messages("153niagara"):
if cherry_pick.should_import(msg, "153niagara", latest_draft_msgs=latest):
if msg.get("files"):
picked_docs += 1
elif (msg.get("text") or "").strip():
picked_chat += 1
assert picked_docs >= 1
# Data-only mode: far fewer bare-text picks than files
assert picked_chat <= picked_docs + 5
def test_wilson_not_in_auto_channels(cherry_pick):
assert "29wilson" not in cherry_pick.AUTO_CHANNELS
assert "520steeles" not in cherry_pick.AUTO_CHANNELS
assert cherry_pick.RETURN_CHANNELS >= {"29wilson", "520steeles"}
def test_dry_run_pick_count(cherry_pick, export, empty_manifest, tmp_path, monkeypatch):
path, manifest = empty_manifest
monkeypatch.setattr(cherry_pick, "MANIFEST_PATH", tmp_path / "manifest.json")
monkeypatch.setattr(cherry_pick, "DEFAULT_EXPORT", export.paths.root)
import sys
sys.argv = ["auto-cherry-pick.py", "--dry-run"]
# Run pick logic inline
cherry_pick.reset_all_picks(manifest)
total = 0
for ch_name in sorted(cherry_pick.AUTO_CHANNELS):
if ch_name in cherry_pick.RETURN_CHANNELS:
continue
if not export.channel_folder(ch_name):
continue
latest, _ = cherry_pick.build_latest_draft_winners(export, ch_name)
for msg in export.iter_channel_messages(ch_name):
if cherry_pick.should_import(msg, ch_name, latest_draft_msgs=latest):
total += 1
assert 0 < total < 200 # mini fixture stays small
+63
View File
@@ -0,0 +1,63 @@
"""Additional auto-cherry-pick heuristic coverage."""
from __future__ import annotations
def test_informative_image_casual_name(cherry_pick):
f = {"name": "MVIMG_20190105.jpg", "mimetype": "image/jpeg"}
assert not cherry_pick.informative_image(f, "nice photo")
assert cherry_pick.informative_image(
f, "Invoice total $1,200 due 416-555-0100"
)
def test_informative_filename(cherry_pick):
assert cherry_pick.informative_filename("lease_agreement.pdf")
assert not cherry_pick.informative_filename("IMG_0001.jpg")
def test_msg_selection_skips_old_draft(cherry_pick):
msg = {
"id": "ch:1",
"files": [
{"id": "D1", "name": "draft_v1.docx"},
{"id": "D2", "name": "photo.jpg", "mimetype": "image/jpeg"},
],
}
sel = cherry_pick.msg_selection(msg, latest_draft_files={"D1"})
assert sel["files"].get("D1") is True
assert "D2" not in sel["files"] or sel["files"].get("D2") is True # image w/ name
def test_draft_group_key_strips_draft_prefix(cherry_pick):
key = cherry_pick.draft_group_key("draft_-_hot_plate_v2.docx")
assert "draft" not in key.split()[0] or "hot" in key
assert "hot" in key.replace("_", " ")
def test_text_is_factual_requires_signals(cherry_pick):
assert not cherry_pick.text_is_factual("sounds good thanks")
assert cherry_pick.text_is_factual("Pay $1,500 to TD account 12345")
def test_is_doc_and_image(cherry_pick):
assert cherry_pick.is_doc({"name": "x.pdf"})
assert cherry_pick.is_image({"name": "x.jpg", "mimetype": "image/jpeg"})
def test_channel_entry_skip_shape(cherry_pick):
entry = cherry_pick.channel_entry_skip("note")
assert entry["status"] == "skip"
assert entry["messages"] == {}
def test_reset_only_writes(empty_manifest, cherry_pick, tmp_path, monkeypatch):
path, _manifest = empty_manifest
path.write_text(__import__("json").dumps(_manifest, indent=2) + "\n", encoding="utf-8")
monkeypatch.setattr(cherry_pick, "MANIFEST_PATH", path)
import sys
monkeypatch.setattr(sys, "argv", ["auto-cherry-pick.py", "--reset"])
assert cherry_pick.main() == 0
saved = __import__("json").loads(path.read_text())
assert saved["channels"] == {}
+260
View File
@@ -0,0 +1,260 @@
"""Tests targeting previously uncovered branches."""
from __future__ import annotations
import io
import json
import urllib.error
import urllib.request
import zipfile
from pathlib import Path
import pytest
def _status(base: str, path: str, method: str = "GET", body: bytes | None = None):
req = urllib.request.Request(
f"{base}{path}",
data=body,
headers={"Content-Type": "application/json"} if body else {},
method=method,
)
try:
with urllib.request.urlopen(req, timeout=5) as resp:
return resp.status, resp.read()
except urllib.error.HTTPError as exc:
return exc.code, exc.read()
# --- server.py ---
def test_file_preview_bad_docx_zip():
import server
out = server.file_preview_payload_bytes(b"PK\x03\x04garbage", "bad.docx")
assert out["ok"] is False
assert "DOCX" in out["error"] or "Could not" in out["error"]
def test_file_preview_quip_html():
import server
html = '<div class="quip-canvas-content"><p>Canvas</p></div>'
out = server.file_preview_payload_bytes(html.encode(), "doc.quip")
assert out["ok"] and "Canvas" in out["html"]
def test_static_file_not_found(api):
status, _ = _status(api["base"], "/static/no-such-asset.css")
assert status == 404
def test_uploads_missing_file(api):
status, _ = _status(api["base"], "/uploads/__uploads/missing/deadbeef.bin")
assert status in (400, 404)
def test_file_preview_by_file_id_not_found(api):
status, body = _status(api["base"], "/api/file-preview?file_id=UNKNOWN_FILE_XYZ")
assert status == 404
data = json.loads(body.decode())
assert data["ok"] is False
def test_file_preview_by_rel_on_disk(api, review_server):
rel_dir = review_server["httpd"].RequestHandlerClass.export.paths.root
target = rel_dir / "__uploads" / "T1"
target.mkdir(parents=True, exist_ok=True)
(target / "note.txt").write_text("fixture preview text", encoding="utf-8")
status, body = _status(api["base"], "/api/file-preview?rel=__uploads/T1/note.txt")
assert status == 200
data = json.loads(body.decode())
assert data["ok"]
assert "fixture preview" in data["html"]
def test_manifest_post_empty_body(api):
status, _ = _status(
api["base"],
"/api/manifest",
method="POST",
body=b"",
)
assert status == 400
def test_manifest_post_wrong_path(api):
status, _ = _status(api["base"], "/api/manifest/extra", method="POST", body=b"{}")
assert status == 404
def test_manifest_post_invalid_import_mode_defaults(api):
api["post"](
"/api/manifest",
{
"channels": {
"medical": {
"status": "partial",
"import_mode": "bogus",
"messages": {},
}
}
},
)
med = api["get"]("/api/manifest")["channels"]["medical"]
assert med["import_mode"] == "all"
def test_manifest_post_skips_non_dict_user(api):
users = api["get"]("/api/users")["users"]
uid = users[0]["id"]
api["post"](
"/api/manifest",
{"users": {"bad": "nope", uid: {"import": True, "notes": "", "mattermost_username": ""}}},
)
again = api["get"]("/api/users")["users"]
assert any(u["id"] == uid for u in again)
def test_safe_export_path_rejects_dotdot():
import server
from slack_export import SlackExport
from unittest.mock import Mock
root = Path(__file__).resolve().parents[1] / "tests/fixtures/mini-export"
handler = Mock(spec=server.ReviewHandler)
handler.export = SlackExport(root)
assert server.ReviewHandler._safe_export_path(handler, "../outside") is None
assert server.ReviewHandler._safe_export_path(handler, "/etc/passwd") is None
# --- slack_export.py ---
@pytest.fixture
def rich_export(tmp_path):
from slack_export import SlackExport
root = tmp_path / "rich"
root.mkdir()
(root / "channels.json").write_text("[]", encoding="utf-8")
(root / "users.json").write_text(
json.dumps([{"id": "U1", "name": "u", "profile": {"real_name": "U"}}]),
encoding="utf-8",
)
(root / "canvases.json").write_text(
json.dumps([{"id": "CV1", "name": "canvas.pdf", "mimetype": "application/pdf"}]),
encoding="utf-8",
)
ch = root / "ch1"
ch.mkdir()
(ch / "meta.json").write_text('{"not": "a list"}', encoding="utf-8")
(ch / "2016-01-01.json").write_text(
json.dumps(
[
{"subtype": "channel_leave", "ts": "1.0"},
{"subtype": "channel_purpose", "purpose": "Purpose here", "ts": "2.0"},
{"files": [{"id": "NO_NAME"}]},
]
),
encoding="utf-8",
)
return SlackExport(root)
def test_file_index_from_canvases_and_skips_bad_rows(rich_export):
idx = rich_export.ensure_file_index()
assert "CV1" in idx
assert rich_export.file_record("NO_NAME") is None
def test_fetch_no_download_url(rich_export):
rich_export._file_index = {
"X1": {
"id": "X1",
"name": "a.bin",
"local_relpath": "__uploads/X1/a.bin",
"local_exists": False,
"url_private_download": None,
}
}
with pytest.raises(FileNotFoundError, match="No download URL"):
rich_export.fetch_file_bytes("X1")
def test_fetch_http_error(rich_export, monkeypatch):
rich_export._file_index = {
"X2": {
"id": "X2",
"name": "b.bin",
"local_relpath": "__uploads/X2/b.bin",
"local_exists": False,
"url_private_download": "https://example.com/x?token=xoxe-abc",
}
}
def _fail(*args, **kwargs):
raise urllib.error.HTTPError("url", 401, "Unauthorized", None, None)
monkeypatch.setattr("urllib.request.urlopen", _fail)
with pytest.raises(FileNotFoundError, match="401"):
rich_export.fetch_file_bytes("X2")
def test_normalize_leave_and_purpose(rich_export):
msgs = rich_export.iter_channel_messages("ch1")
texts = [m["text"] for m in msgs]
assert any("left" in t for t in texts)
assert any("Purpose" in t for t in texts)
def test_read_non_list_json_returns_empty(rich_export):
assert rich_export.read_messages_file("ch1", "meta") == []
def test_list_dates_missing_channel(rich_export):
assert rich_export.list_channel_date_files("missing-channel") == []
def test_search_files_query_filters_out(rich_export):
ch = rich_export.paths.root / "hits"
ch.mkdir()
(ch / "2017-01-01.json").write_text(
json.dumps(
[
{
"type": "message",
"user": "U1",
"ts": "1.0",
"text": "alpha",
"files": [{"id": "F9", "name": "pic.png", "mimetype": "image/png"}],
}
]
),
encoding="utf-8",
)
ex = rich_export
out = ex.search_files_global("zzzznomatch", file_type="image", limit=10)
assert out["hits"] == []
def test_search_global_hits_limit(export):
out = export.search_global("a", limit=1)
assert len(out["messages"]) <= 1
def test_search_messages_stops_at_limit(export):
hits = export.search_messages("zoey", "a", limit=2)
assert len(hits) <= 2
def test_classify_word_doc(export):
assert export.classify_file({"name": "x.doc", "mimetype": "application/msword"}) == "doc"
def test_register_file_raw_skips_incomplete(export):
export._file_index = {}
export._register_file_raw({"name": "onlyname"})
export._register_file_raw({"id": "idonly"})
assert export.file_record("onlyname") is None
assert export.file_record("idonly") is None
+113
View File
@@ -0,0 +1,113 @@
"""Tests for small helpers in slack_export and server."""
from __future__ import annotations
import io
import json
import zipfile
from pathlib import Path
import pytest
from slack_export import SlackExport, load_json, resolve_paths, slack_ts_to_iso, user_display
def test_slack_ts_to_iso():
assert slack_ts_to_iso("1476210563.000002") is not None
assert "2016" in slack_ts_to_iso("1476210563.000002")
assert slack_ts_to_iso(None) is None
assert slack_ts_to_iso("not-a-ts") is None
def test_user_display_fallbacks():
assert user_display(None) == "unknown"
assert user_display({"name": "bot", "is_bot": True}) == "bot"
assert user_display({"profile": {"display_name": "Ilia"}, "name": "jedi"}) == "Ilia"
def test_resolve_paths_custom_root(fixture_export_path):
paths = resolve_paths(fixture_export_path)
assert paths.root == fixture_export_path.resolve()
assert paths.channels_json.name == "channels.json"
def test_load_json_roundtrip(tmp_path):
p = tmp_path / "x.json"
p.write_text('{"a": 1}', encoding="utf-8")
assert load_json(p) == {"a": 1}
def test_server_manifest_helpers(tmp_path, fixture_export_path):
import server
path = tmp_path / "m.json"
default = server.default_manifest(str(fixture_export_path))
assert default["version"] == 1
loaded = server.load_manifest(path, str(fixture_export_path))
assert loaded["export_path"] == str(fixture_export_path)
path.write_text("{not json", encoding="utf-8")
with pytest.raises(json.JSONDecodeError):
server.load_manifest(path, str(fixture_export_path))
server.save_manifest(path, {"version": 1, "channels": {}})
assert path.is_file()
assert "updated_at" in load_json(path)
def test_file_preview_payload_bytes_text_and_html():
import server
txt = server.file_preview_payload_bytes(b"hello world", "notes.txt")
assert txt["ok"] and "hello" in txt["html"]
html = server.file_preview_payload_bytes(
b"<html><body><h1>Title</h1></body></html>", "page.html"
)
assert html["ok"] and html["kind"] == "html"
bad = server.file_preview_payload_bytes(b"\x00\x01\xff", "binary.dat")
assert not bad["ok"]
def test_file_preview_payload_bytes_docx(tmp_path):
import server
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr(
"word/document.xml",
'<?xml version="1.0"?>'
'<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
"<w:p><w:r><w:t>Lease terms</w:t></w:r></w:p></w:document>",
)
raw = buf.getvalue()
out = server.file_preview_payload_bytes(raw, "lease.docx")
assert out["ok"]
assert "Lease" in out["html"]
path = tmp_path / "lease.docx"
path.write_bytes(raw)
assert "Lease" in server.file_preview_payload(path)["html"]
def test_docx_to_html_file(tmp_path):
import server
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w") as zf:
zf.writestr(
"word/document.xml",
'<?xml version="1.0"?>'
'<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">'
"<w:p><w:r><w:t>Line one</w:t></w:r></w:p></w:document>",
)
path = tmp_path / "t.docx"
path.write_bytes(buf.getvalue())
assert "Line one" in server.docx_to_html(path)
def test_export_missing_root_raises():
with pytest.raises(FileNotFoundError):
SlackExport("/nonexistent/export/path")
+151
View File
@@ -0,0 +1,151 @@
"""Tests that manifest selections produce a valid filtered Slack export."""
from __future__ import annotations
import json
import shutil
import sys
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[1]
def _load_prepare():
import importlib.util
path = REPO / "scripts" / "prepare-export.py"
spec = importlib.util.spec_from_file_location("prepare_export", path)
mod = importlib.util.module_from_spec(spec)
assert spec.loader
spec.loader.exec_module(mod)
return mod
@pytest.fixture
def prepare_mod():
return _load_prepare()
def test_channel_included_rules(prepare_mod):
assert prepare_mod.channel_included({"status": "import", "import_mode": "all"})
assert not prepare_mod.channel_included({"status": "skip"})
assert prepare_mod.channel_included(
{
"status": "partial",
"import_mode": "pick",
"messages": {"medical:1.0": {"action": "import", "checked": True}},
}
)
assert not prepare_mod.channel_included(
{"status": "partial", "import_mode": "pick", "messages": {}}
)
def test_message_and_files_filter(prepare_mod):
entry = {
"status": "partial",
"import_mode": "pick",
"messages": {
"ch:1000.1": {
"action": "import",
"files": {"F1": True, "F2": False},
}
},
}
assert prepare_mod.message_included(entry, "ch:1000.1")
assert not prepare_mod.message_included(entry, "ch:1000.2")
raw = {
"ts": "1000.1",
"text": "hi",
"files": [
{"id": "F1", "name": "a.pdf"},
{"id": "F2", "name": "b.pdf"},
],
}
out = prepare_mod.filter_files_on_message(entry, "ch:1000.1", raw)
assert len(out["files"]) == 1
assert out["files"][0]["id"] == "F1"
def test_prepare_export_writes_slack_shape(prepare_mod, fixture_export_path, tmp_path):
from slack_export import SlackExport
export = SlackExport(fixture_export_path)
msgs = export.iter_channel_messages("medical")
pick = msgs[0]
key = pick["id"]
manifest = {
"channels": {
"medical": {
"status": "partial",
"import_mode": "pick",
"import": True,
"messages": {key: {"action": "import", "checked": True, "files": {}}},
}
}
}
out = tmp_path / "prepared"
stats = prepare_mod.prepare_export(fixture_export_path, manifest, out, export_mod=export)
assert stats["medical"] == 1
assert (out / "channels.json").is_file()
assert (out / "users.json").is_file()
assert (out / "medical").is_dir()
day_files = list((out / "medical").glob("*.json"))
assert len(day_files) == 1
written = json.loads(day_files[0].read_text(encoding="utf-8"))
assert len(written) == 1
assert written[0]["ts"] == pick["ts"]
assert (out / "prepare-export-summary.json").is_file()
def test_prepare_whole_channel(prepare_mod, fixture_export_path, tmp_path):
from slack_export import SlackExport
export = SlackExport(fixture_export_path)
total = len(export.iter_channel_messages("medical"))
manifest = {
"channels": {
"medical": {
"status": "import",
"import_mode": "all",
"import": True,
"messages": {},
}
}
}
out = tmp_path / "prepared-all"
stats = prepare_mod.prepare_export(fixture_export_path, manifest, out, export_mod=export)
assert stats["medical"] == total
def test_prepare_skips_channel(prepare_mod, fixture_export_path, tmp_path):
from slack_export import SlackExport
export = SlackExport(fixture_export_path)
manifest = {
"channels": {
"medical": {"status": "skip", "import": False, "messages": {}},
"zoey": {
"status": "partial",
"import_mode": "pick",
"import": True,
"messages": {
export.iter_channel_messages("zoey")[0]["id"]: {
"action": "import",
"checked": True,
}
},
},
}
}
out = tmp_path / "prepared-skip"
stats = prepare_mod.prepare_export(fixture_export_path, manifest, out, export_mod=export)
assert "medical" not in stats
assert stats["zoey"] == 1
meta = json.loads((out / "channels.json").read_text(encoding="utf-8"))
names = {c["name"] for c in meta}
assert "medical" not in names
assert "zoey" in names
+50
View File
@@ -0,0 +1,50 @@
"""Tests for scripts/preset-skips.py."""
from __future__ import annotations
import importlib.util
import json
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[1]
def _load_preset_skips():
path = REPO / "scripts" / "preset-skips.py"
spec = importlib.util.spec_from_file_location("preset_skips", path)
mod = importlib.util.module_from_spec(spec)
assert spec.loader
spec.loader.exec_module(mod)
return mod
def test_load_preset_tiers():
mod = _load_preset_skips()
preset = mod.load_preset(REPO / "presets" / "levkin-skips.json")
assert "medical" in preset["skip_all"]
assert "zoey" in preset["review_priority"]
assert "29wilson" in preset["tier_b"]
def test_channel_entry_skip():
mod = _load_preset_skips()
entry = mod.channel_entry_skip("tier-a")
assert entry["status"] == "skip"
assert entry["reviewed"] is True
def test_main_writes_manifest(tmp_path, fixture_export_path, monkeypatch):
mod = _load_preset_skips()
manifest_path = tmp_path / "manifest.json"
monkeypatch.setattr(mod, "MANIFEST_PATH", manifest_path)
monkeypatch.setattr(mod, "DEFAULT_EXPORT", fixture_export_path)
import sys
sys.argv = ["preset-skips.py", str(fixture_export_path)]
assert mod.main() == 0
data = json.loads(manifest_path.read_text())
assert data["channels"]["medical"]["status"] == "skip"
assert "zoey" not in data["channels"] or data["channels"].get("zoey", {}).get("status") != "skip"
+112
View File
@@ -0,0 +1,112 @@
"""HTTP API tests against the review server (mini-export)."""
from __future__ import annotations
import json
def test_health(api):
data = api["get"]("/api/health")
assert data["ok"] is True
assert "mini-export" in data["export_path"]
def test_summary(api):
data = api["get"]("/api/summary")
assert data["channel_count"] >= 3
assert data["total_messages"] > 0
def test_channels_list(api):
data = api["get"]("/api/channels")
names = {c["name"] for c in data["channels"]}
assert "medical" in names
med = next(c for c in data["channels"] if c["name"] == "medical")
assert med["status"] == "partial"
assert med["import_mode"] == "pick"
def test_channel_messages_all(api):
data = api["get"]("/api/channels/medical/messages/all")
assert data["channel"] == "medical"
assert data["total"] == len(data["messages"])
assert data["total"] > 0
def test_search_messages(api):
data = api["get"]("/api/search?q=doctor&limit=10")
assert "messages" in data
def test_search_files(api):
data = api["get"]("/api/search-files?type=pdf&q=&limit=10")
assert data["file_type"] == "pdf"
assert "hits" in data
def test_manifest_roundtrip(api):
got = api["get"]("/api/manifest")
assert "channels" in got
got["global_notes"] = "pytest was here"
api["post"]("/api/manifest", {"global_notes": "pytest was here", "channels": got["channels"], "users": {}})
again = api["get"]("/api/manifest")
assert again["global_notes"] == "pytest was here"
def test_manifest_merge_preserves_other_channel_picks(api):
api["post"](
"/api/manifest",
{
"channels": {
"medical": {
"import": True,
"status": "partial",
"import_mode": "pick",
"reviewed": True,
"messages": {
"medical:test:1": {
"action": "import",
"checked": True,
"files": {},
"notes": "",
"merge_target": "",
}
},
}
}
},
)
api["post"](
"/api/manifest",
{
"channels": {
"zoey": {
"import": True,
"status": "partial",
"import_mode": "pick",
"reviewed": True,
"messages": {
"zoey:test:2": {
"action": "import",
"checked": True,
"files": {},
"notes": "",
"merge_target": "",
}
},
}
}
},
)
merged = api["get"]("/api/manifest")
assert "medical:test:1" in merged["channels"]["medical"]["messages"]
assert "zoey:test:2" in merged["channels"]["zoey"]["messages"]
def test_static_index(api):
import urllib.request
with urllib.request.urlopen(f"{api['base']}/", timeout=5) as resp:
html = resp.read().decode()
assert "Slack import review" in html
assert "filter-status" in html
+162
View File
@@ -0,0 +1,162 @@
"""Additional HTTP API and server module coverage."""
from __future__ import annotations
import json
import urllib.error
import urllib.request
import pytest
def _get_raw(base: str, path: str) -> tuple[int, bytes]:
try:
with urllib.request.urlopen(f"{base}{path}", timeout=5) as resp:
return resp.status, resp.read()
except urllib.error.HTTPError as exc:
return exc.code, exc.read()
def test_users_list(api):
data = api["get"]("/api/users")
assert isinstance(data["users"], list)
if data["users"]:
assert "id" in data["users"][0]
def test_channel_search_endpoint(api):
data = api["get"]("/api/channels/medical/search?q=doctor")
assert data["channel"] == "medical"
assert isinstance(data["hits"], list)
def test_channel_messages_paged(api):
data = api["get"]("/api/channels/zoey/messages?limit=3&offset=0")
assert data["channel"] == "zoey"
assert len(data["messages"]) <= 3
assert "dates" in data
def test_channels_filter_query(api):
data = api["get"]("/api/channels?q=med")
names = {c["name"] for c in data["channels"]}
assert "medical" in names
def test_file_preview_missing_rel(api):
status, body = _get_raw(api["base"], "/api/file-preview?rel=../outside.txt")
assert status == 404
data = json.loads(body.decode())
assert data["ok"] is False
def test_file_preview_traversal_blocked(api):
status, _ = _get_raw(api["base"], "/api/file-preview?rel=..%2F..%2Fetc%2Fpasswd")
assert status in (400, 404)
def test_file_download_bad_id(api):
status, _ = _get_raw(api["base"], "/api/file-download?file_id=NOPE")
assert status == 404
def test_file_download_missing_param(api):
status, _ = _get_raw(api["base"], "/api/file-download")
assert status == 400
def test_uploads_path_traversal(api):
status, _ = _get_raw(api["base"], "/uploads/..%2F..%2Fetc%2Fpasswd")
assert status == 400
def test_static_css(api):
status, body = _get_raw(api["base"], "/static/app.css")
assert status == 200
assert b"message" in body or len(body) > 0
def test_options_cors(api):
req = urllib.request.Request(
f"{api['base']}/api/manifest",
method="OPTIONS",
)
with urllib.request.urlopen(req, timeout=5) as resp:
assert resp.status == 204
assert resp.headers.get("Access-Control-Allow-Origin") == "*"
def test_manifest_invalid_json(api):
req = urllib.request.Request(
f"{api['base']}/api/manifest",
data=b"{not valid json",
headers={"Content-Type": "application/json"},
method="POST",
)
with pytest.raises(urllib.error.HTTPError) as exc:
urllib.request.urlopen(req, timeout=5)
assert exc.value.code == 400
def test_not_found_route(api):
status, _ = _get_raw(api["base"], "/api/does-not-exist")
assert status == 404
def test_manifest_post_users(api):
users_before = api["get"]("/api/users")["users"]
assert users_before
uid = users_before[0]["id"]
api["post"](
"/api/manifest",
{
"users": {
uid: {
"import": True,
"notes": "mapped",
"mattermost_username": "testuser",
}
}
},
)
users = api["get"]("/api/users")["users"]
match = next((u for u in users if u["id"] == uid), None)
assert match and match["import"] and match["mattermost_username"] == "testuser"
def test_manifest_post_invalid_channel_entry_skipped(api):
api["post"](
"/api/manifest",
{"channels": {"bad": "not-a-dict", "medical": {"status": "partial", "import_mode": "pick"}}},
)
med = api["get"]("/api/manifest")["channels"]["medical"]
assert med["status"] == "partial"
def test_file_download_local_upload(api, tmp_path, review_server):
"""Download endpoint serves bytes when upload is on disk."""
import sys
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parents[1]))
from slack_export import SlackExport
root = tmp_path / "dl_export"
root.mkdir()
(root / "channels.json").write_text("[]", encoding="utf-8")
(root / "users.json").write_text("[]", encoding="utf-8")
up = root / "__uploads" / "FID1"
up.mkdir(parents=True)
(up / "doc.pdf").write_bytes(b"%PDF-1.4")
ex = SlackExport(root)
ex.ensure_file_index()
ex._file_index["FID1"] = {
"id": "FID1",
"name": "doc.pdf",
"local_relpath": "__uploads/FID1/doc.pdf",
"local_exists": True,
"url_private_download": None,
}
review_server["httpd"].RequestHandlerClass.export = ex
status, body = _get_raw(api["base"], "/api/file-download?file_id=FID1")
assert status == 200
assert body.startswith(b"%PDF")
+58
View File
@@ -0,0 +1,58 @@
"""Unit tests for slack_export.py (mini-export fixture)."""
from __future__ import annotations
def test_fixture_has_expected_channels(export):
names = {c["name"] for c in export.build_summary()["channels"]}
assert names >= {"medical", "zoey", "153niagara"}
def test_normalize_message_shape(export):
msgs = export.iter_channel_messages("medical")
msg = msgs[0]
assert "id" in msg
assert "channel" in msg
assert msg["channel"] == "medical"
assert "user_name" in msg
assert "text" in msg
def test_classify_file_types(export):
assert export.classify_file({"name": "lease.pdf", "mimetype": "application/pdf"}) == "pdf"
assert export.classify_file({"name": "photo.jpg", "mimetype": "image/jpeg"}) == "image"
assert export.classify_file({"name": "data.xlsx", "mimetype": ""}) == "spreadsheet"
def test_search_channels(export):
hits = export.search_channels("med")
assert any(c["name"] == "medical" for c in hits)
assert not any(c["name"] == "zoey" for c in hits)
def test_search_global_text(export):
result = export.search_global("doctor", limit=20)
assert result["query"] == "doctor"
assert isinstance(result["messages"], list)
def test_search_files_global_images(export):
result = export.search_files_global("", file_type="image", limit=30)
assert result["file_type"] == "image"
for hit in result["hits"]:
kinds = set(hit.get("file_kinds") or [])
assert "image" in kinds
def test_channel_messages_all_sorted(export):
data = export.channel_messages_all("zoey")
assert data["channel"] == "zoey"
assert data["total"] == len(data["messages"])
times = [m.get("time") or "" for m in data["messages"]]
assert times == sorted(times)
def test_upload_path_safe(export):
rel = export.upload_relpath("F123", "my file.pdf")
assert rel == "__uploads/F123/my file.pdf"
assert ".." not in rel
+203
View File
@@ -0,0 +1,203 @@
"""Additional slack_export coverage — normalize, index, pagination, search."""
from __future__ import annotations
import json
from pathlib import Path
import pytest
from slack_export import SlackExport
@pytest.fixture
def export_with_synthetic(tmp_path, fixture_export_path):
"""Mini export plus synthetic message edge cases."""
ch = tmp_path / "synthetic"
ch.mkdir()
(tmp_path / "channels.json").write_text(
json.dumps([{"name": "synthetic", "id": "C1"}]),
encoding="utf-8",
)
(tmp_path / "users.json").write_text(
json.dumps(
[
{
"id": "U1",
"name": "ilia",
"profile": {"display_name": "Ilia", "real_name": "Ilia L"},
}
]
),
encoding="utf-8",
)
messages = [
{"type": "message", "user": "U1", "ts": "1000.1", "subtype": "channel_join"},
{
"type": "message",
"user": "U1",
"ts": "1000.2",
"subtype": "channel_topic",
"topic": "Rentals",
},
{
"type": "message",
"user": "U1",
"ts": "1000.3",
"subtype": "file_share",
"files": [{"id": "F1", "name": "invoice.pdf", "mimetype": "application/pdf"}],
},
{
"type": "message",
"user": "U1",
"ts": "1000.4",
"thread_ts": "1000.4",
"reply_count": 2,
"text": "Thread root",
},
{
"type": "message",
"user": "U1",
"ts": "1000.5",
"thread_ts": "1000.4",
"text": "Reply",
},
{"type": "message", "bot_id": "B1", "ts": "1000.6", "text": "bot says hi"},
{"type": "message", "user": "U1", "ts": "1000.7", "blocks": [{"type": "section"}]},
{"type": "message", "user": "U1", "ts": "1000.8", "attachments": [{"text": "card"}]},
]
(ch / "2016-10-10.json").write_text(json.dumps(messages), encoding="utf-8")
return SlackExport(tmp_path)
def test_classify_all_file_kinds(export):
assert export.classify_file({"name": "x.pptx"}) == "slides"
assert export.classify_file({"name": "x.mp4", "mimetype": "video/mp4"}) == "video"
assert export.classify_file({"name": "x.mp3"}) == "audio"
assert export.classify_file({"name": "x.zip"}) == "archive"
assert export.classify_file({"name": "readme.md"}) == "text"
assert export.classify_file({"name": "unknown.bin"}) == "other"
def test_normalize_message_subtypes(export_with_synthetic):
msgs = export_with_synthetic.iter_channel_messages("synthetic")
by_ts = {m["ts"]: m for m in msgs}
assert "joined" in by_ts["1000.1"]["text"]
assert "Rentals" in by_ts["1000.2"]["text"]
assert "invoice.pdf" in by_ts["1000.3"]["text"]
assert by_ts["1000.5"]["is_thread_reply"]
assert by_ts["1000.4"]["thread_reply_count"] >= 1
assert by_ts["1000.6"]["is_bot"]
assert by_ts["1000.7"]["text"] == "[rich message]"
assert by_ts["1000.8"]["text"] == "[attachment]"
def test_ensure_file_index(export_with_synthetic):
idx = export_with_synthetic.ensure_file_index()
assert "F1" in idx
assert idx["F1"]["name"] == "invoice.pdf"
def test_file_record_unknown(export):
assert export.file_record("NONEXISTENT_ID_XYZ") is None
def test_channel_messages_page_pagination(export):
page = export.channel_messages_page("zoey", limit=2, offset=0)
assert page["channel"] == "zoey"
assert len(page["messages"]) <= 2
assert page["total_in_date"] >= len(page["messages"])
if page["has_more"]:
page2 = export.channel_messages_page("zoey", limit=2, offset=2)
assert page2["offset"] == 2
def test_channel_messages_page_empty_channel(tmp_path):
empty = tmp_path / "empty_export"
empty.mkdir()
(empty / "channels.json").write_text("[]", encoding="utf-8")
(empty / "users.json").write_text("[]", encoding="utf-8")
(empty / "ghost").mkdir()
ex = SlackExport(empty)
out = ex.channel_messages_page("ghost")
assert out["messages"] == []
assert out["has_more"] is False
def test_search_messages_channel_scoped(export):
hits = export.search_messages("medical", "doctor", limit=5)
assert isinstance(hits, list)
assert export.search_messages("medical", "", limit=5) == []
def test_search_global_empty_query(export):
out = export.search_global(" ", limit=5)
assert out["channels"] == []
assert out["messages"] == []
def test_search_global_channel_name(export):
out = export.search_global("niagara", limit=10)
assert any(c["name"] == "153niagara" for c in out["channels"])
def test_search_files_with_query(export):
out = export.search_files_global("proof", file_type="all", limit=10)
assert "hits" in out
def test_channel_stats_missing(export):
stats = export.channel_stats("no-such-channel-xyz")
assert stats["exists_in_export"] is False
assert stats["message_count"] == 0
def test_read_messages_invalid_date(export):
assert export.read_messages_file("medical", "2099-99-99") == []
def test_attach_thread_reply_counts(export_with_synthetic):
msgs = export_with_synthetic.iter_channel_messages("synthetic")
assert any(m.get("thread_reply_count", 0) > 0 for m in msgs)
def test_fetch_file_bytes_local(tmp_path, export_with_synthetic):
rec = export_with_synthetic.file_record("F1")
assert rec
local = export_with_synthetic.paths.root / rec["local_relpath"]
local.parent.mkdir(parents=True, exist_ok=True)
local.write_bytes(b"%PDF-1.4 test")
data, meta = export_with_synthetic.fetch_file_bytes("F1")
assert data.startswith(b"%PDF")
assert meta["id"] == "F1"
def test_fetch_file_bytes_remote(export_with_synthetic, monkeypatch):
rec = export_with_synthetic.file_record("F1")
local = export_with_synthetic.paths.root / rec["local_relpath"]
if local.is_file():
local.unlink()
class _Resp:
def read(self):
return b"remote-bytes"
def __enter__(self):
return self
def __exit__(self, *args):
pass
monkeypatch.setattr("urllib.request.urlopen", lambda *a, **k: _Resp())
rec_entry = export_with_synthetic.ensure_file_index()["F1"]
rec_entry["url_private_download"] = "https://files.slack.com/x?token=xoxe-test"
data, meta = export_with_synthetic.fetch_file_bytes("F1")
assert data == b"remote-bytes"
assert local.is_file()
assert meta["local_exists"] is True
def test_fetch_file_bytes_unknown_id(export):
with pytest.raises(FileNotFoundError, match="Unknown file"):
export.fetch_file_bytes("NO_SUCH_FILE")