Display an xkcd

36

6

xkcd is everyone's favorite webcomic, and you will be writing a program that will bring a little bit more humor to us all.
Your objective in this challenge is to write a program which will take a number as input and display that xkcd and its title-text (mousover text).

Input

Your program will take a positive integer as input (not necessarily one for which there exists a valid comic) and display that xkcd: for example, an input of 1500 should display the comic "Upside-Down Map" at xkcd.com/1500, and then either print its title-text to the console or display it with the image.

Due to their proximity across the channel, there's long been tension between North Korea and the United Kingdom of Great Britain and Southern Ireland. Due to their proximity across the channel, there's long been tension between North Korea and the United Kingdom of Great Britain and Southern Ireland.

Test case 2, for n=859:

Brains aside, I wonder how many poorly-written xkcd.com-parsing scripts will break on this title (or ;;"''{<<[' this mouseover text."

Brains aside, I wonder how many poorly-written xkcd.com-parsing scripts will break on this title (or ;;"''{<<[' this mouseover text."

Your program should also be able to function without any input, and perform the same task for the most recent xkcd found at xkcd.com, and it should always display the most recent one even when a new one goes up.

You do not have to get the image directly from xkcd.com, you can use another database as long as it is up-to-date and already existed before this challenge went up. URL shortners, that is, urls with no purpose other than redirecting to somewhere else, are not allowed.

You may display the image in any way you chose, including in a browser. You may not, however, directly display part of another page in an iframe or similar. CLARIFICATION: you cannot open a preexisting webpage, if you wish to use the browser you have to create a new page. You must also actually display an image - outputting an image file is not allowed.

You can handle the case that there isn't an image for a particular comic (e.g. it is interactive or the program was passed a number greater than the amount of comics that have been released) in any reasonable way you wish, including throwing an exception, or printing out an at least single-character string, as long as it somehow signifies to the user that there isn't an image for that input.

You can only display an image and output its title-text, or output an error message for an invalid comic. Other output is not allowed.

This is a challenge, so the fewest bytes wins.

Pavel

Posted 2016-10-27T16:43:13.190

Reputation: 8 585

Is output aside from the image/alt-text allowed? i.e. the entire source of the page? – Luke – 2016-10-27T17:00:25.437

1@LukeFarritor You can only display the image and output the title text or output some form of error message for an invalid comic. – Pavel – 2016-10-27T17:01:45.807

9If your sample size is 1, import antigravity in Python ;) – Wayne Werner – 2016-10-27T18:11:20.597

15

Funny fact n=404 http://xkcd.com/404 is a 404 page.

– Magic Octopus Urn – 2016-10-27T18:15:45.707

Is simply finding and outputting the <img src="" alt=""/> to an HTML file okay? – Magic Octopus Urn – 2016-10-27T18:19:04.153

11xkcd is everyone's favorite webcomic [Citation needed] – Sanchises – 2016-10-27T18:21:47.307

@carusocomputing the source would have to be a file downloaded to your computer and the program would have to open the html file in a browser. – Pavel – 2016-10-27T18:22:50.467

11

Test case: 859

– betseg – 2016-10-27T18:24:08.210

@betseg Lol, QUICK! Everyone use use .eval() statements. – Magic Octopus Urn – 2016-10-27T20:07:32.087

If we can't open a pre-existing webpage, would it be cheating to download the HTML remotely into a local HTML file and open that? – JAL – 2016-10-27T20:10:42.577

@JAL you A) cannot display anything but the image and title text, and B) can only use files downloaded by the program to your computer as the src for img tags. – Pavel – 2016-10-27T20:43:57.993

Is it acceptable to print out the alt text after the image window is closed? – a spaghetto – 2016-10-27T23:13:08.603

@quartata Yes, that would be acceptable. – Pavel – 2016-10-27T23:28:12.927

Something that people may not be aware of but which may be relevant: there's an XKCD API.

– Mac – 2016-10-28T11:38:57.343

@EriktheGolfer I just figured that I'd accept the current lowest answer and then change it if anyone comes up with a lower one. I'm fairly new here, and was not aware that that was how it was supposed work. – Pavel – 2016-10-28T22:40:19.460

Answers

13

Perl + curl + feh, 86 84 75 bytes

`curl xkcd.com/$_/`=~/<img src="(.*)" title="(.*?)"/;$_=$2;`feh "http:$1"`

Requires the -p switch. I have accounted for this in the byte count.

a spaghetto

Posted 2016-10-27T16:43:13.190

Reputation: 10 647

@Matt It worked on all the comics I tried. It only matches images with alt-text so. – a spaghetto – 2016-10-27T17:33:53.130

You may not need the quotes around the src attribute. – Conor O'Brien – 2016-10-27T18:24:41.917

I don't think you need the ? in the first match group. You could use -p and $_=$2 instead of print$2, but then the title text is printed only after feh is closed. Not sure if that's valid. – m-chrzan – 2016-10-27T19:17:43.953

@m-chrzan Yeah looks like I can drop the reluctant quantifier there, thanks. I thought about using -p but wasn't sure how the OP would feel about it. – a spaghetto – 2016-10-27T23:11:29.077

@ConorO'Brien Unfortunately Randall observes good HTML coding practices... and capturing the quotes doesn't then quote the arguments due to how backticks work in Perl. – a spaghetto – 2016-10-27T23:12:47.453

@quartata Oh... – Conor O'Brien – 2016-10-27T23:19:26.600

perl + curl = ... pceurl? – Reinstate Monica -- notmaynard – 2016-10-28T14:42:04.633

You should be able to replace curl -L http://xkcd.com/$_ with curl xkcd.com/$_/. – Dennis – 2016-10-28T23:07:11.860

@Dennis Doesn't work on my machine. – a spaghetto – 2016-10-28T23:12:12.587

Do you provide input with or without a trailing linefeed? – Dennis – 2016-10-28T23:13:03.120

@Dennis Without a trailing linefeed it works. Strange. – a spaghetto – 2016-10-29T00:30:35.350

http://xkcd.com/n redirects to http://xkcd.com/n/. If the input has a trailing linefeed, curl xkcd.com/$_ will fail because of the redirect, then / (on the next line) causes a syntax error. – Dennis – 2016-10-29T00:32:42.383

Oh, I see now. Oops. – a spaghetto – 2016-10-29T00:36:06.053

9

PowerShell v3+ 110 99 107 103 Bytes

iwr($x=((iwr "xkcd.com/$args").images|?{$_.title})).src.Trim("/") -outf x.jpg;if($x){ii x.jpg;$x.title}

Thanks to Timmy for helping save some bytes by using inline assignments.

If no arguments are passed then $args is null and it will just get the current comic. Download the picture, by matching the one with alt text, into a file in the current running directory of the script. Then display it with the default viewer of jpg's. The alt text is then displayed to console. iwr is an alias for Invoke-WebRequest

If the number passed (or any invalid input for that matter) does not match the process fails with at least a 404 error.

iwr(                                  # Request the comic image from XKCD
  $x=((iwr "xkcd.com/$args").images|  # Primary search to locate either the current image
                                      # or one matching an argument passed
     ?{$_.title}))                    # Find the image with alt text
        .src.Trim("/")                # Using the images associated link and strip the leading slashes
  -outf x.jpg                         # Output the image to the directory local to where the script was run
if($x){                               # Test if the image acquisition was successful
    ii x.jpg                          # Open the picture in with the default jpg viewer
    $x.title                          # Display alt text to console
}                                     # I'm a closing bracket.

Matt

Posted 2016-10-27T16:43:13.190

Reputation: 1 075

Only just got my comment privilege on this sub now, check out my very similar answer

– colsw – 2016-10-28T10:00:59.273

@ConnorLSW Nice approach in your answer. Things to think about for next time. – Matt – 2016-10-28T12:16:35.580

8

AutoIt, 440 bytes

Yes, it's long, but it's stable.

#include<IE.au3>
#include<GDIPlus.au3>
Func _($0='')
_GDIPlus_Startup()
$1=_IECreate('xkcd.com/'&$0)
For $3 In $1.document.images
ExitLoop $3.title<>''
Next
$4=_GDIPlus_BitmapCreateFromMemory(InetRead($3.src),1)
$6=_GDIPlus_ImageGetDimension(_GDIPlus_BitmapCreateFromHBITMAP($4))
GUICreate(ToolTip($3.title),$6[0],$6[1])
GUICtrlSendMsg(GUICtrlCreatePic('',0,0,$6[0],$6[1]),370,0,$4)
_IEQuit($1)
GUISetState()
Do
Until GUIGetMsg()=-3
EndFunc

First of all, this doesn't use RegEx to scrape the site (because I have no time to test this on all comics), but rather uses the Internet Explorer API to iterate through the DOM's img tags until it finds one with a title text.

The binary stream is read from the image URL and rendered into a bitmap using GDIPlus. This is then displayed in a nice, auto-sized GUI with an actual tooltip to make it behave almost exactly like the website.

Here's a test case (_(859)):

)

mınxomaτ

Posted 2016-10-27T16:43:13.190

Reputation: 7 398

4It would be better if you added the bracket back into the image. – Matt – 2016-10-27T19:31:58.593

Wow, just found out that I can run AutoIt under Wine on Ubuntu. Now all I need to do is get IE working on there. On second thoughts... +1 for the answer – ElPedro – 2016-10-27T21:03:29.440

2@ElPedro If you have a modern CPU, use qemu KVM and seamlessRDP (basically a DIY parallels for linux) instead of wine. This integrates Windows apps seamlessly into the linux desktop and has 100% compatibility + GPU passthrough. Just a tip. – mınxomaτ – 2016-10-27T21:05:43.697

Thanks @mınxomaτ. I may give that a go. I work with Windows all day so prefer to use Linux (various flavours) out of work but I am always interested in experimenting :) Tip gratefully received. – ElPedro – 2016-10-27T21:14:14.383

7

Powershell, 93 Bytes

93 Byte version to use local image viewer.

$n=(iwr xkcd.com/$args).images|?{$_.title};$n.title;iwr ("http:"+$n.src) -OutF x.jpg;ii x.*

Saved 2 bytes by removing needless doublequotes, then another lot by using ("http:"+$n.src) instead of "https://"+$n.src.trim("/") - since the img src comes with // already on it, and xkcd doesn't require https.

$n=(iwr xkcd.com/$args).images|?{$_.title};$n.title;saps ("http:"+$n.src)

$n=(iwr "xkcd.com/$args").images|?{$_.title};$n.title;saps ("https://"+$n.src.trim("/"))

extremely similar to Matts powershell answer, (should probably be a comment but low reputation)

Instead this opens a new tab/window in the default browser, and other things, saving some bytes.

iwr is an alias for Invoke-WebRequest

saps is an alias for Start-Process which opens 'it' in the default context.

colsw

Posted 2016-10-27T16:43:13.190

Reputation: 3 195

Skirting the edges of the rules about opening an existing webpage, because this is just directly launching the browser at the pre-existing .jpg (or whatever), but a nice answer. – AdmBorkBork – 2016-10-28T12:47:19.233

@TimmyD might have misunderstood here then - I assumed you can use the xkcd webpage itself - you can just change saps to iwr and append -OutF x.jpg;ii x.* to the end if you want it to open in the default local image viewer. – colsw – 2016-10-28T13:19:03.983

1The OP specified that you aren't allowed to open a pre-existing webpage. The 93 byte version I think is fine though – a spaghetto – 2016-10-28T18:41:06.043

I don't think this is going to always work the same if you give a number that works then a number that does not. It will open the image that existed from the previous run. – Matt – 2016-10-28T19:32:12.247

I accept the 93 byte version, but your shorter one violates the conditions of the puzzle. – Pavel – 2016-10-29T03:07:59.707

4

R, 358 328 310 298 bytes

f=function(x){H="http:";p=paste0;library(XML);a=xpathSApply(htmlParse(p(H,'//xkcd.com/',x)),'//div/img',xmlAttrs)[[1]];download.file(p(H,a[1]),'a');I=`if`(grepl('png',a[1]),png::readPNG,jpeg::readJPEG)('a');d=dim(I)/100;quartz(,d[2],d[1]);par(mar=rep(0,4));frame();rasterImage(I,0,0,1,1);cat(a[2])}

With new lines and comments:

f=function(x){
H="http:"
p=paste0
library(XML) #Needed for xpathSApply, htmlParse and xmlAttrs
# The following line find the first img element and extract its attributes
a=xpathSApply(htmlParse(p(H,'//xkcd.com/',x)),'//div/img',xmlAttrs)[[1]]
download.file(p(H,a[1]),'a') #Download to a file called 'a'
I=`if`(grepl('png',a[1]),png::readPNG,jpeg::readJPEG)('a') #Check if png or jpeg and load the file accordingly
d=dim(I)/100 #convert dimension from pixel to inches (100 ppi).
quartz(,d[2],d[1]) #open a window of the correct dimension
par(mar=rep(0,4)) #Get rid of margins
frame() #Create empty plot
rasterImage(I,0,0,1,1) #Add png/jpeg to the plot
cat(a[2]) #Print title text to stdout
}

Screenshots of test cases:

for x=1500: for x=1500 (png)

for x empty:
for x=''

case when picture is a jpeg:
for x=10 (jpeg)

x=859:
x=859

plannapus

Posted 2016-10-27T16:43:13.190

Reputation: 8 610

I wonder if it's necessary to display the image in the correct dimensions. When I was playing around with this challenge I simply did plot.new();rasterImage(...). – Billywob – 2016-10-28T08:43:18.627

@Billywob Well it would be completely distorted as the default size for the plot is 7x7 inches. It's true that the OP didn't explicitely ask for the image to not be distorted but I prefer it this way :) I am however considering getting rid of the xaxs and yaxs as the result would still be proportionate. – plannapus – 2016-10-28T08:47:44.957

3

PHP, 95 bytes

<?php echo str_replace("title=\"","/>",file_get_contents("https://xkcd.com/".$_GET["id"])); ?>

Save as main.php, run server

php -S localhost:8123

Open http://localhost:8123/main.php?id=1500

Евгений Новиков

Posted 2016-10-27T16:43:13.190

Reputation: 987

2

PHP, 42 bytes

<?=@file('http://xkcd.com/'.$_GET[i])[59];

Save to a file and fire it up in your web server of choice

Jared Mellentine

Posted 2016-10-27T16:43:13.190

Reputation: 101

1Welcome to PPCG! I don't know PHP, but there doesn't seem to be a part of your code that's fetching the image's title text? – Pavel – 2017-07-01T04:39:57.890

2

Python 2.7, 309 299 295 274 bytes

Full program. Definitely more golfable, but having read xkcd comics for so long I couldn't let this pass (who knows if this will be helpful in a future for easily browsing xkcd).

If no input is passed, gets current comic. If a valid comic number is passed as input then gets that comic. If an invalid input (not a number comic in the valid range) is passed, throws an error.

Any suggestions on how to reduce byte count are welcome! Will revisit (and add explanation) when I have more time.

-10 bytes thanks to @Dopapp

-21 bytes thanks to @Shebang

import urllib as u,re
from PIL import Image
h='http://';x='xkcd.com/'
o=u.URLopener()
t=u.urlopen(h+x+raw_input()).read()
c=sum([re.findall(r,t)for r in[h+'imgs.'+x+'c.*s/.*\.\w{1,3}','\.\w{1,3}" t.*e="(.*)" a']],[])
Image.open(o.retrieve(c[0],'1.png')[0]).show();print c[1]

Ioannes

Posted 2016-10-27T16:43:13.190

Reputation: 595

1You can change try:... and except:... to try:n=... and except:n='', saving you 10 bytes total – Daniel – 2016-10-28T01:55:33.537

1Why do you even have a try statement? The program spec says you will always get a positive integer. – Kade – 2016-10-28T12:46:35.550

@Shebang it should also return the latest comic when there is no input. I wasn't able to manage this case without carching the exception of input error. – Ioannes – 2016-10-28T13:53:15.227

1@Joannes Why not use raw_input()? By default the user can press [Enter] and n will contain the empty string anyways. If you remove that try-except block and do t=u.urlopen(h+x+n).read() -> t=u.urlopen(h+x+raw_input()).read() you get it down to 274 bytes. – Kade – 2016-10-28T14:06:16.147

This no longer works, because xkcd image URLs use https. However, it's still valid, because it worked at the time of posting. To make it work now, change line 3 to start with h='https://' for +1 byte. – Mego – 2018-07-08T18:47:12.997

1

Python 3 + Requests + PIL, 192 186 bytes

from requests import*
import PIL.Image as f
from io import*
r=get("https://xkcd.com/%s/info.0.json"%input()).json()
f.open(BytesIO(get(r["img"],stream=1).content)).show()
print(r["alt"])

Opens up an image viewer (whichever is default on the system it's being run on) containing the comic, and posts the title text to the console.

LyricLy

Posted 2016-10-27T16:43:13.190

Reputation: 3 313

1

JavaScript + HTML, 124 + 18 = 142 bytes

i=>fetch(`//crossorigin.me/http://xkcd.com/${i||""}/info.0.json`).then(r=>r.json()).then(d=>(A.innerHTML=d.alt,B.src=d.img))
<img id=B><p id=A>

Cross-origin solution thanks to Kaiido's answer here.

17 bytes (//crossorigin.me/) can be saved if the proxy required to connect to xkcd.com can be subtracted (meta post about this).

Test Snippet

f=
i=>fetch(`//crossorigin.me/http://xkcd.com/${i||""}/info.0.json`).then(r=>r.json()).then(d=>(A.innerHTML=d.alt,B.src=d.img))
<style>img{width:50%}</style><input id=I> <button onclick="f(I.value)">Run</button><br>
<img id=B><p id=A>

Justin Mariner

Posted 2016-10-27T16:43:13.190

Reputation: 4 746

1

Wolfram Language 45 bytes ( Mathematica )

Import["https://xkcd.com/"<>#,"Images"][[2]]&

Usage with number:

%@"1500"

Usage without number:

%@""

Vitaliy Kaurov

Posted 2016-10-27T16:43:13.190

Reputation: 1 561