Skip to content

Commit 0107ed8

Browse files
authored
Merge pull request #688 from jjjake/simplelist-support
first pass at 'ia simplelists'
2 parents a625616 + d9f923c commit 0107ed8

File tree

6 files changed

+293
-2
lines changed

6 files changed

+293
-2
lines changed

HISTORY.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ Release History
1212
- Added ``ia configure --check`` for validating credentials.
1313
- Added ``ia configure --whoami`` for retrieving info about the configured user.
1414

15+
**Features and Improvements**
16+
17+
- Added ``ia simplelists`` command for managing simplelists.
18+
- Added ``ia flag`` command for managing flags.
19+
1520
**Bugfixes**
1621

1722
- Fixed bugs in ``ia copy`` and ``ia move`` where an ``AttributeError`` was being raised.

internetarchive/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '5.3.0.dev2'
1+
__version__ = '5.3.0.dev4'

internetarchive/cli/ia.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,13 @@
3131
ia_copy,
3232
ia_delete,
3333
ia_download,
34+
ia_flag,
3435
ia_list,
3536
ia_metadata,
3637
ia_move,
3738
ia_reviews,
3839
ia_search,
40+
ia_simplelists,
3941
ia_tasks,
4042
ia_upload,
4143
)
@@ -109,11 +111,13 @@ def main():
109111
ia_copy.setup(subparsers)
110112
ia_delete.setup(subparsers)
111113
ia_download.setup(subparsers)
114+
ia_flag.setup(subparsers)
112115
ia_list.setup(subparsers)
113116
ia_metadata.setup(subparsers)
114117
ia_move.setup(subparsers)
115118
ia_reviews.setup(subparsers)
116119
ia_search.setup(subparsers)
120+
ia_simplelists.setup(subparsers)
117121
ia_tasks.setup(subparsers)
118122
ia_upload.setup(subparsers)
119123

internetarchive/cli/ia_flag.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"""
2+
ia_flag.py
3+
4+
'ia' subcommand for managing flags on archive.org.
5+
"""
6+
7+
# Copyright (C) 2012-2025 Internet Archive
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU Affero General Public License as
11+
# published by the Free Software Foundation, either version 3 of the
12+
# License, or (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU Affero General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU Affero General Public License
20+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
from __future__ import annotations
23+
24+
import argparse
25+
26+
27+
def setup(subparsers):
28+
"""Set up argument parser for the 'flag' subcommand.
29+
30+
Args:
31+
subparsers: argparse subparsers object from main CLI
32+
"""
33+
parser = subparsers.add_parser(
34+
"flag",
35+
aliases=["fl"],
36+
help="Manage flags",
37+
)
38+
parser.add_argument(
39+
"identifier",
40+
nargs="?",
41+
type=str,
42+
help="Identifier for the upload",
43+
)
44+
parser.add_argument(
45+
"-u",
46+
"--user",
47+
type=str,
48+
help="User associated with the flag",
49+
)
50+
51+
group = parser.add_argument_group("Add flag operations")
52+
group.add_argument(
53+
"-a",
54+
"--add-flag",
55+
metavar="CATEGORY",
56+
type=str,
57+
help="Add a flag to the item",
58+
)
59+
60+
group = parser.add_argument_group("Delete flag operations")
61+
group.add_argument(
62+
"-d",
63+
"--delete-flag",
64+
metavar="CATEGORY",
65+
type=str,
66+
help="Delete a flag from the item",
67+
)
68+
69+
parser.set_defaults(func=lambda args: main(args, parser))
70+
71+
def main(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
72+
"""Handle flag subcommand execution.
73+
74+
Args:
75+
args: Parsed command-line arguments
76+
parser: Argument parser for error handling
77+
"""
78+
item = args.session.get_item(args.identifier)
79+
if args.user:
80+
flag_user = args.user
81+
else:
82+
flag_user = args.session.config.get("general", {}).get("screenname")
83+
if not flag_user.startswith('@'):
84+
flag_user = f"@{flag_user}"
85+
if args.add_flag:
86+
r = item.add_flag(args.add_flag, flag_user)
87+
j = r.json()
88+
if j.get("status") == "success":
89+
print(f"success: added '{args.add_flag}' flag by {flag_user} to {args.identifier}")
90+
else:
91+
print(f"error: {item.identifier} - {r.text}")
92+
93+
elif args.delete_flag:
94+
r = item.delete_flag(args.delete_flag, flag_user)
95+
j = r.json()
96+
if j.get("status") == "success":
97+
print(f"success: deleted '{args.delete_flag}' flag by {flag_user} from {args.identifier}")
98+
else:
99+
print(f"error: {item.identifier} - {r.text}")
100+
101+
else:
102+
r = item.get_flags()
103+
print(r.text)

internetarchive/cli/ia_simplelists.py

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
"""
2+
ia_simplelists.py
3+
4+
'ia' subcommand for managing simplelists on archive.org.
5+
"""
6+
7+
# Copyright (C) 2012-2025 Internet Archive
8+
#
9+
# This program is free software: you can redistribute it and/or modify
10+
# it under the terms of the GNU Affero General Public License as
11+
# published by the Free Software Foundation, either version 3 of the
12+
# License, or (at your option) any later version.
13+
#
14+
# This program is distributed in the hope that it will be useful,
15+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
16+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+
# GNU Affero General Public License for more details.
18+
#
19+
# You should have received a copy of the GNU Affero General Public License
20+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21+
22+
from __future__ import annotations
23+
24+
import argparse
25+
import sys
26+
27+
from internetarchive.utils import json
28+
29+
30+
def setup(subparsers):
31+
"""Set up argument parser for the 'simplelists' subcommand.
32+
33+
Args:
34+
subparsers: argparse subparsers object from main CLI
35+
"""
36+
parser = subparsers.add_parser("simplelists",
37+
aliases=["sl"],
38+
help="Manage simplelists")
39+
parser.add_argument(
40+
"identifier",
41+
nargs="?",
42+
type=str,
43+
help="Identifier for the upload"
44+
)
45+
46+
group = parser.add_argument_group("List operations")
47+
group.add_argument(
48+
"-p", "--list-parents",
49+
action="store_true",
50+
help="List parent lists for the given identifier"
51+
)
52+
group.add_argument(
53+
"-c", "--list-children",
54+
action="store_true",
55+
help="List children in parent list"
56+
)
57+
group.add_argument(
58+
"-l", "--list-name",
59+
type=str,
60+
help="Name of the list to operate on"
61+
)
62+
63+
group = parser.add_argument_group("Modification operations")
64+
group.add_argument(
65+
"-s", "--set-parent",
66+
metavar="PARENT",
67+
type=str,
68+
help="Add identifier to specified parent list"
69+
)
70+
group.add_argument(
71+
"-n", "--notes",
72+
metavar="NOTES",
73+
type=str,
74+
help="Notes to attach to the list membership"
75+
)
76+
group.add_argument(
77+
"-r", "--remove-parent",
78+
metavar="PARENT",
79+
type=str,
80+
help="Remove identifier from specified parent list"
81+
)
82+
83+
parser.set_defaults(func=lambda args: main(args, parser))
84+
85+
86+
def submit_patch(patch, args):
87+
"""Submit patch request to simplelists API"""
88+
data = {"-patch": json.dumps(patch), "-target": "simplelists"}
89+
url = f"{args.session.protocol}//{args.session.host}/metadata/{args.identifier}"
90+
return args.session.post(url, data=data)
91+
92+
93+
def _handle_patch_operation(args, parser, operation):
94+
"""Handle set/delete patch operations for simplelists.
95+
96+
:param operation: The patch operation type ('set' or 'delete')
97+
"""
98+
if not args.identifier:
99+
parser.error("Missing required identifier argument")
100+
if not args.list_name:
101+
parser.error("Must specify list name with -l/--list-name")
102+
103+
patch = {
104+
"op": operation,
105+
"parent": args.set_parent or args.remove_parent,
106+
"list": args.list_name,
107+
}
108+
if args.notes:
109+
patch["notes"] = args.notes
110+
111+
r = submit_patch(patch, args)
112+
try:
113+
r.raise_for_status()
114+
print(f"success: {args.identifier}")
115+
except Exception as e:
116+
print(f"error: {args.identifier} - {e!s}", file=sys.stderr)
117+
sys.exit(1)
118+
119+
120+
def main(args: argparse.Namespace, parser: argparse.ArgumentParser) -> None:
121+
"""Handle simplelists subcommand execution.
122+
123+
Args:
124+
125+
args: Parsed command-line arguments
126+
parser: Argument parser for error handling
127+
"""
128+
if args.list_parents:
129+
item = args.session.get_item(args.identifier)
130+
simplelists = item.item_metadata.get("simplelists")
131+
if simplelists:
132+
print(json.dumps(simplelists))
133+
elif args.list_children:
134+
args.list_name = args.list_name or "catchall"
135+
query = f"simplelists__{args.list_name}:{args.identifier or '*'}"
136+
for result in args.session.search_items(query):
137+
print(json.dumps(result))
138+
139+
elif args.set_parent:
140+
_handle_patch_operation(args, parser, "set")
141+
142+
elif args.remove_parent:
143+
_handle_patch_operation(args, parser, "delete")
144+
else:
145+
parser.print_help()
146+
sys.exit(1)

internetarchive/item.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
from functools import total_ordering
3535
from logging import getLogger
3636
from time import sleep
37-
from typing import Mapping, MutableMapping
37+
from typing import Mapping, MutableMapping, Optional
3838
from urllib.parse import quote
3939
from xml.parsers.expat import ExpatError
4040

@@ -901,6 +901,39 @@ def modify_metadata(self,
901901
self.refresh()
902902
return resp
903903

904+
def delete_flag(
905+
self,
906+
category: str,
907+
user: Optional[str] = None, # noqa: UP007
908+
) -> Response:
909+
if user is None:
910+
user = f"@{self.session.config.get('general', {}).get('screenname')}"
911+
url = f'{self.session.protocol}//{self.session.host}/services/flags/admin.php'
912+
headers = {'Accept': 'text/json'} # must be text/json specifically
913+
params = {'identifier': self.identifier, 'category': category, 'user': user}
914+
r = self.session.delete(url, headers=headers, params=params)
915+
return r
916+
917+
def add_flag(
918+
self,
919+
category: str,
920+
user: Optional[str] = None, # noqa: UP007
921+
) -> Response:
922+
if user is None:
923+
user = f"@{self.session.config.get('general', {}).get('screenname')}"
924+
url = f'{self.session.protocol}//{self.session.host}/services/flags/admin.php'
925+
headers = {'Accept': 'text/json'} # must be text/json specifically
926+
params = {'identifier': self.identifier, 'category': category, 'user': user}
927+
r = self.session.put(url, headers=headers, params=params)
928+
return r
929+
930+
def get_flags(self) -> Response:
931+
url = f'{self.session.protocol}//{self.session.host}/services/flags/admin.php'
932+
headers = {'Accept': 'text/json'} # must be text/json specifically
933+
params = {'identifier': self.identifier}
934+
r = self.session.get(url, headers=headers, params=params)
935+
return r
936+
904937
# TODO: `list` parameter name shadows the Python builtin
905938
def remove_from_simplelist(self, parent, list) -> Response:
906939
"""Remove item from a simplelist.

0 commit comments

Comments
 (0)