mwclient-cli 0.1.0__py3-none-any.whl
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.
mwclient-cli/__init__.py
ADDED
mwclient-cli/__main__.py
ADDED
mwclient-cli/cli.py
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
"""Command-line wrapper for mwclient."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import base64
|
|
7
|
+
import importlib
|
|
8
|
+
import inspect
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import sys
|
|
12
|
+
from collections.abc import Iterator, Mapping
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import html2text
|
|
16
|
+
|
|
17
|
+
_ENTITY_LOCATIONS = {
|
|
18
|
+
"site": ("mwclient.client", "Site"),
|
|
19
|
+
"page": ("mwclient.page", "Page"),
|
|
20
|
+
"image": ("mwclient.image", "Image"),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class HelpFormatter(argparse.RawDescriptionHelpFormatter):
|
|
25
|
+
"""Preserve newlines in help text."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def resolve_entity_class(entity: str) -> Any:
|
|
29
|
+
module_name, class_name = _ENTITY_LOCATIONS[entity]
|
|
30
|
+
module = importlib.import_module(module_name)
|
|
31
|
+
return getattr(module, class_name)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def list_public_methods(entity: str) -> dict[str, Any]:
|
|
35
|
+
cls = resolve_entity_class(entity)
|
|
36
|
+
methods: dict[str, Any] = {}
|
|
37
|
+
for name, member in inspect.getmembers(cls, predicate=callable):
|
|
38
|
+
if name.startswith("_"):
|
|
39
|
+
continue
|
|
40
|
+
methods[name] = member
|
|
41
|
+
return methods
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_cli_value(raw: str) -> Any:
|
|
45
|
+
try:
|
|
46
|
+
return json.loads(raw)
|
|
47
|
+
except json.JSONDecodeError:
|
|
48
|
+
return raw
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_keyword_args(items: list[str]) -> dict[str, Any]:
|
|
52
|
+
kwargs: dict[str, Any] = {}
|
|
53
|
+
for item in items:
|
|
54
|
+
if "=" not in item:
|
|
55
|
+
raise ValueError(f"invalid --kw value '{item}', expected key=value")
|
|
56
|
+
key, raw_value = item.split("=", 1)
|
|
57
|
+
if not key:
|
|
58
|
+
raise ValueError("empty --kw key is not allowed")
|
|
59
|
+
kwargs[key] = parse_cli_value(raw_value)
|
|
60
|
+
return kwargs
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def normalize_result(value: Any) -> Any:
|
|
64
|
+
if value is None:
|
|
65
|
+
return None
|
|
66
|
+
if isinstance(value, (str, int, float, bool)):
|
|
67
|
+
return value
|
|
68
|
+
if isinstance(value, bytes):
|
|
69
|
+
return {
|
|
70
|
+
"__type__": "bytes",
|
|
71
|
+
"base64": base64.b64encode(value).decode("ascii"),
|
|
72
|
+
"size": len(value),
|
|
73
|
+
}
|
|
74
|
+
if isinstance(value, Mapping):
|
|
75
|
+
return {str(key): normalize_result(item) for key, item in value.items()}
|
|
76
|
+
if isinstance(value, (list, tuple, set)):
|
|
77
|
+
return [normalize_result(item) for item in value]
|
|
78
|
+
return repr(value)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def build_site(args: argparse.Namespace) -> Any:
|
|
82
|
+
site_class = resolve_entity_class("site")
|
|
83
|
+
site = site_class(
|
|
84
|
+
args.host,
|
|
85
|
+
path=args.path,
|
|
86
|
+
ext=args.ext,
|
|
87
|
+
scheme=args.scheme,
|
|
88
|
+
do_init=not args.no_init,
|
|
89
|
+
force_login=not args.allow_anon,
|
|
90
|
+
clients_useragent=args.clients_useragent,
|
|
91
|
+
)
|
|
92
|
+
if args.username:
|
|
93
|
+
site.login(args.username, args.password)
|
|
94
|
+
return site
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def build_target(site: Any, args: argparse.Namespace) -> Any:
|
|
98
|
+
if args.command == "site":
|
|
99
|
+
return site
|
|
100
|
+
if args.command == "page":
|
|
101
|
+
return site.pages[args.title]
|
|
102
|
+
if args.command == "image":
|
|
103
|
+
return site.images[args.title]
|
|
104
|
+
raise ValueError(f"unsupported command {args.command}")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def print_json(value: Any, indent: int | None) -> None:
|
|
108
|
+
json.dump(normalize_result(value), sys.stdout, indent=indent, ensure_ascii=False)
|
|
109
|
+
sys.stdout.write("\n")
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def print_text(value: str) -> None:
|
|
113
|
+
sys.stdout.write(value.rstrip("\n"))
|
|
114
|
+
sys.stdout.write("\n")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def print_method_list(entity: str) -> int:
|
|
118
|
+
methods = list_public_methods(entity)
|
|
119
|
+
for name in sorted(methods):
|
|
120
|
+
signature = inspect.signature(methods[name])
|
|
121
|
+
print(f"{entity}.{name}{signature}")
|
|
122
|
+
return 0
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def html_to_markdown(value: str) -> str:
|
|
126
|
+
converter = html2text.HTML2Text()
|
|
127
|
+
converter.body_width = 0
|
|
128
|
+
converter.ignore_images = False
|
|
129
|
+
converter.ignore_links = False
|
|
130
|
+
return converter.handle(value).strip()
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def normalize_page_title(value: str) -> str:
|
|
134
|
+
return value.replace("_", " ").strip()
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def extract_parse_html(parse_result: Any) -> str | None:
|
|
138
|
+
if not isinstance(parse_result, Mapping):
|
|
139
|
+
return None
|
|
140
|
+
text_data = parse_result.get("text")
|
|
141
|
+
if not isinstance(text_data, Mapping):
|
|
142
|
+
return None
|
|
143
|
+
text_html = text_data.get("*")
|
|
144
|
+
return text_html if isinstance(text_html, str) else None
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def maybe_convert_markdown(args: argparse.Namespace, target: Any, result: Any) -> Any:
|
|
148
|
+
if not getattr(args, "markdown", False):
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
if args.command == "page" and args.method == "text" and isinstance(result, str):
|
|
152
|
+
parse_data = target.site.parse(text=result, title=getattr(target, "name", None))
|
|
153
|
+
parse_html = extract_parse_html(parse_data)
|
|
154
|
+
title = normalize_page_title(
|
|
155
|
+
str(getattr(target, "name", getattr(target, "title", args.title)))
|
|
156
|
+
)
|
|
157
|
+
heading = f"# {title}".strip()
|
|
158
|
+
if parse_html:
|
|
159
|
+
body = html_to_markdown(parse_html).strip()
|
|
160
|
+
return f"{heading}\n\n{body}" if body else heading
|
|
161
|
+
body = result.strip()
|
|
162
|
+
return f"{heading}\n\n{body}" if body else heading
|
|
163
|
+
|
|
164
|
+
if args.command == "site" and args.method == "parse":
|
|
165
|
+
parse_html = extract_parse_html(result)
|
|
166
|
+
if parse_html:
|
|
167
|
+
return html_to_markdown(parse_html)
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
return result
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
174
|
+
parser = argparse.ArgumentParser(
|
|
175
|
+
prog="mwclient-cli",
|
|
176
|
+
formatter_class=HelpFormatter,
|
|
177
|
+
description=(
|
|
178
|
+
"Command-line wrapper around mwclient\n\n"
|
|
179
|
+
"Use one of these targets:\n"
|
|
180
|
+
" site methods from mwclient.Site\n"
|
|
181
|
+
" page methods from mwclient.page.Page\n"
|
|
182
|
+
" image methods from mwclient.image.Image"
|
|
183
|
+
),
|
|
184
|
+
epilog=(
|
|
185
|
+
"Examples:\n"
|
|
186
|
+
" mwclient-cli methods site\n"
|
|
187
|
+
" mwclient-cli --host host.docker.internal --scheme http --path /w/ "
|
|
188
|
+
"page \"Main Page\" text\n"
|
|
189
|
+
" mwclient-cli --host host.docker.internal --scheme http --path /w/ "
|
|
190
|
+
"site search --arg space --kw what=text --max-items 5"
|
|
191
|
+
),
|
|
192
|
+
)
|
|
193
|
+
parser.add_argument(
|
|
194
|
+
"--host",
|
|
195
|
+
default=os.getenv("MWCLI_HOST"),
|
|
196
|
+
metavar="HOST",
|
|
197
|
+
help="Wiki host (no scheme). Env fallback: MWCLI_HOST",
|
|
198
|
+
)
|
|
199
|
+
parser.add_argument(
|
|
200
|
+
"--path",
|
|
201
|
+
default=os.getenv("MWCLI_PATH", "/w/"),
|
|
202
|
+
metavar="PATH",
|
|
203
|
+
help="MediaWiki script path with trailing slash. Default: /w/. Env: MWCLI_PATH",
|
|
204
|
+
)
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
"--ext",
|
|
207
|
+
default=os.getenv("MWCLI_EXT", ".php"),
|
|
208
|
+
metavar="EXT",
|
|
209
|
+
help="Script extension. Default: .php. Env: MWCLI_EXT",
|
|
210
|
+
)
|
|
211
|
+
parser.add_argument(
|
|
212
|
+
"--scheme",
|
|
213
|
+
default=os.getenv("MWCLI_SCHEME", "https"),
|
|
214
|
+
metavar="SCHEME",
|
|
215
|
+
help="URL scheme: http or https. Default: https. Env: MWCLI_SCHEME",
|
|
216
|
+
)
|
|
217
|
+
parser.add_argument(
|
|
218
|
+
"--username",
|
|
219
|
+
default=os.getenv("MWCLI_USERNAME"),
|
|
220
|
+
metavar="USERNAME",
|
|
221
|
+
help="Login username. If set, --password is required. Env: MWCLI_USERNAME",
|
|
222
|
+
)
|
|
223
|
+
parser.add_argument(
|
|
224
|
+
"--password",
|
|
225
|
+
default=os.getenv("MWCLI_PASSWORD"),
|
|
226
|
+
metavar="PASSWORD",
|
|
227
|
+
help="Login password. If set, --username is required. Env: MWCLI_PASSWORD",
|
|
228
|
+
)
|
|
229
|
+
parser.add_argument(
|
|
230
|
+
"--clients-useragent",
|
|
231
|
+
default=os.getenv("MWCLI_USER_AGENT"),
|
|
232
|
+
metavar="UA",
|
|
233
|
+
help="Custom user-agent prefix. Env: MWCLI_USER_AGENT",
|
|
234
|
+
)
|
|
235
|
+
parser.add_argument(
|
|
236
|
+
"--allow-anon",
|
|
237
|
+
action="store_true",
|
|
238
|
+
help="Allow unauthenticated edits (sets force_login=False on Site)",
|
|
239
|
+
)
|
|
240
|
+
parser.add_argument(
|
|
241
|
+
"--no-init",
|
|
242
|
+
action="store_true",
|
|
243
|
+
help="Skip initial site_init() call during Site creation",
|
|
244
|
+
)
|
|
245
|
+
parser.add_argument(
|
|
246
|
+
"--indent",
|
|
247
|
+
type=int,
|
|
248
|
+
default=None,
|
|
249
|
+
metavar="N",
|
|
250
|
+
help="Pretty-print JSON output with N-space indentation",
|
|
251
|
+
)
|
|
252
|
+
parser.add_argument(
|
|
253
|
+
"--markdown",
|
|
254
|
+
action="store_true",
|
|
255
|
+
help=(
|
|
256
|
+
"Convert content-read methods to Markdown "
|
|
257
|
+
"(currently: page text, site parse)"
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
subparsers = parser.add_subparsers(
|
|
262
|
+
dest="command",
|
|
263
|
+
required=True,
|
|
264
|
+
title="commands",
|
|
265
|
+
metavar="COMMAND",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
methods_parser = subparsers.add_parser(
|
|
269
|
+
"methods",
|
|
270
|
+
formatter_class=HelpFormatter,
|
|
271
|
+
help="list available methods on site/page/image targets",
|
|
272
|
+
description="List callable public methods and signatures.",
|
|
273
|
+
epilog=(
|
|
274
|
+
"Examples:\n"
|
|
275
|
+
" mwclient-cli methods all\n"
|
|
276
|
+
" mwclient-cli methods page"
|
|
277
|
+
),
|
|
278
|
+
)
|
|
279
|
+
methods_parser.add_argument(
|
|
280
|
+
"entity",
|
|
281
|
+
choices=["site", "page", "image", "all"],
|
|
282
|
+
help="Target entity to inspect",
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
for entity in ("site", "page", "image"):
|
|
286
|
+
entity_parser = subparsers.add_parser(
|
|
287
|
+
entity,
|
|
288
|
+
formatter_class=HelpFormatter,
|
|
289
|
+
help=f"call {entity} method by name",
|
|
290
|
+
description=(
|
|
291
|
+
f"Call public method on {entity} target.\n"
|
|
292
|
+
"Arguments passed via repeated --arg and --kw options."
|
|
293
|
+
),
|
|
294
|
+
epilog=(
|
|
295
|
+
"Examples:\n"
|
|
296
|
+
f" mwclient-cli --host HOST {entity} "
|
|
297
|
+
+ ("\"Main Page\" " if entity == "page" else "\"Example.png\" " if entity == "image" else "")
|
|
298
|
+
+ "METHOD --arg VALUE --kw key=value\n"
|
|
299
|
+
f" mwclient-cli methods {entity}"
|
|
300
|
+
),
|
|
301
|
+
)
|
|
302
|
+
if entity in {"page", "image"}:
|
|
303
|
+
entity_parser.add_argument("title", help=f"{entity} title")
|
|
304
|
+
entity_parser.add_argument("method", help=f"{entity} method name")
|
|
305
|
+
entity_parser.add_argument(
|
|
306
|
+
"--arg",
|
|
307
|
+
action="append",
|
|
308
|
+
default=[],
|
|
309
|
+
metavar="VALUE",
|
|
310
|
+
help="Positional method arg (JSON parsed, fallback to string)",
|
|
311
|
+
)
|
|
312
|
+
entity_parser.add_argument(
|
|
313
|
+
"--kw",
|
|
314
|
+
action="append",
|
|
315
|
+
default=[],
|
|
316
|
+
metavar="KEY=VALUE",
|
|
317
|
+
help="Keyword method arg (value JSON parsed, fallback to string)",
|
|
318
|
+
)
|
|
319
|
+
entity_parser.add_argument(
|
|
320
|
+
"--stream",
|
|
321
|
+
action="store_true",
|
|
322
|
+
help="Stream list/tuple results as one JSON object per line",
|
|
323
|
+
)
|
|
324
|
+
entity_parser.add_argument(
|
|
325
|
+
"--max-items",
|
|
326
|
+
type=int,
|
|
327
|
+
default=None,
|
|
328
|
+
metavar="N",
|
|
329
|
+
help="Limit number of emitted items for iterator/list results",
|
|
330
|
+
)
|
|
331
|
+
entity_parser.add_argument(
|
|
332
|
+
"--markdown",
|
|
333
|
+
action="store_true",
|
|
334
|
+
default=argparse.SUPPRESS,
|
|
335
|
+
help="Convert content-read methods to Markdown",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
return parser
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def run(argv: list[str] | None = None) -> int:
|
|
342
|
+
parser = build_parser()
|
|
343
|
+
args = parser.parse_args(argv)
|
|
344
|
+
|
|
345
|
+
if args.command == "methods":
|
|
346
|
+
if args.entity == "all":
|
|
347
|
+
for entity in ("site", "page", "image"):
|
|
348
|
+
print_method_list(entity)
|
|
349
|
+
return 0
|
|
350
|
+
return print_method_list(args.entity)
|
|
351
|
+
|
|
352
|
+
if not args.host:
|
|
353
|
+
parser.error("missing --host (or env MWCLI_HOST)")
|
|
354
|
+
|
|
355
|
+
if bool(args.username) != bool(args.password):
|
|
356
|
+
parser.error("set both --username and --password or neither")
|
|
357
|
+
|
|
358
|
+
positionals = [parse_cli_value(raw) for raw in args.arg]
|
|
359
|
+
try:
|
|
360
|
+
kwargs = parse_keyword_args(args.kw)
|
|
361
|
+
except ValueError as exc:
|
|
362
|
+
parser.error(str(exc))
|
|
363
|
+
|
|
364
|
+
target = build_target(build_site(args), args)
|
|
365
|
+
methods = list_public_methods(args.command)
|
|
366
|
+
if args.method not in methods:
|
|
367
|
+
parser.error(f"unknown method {args.command}.{args.method}")
|
|
368
|
+
method = getattr(target, args.method)
|
|
369
|
+
|
|
370
|
+
result = method(*positionals, **kwargs)
|
|
371
|
+
result = maybe_convert_markdown(args, target, result)
|
|
372
|
+
if isinstance(result, Iterator):
|
|
373
|
+
max_items = args.max_items
|
|
374
|
+
count = 0
|
|
375
|
+
for item in result:
|
|
376
|
+
print_json(item, args.indent)
|
|
377
|
+
count += 1
|
|
378
|
+
if max_items is not None and count >= max_items:
|
|
379
|
+
break
|
|
380
|
+
return 0
|
|
381
|
+
|
|
382
|
+
if args.stream and isinstance(result, (list, tuple)):
|
|
383
|
+
max_items = args.max_items
|
|
384
|
+
items = result if max_items is None else result[:max_items]
|
|
385
|
+
for item in items:
|
|
386
|
+
print_json(item, args.indent)
|
|
387
|
+
return 0
|
|
388
|
+
|
|
389
|
+
if args.markdown and isinstance(result, str):
|
|
390
|
+
print_text(result)
|
|
391
|
+
else:
|
|
392
|
+
print_json(result, args.indent)
|
|
393
|
+
return 0
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def main() -> None:
|
|
397
|
+
raise SystemExit(run())
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
if __name__ == "__main__":
|
|
401
|
+
main()
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mwclient-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI wrapper around mwclient
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: html2text==2025.4.15
|
|
7
|
+
Requires-Dist: mwclient==0.11.0
|
|
8
|
+
Provides-Extra: test
|
|
9
|
+
Requires-Dist: pytest==9.0.2; extra == 'test'
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# mwcli
|
|
13
|
+
|
|
14
|
+
CLI wrapper around `mwclient`. Built for Agents.
|
|
15
|
+
|
|
16
|
+
Exposes `mwclient` methods from 3 targets:
|
|
17
|
+
- `site` -> `mwclient.Site`
|
|
18
|
+
- `page` -> `mwclient.page.Page`
|
|
19
|
+
- `image` -> `mwclient.image.Image`
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
From project dir:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install -r requirements.txt
|
|
27
|
+
pip install -e .
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Command shape
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
python -m mwcli [connection flags] <site|page|image> <target args> <method> [--arg ...] [--kw ...] [--markdown]
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Connection flags:
|
|
37
|
+
- `--host` required (or `MWCLI_HOST`)
|
|
38
|
+
- `--scheme` default `https`
|
|
39
|
+
- `--path` default `/w/`
|
|
40
|
+
- `--ext` default `.php`
|
|
41
|
+
- `--username`, `--password` optional auth
|
|
42
|
+
|
|
43
|
+
Method args:
|
|
44
|
+
- `--arg VALUE` positional arg, JSON-parsed when possible
|
|
45
|
+
- `--kw KEY=VALUE` keyword arg, value JSON-parsed when possible
|
|
46
|
+
- `--max-items N` cap iterator/list output
|
|
47
|
+
- `--stream` print list/tuple one JSON per line
|
|
48
|
+
- `--markdown` convert content-read output to Markdown (see below)
|
|
49
|
+
|
|
50
|
+
Tip: quote strings with spaces/pipes.
|
|
51
|
+
|
|
52
|
+
## Discover available methods
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python -m mwcli methods all
|
|
56
|
+
python -m mwcli methods site
|
|
57
|
+
python -m mwcli methods page
|
|
58
|
+
python -m mwcli methods image
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## All commands
|
|
62
|
+
|
|
63
|
+
Top-level commands:
|
|
64
|
+
|
|
65
|
+
- `python -m mwcli methods {all|site|page|image}`
|
|
66
|
+
- `python -m mwcli site <method> [--arg ...] [--kw ...]`
|
|
67
|
+
- `python -m mwcli page "<title>" <method> [--arg ...] [--kw ...]`
|
|
68
|
+
- `python -m mwcli image "<title>" <method> [--arg ...] [--kw ...]`
|
|
69
|
+
|
|
70
|
+
`site` methods (mwclient 0.11.0):
|
|
71
|
+
|
|
72
|
+
- `allcategories`, `allimages`, `alllinks`, `allpages`, `allusers`
|
|
73
|
+
- `api`, `ask`, `blocks`, `checkuserlog`, `chunk_upload`, `clientlogin`
|
|
74
|
+
- `deletedrevisions`, `email`, `expandtemplates`, `exturlusage`
|
|
75
|
+
- `get`, `get_token`, `handle_api_result`, `logevents`, `login`
|
|
76
|
+
- `parse`, `patrol`, `post`, `random`, `raw_api`, `raw_call`, `raw_index`
|
|
77
|
+
- `recentchanges`, `require`, `revisions`, `search`, `site_init`
|
|
78
|
+
- `upload`, `usercontributions`, `users`, `version_tuple_from_generator`, `watchlist`
|
|
79
|
+
|
|
80
|
+
`page` methods:
|
|
81
|
+
|
|
82
|
+
- `append`, `backlinks`, `can`, `categories`, `delete`, `edit`, `embeddedin`
|
|
83
|
+
- `extlinks`, `get_token`, `handle_edit_error`, `images`, `iwlinks`, `langlinks`, `links`
|
|
84
|
+
- `move`, `normalize_title`, `prepend`, `purge`, `redirects_to`, `resolve_redirect`
|
|
85
|
+
- `revisions`, `save`, `strip_namespace`, `templates`, `text`, `touch`
|
|
86
|
+
|
|
87
|
+
`image` methods:
|
|
88
|
+
|
|
89
|
+
- all `page` methods, plus:
|
|
90
|
+
- `download`, `duplicatefiles`, `imagehistory`, `imageusage`
|
|
91
|
+
|
|
92
|
+
To confirm runtime command surface on your installed version:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python -m mwcli methods all
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Main usage examples
|
|
99
|
+
|
|
100
|
+
### Get page content
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
104
|
+
page "Main Page" text
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Read one section:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
111
|
+
page "Main Page" text --kw section=1
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Read as Markdown (`page text` uses `site parse` + `html2text`):
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
118
|
+
page "Main Page" text --markdown
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
The markdown output starts with:
|
|
122
|
+
|
|
123
|
+
```md
|
|
124
|
+
# Main Page
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Search
|
|
128
|
+
|
|
129
|
+
Full text search:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
133
|
+
site search --arg "space" --kw what=text --max-items 10
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Title-only search:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
140
|
+
site search --arg "Main" --kw what=title --max-items 10
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Authentication + edit
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
147
|
+
--username "Admin" --password "secret" \
|
|
148
|
+
page "Sandbox" edit \
|
|
149
|
+
--arg "Edited from mwcli ~~~~" \
|
|
150
|
+
--kw summary="mwcli test edit" \
|
|
151
|
+
--kw bot=false
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### File upload
|
|
155
|
+
|
|
156
|
+
Local file upload:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
160
|
+
--username "Admin" --password "secret" \
|
|
161
|
+
site upload \
|
|
162
|
+
--kw file="/tmp/example.png" \
|
|
163
|
+
--kw filename="Example.png" \
|
|
164
|
+
--kw description="Uploaded by mwcli"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Upload from URL:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
171
|
+
--username "Admin" --password "secret" \
|
|
172
|
+
site upload \
|
|
173
|
+
--kw url="https://example.com/example.png" \
|
|
174
|
+
--kw filename="ExampleFromURL.png"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Semantic MediaWiki ask API
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
181
|
+
site ask --arg '[[Category:Item]]|?Has author|?Has status' --max-items 20
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
With an explicit title context:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
188
|
+
site ask --arg '[[Category:Item]]|?Has author' --kw title="Main Page" --max-items 20
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Fetch all SMW properties for a page (`smwbrowse`):
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
python -m mwcli --indent 2 --host host.docker.internal --scheme http --path /w/ \
|
|
195
|
+
site raw_api --arg smwbrowse --arg GET --kw browse=subject \
|
|
196
|
+
--kw params='"{\"subject\":\"Main Page\",\"ns\":0}"'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Note: `smwbrowse` expects `params` as a JSON string, so pass a JSON object wrapped as a quoted string (double-encoded).
|
|
200
|
+
|
|
201
|
+
### Arbitrary API calls
|
|
202
|
+
|
|
203
|
+
Use `get` / `post` / `api` / `raw_api` directly.
|
|
204
|
+
|
|
205
|
+
Get siteinfo:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
209
|
+
site get --arg query --kw meta=siteinfo --kw siprop='general|namespaces'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Generic `api` call with GET method:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
216
|
+
site api --arg query --arg GET --kw prop=info --kw titles="Main Page"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Raw API (no extra wrapper logic):
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
223
|
+
site raw_api --arg query --arg GET --kw list=search --kw srsearch=space
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Parse wikitext/HTML directly as Markdown:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
230
|
+
site parse --kw text=$'== Header ==\n\nBody' --markdown
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`--markdown` currently applies to:
|
|
234
|
+
|
|
235
|
+
- `page text`
|
|
236
|
+
- `site parse`
|
|
237
|
+
|
|
238
|
+
Implementation uses `html2text`: https://pypi.org/project/html2text/
|
|
239
|
+
|
|
240
|
+
### Page and image list iterators
|
|
241
|
+
|
|
242
|
+
Recent changes:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
246
|
+
site recentchanges --kw prop='title|timestamp|user|comment' --max-items 5
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Image usage:
|
|
250
|
+
|
|
251
|
+
```bash
|
|
252
|
+
python -m mwcli --host host.docker.internal --scheme http --path /w/ \
|
|
253
|
+
image "Example.png" imageusage --max-items 10
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Env vars
|
|
257
|
+
|
|
258
|
+
You can use env vars instead of flags:
|
|
259
|
+
|
|
260
|
+
- `MWCLI_HOST`
|
|
261
|
+
- `MWCLI_PATH` (default `/w/`)
|
|
262
|
+
- `MWCLI_EXT` (default `.php`)
|
|
263
|
+
- `MWCLI_SCHEME` (default `https`)
|
|
264
|
+
- `MWCLI_USERNAME`
|
|
265
|
+
- `MWCLI_PASSWORD`
|
|
266
|
+
- `MWCLI_USER_AGENT`
|
|
267
|
+
|
|
268
|
+
Then run shorter commands, for example:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
export MWCLI_HOST=host.docker.internal
|
|
272
|
+
export MWCLI_SCHEME=http
|
|
273
|
+
export MWCLI_PATH=/w/
|
|
274
|
+
python -m mwcli site search --arg "space" --kw what=text --max-items 5
|
|
275
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
mwclient-cli/__init__.py,sha256=ijQ-WRX2098384M1mIsFFZuAQsN2yd9cdMSlUPs-_ss,69
|
|
2
|
+
mwclient-cli/__main__.py,sha256=bYt9eEaoRQWdejEHFD8REx9jxVEdZptECFsV7F49Ink,30
|
|
3
|
+
mwclient-cli/cli.py,sha256=nfpGfZhhrt6K9-BuQ-rPX51HmW5KBlQFQMhaZKyqzFE,12458
|
|
4
|
+
mwclient_cli-0.1.0.dist-info/METADATA,sha256=ShUr_rt3zc64UN7G_JmoX0oD27NoZYWfhyL5Tm0b-2w,6959
|
|
5
|
+
mwclient_cli-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
6
|
+
mwclient_cli-0.1.0.dist-info/entry_points.txt,sha256=kwASmUbnKbzvecArk8v0BWnoQQHQBnEvxV7cc0OnONI,48
|
|
7
|
+
mwclient_cli-0.1.0.dist-info/RECORD,,
|