• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

AntiCompositeNumber / iNaturalistReviewer / 8398545042

23 Mar 2024 01:46AM UTC coverage: 66.598%. Remained the same
8398545042

push

github

AntiCompositeNumber
Relicense from Apache-2.0 to GPL-3.0-or-later

The only commits to this repository not from me are below the threshold
for copyright protection:
* 577fc96
* 3eefa01

336 of 503 branches covered (66.8%)

Branch coverage included in aggregate %.

635 of 955 relevant lines covered (66.49%)

0.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/inrcli.py
1
#!/usr/bin/env python3
2
# coding: utf-8
3
# SPDX-License-Identifier: GPL-3.0-or-later
4
# Copyright 2023 AntiCompositeNumber
5

6
import click
×
7
import pywikibot  # type: ignore
×
8
import pywikibot.bot  # type: ignore
×
9
import logging
×
10
import os
×
11
import sys
×
12
import difflib
×
13
import webbrowser
×
14
from typing import Sequence, Dict, Optional
×
15

16
os.environ["LOG_FILE"] = "stderr"
×
17
os.environ["LOG_LEVEL"] = "WARNING"
×
18

19
import inrbot  # noqa: E402
×
20

21
inrbot.run_override = True
×
22
inrbot.summary_tag = f"(inrcli {inrbot.__version__})"
×
23
site = inrbot.site
×
24
logger = logging.getLogger("manual")
×
25
ids: Dict[pywikibot.Page, Optional[inrbot.iNaturalistID]] = {}
×
26
auto_open = False
×
27

28
inrbot.config.update(
×
29
    {
30
        "fail_tag": "{{copyvio|License review not passed: "
31
        "iNaturalist author is using $review_license: $source_url}}\n",
32
        "fail_warn": "\n\n{{subst:Copyvionote |1=$filename "
33
        "|2=License review "
34
        "not passed: iNaturalist author is using $review_license: $source_url }} ~~~~",
35
        "review_summary": "Semi-automatic license review: "
36
        "$status $review_license $tag",
37
        "old_fail_warn": "\n\n{{subst:image permission|1=$filename}} "
38
        "License review not passed: iNaturalist author is "
39
        "using $review_license: $source_url. ~~~~",
40
        "use_wayback": False,
41
    }
42
)
43

44

45
class SkipFile(Exception):
×
46
    pass
×
47

48

49
def manual_compare(
×
50
    com_img: inrbot.CommonsImage, ina_img: inrbot.iNaturalistImage, **kwargs
51
):
52
    if ids.get(com_img.page, None) == ina_img.id and ina_img.id.type == "photos":
×
53
        return True
×
54
    else:
55
        return ask_compare(com_img, ina_img)
×
56

57

58
def ask_compare(com_img: inrbot.CommonsImage, ina_img: inrbot.iNaturalistImage):
×
59
    if click.confirm(f"Show {ina_img.id}?", default=False):
×
60
        com_img.image.show(title=com_img.page.title())
×
61
        ina_img.image.show(title=str(ina_img.id))
×
62
    res = click.confirm("Do these images match?", default=False)
×
63
    return res
×
64

65

66
inrbot.compare_methods.insert(0, ("manual", manual_compare))
×
67

68

69
class ManualCommonsPage(inrbot.CommonsPage):
×
70
    def __init__(self, *args, **kwargs):
×
71
        super().__init__(*args, **kwargs)
×
72

73
    def check_can_run(self) -> bool:
×
74
        """Determinies if the bot should run on this page and returns a bool."""
75
        page = self.page
×
76
        if (
×
77
            (page.title() in inrbot.skip)
78
            or (not page.has_permission("edit"))
79
            or (not page.botMayEdit())
80
        ):
81
            return False
×
82
        else:
83
            return True
×
84

85
    def get_old_archive_(self):
×
86
        return super().get_old_archive()
×
87

88
    def get_old_archive(self):
×
89
        # Archives will have already been reviewed by the archive_status_hook
90
        if self.status != "fail":
×
91
            return super().get_old_archive()
×
92
        return ""
×
93

94
    def archive_status_hook(self) -> None:
×
95
        if self._status == "fail":
×
96
            super().get_old_archive()
×
97
            if self.archive:
×
98
                print(
×
99
                    f"This file would fail because of the {self.ina_license} license, "
100
                    f"but an archived copy is available at {self.archive}."
101
                )
102
                new_license = click.prompt(
×
103
                    "Archive license (leave blank for no change)",
104
                    default=self.ina_license,
105
                )
106
                if new_license:
×
107
                    self.ina_license = new_license
×
108
                    del self.status
×
109
                    self.status
×
110

111
    def pre_save(self, new_text, summary, **kwargs):
×
112
        print(
×
113
            f"{self.page.title(as_link=True)} reviewed with status {self.status} "
114
            f"and license {self.ina_license}"
115
        )
116
        if self.status == "error":
×
117
            raise RuntimeError
×
118

119
        diff = difflib.unified_diff(
×
120
            self.page.get().split("\n"), new_text.split("\n"), lineterm=""
121
        )
122
        print("\n".join(diff))
×
123
        choice = click.confirm("Save the page?", default=True)
×
124
        if choice:
×
125
            return new_text, summary
×
126
        else:
127
            raise RuntimeError
×
128

129
    def id_hook(
×
130
        self,
131
        observations: Sequence[inrbot.iNaturalistID] = [],
132
        photos: Sequence[inrbot.iNaturalistID] = [],
133
        **kwargs,
134
    ):
135
        try:
×
136
            return ids[self.page]
×
137
        except KeyError:
×
138
            return self.ask_url(observations=observations, photos=photos)
×
139
        return None
140

141
    def ask_url(
×
142
        self,
143
        observations: Sequence[inrbot.iNaturalistID] = [],
144
        photos: Sequence[inrbot.iNaturalistID] = [],
145
    ):
146
        print(f"Commons page: {self.page.full_url()}")
×
147
        if observations:
×
148
            print(f"Observation ID found: {str(observations[0])}")
×
149
        if photos:
×
150
            print(f"Photo ID found: {str(photos[0])}")
×
151
        if auto_open:
×
152
            webbrowser.open(self.page.full_url())
×
153
            if observations and not photos:
×
154
                webbrowser.open(str(observations[0]))
×
155
            elif photos:
×
156
                webbrowser.open(str(photos[0]))
×
157

158
        res = click.prompt(
×
159
            "Is this ID correct? [Y/n/r/s/q]",
160
            default="Y",
161
            type=click.Choice("ynrsq", case_sensitive=False),
162
            show_default=False,
163
            show_choices=False,
164
        ).lower()
165
        if res == "y":
×
166
            correct_id = True
×
167
        elif res == "n":
×
168
            correct_id = False
×
169
        elif res == "r":
×
170
            self.remove_untagged_log()
×
171
            raise SkipFile
×
172
        elif res == "s":
×
173
            raise SkipFile
×
174
        elif res == "q":
×
175
            sys.exit()
×
176

177
        if not correct_id:
×
178
            url = click.prompt("iNaturalist Photos URL")
×
179
            ina_id = inrbot.parse_ina_url(url)
×
180
            if observations:
×
181
                self.page.text = self.page.text.replace(str(observations[0]), url)
×
182
            if photos:
×
183
                self.page.text = self.page.text.replace(str(photos[0]), url)
×
184
        elif observations and not photos:
×
185
            url = click.prompt("iNaturalist Photos URL", default="")
×
186
            ina_id = inrbot.parse_ina_url(url)
×
187
        elif photos:
×
188
            ina_id = photos[0]
×
189
        else:
190
            ina_id = None
×
191
        ids[self.page] = ina_id
×
192
        return ina_id
×
193

194
    def log_untagged_error(self) -> None:
×
195
        # Errors while running in CLI do not need to be logged on-wiki
196
        return
×
197

198

199
inrbot.id_hooks.append(ManualCommonsPage.id_hook)
×
200
inrbot.lock_hooks.append(ManualCommonsPage.archive_status_hook)
×
201
inrbot.pre_save_hooks.append(ManualCommonsPage.pre_save)
×
202

203

204
@click.command()
×
205
@click.argument("target")
×
206
@click.option("--url")
×
207
@click.option("--simulate/--no-simulate")
×
208
@click.option("--reverse", is_flag=True, default=False)
×
209
@click.option("-o", "--auto-open", "auto_open_", is_flag=True, default=False)
×
210
def main(target, url="", simulate=False, reverse=False, auto_open_=False):
×
211
    inrbot.simulate = simulate
×
212
    global auto_open
213
    auto_open = auto_open_
×
214
    if target == "auto":
×
215
        cat = pywikibot.Category(
×
216
            site, "Category:iNaturalist images needing human review"
217
        )
218
        dtt = pywikibot.Page(site, "Template:Deletion template tag")
×
219
        for page in cat.articles(namespaces=6, reverse=reverse):
×
220
            if dtt in set(page.itertemplates()):
×
221
                continue
×
222
            try:
×
223
                mcp = ManualCommonsPage(pywikibot.FilePage(page))
×
224
                mcp.review_file()
×
225
            except SkipFile:
×
226
                continue
×
227
    elif target == "errors":
×
228
        log_page = pywikibot.Page(site, inrbot.config["untagged_log_page"])
×
229
        dtt = pywikibot.Page(site, "Template:Deletion template tag")
×
230
        for page in log_page.linkedPages(namespaces=6, follow_redirects=True):
×
231
            mcp = ManualCommonsPage(pywikibot.FilePage(page))
×
232
            if (
×
233
                not page.exists()
234
                or dtt in set(page.itertemplates())
235
                or mcp.check_has_template()
236
            ):
237
                mcp.remove_untagged_log()
×
238
                continue
×
239
            try:
×
240
                mcp.review_file()
×
241
            except SkipFile:
×
242
                continue
×
243
    elif target == "ask":
×
244
        while True:
×
245
            new_target = click.prompt("Target", default="")
×
246
            if not new_target:
×
247
                break
×
248
            ManualCommonsPage(pywikibot.FilePage(site, new_target)).review_file()
×
249
    else:
250
        page = pywikibot.FilePage(site, target)
×
251
        if url:
×
252
            # TODO: Add validation
253
            ids[page] = inrbot.parse_ina_url(url)
×
254

255
        ManualCommonsPage(page).review_file()
×
256

257

258
if __name__ == "__main__":
×
259
    main()
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc