diff --git a/wikicurses.1 b/wikicurses.1 index cc8a72a..612294d 100644 --- a/wikicurses.1 +++ b/wikicurses.1 @@ -35,6 +35,10 @@ G or end .RS 4 Scroll to bottom .RE +.PP +:images +.RS 4 +Launch a program (default: feh) with images from the article .SS Pager .PP c diff --git a/wikicurses.conf.5 b/wikicurses.conf.5 index b939b6d..4d8de68 100644 --- a/wikicurses.conf.5 +++ b/wikicurses.conf.5 @@ -49,6 +49,17 @@ Hide the References section at the bottom of the page and strip citations from t .RE .SS keymap This section configures the keyboard bindings of wikicurses, in the format "key=command". Command can be any ex command supported by wikicurses. +.SS images +.PP +program +.RS 4 +The name of the command to use to show images. It should be able to take space separated URLs as arguments. Defaults to feh if unspecified. +.RE +.PP +arguments +.RS 4 +The list of arguments given to the image display program. The URLs are appended to this. This is a space separated list. Example: `--flag -abc -d --option-with-arg arg`. +.RE .SS Other Sections Other sections are treated as wiki entries. The url is the url for api.php on the wiki. The username and password are required for editing. .PP diff --git a/wikicurses/main.py b/wikicurses/main.py index bd7797c..5e5e4be 100644 --- a/wikicurses/main.py +++ b/wikicurses/main.py @@ -1,9 +1,11 @@ import os import re +import json import argparse import tempfile import subprocess import urllib.parse +from functools import lru_cache import urwid @@ -35,6 +37,111 @@ def tabComplete(text, matches): return match +def showImages(): + """ + Launch program to display the images for the current article. + + The program is specified in the config, along with it's args. + Otherwise, the default is `feh`. + """ + if 'wikipedia.org' not in wiki.siteurl: + ex.notify('Cancelled: Only works on Wikipedia') + return + + targets = fetchImageTargets(page.title) + + if not targets: + ex.notify('No relevant images found') + return + + ## We make a copy of the returned list here, + ## because it is memoized with `lru_cache` and we will be deleting + ## elements from it non-permanently. + image_info = list(fetchImageInfo(page.title)) + filtered = [] + + for target in targets: + for index, i in enumerate(image_info): + title, url = i + if target in title: + filtered.append(url) + del image_info[index] # prevent more than one occurrence of image + break + + try: + command = [settings.conf.get('images', 'program')] + except (settings.configparser.NoOptionError, + settings.configparser.NoSectionError): + command = ['feh'] + + try: + args = settings.conf.get('images', 'arguments').split(' ') + except (settings.configparser.NoOptionError, + settings.configparser.NoSectionError): + if command == ['feh']: + args = ['-q', '--scale-down', '--image-bg', 'white', '-g', '400x400'] + else: + args = [] + + command.extend(args) + command.extend(filtered) + + try: + with open(os.devnull, 'w') as fp: + subprocess.Popen(command, stdout=fp, stderr=fp) + except FileNotFoundError: + ex.notify('Program not found') + return + + +@lru_cache() +def fetchImageTargets(page_title): + """ + Get filenames of relevant images from the current article. + + These are used to filter out non-relevant images from the API result. + """ + url = re.sub(r'api.php', 'index.php', wiki.siteurl) + page_title = re.sub(r' ', '_', page_title) + raw = wiki._query(customurl=url, action="raw", title=page_title) + + ### Finding targets... + targets = [] + + ## of the form `[[Image:foobar.png]]` + for match in re.finditer(r'\b(?:File:|Image:)([^]|\n\r]+)', raw): + targets.append(match.group(1)) + + ## of the form `image1 = foobar.png`, `image2=foobar.png`, + ## and `image = bar baz.png` etc... + for match in re.finditer( + r'(image\d?[\t ]*?=[\t ]*)(.+?\..+?)((?=[^A-Za-z0-9\.])|(?:$))', + raw): + targets.append(match.group(2)) + + return targets + + +@lru_cache() +def fetchImageInfo(page_title): + """ + Use API to fetch all image titles and urls on current article. + + Returns a list of `(title, url)` tuples. + """ + page_title = re.sub(r' ', '_', page_title) + result = wiki._query(action="query", titles=page_title, generator="images", + prop="imageinfo", iiprop="url", format="json") + + json_result = json.loads(result) + info = [] + + for v in json_result['query']['pages'].values(): + info.append((v['title'], v['imageinfo'][0]['url'])) + + return info + + class SearchBox(urwid.Edit): title = "Search" @@ -495,7 +602,7 @@ def cancel(button): 'extlinks': Extlinks, 'langs': Langs} cmds = tuple(overlaymap) + ('quit', 'bmark', 'open', 'edit', 'clearcache', - 'help', 'back', 'forward', 'random') + 'help', 'back', 'forward', 'random', 'images') def processCmd(cmd, *args): global current @@ -528,6 +635,8 @@ def processCmd(cmd, *args): openPage(history[current], browsinghistory=True) elif cmd == 'random': openPage(wiki.random()) + elif cmd == 'images': + showImages() elif cmd: ex.notify(cmd + ': Unknown Command') diff --git a/wikicurses/wiki.py b/wikicurses/wiki.py index 4b8b8d1..1113054 100644 --- a/wikicurses/wiki.py +++ b/wikicurses/wiki.py @@ -87,7 +87,12 @@ def mainpage(self): def _query(self, post=False, **kwargs): params = {k: v for k, v in kwargs.items() if v is not False} data = urllib.parse.urlencode(params) - url = self.siteurl + + try: + url = params['customurl'] + except KeyError: + url = self.siteurl + if post: data = data.encode() if not post: