2
\$\begingroup\$

Your goal in this code golf is to compile all the Stack Exchange sites' favicons.


Getting the Domains

The domains you will be using will be:

  • *.stackexchange.com
  • stackexchange.com
  • stackoverflow.com
  • superuser.com
  • serverfault.com
  • askubuntu.com
  • mathoverflow.net
  • stackapps.com
  • stackauth.com

Everything but stackexchange.com and stackauth.com, as of now, can be gathered from /sites.

All the meta counterparts will also be included. (just prepend meta. to the beginning of any of the domains to get the meta site)

The list above shows the order of the sites. Sites without their own domain (*.stackexchange.com, i.e. codegolf.stackexchange.com) are first in the stylesheet, and are sorted alphabetically. After you've sorted the subdomain sites, the rest of the domains are in the order listed above.

Then, you can append /favicon.ico to each one of the domains you dynamically compiled and get the 32 by 32 pixel image that results from it.

Here's another list for domains that redirect to *.stackexchange.com sites:

  • askdifferent.comapple.stackexchange.com
  • arqade.comgaming.stackexchange.com
  • seasonedadvice.comcooking.stackexchange.com

Output the Images

As soon as you have all of the favicons from the sites and their meta counterparts, your code combines them. Optionally, convert them into .PNG files (you gathered .ICOs, and trying to get favicon.png doesn't work) After that, you can start combining the images into one rectangle.

There will be 10 columns, each 32 pixels in width, making a total of 320 pixels. The number of rows will match those needed to fit all the favicons, and they are 32 pixels in height.

Your code should output an image with all of the favicons you gathered as a transparent .PNG file, which is already , like seen below:


Scoring

This is and so the least bytes wins!

Leaderboard

Here is a Stack Snippet to generate both a regular leaderboard and an overview of winners by language.

To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

# Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

# Ruby, <s>104</s> <s>101</s> 96 bytes

var QUESTION_ID=86753;function answersUrl(e){return"http://api.stackexchange.com/2.2/questions/"+QUESTION_ID+"/answers?page="+e+"&pagesize=100&order=desc&sort=creation&site=codegolf&filter="+ANSWER_FILTER}function getAnswers(){jQuery.ajax({url:answersUrl(page++),method:"get",dataType:"jsonp",crossDomain:!0,success:function(e){answers.push.apply(answers,e.items),e.has_more?getAnswers():process()}})}function shouldHaveHeading(e){var a=!1,r=e.body_markdown.split("\n");try{a|=/^#/.test(e.body_markdown),a|=["-","="].indexOf(r[1][0])>-1,a&=LANGUAGE_REG.test(e.body_markdown)}catch(n){}return a}function shouldHaveScore(e){var a=!1;try{a|=SIZE_REG.test(e.body_markdown.split("\n")[0])}catch(r){}return a}function getAuthorName(e){return e.owner.display_name}function process(){answers=answers.filter(shouldHaveScore).filter(shouldHaveHeading),answers.sort(function(e,a){var r=+(e.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0],n=+(a.body_markdown.split("\n")[0].match(SIZE_REG)||[1/0])[0];return r-n});var e={},a=1,r=null,n=1;answers.forEach(function(s){var t=s.body_markdown.split("\n")[0],o=jQuery("#answer-template").html(),l=(t.match(NUMBER_REG)[0],(t.match(SIZE_REG)||[0])[0]),c=t.match(LANGUAGE_REG)[1],i=getAuthorName(s);l!=r&&(n=a),r=l,++a,o=o.replace("{{PLACE}}",n+".").replace("{{NAME}}",i).replace("{{LANGUAGE}}",c).replace("{{SIZE}}",l).replace("{{LINK}}",s.share_link),o=jQuery(o),jQuery("#answers").append(o),e[c]=e[c]||{lang:c,user:i,size:l,link:s.share_link}});var s=[];for(var t in e)e.hasOwnProperty(t)&&s.push(e[t]);s.sort(function(e,a){return e.lang>a.lang?1:e.lang<a.lang?-1:0});for(var o=0;o<s.length;++o){var l=jQuery("#language-template").html(),t=s[o];l=l.replace("{{LANGUAGE}}",t.lang).replace("{{NAME}}",t.user).replace("{{SIZE}}",t.size).replace("{{LINK}}",t.link),l=jQuery(l),jQuery("#languages").append(l)}}var ANSWER_FILTER="!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe",answers=[],page=1;getAnswers();var SIZE_REG=/\d+(?=[^\d&]*(?:&lt;(?:s&gt;[^&]*&lt;\/s&gt;|[^&]+&gt;)[^\d&]*)*$)/,NUMBER_REG=/\d+/,LANGUAGE_REG=/^#*\s*([^,]+)/;
body{text-align:left!important}#answer-list,#language-list{padding:10px;width:290px;float:left}table thead{font-weight:700}table td{padding:5px}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script><link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"><div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr></thead> <tbody id="answers"> </tbody> </table></div><div id="language-list"> <h2>Winners by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr></thead> <tbody id="languages"> </tbody> </table></div><table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table><table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr></tbody></table>

\$\endgroup\$
  • 4
    \$\begingroup\$ The list of Stack Exchange sites is not fixed; subdomains may be added or removed at all times. The challenge spec should include the list that is required for this task. \$\endgroup\$ – Dennis Jul 27 '16 at 22:09
  • 2
    \$\begingroup\$ OK, so what about SE sites that aren't a subdomain of SE.com or SO.com? Should they be excluded? \$\endgroup\$ – Dennis Jul 27 '16 at 22:20
  • 1
    \$\begingroup\$ What about parse the sites from stackexchange.com/sites ? \$\endgroup\$ – Giacomo Garabello Jul 28 '16 at 15:17
  • 1
    \$\begingroup\$ @GiacomoGarabello That is an option. \$\endgroup\$ – haykam Jul 28 '16 at 16:18
  • 1
    \$\begingroup\$ @Dennis, Seasoned Advice is actually at cooking.stackexchange.com and seasonedadvice.com is just a redirect, much like arqade.com redirects to gaming.stackexchange.com. \$\endgroup\$ – haykam Jul 29 '16 at 1:24
9
\$\begingroup\$

JavaScript (ES6), 691 687 680 663 646 657 bytes

fetch(new Request('//crossorigin.me/http://stackexchange.com/sites')).then(r=>r.text(t=(d=document).body.appendChild(c=d.createElement('canvas')).getContext('2d')).then(a=>(d=new DOMParser(c.width=320).parseFromString(a,'text/html'),s=[...d.querySelectorAll('.grid-view-container>a')].map(e=>!~(h=e.href[p='slice'](7))[o='indexOf']('met')?h:h[p](5)),c.height=-~(s.length/5)*32,s.sort((a,b)=>(e=a[o](k='.s'),g=b[o](k),~e&&~g?a[l='localeCompare'](b):!~e&&!~g?a[p](e)[l](b[p](g)):g-e)).concat(s.filter(u=>!~u[o]('ka')).map(u=>'meta.'+u))).map((u,i,x,y)=>(u=`http://${u}favicon.ico`,x=i%10,y=(i-x)/10,i=new Image,i.src=u,i.onload=_=>t.drawImage(i,x*32,y*32)))))

Oof. Hopefully I can trim down the byte count.

Basically:

  • CORS request to /sites, parse DOM and scrape the site list from there using a query selector.
  • Sort first by *.stackexchange.com sites alphabetically, then by independent domain sites alphabetically.
  • Append the meta sites, filtering out meta.stackexchange.com and stackapps.com since they're both listed on /sites and are without their own meta site.
  • Asynchronously load each favicon.ico without CORS since we don't care about tainting the canvas.
  • Use each site's index i from the sorted array to place the loaded image in the correct position on the sprite-sheet.

You can right-click on the image and select "Save Image as", so I don't think I need to write more just to explicitly prompt the image for download, since it's already enough work just displaying it.

Revisions

  1. Inlining global declarations saved 2 bytes. Submission is now one loooong line.
  2. Removing () from new Image() saved another 2 bytes.
  3. Changing Math.ceil(...) to -~(...) saved 7 bytes. Doesn't apply now, but in the event that the amount of sites is a multiple of 10, it's still okay because "There are 10 columns, which is 320 pixels in width, and unlimited rows/height" regardless of the amount of sites.
  4. Saved 17 bytes by moving a global declaration and refactoring an unneeded ternary operator in a .map() function.
  5. Saved another 17 bytes by storing long repeated method names to variables.
  6. Cost 11 bytes to factor in stackexchange.com and its meta... gross.
\$\endgroup\$
  • \$\begingroup\$ Nothing seems to shows up when I run your code snippet and save the image... \$\endgroup\$ – R. Kap Jul 29 '16 at 18:46
  • \$\begingroup\$ @R.Kap does the sprite-sheet populate with all the favicons when viewing the program in progress? I'm running on Chrome 52 and it works perfectly. Make sure your browser supports the Request API and ES6. Check for errors in your console when running the program. If there are any, it would be helpful for me if you could share those. \$\endgroup\$ – Patrick Roberts Jul 29 '16 at 18:47
  • \$\begingroup\$ Well, I am running the snippet in Firefox and don't really know if it supports the Request API. However, I know it does support ES6 since a lot of other ES6 snippets on PPCG do work. There are no errors in the console, so t's most likely that Firefox may not support the Request API. \$\endgroup\$ – R. Kap Jul 29 '16 at 18:52
  • \$\begingroup\$ codegolf.stackexchange.com/a/86853/42091 Since you're in Firefox and there are no errors, my guess is that it's blocking "non-secure requests from HTTPS to HTTP". Just click that link and make sure your URL is HTTP instead of HTTPS when running the script. \$\endgroup\$ – Patrick Roberts Jul 29 '16 at 18:54
  • 1
    \$\begingroup\$ @Peanut I don't appreciate you editing my answer without my permission. I'm reverting it. Don't do that again. \$\endgroup\$ – Patrick Roberts Jul 30 '16 at 23:37
3
\$\begingroup\$

Python 3.5, 591 549 541 557 570 bytes:

(+16 bytes (541 -> 557) to add the ability for the final image to dynamically expand in height based on the number of favicons gathered.)

(+13 bytes (557 -> 570) since URL shorteners are apparently not allowed.)

from urllib.request import*;from PIL import Image as I;from io import*;import re
H='http://';U=urlopen;u=y=0;V=sorted(re.findall('<h2><a href="%s((?!meta).+?.stackexchange.com)">'%H,U(H+'stackexchange.com/sites?view=list').read().decode()[7167:]))+['%s.com'%i for i in'stackexchange stackoverflow superuser serverfault askubuntu mathoverflow stackapps stackauth'.split()];i=I.new("RGBA",(320,-~(len(V)*2//10)*32))
for l in V+['meta.'+i for i in V]:
 try:
  i.paste(I.open(BytesIO(U(H+l+'/favicon.ico').read())),(u,y));u+=32
  if u>319:u=0;y+=32
 except:0
i.save('O.png')

Quite long, but hey, I beat Javascript! :D This imports the modules for the following uses:

  • urllib to fetch website and image data.
  • io module for the BytesIO function to decode the fetched image data for PIL.
  • re for the use of regular expressions to parse the website data.
  • PIL to create the final image from the fetched image data.

Creates and saves a 320 x 1024 PNG image named O.png in the current working directory, with all the favicons in the order specified. I will try to golf this down more over time as much as I can, but for now, I could not be happier. :)

Final Output Image:

Final

\$\endgroup\$
  • \$\begingroup\$ +1 because it doesn't have duplicated Stack Overflow favicons, unlike the JavaScript version \$\endgroup\$ – haykam Jul 30 '16 at 0:59
  • \$\begingroup\$ Got an error: SyntaxError: Unexpected keyword 'case'. Expected a closing '}' at the end of a block statement. \$\endgroup\$ – haykam Jul 30 '16 at 3:03
  • \$\begingroup\$ @Peanut It works perfectly fine for me with Python 3.5 on Mac. \$\endgroup\$ – R. Kap Jul 30 '16 at 3:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.