mataroa-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mataroa_mcp-0.1.0/.gitignore +4 -0
- mataroa_mcp-0.1.0/.vscode/settings.json +7 -0
- mataroa_mcp-0.1.0/LICENSE +21 -0
- mataroa_mcp-0.1.0/PKG-INFO +175 -0
- mataroa_mcp-0.1.0/README.md +144 -0
- mataroa_mcp-0.1.0/images/screenshot.png +0 -0
- mataroa_mcp-0.1.0/pyproject.toml +37 -0
- mataroa_mcp-0.1.0/src/mataroa_mcp/__init__.py +3 -0
- mataroa_mcp-0.1.0/src/mataroa_mcp/client.py +142 -0
- mataroa_mcp-0.1.0/src/mataroa_mcp/models.py +63 -0
- mataroa_mcp-0.1.0/src/mataroa_mcp/server.py +257 -0
- mataroa_mcp-0.1.0/tests/__init__.py +0 -0
- mataroa_mcp-0.1.0/tests/conftest.py +97 -0
- mataroa_mcp-0.1.0/tests/test_client.py +243 -0
- mataroa_mcp-0.1.0/tests/test_integration.py +108 -0
- mataroa_mcp-0.1.0/tests/test_server.py +318 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ayush
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mataroa-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for managing a Mataroa blog via natural language
|
|
5
|
+
License: MIT License
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2026 Ayush
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Requires-Dist: httpx>=0.27.0
|
|
29
|
+
Requires-Dist: mcp[cli]>=1.0.0
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# mataroa-mcp
|
|
33
|
+
|
|
34
|
+
An MCP server that lets you manage your [Mataroa](https://mataroa.blog) blog through natural language in Claude Desktop, Claude Code, or any MCP-compatible AI client.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## What you can do
|
|
39
|
+
|
|
40
|
+
Tell Claude things like:
|
|
41
|
+
|
|
42
|
+
- *"Show me all my blog posts"*
|
|
43
|
+
- *"Write a post about my weekend hike and save it as a draft"*
|
|
44
|
+
- *"Publish my sourdough post"*
|
|
45
|
+
- *"Are there any comments waiting for approval?"*
|
|
46
|
+
- *"Delete the draft called testing-123"*
|
|
47
|
+
- *"Create an About page for my blog"*
|
|
48
|
+
|
|
49
|
+
Claude will call the right tool automatically.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Setup (5 minutes)
|
|
54
|
+
|
|
55
|
+
### 1. Get your Mataroa API key
|
|
56
|
+
|
|
57
|
+
1. Go to [mataroa.blog](https://mataroa.blog) and log in (or create a free account).
|
|
58
|
+
2. Open **Dashboard → API** and copy your API key.
|
|
59
|
+
|
|
60
|
+
### 2. Install the server
|
|
61
|
+
|
|
62
|
+
You need Python 3.10 or later. Clone this repo and install it locally:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
git clone https://github.com/ayush111111/matoroa-mcp.git
|
|
66
|
+
cd matoroa-mcp
|
|
67
|
+
pip install -e .
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 3. Add to Claude Desktop
|
|
71
|
+
|
|
72
|
+
Open your Claude Desktop config file:
|
|
73
|
+
|
|
74
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
75
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
76
|
+
|
|
77
|
+
If there is no `"mcpServers"` key, add it at the top level alongside any existing keys:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"mataroa": {
|
|
83
|
+
"command": "python",
|
|
84
|
+
"args": ["-m", "mataroa_mcp.server"],
|
|
85
|
+
"env": {
|
|
86
|
+
"MATAROA_API_KEY": "paste-your-key-here"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"...your other existing config keys...": "..."
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> **Note:** Once `mataroa-mcp` is published to PyPI you can replace `"command": "python", "args": ["-m", "mataroa_mcp.server"]` with `"command": "uvx", "args": ["mataroa-mcp"]` and skip the clone/install step entirely.
|
|
95
|
+
|
|
96
|
+
### 4. Restart Claude Desktop
|
|
97
|
+
|
|
98
|
+
Quit and reopen Claude Desktop. You should see a small plug icon in the chat bar indicating MCP tools are active.
|
|
99
|
+
|
|
100
|
+
### 5. Test it
|
|
101
|
+
|
|
102
|
+
Type: *"Show me my blog posts"* — Claude should list them.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Add to Claude Code
|
|
107
|
+
|
|
108
|
+
Create or edit `.mcp.json` in your project root:
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"mcpServers": {
|
|
113
|
+
"mataroa": {
|
|
114
|
+
"command": "python",
|
|
115
|
+
"args": ["-m", "mataroa_mcp.server"],
|
|
116
|
+
"env": {
|
|
117
|
+
"MATAROA_API_KEY": "paste-your-key-here"
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## Available tools
|
|
127
|
+
|
|
128
|
+
| Tool | What it does |
|
|
129
|
+
|------|-------------|
|
|
130
|
+
| `list_posts` | List all posts (published + drafts) |
|
|
131
|
+
| `list_drafts` | List only unpublished drafts |
|
|
132
|
+
| `get_post` | Get a single post by slug |
|
|
133
|
+
| `create_post` | Create a new post |
|
|
134
|
+
| `update_post` | Edit an existing post |
|
|
135
|
+
| `delete_post` | Permanently delete a post |
|
|
136
|
+
| `publish_post` | Publish a draft (sets today's date, or a date you choose) |
|
|
137
|
+
| `unpublish_post` | Revert a published post back to draft |
|
|
138
|
+
| `list_comments` | List all comments (or just for one post) |
|
|
139
|
+
| `list_pending_comments` | List comments awaiting moderation |
|
|
140
|
+
| `approve_comment` | Approve a pending comment |
|
|
141
|
+
| `delete_comment` | Delete a comment |
|
|
142
|
+
| `list_pages` | List all pages |
|
|
143
|
+
| `get_page` | Get a single page by slug |
|
|
144
|
+
| `create_page` | Create a new page |
|
|
145
|
+
| `update_page` | Edit an existing page |
|
|
146
|
+
| `delete_page` | Permanently delete a page |
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Notes
|
|
151
|
+
|
|
152
|
+
- **API key is never stored in code** — it's always read from the `MATAROA_API_KEY` environment variable.
|
|
153
|
+
- **Mataroa has no rate limiting**, so you can make requests freely.
|
|
154
|
+
- **Blog-level settings** (title, custom domain, etc.) are not accessible via the Mataroa API and therefore not exposed here.
|
|
155
|
+
- Draft posts have `published_at: null`. Publishing sets it to a date; unpublishing clears it.
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Running tests
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Unit tests (no API key needed)
|
|
163
|
+
pip install -e .
|
|
164
|
+
pip install pytest pytest-asyncio respx
|
|
165
|
+
pytest tests/test_client.py tests/test_server.py -v
|
|
166
|
+
|
|
167
|
+
# Integration tests (requires a real Mataroa API key)
|
|
168
|
+
MATAROA_API_KEY=your-key pytest tests/test_integration.py --run-integration -v
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# mataroa-mcp
|
|
2
|
+
|
|
3
|
+
An MCP server that lets you manage your [Mataroa](https://mataroa.blog) blog through natural language in Claude Desktop, Claude Code, or any MCP-compatible AI client.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What you can do
|
|
8
|
+
|
|
9
|
+
Tell Claude things like:
|
|
10
|
+
|
|
11
|
+
- *"Show me all my blog posts"*
|
|
12
|
+
- *"Write a post about my weekend hike and save it as a draft"*
|
|
13
|
+
- *"Publish my sourdough post"*
|
|
14
|
+
- *"Are there any comments waiting for approval?"*
|
|
15
|
+
- *"Delete the draft called testing-123"*
|
|
16
|
+
- *"Create an About page for my blog"*
|
|
17
|
+
|
|
18
|
+
Claude will call the right tool automatically.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Setup (5 minutes)
|
|
23
|
+
|
|
24
|
+
### 1. Get your Mataroa API key
|
|
25
|
+
|
|
26
|
+
1. Go to [mataroa.blog](https://mataroa.blog) and log in (or create a free account).
|
|
27
|
+
2. Open **Dashboard → API** and copy your API key.
|
|
28
|
+
|
|
29
|
+
### 2. Install the server
|
|
30
|
+
|
|
31
|
+
You need Python 3.10 or later. Clone this repo and install it locally:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
git clone https://github.com/ayush111111/matoroa-mcp.git
|
|
35
|
+
cd matoroa-mcp
|
|
36
|
+
pip install -e .
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 3. Add to Claude Desktop
|
|
40
|
+
|
|
41
|
+
Open your Claude Desktop config file:
|
|
42
|
+
|
|
43
|
+
- **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
44
|
+
- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json`
|
|
45
|
+
|
|
46
|
+
If there is no `"mcpServers"` key, add it at the top level alongside any existing keys:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"mcpServers": {
|
|
51
|
+
"mataroa": {
|
|
52
|
+
"command": "python",
|
|
53
|
+
"args": ["-m", "mataroa_mcp.server"],
|
|
54
|
+
"env": {
|
|
55
|
+
"MATAROA_API_KEY": "paste-your-key-here"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"...your other existing config keys...": "..."
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> **Note:** Once `mataroa-mcp` is published to PyPI you can replace `"command": "python", "args": ["-m", "mataroa_mcp.server"]` with `"command": "uvx", "args": ["mataroa-mcp"]` and skip the clone/install step entirely.
|
|
64
|
+
|
|
65
|
+
### 4. Restart Claude Desktop
|
|
66
|
+
|
|
67
|
+
Quit and reopen Claude Desktop. You should see a small plug icon in the chat bar indicating MCP tools are active.
|
|
68
|
+
|
|
69
|
+
### 5. Test it
|
|
70
|
+
|
|
71
|
+
Type: *"Show me my blog posts"* — Claude should list them.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Add to Claude Code
|
|
76
|
+
|
|
77
|
+
Create or edit `.mcp.json` in your project root:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"mataroa": {
|
|
83
|
+
"command": "python",
|
|
84
|
+
"args": ["-m", "mataroa_mcp.server"],
|
|
85
|
+
"env": {
|
|
86
|
+
"MATAROA_API_KEY": "paste-your-key-here"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Available tools
|
|
96
|
+
|
|
97
|
+
| Tool | What it does |
|
|
98
|
+
|------|-------------|
|
|
99
|
+
| `list_posts` | List all posts (published + drafts) |
|
|
100
|
+
| `list_drafts` | List only unpublished drafts |
|
|
101
|
+
| `get_post` | Get a single post by slug |
|
|
102
|
+
| `create_post` | Create a new post |
|
|
103
|
+
| `update_post` | Edit an existing post |
|
|
104
|
+
| `delete_post` | Permanently delete a post |
|
|
105
|
+
| `publish_post` | Publish a draft (sets today's date, or a date you choose) |
|
|
106
|
+
| `unpublish_post` | Revert a published post back to draft |
|
|
107
|
+
| `list_comments` | List all comments (or just for one post) |
|
|
108
|
+
| `list_pending_comments` | List comments awaiting moderation |
|
|
109
|
+
| `approve_comment` | Approve a pending comment |
|
|
110
|
+
| `delete_comment` | Delete a comment |
|
|
111
|
+
| `list_pages` | List all pages |
|
|
112
|
+
| `get_page` | Get a single page by slug |
|
|
113
|
+
| `create_page` | Create a new page |
|
|
114
|
+
| `update_page` | Edit an existing page |
|
|
115
|
+
| `delete_page` | Permanently delete a page |
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Notes
|
|
120
|
+
|
|
121
|
+
- **API key is never stored in code** — it's always read from the `MATAROA_API_KEY` environment variable.
|
|
122
|
+
- **Mataroa has no rate limiting**, so you can make requests freely.
|
|
123
|
+
- **Blog-level settings** (title, custom domain, etc.) are not accessible via the Mataroa API and therefore not exposed here.
|
|
124
|
+
- Draft posts have `published_at: null`. Publishing sets it to a date; unpublishing clears it.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Running tests
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Unit tests (no API key needed)
|
|
132
|
+
pip install -e .
|
|
133
|
+
pip install pytest pytest-asyncio respx
|
|
134
|
+
pytest tests/test_client.py tests/test_server.py -v
|
|
135
|
+
|
|
136
|
+
# Integration tests (requires a real Mataroa API key)
|
|
137
|
+
MATAROA_API_KEY=your-key pytest tests/test_integration.py --run-integration -v
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
|
Binary file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mataroa-mcp"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "MCP server for managing a Mataroa blog via natural language"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { file = "LICENSE" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"mcp[cli]>=1.0.0",
|
|
14
|
+
"httpx>=0.27.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
mataroa-mcp = "mataroa_mcp.server:main"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/mataroa_mcp"]
|
|
22
|
+
|
|
23
|
+
[tool.pytest.ini_options]
|
|
24
|
+
testpaths = ["tests"]
|
|
25
|
+
addopts = "-v"
|
|
26
|
+
asyncio_mode = "auto"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.envs.default]
|
|
29
|
+
dependencies = [
|
|
30
|
+
"pytest>=8.0",
|
|
31
|
+
"pytest-asyncio>=0.23",
|
|
32
|
+
"respx>=0.21",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.envs.default.scripts]
|
|
36
|
+
test = "pytest tests/test_client.py tests/test_server.py"
|
|
37
|
+
test-all = "pytest"
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Thin async wrapper around the Mataroa REST API using httpx."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
|
|
9
|
+
BASE_URL = "https://mataroa.blog/api"
|
|
10
|
+
TIMEOUT = 30.0
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MataroaClient:
|
|
14
|
+
def __init__(self, api_key: str) -> None:
|
|
15
|
+
self._headers = {
|
|
16
|
+
"Authorization": f"Bearer {api_key}",
|
|
17
|
+
"Content-Type": "application/json",
|
|
18
|
+
"Accept": "application/json",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def _client(self) -> httpx.AsyncClient:
|
|
22
|
+
return httpx.AsyncClient(
|
|
23
|
+
base_url=BASE_URL,
|
|
24
|
+
headers=self._headers,
|
|
25
|
+
timeout=TIMEOUT,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
# ── Posts ──────────────────────────────────────────────────────────────
|
|
29
|
+
|
|
30
|
+
async def list_posts(self) -> dict[str, Any]:
|
|
31
|
+
async with self._client() as c:
|
|
32
|
+
r = await c.get("/posts/")
|
|
33
|
+
return _parse(r)
|
|
34
|
+
|
|
35
|
+
async def get_post(self, slug: str) -> dict[str, Any]:
|
|
36
|
+
async with self._client() as c:
|
|
37
|
+
r = await c.get(f"/posts/{slug}/")
|
|
38
|
+
return _parse(r)
|
|
39
|
+
|
|
40
|
+
async def create_post(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
41
|
+
async with self._client() as c:
|
|
42
|
+
r = await c.post("/posts/", json=payload)
|
|
43
|
+
return _parse(r)
|
|
44
|
+
|
|
45
|
+
async def update_post(self, slug: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
46
|
+
async with self._client() as c:
|
|
47
|
+
r = await c.patch(f"/posts/{slug}/", json=payload)
|
|
48
|
+
return _parse(r)
|
|
49
|
+
|
|
50
|
+
async def delete_post(self, slug: str) -> dict[str, Any]:
|
|
51
|
+
async with self._client() as c:
|
|
52
|
+
r = await c.delete(f"/posts/{slug}/")
|
|
53
|
+
return _parse(r)
|
|
54
|
+
|
|
55
|
+
# ── Comments ───────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
async def list_comments(self) -> dict[str, Any]:
|
|
58
|
+
async with self._client() as c:
|
|
59
|
+
r = await c.get("/comments/")
|
|
60
|
+
return _parse(r)
|
|
61
|
+
|
|
62
|
+
async def list_comments_for_post(self, slug: str) -> dict[str, Any]:
|
|
63
|
+
async with self._client() as c:
|
|
64
|
+
r = await c.get(f"/posts/{slug}/comments/")
|
|
65
|
+
return _parse(r)
|
|
66
|
+
|
|
67
|
+
async def list_pending_comments(self) -> dict[str, Any]:
|
|
68
|
+
async with self._client() as c:
|
|
69
|
+
r = await c.get("/comments/pending/")
|
|
70
|
+
return _parse(r)
|
|
71
|
+
|
|
72
|
+
async def get_comment(self, comment_id: int) -> dict[str, Any]:
|
|
73
|
+
async with self._client() as c:
|
|
74
|
+
r = await c.get(f"/comments/{comment_id}/")
|
|
75
|
+
return _parse(r)
|
|
76
|
+
|
|
77
|
+
async def approve_comment(self, comment_id: int) -> dict[str, Any]:
|
|
78
|
+
async with self._client() as c:
|
|
79
|
+
r = await c.post(f"/comments/{comment_id}/approve/")
|
|
80
|
+
return _parse(r)
|
|
81
|
+
|
|
82
|
+
async def delete_comment(self, comment_id: int) -> dict[str, Any]:
|
|
83
|
+
async with self._client() as c:
|
|
84
|
+
r = await c.delete(f"/comments/{comment_id}/")
|
|
85
|
+
return _parse(r)
|
|
86
|
+
|
|
87
|
+
# ── Pages ──────────────────────────────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
async def list_pages(self) -> dict[str, Any]:
|
|
90
|
+
async with self._client() as c:
|
|
91
|
+
r = await c.get("/pages/")
|
|
92
|
+
return _parse(r)
|
|
93
|
+
|
|
94
|
+
async def get_page(self, slug: str) -> dict[str, Any]:
|
|
95
|
+
async with self._client() as c:
|
|
96
|
+
r = await c.get(f"/pages/{slug}/")
|
|
97
|
+
return _parse(r)
|
|
98
|
+
|
|
99
|
+
async def create_page(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
100
|
+
async with self._client() as c:
|
|
101
|
+
r = await c.post("/pages/", json=payload)
|
|
102
|
+
return _parse(r)
|
|
103
|
+
|
|
104
|
+
async def update_page(self, slug: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
105
|
+
async with self._client() as c:
|
|
106
|
+
r = await c.patch(f"/pages/{slug}/", json=payload)
|
|
107
|
+
return _parse(r)
|
|
108
|
+
|
|
109
|
+
async def delete_page(self, slug: str) -> dict[str, Any]:
|
|
110
|
+
async with self._client() as c:
|
|
111
|
+
r = await c.delete(f"/pages/{slug}/")
|
|
112
|
+
return _parse(r)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _parse(response: httpx.Response) -> dict[str, Any]:
|
|
116
|
+
"""Normalise every API response into a plain dict."""
|
|
117
|
+
status = response.status_code
|
|
118
|
+
|
|
119
|
+
if status == 204 or response.content == b"":
|
|
120
|
+
return {"ok": True}
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
data = response.json()
|
|
124
|
+
except Exception:
|
|
125
|
+
return {
|
|
126
|
+
"ok": False,
|
|
127
|
+
"error": f"Mataroa API returned non-JSON response (HTTP {status})",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if status == 401:
|
|
131
|
+
return {"ok": False, "error": "Authentication failed. Check your MATAROA_API_KEY."}
|
|
132
|
+
if status == 404:
|
|
133
|
+
return {"ok": False, "error": data.get("detail", "Resource not found.")}
|
|
134
|
+
if status >= 500:
|
|
135
|
+
return {"ok": False, "error": f"Mataroa API server error (HTTP {status})."}
|
|
136
|
+
if status >= 400:
|
|
137
|
+
return {"ok": False, "error": data.get("detail", f"API error (HTTP {status}): {data}")}
|
|
138
|
+
|
|
139
|
+
# Successful responses don't always include an "ok" key — normalise.
|
|
140
|
+
if isinstance(data, dict) and "ok" not in data:
|
|
141
|
+
data["ok"] = True
|
|
142
|
+
return data
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Pydantic models for Mataroa API request/response objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Post(BaseModel):
|
|
10
|
+
title: str
|
|
11
|
+
slug: str
|
|
12
|
+
body: Optional[str] = None
|
|
13
|
+
published_at: Optional[str] = None
|
|
14
|
+
url: Optional[str] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PostCreate(BaseModel):
|
|
18
|
+
title: str
|
|
19
|
+
body: Optional[str] = None
|
|
20
|
+
published_at: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class PostUpdate(BaseModel):
|
|
24
|
+
title: Optional[str] = None
|
|
25
|
+
slug: Optional[str] = None
|
|
26
|
+
body: Optional[str] = None
|
|
27
|
+
published_at: Optional[str] = None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Comment(BaseModel):
|
|
31
|
+
id: int
|
|
32
|
+
post_slug: str
|
|
33
|
+
post_title: str
|
|
34
|
+
post_url: str
|
|
35
|
+
url: str
|
|
36
|
+
created_at: str
|
|
37
|
+
name: str
|
|
38
|
+
email: Optional[str] = None
|
|
39
|
+
body: str
|
|
40
|
+
is_approved: bool
|
|
41
|
+
is_author: bool
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class Page(BaseModel):
|
|
45
|
+
title: str
|
|
46
|
+
slug: str
|
|
47
|
+
body: Optional[str] = None
|
|
48
|
+
is_hidden: bool = False
|
|
49
|
+
url: Optional[str] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class PageCreate(BaseModel):
|
|
53
|
+
title: str
|
|
54
|
+
slug: str
|
|
55
|
+
body: Optional[str] = None
|
|
56
|
+
is_hidden: bool = False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PageUpdate(BaseModel):
|
|
60
|
+
title: Optional[str] = None
|
|
61
|
+
slug: Optional[str] = None
|
|
62
|
+
body: Optional[str] = None
|
|
63
|
+
is_hidden: Optional[bool] = None
|