User:GreenC bot/Job 18/source

#!/usr/local/bin/gawk -bE     

#
# vebug - https://en.wikipedia.org/wiki/User:GreenC_bot/Job_18
#

# The MIT License (MIT)
#    
# Copyright (c) August 2019 User:GreenC (en.wikipedia.org)
#   
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR                   
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

BEGIN {
  BotName = "vebug"
}

@include "botwiki.awk"
@include "library.awk"
@include "json.awk"

BEGIN {

  Mode = "bot"   # set to "find" and it will search only and exit with a 1 (found something) or 0 (found nothing)
                 #  in "find" mode, run via 'project -s' to search local cache for articles containing actionable matches
                 # set to anything else and it will process the article.

  IGNORECASE = 1

  ReSpace = "[\n\r\t]*[ ]*[\n\r\t]*[ ]*[\n\r\t]*"
  ReSups = "[<]sup[^>]*[>][^<]+[<][/]sup[>]"

  delete citeTable

  Optind = Opterr = 1
  while ((C = getopt(ARGC, ARGV, "hs:l:n:")) != -1) {
      opts++
      if(C == "s")                 #  -s <file>      article.txt source to process.
        articlename = verifyval(Optarg)
      if(C == "l")                 #  -l <dir/>      Directory where logging is sent.. end with "/"
        logdir = verifyval(Optarg)
      if(C == "n")                 #  -n <name>      Wikipedia name of article
        wikiname = verifyval(Optarg)
      if(C == "h") {
        usage()
        exit
      }
  }

  if( ! opts || articlename == "" ) {
    stdErr("Error in vebug.awk (1)")
    print "0"
    exit
  }

  if(wikiname == "" || logdir == "")
    Logfile = "/dev/null"
  else {
    if(substr(logdir, length(logdir), 1) != "/")
      logdir = logdir "/"
    Logfile = logdir "logvebug"
  }

  # Path to data directory ends in "/"
  DataDir = articlename
  gsub(regesc3(basename(articlename)) "$", "", DataDir) 

  # Number of changes made to article
  Count = 0

  main()

}

#
# Run the program, save the page to disk
#
function main(  article,articlenew,articlenewname,editsummaryname,bn,plural) {

  checkexists(articlename, "vebug.awk main()", "exit")
  article = readfile(articlename)
  if(length(article) < 10) {
    print "0"
    exit
  }

  article = deflate(article)

  articlenew = vebug(article)

  if(article != articlenew && length(articlenew) > 10 && Count > 0) {

    articlenew = inflate(articlenew)

    articlenewname = DataDir "article.vebug.txt"
    printf("%s", articlenew) > articlenewname 
    close(articlenewname)

    editsummaryname = DataDir "editsummary.vebug.txt"
    if(Count > 1)
      plural = "s"
    printf("Restore %s cite" plural " deleted by a bug in VisualEditor ([[User:GreenC_bot/Job_18|vebug bot]])", Count) > editsummaryname  # Customize the edit summary to be more specific
    close(editsummaryname)

    print Count
    exit

  }
  print "0"
  exit

}

#
# vebug - parse page, load data into citeTable[][], modify page, return it to main()
#
function vebug(article,  c,field,sep,i,field2,sep2,j,d,k,dest,dest2,fieldi,field2txt,setdone,key,vertemp,re,foundit) {

  # Special case
  # Regularize <sup>[[User:Claudia Diaz2/sandbox#cite%20note-5|[6]]<nowiki>]</nowiki></sup>
  c = patsplit(article, field, /[|][[][^]]*[]]{2}[<]nowiki[>][]][<][/]nowiki[>]/, sep)  
  for(k = 1; k <= c; k++) {
    if(match(reverse(sep[k-1]), /[^[]+[[]{2}/, dest) ) {
      origcite = field[k]
      gsub(/<\/?nowiki>/, "", field[k])
      field[k] = reverse(dest[0]) field[k] 
      origTable[gsubi("^[<]nowiki[^>]*[>]|[<][/]nowiki[>]", "", field[k])] = reverse(dest[0]) origcite
      sep[k-1] = gsubs(reverse(dest[0]), "", sep[k-1])
    }
  }
  article = unpatsplit(field, sep)

  # Special case
  # Regularize [[Politics of Venezuela#cite%20note-19|<sup>[1</sup>]]
  c = patsplit(article, field, /[|][<]sup[>][[][0-9]*[<][/]sup[>][]]{2}/, sep)  
  for(k = 1; k <= c; k++) {
    if(match(reverse(sep[k-1]), /[^[]+[[]{2}/, dest) ) {
      origcite = field[k]
      sub(/[<]sup[>][[][^<]*[<][/]sup[>]/, "[]", field[k])
      field[k] = reverse(dest[0]) field[k] 
      origTable[gsubi("^[<]nowiki[^>]*[>]|[<][/]nowiki[>]", "", field[k])] = reverse(dest[0]) origcite
      sep[k-1] = gsubs(reverse(dest[0]), "", sep[k-1])
    }
  }
  article = unpatsplit(field, sep)

  # Special case
  # Regularize <sup>[[User:Claudia Diaz2/sandbox#cite%20note-5|<nowiki>6]</nowiki>]]</sup>
  c = patsplit(article, field, /[|][<]nowiki[>][^]]*[]]{1}[<][/]nowiki[>][]]{2}/, sep)  
  for(k = 1; k <= c; k++) {
    if(match(reverse(sep[k-1]), /[^[]+[[]{2}/, dest) ) {
      origcite = field[k]
      sub(/<nowiki>[^]]*[^]]/, "[", field[k])
      sub(/<\/nowiki>/, "", field[k])
      field[k] = reverse(dest[0]) field[k] 
      origTable[gsubi("^[<]nowiki[^>]*[>]|[<][/]nowiki[>]", "", field[k])] = reverse(dest[0]) origcite
      sep[k-1] = gsubs(reverse(dest[0]), "", sep[k-1])
    }
  }
  article = unpatsplit(field, sep)

  # Special case
  # Regularize [[2017 Women's March#cite%20note-FT%20100%2C000-13|<span>[13]</span>]]
  # works for <sup> or <span>
  split("sup span", tag, " ")
  for(k = 1; k <= 2; k++) {
    re = "[<]" tag[k] "[^>]*[>][[][0-9]+[]][<][/]" tag[k] "[>][]]{2}"
    c = patsplit(article, field, re, sep)
    for(i = 1; i <= c; i++) {
      if(match(reverse(sep[i-1]), /[|][^[]+[[]{2}/, dest) ) {    
        origcite = field[i]        
        re = "^[<]" tag[k] "[^>]*[>]|[<][/]" tag[k] "[>]"                              
        gsub(re, "", field[i])     
        field[i] = "<sup>" reverse(dest[0]) field[i] "</sup>"    
        origTable[gsubi("^[<]" tag[k] "[^>]*[>]|[<][/]" tag[k] "[>]","",field[i])] = reverse(dest[0]) origcite  
        sep[i-1] = gsubs(reverse(dest[0]), "", sep[i-1])         
      }
    }
    article = unpatsplit(field,sep)
  }

  # Special case
  # Convert cases not surrounded by <sup> or <span> 
  d = patsplit(article, field2, ReSups, sep2)           # Everything already surrounded by sup
  c = patsplit(reverse(article), field, /[]]{3}[0-9]{0,3}[[][|][0-9]{1,3}[-][^[]+[[]{2}/, sep)
  for(i = 1; i <= c; i++) {
    foundit = 0
    for(j = 1; j <= d; j++) {
      if(field2[j] ~ regesc3(reverse(field[i]))) {
        foundit = 1
      }
    }
    if( ! foundit) {
      origTable["<sup>" reverse(field[i]) "</sup>"] = reverse(field[i])
      field[i] = ">pus/<" field[i] ">pus<"
    }
  }
  article = reverse(unpatsplit(field, sep))

  # Standard: convert cites surrounded by <sup>..</sup>

  c = patsplit(article, field, ReSups, sep)
  for(i = 1; i <= c; i++) {

    if(field[i] !~ /[{][{][^{]*[{][{]/ && field[i] ~ /cite[%]20/) {  # skip embeded templates not found by deflate()

      sendlog(logdir "logsups", wikiname, field[i])

      # Encode embedded [0-9] so it can be parsed
      # <sup>[[Group of Eight#cite%20note-19|[19]]][[Group of Eight#cite%20note-20|[20]]]</sup> -->
      #   <sup>[[Group of Eight#cite%20note-19|VEBUGO19VEBUGC]][[Group of Eight#cite%20note-20|VEBUGO20VEBUGC]]</sup>
      fieldi = field[i]
      while(match(fieldi, "[[][0-9]+[]]", dest)) {
        if(match(dest[0], /[0-9]+/, dest2)) 
          field[i] = gsubs(dest[0], "VEBUGO" dest2[0] "VEBUGC", field[i])
        sub("[[][0-9]+[]]", "", fieldi)
      }

      # Populate citeTable[][]
      delete citeTable
      d = patsplit(field[i], field2, /[[]{2}[^]]+[]]{1,3}/, sep2)
      for(j = 1; j <= d; j++) {

        # Decoded
        field2txt = field2[j]
        field2txt = gsubs("VEBUGO", "[", field2txt)
        field2txt = gsubs("VEBUGC", "]", field2txt)
        citeTable[field2txt]["decoded"] = field2txt

        key = field2txt


        # Encoded
        citeTable[key]["encoded"] = field2[j]

        if( empty(origTable[key]))
          origTable[key] = key

        # Cite number
        getCiteNumbers(key)
        if(abort(key, "citenumber")) continue

        # Primary article
        getTitle(key, "artprimary")
        if(abort(key, "artprimary")) continue

        # Secondary article title eg. Group of Eight 
        getTitle(key, "artsecondary")
        if(abort(key, "artsecondary")) continue

        # Time/revision it was last added to primary article
        getPrimaryRevTime(key)
        if(abort(key, "artprimaryrevid")) continue
        if(abort(key, "artprimarytimestamp")) continue

        # Time/revision it existed in secondary article
        getSecondaryRevTime(key)
        if(abort(key, "artsecondaryrevid")) continue
        if(abort(key, "artsecondarytimestamp")) continue
        if(abort(key, "artsecondarywikitext")) continue

        setdone = 0  # if 1, ref is established early in process due to missing data
        settype = 0

        if(! empty(citeTable[key]["citenumbermismatch"])) {
          split(citeTable[key]["citenumbermismatch"], cites, /[|]/)
          vertemp = "{{verify source |date=" getDateToday() " |reason=This ref was deleted Special:Diff/" citeTable[key]["artprimaryrevid"] " by a bug in VisualEditor and later identified by a bot. The original cite can be found at Special:Permalink/" citeTable[key]["artsecondaryrevid"] " (or in a rev close to it) in either cite #" cites[1] " or cite #" cites[2] " - find and verify the cite and replace this template with it (1). [[User:GreenC_bot/Job_18]]}}"
          field2[j] = "<ref>Citation error. See inline comment how to fix. " vertemp "</ref>"
          settype = 1
          setdone = 1
        }

        # Get cite from secondary by its number
        if( ! setdone) {
          getSecondaryCiteByNumer(key)
          if( empty(citeTable[key]["citesecondaryplaintext"]) ) {
            vertemp = "{{verify source |date=" getDateToday() " |reason=This ref was deleted Special:Diff/" citeTable[key]["artprimaryrevid"] " by a bug in VisualEditor and later identified by a bot. The original cite can be found at Special:Permalink/" citeTable[key]["artsecondaryrevid"] " (or in a rev close to it) as cite #" citeTable[key]["citenumber"] " - find and verify the cite and replace this template with it (2). [[User:GreenC_bot/Job_18]]}}"
            field2[j] = "<ref>Citation error. See inline comment how to fix. " vertemp "</ref>"
            settype = 2
            setdone = 1

          }
        }

        # Upload page to wikipedia
        if( ! setdone) {
          if( generateCitationsPage(key) == 0) {
            vertemp = "{{verify source |date=" getDateToday() " |reason=This ref was deleted Special:Diff/" citeTable[key]["artprimaryrevid"] " by a bug in VisualEditor and later restored by a bot in plain-text form. The original cite can be found at Special:Permalink/" citeTable[key]["artsecondaryrevid"] " (or in a rev close to it) as cite #" citeTable[key]["citenumber"] " - find and verify the cite and replace this template with it (3). [[User:GreenC_bot/Job_18]]}}"
            field2[j] = "<ref>" citeTable[key]["citesecondaryplaintext"] " " vertemp "</ref>"
            settype = 3
            setdone = 1
          }
        }

        # Parse page
        if( ! setdone) {
          parseCitationsPage(key)
          if( empty(citeTable[key]["citesecondarywikicite"]) ) { 
            vertemp = "{{verify source |date=" getDateToday() " |reason=This ref was deleted Special:Diff/" citeTable[key]["artprimaryrevid"] " by a bug in VisualEditor and later restored by a bot in plain-text form. The original cite can be found at Special:Permalink/" citeTable[key]["artsecondaryrevid"] " (or in a rev close it it) as cite #" citeTable[key]["citenumber"] " - find and verify the cite and replace this template with it (4). [[User:GreenC_bot/Job_18]]}}"
            field2[j] = "<ref>" citeTable[key]["citesecondaryplaintext"] " " vertemp "</ref>"
            settype = 4
            setdone = 1
          }
        }

        if( ! setdone) {
          vertemp = "{{verify source |date=" getDateToday() " |reason=This ref was deleted Special:Diff/" citeTable[key]["artprimaryrevid"] " by a bug in VisualEditor and later restored by a bot from the original cite located at Special:Permalink/" citeTable[key]["artsecondaryrevid"] " cite #" citeTable[key]["citenumber"] " - verify the cite is accurate and delete this template. [[User:GreenC_bot/Job_18]]}}"
          field2[j] = "<ref>" citeTable[key]["citesecondarywikicite"] " " vertemp "</ref>"
          sendlog(logdir "logconvert", wikiname, key " ---- " citeTable[key]["citesecondarywikicite"] )
        }
        else {
          sendlog(logdir "logconvert", wikiname, key " ---- " settype )
        }

        Count++
        
      }

      field[i] = unpatsplit(field2,sep2)
      gsub(/^[<]sup[^>]*[>]|[<][/]sup[>]$/, "", field[i])

      # Save debuggung info to table.out in data directory
      saveTable()

    }
  } 

  article = unpatsplit(field, sep)

  # Check for missed sups 
  if(article ~ /[#]cite[%]20/ || article ~ /VEBUG[OC]/ ) {
    sendlog(logdir "logmissed", wikiname, "Contains cite%20 or VEBUGO . aborting")
    print article > DataDir "article.abort.txt"
    Count = 0         # Abort changes to article
  }

  return article

}


#
# Get citations numbers
#
#   Populates:
#    citeTable[key]["citenumber"]  (a single cite number best guess, usually the second number)
#    citeTable[key]["citenumbermismatch"]  (two cites numbers sep by "|" if different, otherwise blank)
#
function getCiteNumbers(key,  buf1,buf2,dest) {

        citeTable[key]["citenumber"] = ""
        citeTable[key]["citenumbermismatch"] = ""

        # -55|[note 1]]$  (get "note 1")
        if(match(reverse(citeTable[key]["decoded"]), /^[]]{2}[^[]*[[][|]/, dest)) {
          gsub(/^[]]{2,3}|[[][|]$/, "", dest[0])
          buf1 = reverse(dest[0])
          if(! empty(strip(buf1)))
            citeTable[key]["citenumber"] = buf1
        }

        # -55|[note 1]]$  (get "55")
        if(match(reverse(citeTable[key]["decoded"]), /[|][^-]*[^-]/, dest)) {
          gsub(/^[|]/, "", dest[0])
          if(! empty(strip(dest[0])))
            buf2 = reverse(dest[0])
        }

        if(! empty(citeTable[key]["citenumber"]) && ! empty(buf2)) {
          if(buf2 != citeTable[key]["citenumber"])
            citeTable[key]["citenumbermismatch"] = buf2 "|" citeTable[key]["citenumber"] 
        }
        if( ! isanumber(citeTable[key]["citenumber"]) && isanumber(buf2))
          citeTable[key]["citenumber"] = buf2

}
#
# Parse citations page User:GreenC/testcases/iabdebug
#
#   Populate citeTable[key]["citesecondarywikicite"]
#
function parseCitationsPage(key, begin,i,a,b,fp,np,c,d,e) {

  citeTable[key]["citesecondarywikicite"] = ""

  # Convert page to plain-text
  command = "w3m -dump -cols 10000 'https://en.wikipedia.org/wiki/User:GreenC/testcases/iabdebug'"
  fp = sys2var(command)

  # Extract core surrounded by "%%%%" and "^^^^"
  begin = 0
  for(i = 1; i <= splitn(fp, a, i); i++) {
    if(a[i] ~ /^[%]{4}$/) { 
      begin = 1
      continue
    }
    if(a[i] ~ /^[\\^]{4}$/) begin = 0
    if(begin) 
      np = np "\n" a[i]
  }

  # Extract records surrounded by "@@@@"
  c = split(np, b, /[@]{4}\n/)
  for(i = 1; i <= c; i++) {
    if(! empty(strip(b[i]))) {

      d = split(strip(b[i]), e, /[+]{4}\n/)

      e[1] = gsubs("^[dead link]", "", e[1])

      # print strip(e[1]) " = " citeTable[key]["citesecondaryplaintext"]

      if(strip(e[1]) == citeTable[key]["citesecondaryplaintext"]) {
        citeTable[key]["citesecondarywikicite"] = strip(e[2])
        break
      }
    }
  }
  

}

#
# Generate a list of citations in parsable format and upload to User:GreenC/testcases/iabdebug
#
function generateCitationsPage(key,  c,b,i,k,bp,np,status) {

  bp = "\n<p>\n"

  np = setdatetype(citeTable[key]["artsecondarywikitext"]) bp  # need to set date type so CS1|2 date display is consistent

  np = np "%%%%" bp
  c = split(citeTable[key]["artsecondarywikitext"], b, "<ref[^>]*>")
  for(i = 1; i <= c; i++) {
    k = strip(substr(b[i], 1, match(b[i], "</ref>") - 1))
    if( ! empty(k)) {
      np = np "@@@@" bp
      np = np k bp
      np = np "++++" bp
      np = np "<nowiki>" k "</nowiki>" bp
    }
  }  
  np = np "^^^^" bp  

  # Upload page
  for(i = 1; i <= 3; i++) {
    status = sys2varPipe(np, "wikiget -E " shquote("User:GreenC/testcases/iabdebug") " -S " shquote(citeTable[key]["artprimary"] " -/- " citeTable[key]["artsecondary"]) " -P STDIN") 
    if(status ~ "Success" || status ~ "No change") {
      sleep(5)
      return 1
    }
    else
      sleep(5)
  }

  sendlog(logdir "syslog", wikiname, "Error: Unable to upload to User page, wikiget returns: " status)

  return 0
 
}

#
# Get plain-text version of given cite number in secondary article
#  populates citeTable[key]["citesecondaryplaintext"]
#  on error set to blank
#
function getSecondaryCiteByNumer(key,  command,fp,citenum,a,i) {

  citeTable[key]["citesecondaryplaintext"] = ""

  # Plain text of secondary article
  command = "w3m -dump -cols 10000 " shquote("https://en.wikipedia.org/w/index.php?title=" urlencodeawk(citeTable[key]["artsecondary"]) "&oldid=" citeTable[key]["artsecondaryrevid"] )
  fp = sys2var(command)

  # Get the cite # in plain-text
  if( int(citeTable[key]["citenumber"]) < 10)
    citenum = "^[ ]" citeTable[key]["citenumber"] "[.][ ][\\^]"
  else
    citenum = "^" citeTable[key]["citenumber"] "[.][ ][\\^]"

  for(i = 1; i <= splitn(fp, a, i); i++) {
    if(a[i] ~ citenum) {
      sub(citenum, "", a[i])
      gsub(/[\\^][a-z]{1,3}[ ]/, "", a[i])

      a[i] = gsubs("^[dead link]", "", a[i])

      citeTable[key]["citesecondaryplaintext"] = strip(a[i])
      # break # keep going to get past any duplicates in 'notes' section .. this is imperfect though
    }
  }

}


#
# What time and revision was the bug added to the primary article? 
#  populates:
#    citeTable[key]["artsecondaryrevid"] 
#    citeTable[key]["artsecondarytimestamp"]
#    citeTable[key]["artsecondarywikitext"]
#  on error they are blank 
# 
function getSecondaryRevTime(key,  jsona,i,cont,unixprimary,command,j,arrevid,artimestamp,arcontinue,a,maxrevs,prevcontinue) {

  citeTable[key]["artsecondaryrevid"] = ""
  citeTable[key]["artsecondarytimestamp"] = ""
  citeTable[key]["artsecondarywikitext"] = ""

  if(! empty(citeTable[key]["artprimarytimestamp"]))
    unixPrimary = unixTime(citeTable[key]["artprimarytimestamp"])
  else
    return

  maxrevs = 20
  i       = 0
  cont    = 1

  while(cont) {
    i++
    if(i > maxrevs) { 
      sendlog(logdir "syslog", wikiname, "Error: Exceeded " maxrevs " API requests in getSecondaryRevTime()")
      break  # sanity break
    }

    if(empty(arcontinue["continue"]))
      command = "wget -q -O- " shquote("https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles=" urlencodeawk(citeTable[key]["artsecondary"]) "&rvlimit=50&rvslots=main&rvprop=timestamp%7Cuser%7Cids&rvdir=older&format=json") 
    else
      command = "wget -q -O- " shquote("https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles=" urlencodeawk(citeTable[key]["artsecondary"]) "&rvlimit=50&rvslots=main&rvprop=timestamp%7Cuser%7Cids&rvdir=older&format=json&rvcontinue=" urlencodeawk(arcontinue["continue"]) ) 

    if(query_json(sys2var(command), jsona) >= 0) {

      #debug
      #awkenough_dump(jsona, "jsona")

      # jsona["query","pages","1196634","revisions","1","revid"]=7741763
      splitja(jsona, arrevid, 5, "revid")
      # jsona["query","pages","1196634","revisions","1","timestamp"]=2004-11-22T05:21:18Z
      splitja(jsona, artimestamp, 5, "timestamp")
      # jsona["continue","rvcontinue"]=20041122055516|7769047
      splitja(jsona, arcontinue, 1, "rvcontinue")

      for(j = 1; j <= length(arrevid); j++) {
        if(unixTime(artimestamp[j]) <= unixPrimary) {
          if(unixTime(artimestamp[j]) == unixPrimary && citeTable[key]["artprimary"] == citeTable[key]["artsecondary"]) { # same article same diff get prev
            if(j == 50) break # size of block requested in API
            j++
          }
          delete a
          tup(getwikisource(citeTable[key]["artsecondary"], "dontfollow", "wikipedia.org", "en", arrevid[j]), a)
          if(a[1] != "REDIRECT" && ! empty(a[1])) {
            citeTable[key]["artsecondaryrevid"] = arrevid[j]
            citeTable[key]["artsecondarytimestamp"] = artimestamp[j]
            citeTable[key]["artsecondarywikitext"] = a[1]
            cont = 0
            break
          }
          else {
            if( empty(a[1]) && empty(a[2]) ) {  # revision is empty
              sendlog(logdir "syslog", wikiname, "Error: empty secondary revision (" citeTable[key]["artsecondary"] "): " artimestamp[j])
            }
            cont = 0
            break
          }
        }
      }
      prevcontinue = arcontinue["continue"]
    }
  }

}

#
# Get article title (follow redirects)
#
function getTitle(key1,key2,   dest,a) {

        citeTable[key1][key2] = ""

        if(key2 == "artsecondary") {
          if(match(key1, /^[[][^#]+[^#]/, dest) > 0) {
            sub(/^[[]{2}/, "", dest[0])
            tup(getwikisource(dest[0], "dontfollow", "wikipedia.org", "en"), a)
            if(a[1] == "REDIRECT") {
              gsub(/^#REDIRECT[ ]*[[]{2}|[]]$/, "", a[2])
              citeTable[key1][key2] = a[2]
            }
            else
              citeTable[key1][key2] = dest[0]
          }
        }
        else if(key2 = "artprimary") {
          tup(getwikisource(wikiname, "dontfollow", "wikipedia.org", "en"), a)
          if(a[1] == "REDIRECT") {
            gsub(/^#REDIRECT [[]{2}|[]]$/, "", a[2])
            citeTable[key1][key2] = a[2]
          }
          else
            citeTable[key1][key2] = wikiname
        }
}

#
# What time and revision was the bug added to the primary article? 
#  populates:
#    citeTable[key]["artprimaryrevid"] 
#    citeTable[key]["artprimarytimestamp"]
#  on error they are blank 
# 
function getPrimaryRevTime(key,  jsona,i,cont,command,arrevid,atimestamp,arcontinue,j,a,prevrevid,prevtimestamp,maxrevs,prevcontinue,jsonin) {

  citeTable[key]["artprimaryrevid"] = ""
  citeTable[key]["artprimarytimestamp"] = ""

  maxrevs = 20
  i       = 0
  cont    = 1

  while(cont) {
    i++
    if(i > maxrevs) { 
      sendlog(logdir "syslog", wikiname, "Error: Exceeded " maxrevs " API requests in getPrimaryRevTime()")
      break  # sanity break
    }

    if(empty(arcontinue["continue"]))
      command = "wget -q -O- " shquote("https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles=" urlencodeawk(citeTable[key]["artprimary"]) "&rvlimit=50&rvslots=main&rvprop=timestamp%7Cuser%7Cids&rvdir=older&format=json") 
    else
      command = "wget -q -O- " shquote("https://en.wikipedia.org/w/api.php?action=query&prop=revisions&titles=" urlencodeawk(citeTable[key]["artprimary"]) "&rvlimit=50&rvslots=main&rvprop=timestamp%7Cuser%7Cids&rvdir=older&format=json&rvcontinue=" urlencodeawk(arcontinue["continue"]) ) 

    jsonin = sys2var(command)

    if(query_json(jsonin, jsona) >= 0) {

      #debug
      #awkenough_dump(jsona, "jsona")


      # jsona["query","pages","1196634","revisions","1","revid"]=7741763
      splitja(jsona, arrevid, 5, "revid")
      # jsona["query","pages","1196634","revisions","1","timestamp"]=2004-11-22T05:21:18Z
      splitja(jsona, artimestamp, 5, "timestamp")
      # jsona["continue","rvcontinue"]=20041122055516|7769047
      splitja(jsona, arcontinue, 1, "rvcontinue")


      if(arcontinue["continue"] == prevcontinue) {  # reached last revision
        j = length(arrevid) 
        delete a
        tup(getwikisource(citeTable[key]["artprimary"], "dontfollow", "wikipedia.org", "en", arrevid[j]), a)
        if(a[1] != "REDIRECT" && ! empty(a[1])) {
          citeTable[key]["artprimaryrevid"] = arrevid[j]
          citeTable[key]["artprimarytimestamp"] = artimestamp[j]
          citeTable[key]["artprimarywikitext"] = a[1]
          cont = 0
          break
        }
        else {
          sendlog(logdir "syslog", wikiname, "Error: empty primary revision: " artimestamp[j])
          cont = 0
          break
        }
      }

      for(j = 1; j <= length(arrevid); j++) {    # step through each revision for this batch

        tup(getwikisource(citeTable[key]["artprimary"], "dontfollow", "wikipedia.org", "en", arrevid[j]), a)

        if(a[1] != "REDIRECT" && ! empty(a[1])) {
          # if(countsubstring(a[1], citeTable[key]["decoded"]) == 0) {
          if(countsubstring(a[1], origTable[key]) == 0) {
            if(! empty(prevrevid)) {
              citeTable[key]["artprimaryrevid"] = prevrevid
              citeTable[key]["artprimarytimestamp"] = prevtimestamp
            }
            else {
              citeTable[key]["artprimaryrevid"] = arrevid[j]
              citeTable[key]["artprimarytimestamp"] = artimestamp[j]
            }
            cont = 0
            break
          }
          else {
            prevrevid = arrevid[j]
            prevtimestamp = artimestamp[j]
          }
        }
        else {
          if( empty(a[1]) && empty(a[2]) ) {  # revision is empty
            if( citeTable[key]["artprimary"] != citeTable[key]["secondary"] ) {
              citeTable[key]["artprimaryrevid"] = arrevid[j]
              citeTable[key]["artprimarytimestamp"] = artimestamp[j]
            }
            else 
              sendlog(logdir "syslog", wikiname, "Error: empty primary revision: " artimestamp[j])
            cont = 0
            break
          }
        }
      }
      prevcontinue = arcontinue["continue"]
    }
  }
}

#
# Given a Wikipedia datestring, return unix-time string (seconds since 1970)
#
function unixTime(s) {
  return sys2var("date --date=" shquote(s) " +%s")
}

#
# Return todays date as "August 2019"
#
function getDateToday() {
  return sys2var("date +\"%B %Y\"")
}

#
# Determine article date type {{set dmy dates}} etc
#  imported from medilibrary.nim
#
function setdatetype(art,   reDmy,reMdy,i,a) {

  reDmy = "[{]{2}" ReSpace "use" ReSpace "dmy" ReSpace "d?a?t?e?s?|[{]{2}" ReSpace "dmy" ReSpace "[|]|[{]{2}" ReSpace "dmy" ReSpace "[}]|[{]{2}" "use[ -]?dmy"
  reMdy = "[{]{2}" ReSpace "use" ReSpace "mdy" ReSpace "d?a?t?e?s?|[{]{2}" ReSpace "mdy" ReSpace "[|]|[{]{2}" ReSpace "mdy" ReSpace "[}]|[{]{2}" "use[ -]?mdy"
  for(i = 1; i <= splitn(art, a, i); i++) {
    if(a[i] ~ reDmy)
      return "{{dmy}}"
    if(a[i] ~ reMdy)
      return "{{mdy}}"
  }
  return ""
}

#
# Abort check
#
function abort(key1, key2) {

        if( empty( strip(citeTable[key1][key2]) ) ) {

          if(key2 == "citesecondaryplaintext") {
            sendlog(logdir "logabort", wikiname, key1 " ---- " citeTable[key1]["citesecondaryplaintext"] " ---- " key2 " missing")
          }
          else {
            sendlog(logdir "logabort", wikiname, key1 " ---- " key2 " missing")
          }
          return 1
        }
        return 0
} 

#
# Reverse string
#
function reverse(s,   i,len,a,r) {
   len = split(s, a, "")
   for(i = 1; i <= len; i++) 
      r = a[i] r
   return r
}


function saveTable() {
      # Save debuggung info to table.out in data directory
      printtable = 1
      if(printtable) {
        for(kk in citeTable) {
          print "\n -------------- \n" >> DataDir "table.out"
          print "origTable[" kk "] = " origTable[kk] >> DataDir "table.out"
          for(ll in citeTable[kk]) {
            if(ll != "artsecondarywikitext")
              print "citeTable[" kk "][" ll "] = " citeTable[kk][ll] >> DataDir "table.out"
            else {
              print "citeTable[" kk "][" ll "] = " length(citeTable[kk][ll]) >> DataDir "table.out"
            }
          }
        }
      }

}