stackexchange-favs-to-pinboard

Hex Artifact Content
Login

Artifact f10669f8a12e371823cd82a5731228d0fe2657baa55db16b9f0564ce3d37f83f:


0000: 23 53 74 61 63 6b 45 78 63 68 61 6e 67 65 20 46  #StackExchange F
0010: 61 76 73 20 74 6f 20 50 69 6e 62 6f 61 72 64 0a  avs to Pinboard.
0020: 72 65 71 75 69 72 65 20 27 6f 70 65 6e 2d 75 72  require 'open-ur
0030: 69 27 0a 72 65 71 75 69 72 65 20 27 6a 73 6f 6e  i'.require 'json
0040: 27 0a 72 65 71 75 69 72 65 20 27 6f 70 74 70 61  '.require 'optpa
0050: 72 73 65 27 0a 72 65 71 75 69 72 65 20 27 63 67  rse'.require 'cg
0060: 69 27 0a 72 65 71 75 69 72 65 20 27 6c 6f 67 67  i'.require 'logg
0070: 65 72 27 0a 0a 0a 6f 70 74 70 61 72 73 65 20 3d  er'...optparse =
0080: 20 4f 70 74 69 6f 6e 50 61 72 73 65 72 2e 6e 65   OptionParser.ne
0090: 77 20 64 6f 20 7c 6f 70 74 73 7c 0a 09 6f 70 74  w do |opts|..opt
00a0: 73 2e 6f 6e 28 27 2d 69 27 2c 20 27 2d 2d 69 64  s.on('-i', '--id
00b0: 20 49 44 27 2c 20 22 53 74 61 63 6b 65 78 63 68   ID', "Stackexch
00c0: 61 6e 67 65 20 49 44 22 29 20 7b 20 7c 69 7c 20  ange ID") { |i| 
00d0: 53 74 61 63 6b 49 44 20 3d 20 69 20 7d 0a 09 6f  StackID = i }..o
00e0: 70 74 73 2e 6f 6e 28 27 2d 75 27 2c 20 27 2d 2d  pts.on('-u', '--
00f0: 75 73 65 72 20 55 53 45 52 27 2c 20 22 50 69 6e  user USER', "Pin
0100: 62 6f 61 72 64 20 75 73 65 72 6e 61 6d 65 22 29  board username")
0110: 20 7b 20 7c 75 7c 20 55 73 65 72 20 3d 20 75 20   { |u| User = u 
0120: 7d 0a 09 6f 70 74 73 2e 6f 6e 28 27 2d 74 27 2c  }..opts.on('-t',
0130: 20 27 2d 2d 74 6f 6b 65 6e 20 54 4f 4b 45 4e 27   '--token TOKEN'
0140: 2c 20 22 50 69 6e 62 6f 61 72 64 20 41 50 49 20  , "Pinboard API 
0150: 54 6f 6b 65 6e 22 29 20 7b 20 7c 74 7c 20 54 6f  Token") { |t| To
0160: 6b 65 6e 20 3d 20 74 20 7d 0a 65 6e 64 0a 6f 70  ken = t }.end.op
0170: 74 70 61 72 73 65 2e 70 61 72 73 65 21 0a 0a 0a  tparse.parse!...
0180: 64 65 66 20 67 65 74 5f 73 69 74 65 73 28 69 64  def get_sites(id
0190: 29 0a 09 72 65 73 70 6f 6e 73 65 20 3d 20 6f 70  )..response = op
01a0: 65 6e 28 22 68 74 74 70 73 3a 2f 2f 61 70 69 2e  en("https://api.
01b0: 73 74 61 63 6b 65 78 63 68 61 6e 67 65 2e 63 6f  stackexchange.co
01c0: 6d 2f 32 2e 32 2f 75 73 65 72 73 2f 23 7b 69 64  m/2.2/users/#{id
01d0: 7d 2f 61 73 73 6f 63 69 61 74 65 64 22 29 0a 09  }/associated")..
01e0: 70 61 72 73 65 64 20 3d 20 70 61 72 73 65 28 72  parsed = parse(r
01f0: 65 73 70 6f 6e 73 65 2e 72 65 61 64 29 0a 65 6e  esponse.read).en
0200: 64 0a 0a 0a 64 65 66 20 70 61 72 73 65 28 72 65  d...def parse(re
0210: 73 70 6f 6e 73 65 5f 73 74 72 69 6e 67 29 0a 09  sponse_string)..
0220: 23 53 65 65 6d 73 20 6c 69 6b 65 20 52 75 62 79  #Seems like Ruby
0230: 20 61 75 74 6f 6d 61 74 69 63 61 6c 6c 79 20 68   automatically h
0240: 61 6e 64 6c 65 73 20 67 7a 69 70 20 63 6f 6d 70  andles gzip comp
0250: 72 65 73 73 69 6f 6e 20 6e 6f 77 2e 20 4e 6f 20  ression now. No 
0260: 6e 65 65 64 20 74 6f 20 64 65 63 6f 6d 70 72 65  need to decompre
0270: 73 73 2e 0a 09 70 61 72 73 65 64 20 3d 20 4a 53  ss...parsed = JS
0280: 4f 4e 2e 70 61 72 73 65 28 72 65 73 70 6f 6e 73  ON.parse(respons
0290: 65 5f 73 74 72 69 6e 67 29 0a 65 6e 64 0a 0a 0a  e_string).end...
02a0: 64 65 66 20 67 65 74 5f 66 61 76 73 28 73 69 74  def get_favs(sit
02b0: 65 2c 20 69 64 29 0a 09 70 75 74 73 20 22 46 65  e, id)..puts "Fe
02c0: 74 63 68 69 6e 67 20 66 61 76 73 20 66 6f 72 20  tching favs for 
02d0: 23 7b 73 69 74 65 7d 22 0a 09 68 61 73 5f 6d 6f  #{site}"..has_mo
02e0: 72 65 20 3d 20 74 72 75 65 0a 09 69 74 65 6d 73  re = true..items
02f0: 20 3d 20 5b 5d 0a 09 70 61 67 65 20 3d 20 31 0a   = []..page = 1.
0300: 09 77 68 69 6c 65 20 68 61 73 5f 6d 6f 72 65 0a  .while has_more.
0310: 09 09 72 65 73 70 6f 6e 73 65 20 3d 20 6f 70 65  ..response = ope
0320: 6e 28 22 68 74 74 70 73 3a 2f 2f 61 70 69 2e 73  n("https://api.s
0330: 74 61 63 6b 65 78 63 68 61 6e 67 65 2e 63 6f 6d  tackexchange.com
0340: 2f 32 2e 32 2f 75 73 65 72 73 2f 23 7b 69 64 7d  /2.2/users/#{id}
0350: 2f 66 61 76 6f 72 69 74 65 73 3f 6f 72 64 65 72  /favorites?order
0360: 3d 64 65 73 63 26 73 6f 72 74 3d 61 64 64 65 64  =desc&sort=added
0370: 26 70 61 67 65 53 69 7a 65 3d 31 30 30 26 70 61  &pageSize=100&pa
0380: 67 65 3d 23 7b 70 61 67 65 7d 26 73 69 74 65 3d  ge=#{page}&site=
0390: 23 7b 73 69 74 65 7d 22 29 0a 09 09 23 55 73 65  #{site}")...#Use
03a0: 20 72 65 73 70 6f 6e 73 65 2e 72 65 61 64 20 6e   response.read n
03b0: 6f 74 20 2e 73 74 72 69 6e 67 20 61 73 20 73 6f  ot .string as so
03c0: 6d 65 20 72 65 74 75 72 6e 73 20 61 72 65 20 6c  me returns are l
03d0: 61 72 67 65 20 65 6e 6f 75 67 68 20 74 6f 20 67  arge enough to g
03e0: 6f 20 74 6f 20 61 20 74 65 6d 70 20 66 69 6c 65  o to a temp file
03f0: 0a 09 09 70 61 72 73 65 64 20 3d 20 70 61 72 73  ...parsed = pars
0400: 65 28 72 65 73 70 6f 6e 73 65 2e 72 65 61 64 29  e(response.read)
0410: 0a 09 09 69 74 65 6d 73 20 2b 3d 20 70 61 72 73  ...items += pars
0420: 65 64 5b 22 69 74 65 6d 73 22 5d 0a 09 09 68 61  ed["items"]...ha
0430: 73 5f 6d 6f 72 65 20 3d 20 70 61 72 73 65 64 5b  s_more = parsed[
0440: 22 68 61 73 5f 6d 6f 72 65 22 5d 0a 09 09 70 61  "has_more"]...pa
0450: 67 65 20 2b 3d 20 31 0a 09 65 6e 64 0a 09 70 75  ge += 1..end..pu
0460: 74 73 20 22 23 7b 69 74 65 6d 73 2e 6c 65 6e 67  ts "#{items.leng
0470: 74 68 7d 20 66 61 76 73 22 0a 09 69 74 65 6d 73  th} favs"..items
0480: 0a 65 6e 64 0a 0a 0a 63 6c 61 73 73 20 50 69 6e  .end...class Pin
0490: 62 6f 61 72 64 0a 09 40 40 6c 6f 67 67 65 72 20  board..@@logger 
04a0: 3d 20 4c 6f 67 67 65 72 2e 6e 65 77 28 53 54 44  = Logger.new(STD
04b0: 4f 55 54 29 0a 09 40 40 6c 6f 67 67 65 72 2e 6c  OUT)..@@logger.l
04c0: 65 76 65 6c 20 3d 20 4c 6f 67 67 65 72 3a 3a 49  evel = Logger::I
04d0: 4e 46 4f 0a 09 40 40 72 61 74 65 5f 6c 69 6d 69  NFO..@@rate_limi
04e0: 74 20 3d 20 33 0a 09 41 70 69 5f 75 72 6c 20 3d  t = 3..Api_url =
04f0: 20 22 68 74 74 70 73 3a 2f 2f 61 70 69 2e 70 69   "https://api.pi
0500: 6e 62 6f 61 72 64 2e 69 6e 2f 76 31 2f 22 0a 0a  nboard.in/v1/"..
0510: 0a 09 64 65 66 20 69 6e 69 74 69 61 6c 69 7a 65  ..def initialize
0520: 28 75 73 65 72 2c 20 74 6f 6b 65 6e 29 0a 09 09  (user, token)...
0530: 40 75 73 65 72 20 3d 20 75 73 65 72 0a 09 09 40  @user = user...@
0540: 74 6f 6b 65 6e 20 3d 20 74 6f 6b 65 6e 0a 09 65  token = token..e
0550: 6e 64 0a 0a 0a 09 64 65 66 20 61 64 64 28 75 72  nd....def add(ur
0560: 6c 2c 20 64 65 73 63 72 69 70 74 69 6f 6e 2c 20  l, description, 
0570: 65 78 74 65 6e 64 65 64 3d 6e 69 6c 2c 20 74 61  extended=nil, ta
0580: 67 73 3d 6e 69 6c 2c 20 72 65 70 6c 61 63 65 3d  gs=nil, replace=
0590: 22 6e 6f 22 29 0a 09 09 61 74 74 65 6d 70 74 73  "no")...attempts
05a0: 20 3d 20 31 0a 09 09 70 6f 73 74 65 64 20 3d 20   = 1...posted = 
05b0: 66 61 6c 73 65 0a 09 09 23 41 74 20 6d 69 6e 69  false...#At mini
05c0: 6d 75 6d 20 6d 75 73 74 20 68 61 76 65 20 75 72  mum must have ur
05d0: 6c 20 61 6e 64 20 64 65 73 63 72 69 70 74 69 6f  l and descriptio
05e0: 6e 0a 09 09 61 72 72 61 79 5f 70 61 72 61 6d 65  n...array_parame
05f0: 74 65 72 73 20 3d 20 22 26 75 72 6c 3d 23 7b 43  ters = "&url=#{C
0600: 47 49 2e 65 73 63 61 70 65 28 75 72 6c 29 7d 26  GI.escape(url)}&
0610: 64 65 73 63 72 69 70 74 69 6f 6e 3d 23 7b 43 47  description=#{CG
0620: 49 2e 65 73 63 61 70 65 28 64 65 73 63 72 69 70  I.escape(descrip
0630: 74 69 6f 6e 29 7d 22 0a 09 09 23 43 6f 75 6c 64  tion)}"...#Could
0640: 20 6c 6f 6f 70 20 74 68 72 6f 75 67 68 20 74 68   loop through th
0650: 65 20 62 65 6c 6f 77 0a 09 09 75 6e 6c 65 73 73  e below...unless
0660: 20 65 78 74 65 6e 64 65 64 2e 6e 69 6c 3f 0a 09   extended.nil?..
0670: 09 09 61 72 72 61 79 5f 70 61 72 61 6d 65 74 65  ..array_paramete
0680: 72 73 20 2b 3d 20 22 26 65 78 74 65 6e 64 65 64  rs += "&extended
0690: 3d 23 7b 43 47 49 2e 65 73 63 61 70 65 28 65 78  =#{CGI.escape(ex
06a0: 74 65 6e 64 65 64 29 7d 22 0a 09 09 65 6e 64 0a  tended)}"...end.
06b0: 09 09 75 6e 6c 65 73 73 20 74 61 67 73 2e 6e 69  ..unless tags.ni
06c0: 6c 3f 0a 09 09 09 23 54 4f 44 4f 3a 20 4e 65 65  l?....#TODO: Nee
06d0: 64 20 74 6f 20 63 68 65 63 6b 20 77 68 65 74 68  d to check wheth
06e0: 65 72 20 74 61 67 73 5f 65 73 63 61 70 65 64 20  er tags_escaped 
06f0: 77 69 6c 6c 20 77 6f 72 6b 0a 09 09 09 61 72 72  will work....arr
0700: 61 79 5f 70 61 72 61 6d 65 74 65 72 73 20 2b 3d  ay_parameters +=
0710: 20 22 26 74 61 67 73 3d 23 7b 43 47 49 2e 65 73   "&tags=#{CGI.es
0720: 63 61 70 65 28 74 61 67 73 29 7d 22 0a 09 09 65  cape(tags)}"...e
0730: 6e 64 0a 09 09 75 6e 74 69 6c 20 28 40 40 72 61  nd...until (@@ra
0740: 74 65 5f 6c 69 6d 69 74 20 3e 20 36 30 29 20 7c  te_limit > 60) |
0750: 20 28 61 74 74 65 6d 70 74 73 20 3e 20 33 29 20   (attempts > 3) 
0760: 7c 20 70 6f 73 74 65 64 20 0a 09 09 09 72 65 73  | posted ....res
0770: 70 6f 6e 73 65 20 3d 20 6f 70 65 6e 28 22 23 7b  ponse = open("#{
0780: 41 70 69 5f 75 72 6c 7d 70 6f 73 74 73 2f 61 64  Api_url}posts/ad
0790: 64 3f 61 75 74 68 5f 74 6f 6b 65 6e 3d 23 7b 40  d?auth_token=#{@
07a0: 75 73 65 72 7d 3a 23 7b 40 74 6f 6b 65 6e 7d 22  user}:#{@token}"
07b0: 2b 61 72 72 61 79 5f 70 61 72 61 6d 65 74 65 72  +array_parameter
07c0: 73 2b 22 26 72 65 70 6c 61 63 65 3d 23 7b 72 65  s+"&replace=#{re
07d0: 70 6c 61 63 65 7d 26 66 6f 72 6d 61 74 3d 6a 73  place}&format=js
07e0: 6f 6e 22 29 0a 09 09 09 40 40 6c 6f 67 67 65 72  on")....@@logger
07f0: 2e 64 65 62 75 67 28 72 65 73 70 6f 6e 73 65 2e  .debug(response.
0800: 73 74 72 69 6e 67 29 0a 09 09 09 40 40 6c 6f 67  string)....@@log
0810: 67 65 72 2e 64 65 62 75 67 28 72 65 73 70 6f 6e  ger.debug(respon
0820: 73 65 2e 73 74 61 74 75 73 29 0a 09 09 09 72 65  se.status)....re
0830: 73 70 6f 6e 73 65 5f 6a 73 6f 6e 20 3d 20 4a 53  sponse_json = JS
0840: 4f 4e 2e 70 61 72 73 65 28 72 65 73 70 6f 6e 73  ON.parse(respons
0850: 65 2e 73 74 72 69 6e 67 29 0a 09 09 09 69 66 20  e.string)....if 
0860: 28 72 65 73 70 6f 6e 73 65 2e 73 74 61 74 75 73  (response.status
0870: 5b 30 5d 20 3d 3d 20 22 32 30 30 22 29 20 26 20  [0] == "200") & 
0880: 28 72 65 73 70 6f 6e 73 65 5f 6a 73 6f 6e 5b 22  (response_json["
0890: 72 65 73 75 6c 74 5f 63 6f 64 65 22 5d 20 3d 3d  result_code"] ==
08a0: 20 22 64 6f 6e 65 22 29 0a 09 09 09 09 40 40 6c   "done").....@@l
08b0: 6f 67 67 65 72 2e 69 6e 66 6f 28 22 41 64 64 65  ogger.info("Adde
08c0: 64 20 23 7b 75 72 6c 7d 22 29 0a 09 09 09 09 70  d #{url}").....p
08d0: 6f 73 74 65 64 20 3d 20 74 72 75 65 0a 09 09 09  osted = true....
08e0: 65 6c 73 69 66 20 28 72 65 73 70 6f 6e 73 65 2e  elsif (response.
08f0: 73 74 61 74 75 73 5b 30 5d 20 3d 3d 20 22 32 30  status[0] == "20
0900: 30 22 29 20 26 20 28 72 65 73 70 6f 6e 73 65 5f  0") & (response_
0910: 6a 73 6f 6e 5b 22 72 65 73 75 6c 74 5f 63 6f 64  json["result_cod
0920: 65 22 5d 20 3d 3d 20 22 69 74 65 6d 20 61 6c 72  e"] == "item alr
0930: 65 61 64 79 20 65 78 69 73 74 73 22 29 0a 09 09  eady exists")...
0940: 09 09 40 40 6c 6f 67 67 65 72 2e 69 6e 66 6f 28  ..@@logger.info(
0950: 22 53 6b 69 70 70 69 6e 67 20 23 7b 75 72 6c 7d  "Skipping #{url}
0960: 2c 20 61 6c 72 65 61 64 79 20 65 78 69 73 74 73  , already exists
0970: 22 29 0a 09 09 09 09 70 6f 73 74 65 64 20 3d 20  ").....posted = 
0980: 74 72 75 65 0a 09 09 09 65 6c 73 69 66 20 72 65  true....elsif re
0990: 73 70 6f 6e 73 65 2e 73 74 61 74 75 73 5b 30 5d  sponse.status[0]
09a0: 20 3d 3d 20 22 34 32 39 22 0a 09 09 09 09 23 20   == "429".....# 
09b0: 34 32 39 20 54 6f 6f 20 4d 61 6e 79 20 52 65 71  429 Too Many Req
09c0: 75 65 73 74 73 2c 20 69 6e 63 72 65 61 73 65 20  uests, increase 
09d0: 72 61 74 65 20 6c 69 6d 69 74 0a 09 09 09 09 40  rate limit.....@
09e0: 40 72 61 74 65 5f 6c 69 6d 69 74 20 2a 3d 20 32  @rate_limit *= 2
09f0: 0a 09 09 09 09 40 40 6c 6f 67 67 65 72 2e 77 61  .....@@logger.wa
0a00: 72 6e 28 22 52 61 74 65 20 4c 69 6d 69 74 20 69  rn("Rate Limit i
0a10: 6e 63 72 65 61 73 65 64 20 74 6f 20 23 7b 24 72  ncreased to #{$r
0a20: 61 74 65 5f 6c 69 6d 69 74 7d 20 73 65 63 6f 6e  ate_limit} secon
0a30: 64 73 22 29 0a 09 09 09 65 6e 64 0a 09 09 09 61  ds")....end....a
0a40: 74 74 65 6d 70 74 73 20 2b 3d 20 31 0a 09 09 09  ttempts += 1....
0a50: 23 52 61 74 65 20 6c 69 6d 69 74 20 61 73 20 70  #Rate limit as p
0a60: 65 72 20 50 69 6e 62 6f 61 72 64 20 41 50 49 20  er Pinboard API 
0a70: 72 65 71 75 69 72 65 6d 65 6e 74 73 0a 09 09 09  requirements....
0a80: 73 6c 65 65 70 20 40 40 72 61 74 65 5f 6c 69 6d  sleep @@rate_lim
0a90: 69 74 0a 09 09 65 6e 64 0a 09 09 69 66 20 40 40  it...end...if @@
0aa0: 72 61 74 65 5f 6c 69 6d 69 74 20 3e 20 36 30 0a  rate_limit > 60.
0ab0: 09 09 09 40 40 6c 6f 67 67 65 72 2e 65 72 72 6f  ...@@logger.erro
0ac0: 72 28 22 52 61 74 65 20 6c 69 6d 69 74 20 68 61  r("Rate limit ha
0ad0: 73 20 65 78 63 65 65 64 65 64 20 36 30 20 73 65  s exceeded 60 se
0ae0: 63 73 2c 20 6c 65 74 27 73 20 74 72 79 20 61 67  cs, let's try ag
0af0: 61 69 6e 20 61 6e 6f 74 68 65 72 20 74 69 6d 65  ain another time
0b00: 22 29 0a 09 09 65 6c 73 69 66 20 61 74 74 65 6d  ")...elsif attem
0b10: 70 74 73 20 3e 20 33 0a 09 09 09 40 40 6c 6f 67  pts > 3....@@log
0b20: 67 65 72 2e 65 72 72 6f 72 28 22 46 61 69 6c 65  ger.error("Faile
0b30: 64 20 33 20 74 69 6d 65 73 20 74 6f 20 73 61 76  d 3 times to sav
0b40: 65 20 23 7b 75 72 6c 7d 2c 20 62 6f 6d 62 69 6e  e #{url}, bombin
0b50: 67 20 6f 75 74 22 29 0a 09 09 65 6e 64 0a 09 09  g out")...end...
0b60: 70 6f 73 74 65 64 0a 09 65 6e 64 0a 65 6e 64 0a  posted..end.end.
0b70: 0a 0a 69 66 20 64 65 66 69 6e 65 64 3f 28 53 74  ..if defined?(St
0b80: 61 63 6b 49 44 29 20 61 6e 64 20 64 65 66 69 6e  ackID) and defin
0b90: 65 64 3f 28 55 73 65 72 29 20 61 6e 64 20 64 65  ed?(User) and de
0ba0: 66 69 6e 65 64 3f 28 54 6f 6b 65 6e 29 0a 09 63  fined?(Token)..c
0bb0: 61 63 68 65 5f 66 69 6c 65 20 3d 20 45 4e 56 5b  ache_file = ENV[
0bc0: 22 48 4f 4d 45 22 5d 2b 22 2f 2e 73 74 61 63 6b  "HOME"]+"/.stack
0bd0: 65 78 63 68 61 6e 67 65 5f 66 61 76 73 5f 74 6f  exchange_favs_to
0be0: 5f 70 69 6e 62 6f 61 72 64 22 0a 09 70 62 20 3d  _pinboard"..pb =
0bf0: 20 50 69 6e 62 6f 61 72 64 2e 6e 65 77 28 55 73   Pinboard.new(Us
0c00: 65 72 2c 20 54 6f 6b 65 6e 29 0a 09 69 66 20 46  er, Token)..if F
0c10: 69 6c 65 2e 65 78 69 73 74 73 3f 28 63 61 63 68  ile.exists?(cach
0c20: 65 5f 66 69 6c 65 29 0a 09 09 63 61 63 68 65 20  e_file)...cache 
0c30: 3d 20 4a 53 4f 4e 2e 70 61 72 73 65 28 46 69 6c  = JSON.parse(Fil
0c40: 65 2e 72 65 61 64 28 63 61 63 68 65 5f 66 69 6c  e.read(cache_fil
0c50: 65 29 29 0a 09 65 6c 73 65 0a 09 09 63 61 63 68  e))..else...cach
0c60: 65 20 3d 20 7b 7d 0a 09 65 6e 64 0a 09 70 61 72  e = {}..end..par
0c70: 73 65 64 20 3d 20 67 65 74 5f 73 69 74 65 73 28  sed = get_sites(
0c80: 53 74 61 63 6b 49 44 29 0a 09 70 61 72 73 65 64  StackID)..parsed
0c90: 5b 22 69 74 65 6d 73 22 5d 2e 65 61 63 68 20 64  ["items"].each d
0ca0: 6f 20 7c 73 69 74 65 7c 0a 09 09 66 61 76 73 20  o |site|...favs 
0cb0: 3d 20 67 65 74 5f 66 61 76 73 28 73 69 74 65 5b  = get_favs(site[
0cc0: 22 73 69 74 65 5f 75 72 6c 22 5d 2e 73 75 62 28  "site_url"].sub(
0cd0: 22 68 74 74 70 3a 2f 2f 22 2c 20 22 22 29 2e 73  "http://", "").s
0ce0: 75 62 28 22 68 74 74 70 73 3a 2f 2f 22 2c 20 22  ub("https://", "
0cf0: 22 29 2e 73 75 62 28 22 2e 73 74 61 63 6b 65 78  ").sub(".stackex
0d00: 63 68 61 6e 67 65 22 2c 20 22 22 29 2e 73 75 62  change", "").sub
0d10: 28 22 2e 63 6f 6d 22 2c 20 22 22 29 2c 20 73 69  (".com", ""), si
0d20: 74 65 5b 22 75 73 65 72 5f 69 64 22 5d 29 0a 09  te["user_id"])..
0d30: 09 23 44 6f 6e 27 74 20 6d 61 6b 65 20 6d 6f 72  .#Don't make mor
0d40: 65 20 74 68 61 6e 20 33 30 20 72 65 71 75 65 73  e than 30 reques
0d50: 74 73 20 70 65 72 20 73 65 63 6f 6e 64 0a 09 09  ts per second...
0d60: 73 6c 65 65 70 20 28 31 2e 30 2f 33 30 29 0a 09  sleep (1.0/30)..
0d70: 09 66 61 76 73 2e 65 61 63 68 20 64 6f 20 7c 66  .favs.each do |f
0d80: 61 76 7c 0a 09 09 09 74 69 74 6c 65 20 3d 20 66  av|....title = f
0d90: 61 76 5b 22 74 69 74 6c 65 22 5d 0a 09 09 09 74  av["title"]....t
0da0: 61 67 73 20 3d 20 66 61 76 5b 22 74 61 67 73 22  ags = fav["tags"
0db0: 5d 0a 09 09 09 6c 69 6e 6b 20 3d 20 66 61 76 5b  ]....link = fav[
0dc0: 22 6c 69 6e 6b 22 5d 0a 09 09 09 23 43 68 65 63  "link"]....#Chec
0dd0: 6b 20 63 61 63 68 65 20 74 6f 20 73 65 65 20 69  k cache to see i
0de0: 66 20 61 6c 72 65 61 64 79 20 61 64 64 65 64 20  f already added 
0df0: 74 6f 20 70 69 6e 62 6f 61 72 64 0a 09 09 09 75  to pinboard....u
0e00: 6e 6c 65 73 73 20 63 61 63 68 65 2e 68 61 73 5f  nless cache.has_
0e10: 6b 65 79 3f 28 6c 69 6e 6b 29 0a 09 09 09 09 23  key?(link).....#
0e20: 4e 65 65 64 20 74 6f 20 75 6e 65 73 63 61 70 65  Need to unescape
0e30: 20 73 6f 20 63 61 6e 20 72 65 2d 65 73 63 61 70   so can re-escap
0e40: 65 20 69 6e 20 50 69 6e 62 6f 61 72 64 20 63 6f  e in Pinboard co
0e50: 64 65 0a 09 09 09 09 23 53 74 69 6c 6c 20 77 61  de.....#Still wa
0e60: 6e 74 20 6e 6f 20 64 65 66 61 75 6c 74 20 72 65  nt no default re
0e70: 70 6c 61 63 65 2c 20 6a 75 73 74 20 69 6e 20 63  place, just in c
0e80: 61 73 65 20 63 61 63 68 65 20 64 6f 65 73 6e 27  ase cache doesn'
0e90: 74 20 65 78 69 73 74 0a 09 09 09 09 70 62 2e 61  t exist.....pb.a
0ea0: 64 64 28 6c 69 6e 6b 2c 20 43 47 49 2e 75 6e 65  dd(link, CGI.une
0eb0: 73 63 61 70 65 5f 68 74 6d 6c 28 74 69 74 6c 65  scape_html(title
0ec0: 29 2c 20 6e 69 6c 2c 20 74 61 67 73 2e 6a 6f 69  ), nil, tags.joi
0ed0: 6e 28 22 2c 20 22 29 2b 22 2c 20 73 74 61 63 6b  n(", ")+", stack
0ee0: 65 78 63 68 61 6e 67 65 66 61 76 73 22 2c 20 22  exchangefavs", "
0ef0: 6e 6f 22 29 0a 09 09 09 09 23 41 64 64 20 74 6f  no").....#Add to
0f00: 20 63 61 63 68 65 2e 20 53 68 6f 75 6c 64 20 63   cache. Should c
0f10: 68 65 63 6b 20 66 6f 72 20 73 75 63 63 65 73 73  heck for success
0f20: 20 72 65 61 6c 6c 79 0a 09 09 09 09 63 61 63 68   really.....cach
0f30: 65 5b 6c 69 6e 6b 5d 20 3d 20 74 72 75 65 0a 09  e[link] = true..
0f40: 09 09 65 6e 64 0a 09 09 65 6e 64 0a 09 65 6e 64  ..end...end..end
0f50: 0a 09 23 57 72 69 74 65 20 6f 75 74 20 63 61 63  ..#Write out cac
0f60: 68 65 0a 09 46 69 6c 65 2e 6f 70 65 6e 28 63 61  he..File.open(ca
0f70: 63 68 65 5f 66 69 6c 65 2c 20 22 77 22 29 20 64  che_file, "w") d
0f80: 6f 20 7c 66 69 6c 65 7c 0a 09 09 66 69 6c 65 20  o |file|...file 
0f90: 3c 3c 20 63 61 63 68 65 2e 74 6f 5f 6a 73 6f 6e  << cache.to_json
0fa0: 0a 09 65 6e 64 0a 65 6e 64 0a                    ..end.end.