GuruHits

The Stack

The GuruHits engine was a single Apple HyperCard stack acting as a web CGI: each request arrived as an AppleEvent and HyperTalk scripts built the HTML reply. The recovered stack and its scripts are explored here.

GuruHits did not run on a normal web stack. Behind WebSTAR sat a single HyperCard stack acting as a CGI: every request to the quiz arrived as an AppleEvent, and HyperTalk built the reply. The window below is a faithful, CSS-simulated recreation of that control card — the actual "machine room." Click a button on the card, or pick an object from the list, to read its real recovered script in the script editor. The chrome uses the period fonts (Charcoal-style title bars, Geneva fields, Monaco code); nothing here executes — it is the source, preserved.

guruhits
Script of Stack script

The WebSTAR ACGI engine: each web request arrives as an AppleEvent and returns the HTTP response. (A sibling quiz on the same stack is anonymized as "OtherQuiz".)

-- GuruHits ACGI router — the recovered stack script, shown in full. A sibling quiz
-- that ran on the same stack has been anonymized (its names renamed to "OtherQuiz" /
-- gOther* / otherquiz.db); nothing is removed. Otherwise verbatim. © Joe Savelberg.

on openstack
  global HTTP_10_header,HTTP_ERR_header,gGuruHitsDB,gDBrange,gHighScore,gOtherDB,gOtherDBrange,gOtherHighScore
  if there is a menuitem "HyperCard.acgi" of menu "application" then domenu "Hypercard.acgi"
  put "HTTP/1.0 200 OK" & numtochar(13) & numtochar(10) & "Server: WebSTAR/4.5 ID/ACGI" ¬
  & numtochar(13) & numtochar(10) & "Host: www.guruhits.com" ¬
  & numtochar(13) & numtochar(10) & "MIME-Version: 1.0" & numtochar(13) & numtochar(10) ¬
  & "Content-type: text/html" & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) into HTTP_10_header
  put "HTTP/1.0 503 ERR" & numtochar(13) & numtochar(10) & "Server: WebSTAR/4.5 ID/ACGI" ¬
  & numtochar(13) & numtochar(10) & "Host: www.guruhits.com" ¬
  & numtochar(13) & numtochar(10) & "MIME-Version: 1.0" & numtochar(13) & numtochar(10) ¬
  & "Content-type: text/html" & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) into HTTP_ERR_header
  if there is a menuitem "WebSTAR" of menu "Application" then domenu "WebSTAR"
  put "" into gChecksums
  set hilite of bg btn id 2 to true
  send mouseUp to bg btn id 2
  hide msg
  LoadDocuments
end openstack

on AppleEvent class, ID, sender
  lock screen
  set lockRecent to true
  if there is a menuitem "HyperCard.acgi" of menu "application" then domenu "Hypercard.acgi"
  global FormInformation,customerTCPAddress,partner
  put "" into FormInformation
  put "" into customerTCPAddress
  put "" into partner
  put "" into msg
  put the ticks into starter
  if class & ID is "WWWΩsdoc" then
    request appleEvent "data"
    put it into whichForm
    request appleEvent data with keyword "post"
    if it ≠ "" then
      put it into FormInformation
    else if whichform ≠ "highscore" then
      put "HTTP/1.0 503 ERR" & numtochar(13) & numtochar(10) & "Server: WebSTAR/4.5 ID/ACGI" ¬
      & numtochar(13) & numtochar(10) &"MIME-Version: 1.0" & numtochar(13) & numtochar(10) ¬
      & "Content-type: text/html" & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) into HTTP_ERR_header
      put HTTP_ERR_Header & return & "<HTML><HEAD><TITLE>GuruHits Error!</TITLE></HEAD>" &¬
      "<BODY><H1>GuruHits Error!</H1><HR><H3>You didn't submit a correct GuruHits web form!" &¬
      "<BR>Please go back to the <A HREF=http://www.guruhits.com/>GuruHits site</A> and enter the quiz!</H3></BODY></HMTL>" into output
      reply output
    end if
    request appleEvent data with keyword "addr"
    put it into customerTCPAddress
    request appleEvent data with keyword "DIRE"
    put it into partner
    -- if "~otherquiz" is in partner then put "3690" into siteid
    -- else
    if "~demo" is in partner or "~otherquiz" is in partner then
      if "category=2" is in FormInformation then
        put "3690" into siteid
      else if "category=1" is in FormInformation then
        put "3019" into siteid
      else
        put "3019" into siteid
      end if
    else if "~guruhits" is in partner then put "3019" into siteid
    else put "3019" into siteid
    if whichForm = "quizresult" then
      send quizresult to cd id siteid
    else if whichForm = "quizscreen" then
      send quizscreen to cd id siteid
    else if whichForm = "scoring" then
      send savescore to cd id siteid
    else if whichForm = "Reload" then
      ReloadDocs
    else if whichForm = "QuitHC&<redacted>&DoQuit=ok" then
      quithc
      domenu "quit hypercard"

      -- start other-quiz scripts
    else if whichForm = "OtherQuiz" then
      -- go cd "OtherQuiz"
      OtherQuizForm -- FormInformation,customerTCPAddress
    else if whichForm = "OtherQuizShort" then
      -- go cd "OtherQuizShort"
      OtherQuizFormShort -- FormInformation,customerTCPAddress
    else if whichForm = "OtherQuizCalc" then
      -- go cd "OtherQuizCalc"
      OtherQuizFormCalc -- FormInformation,customerTCPAddress
      -- end other-quiz scripts

    end if
  else
    pass appleEvent
  end if
  if there is a menuitem "WebSTAR 4.5" of menu "Application" then domenu "WebSTAR 4.5"
  put the ticks - starter after msg
end AppleEvent


on LoadDocuments
  global gGuruHitsDB,gDBrange,gHighScore,gOtherDB,gOtherDBrange,gOtherHighScore
  -- load guruhits
  put "guruhits.db" into dbLoc
  open file dbLoc
  read from file dbLoc until eof
  put it into gGuruHitsDB
  close file dbLoc
  if last char of gGuruhitsDB is return then delete last char of gGuruHitsDB
  put number of lines of gGuruHitsDB into gDBrange
  set itemdelimiter to tab
  put item 8 of line 1 of cd fld id 7 of cd id 3019 into gHighScore
  --
  put "otherquiz.db" into dbLoc
  open file dbLoc
  read from file dbLoc until eof
  put it into gOtherDB
  close file dbLoc
  if last char of gOtherDB is return then delete last char of gOtherDB
  put number of lines of gOtherDB into gOtherDBrange
  set itemdelimiter to tab
  put item 8 of line 1 of cd fld id 7 of cd id 3690 into gOtherHighScore

  -- start other-quiz scripts
  global gOtherResults,gOtherResults2,gOtherResults3
  open file "otherresults.html"
  read from file "otherresults.html" until eof
  put it into gOtherResults
  close file "otherresults.html"

  open file "otherresults2.html"
  read from file "otherresults2.html" until eof
  put it into gOtherResults2
  close file "otherresults2.html"

  open file "otherresults3.html"
  read from file "otherresults3.html" until eof
  put it into gOtherResults3
  close file "otherresults3.html"
  -- end other-quiz scripts

end LoadDocuments

on ReloadDocs
  global HTTP_10_header,HTTP_ERR_header
  LoadDocuments
  put HTTP_10_Header & "<HTML><TITLE>GuruHits DB Reloaded</TITLE>" &¬
  "<BODY BGCOLOR=#FFFFFF>The GuruHits DB has been reloaded</BODY></HTML>" into OutPut
  reply OutPut
end ReloadDocs

on quithc
  global HTTP_10_header,HTTP_ERR_header
  put HTTP_10_Header & "<HTML><TITLE>GuruHits Closing Down</TITLE>" &¬
  "<BODY BGCOLOR=#FFFFFF>The GuruHits System has closed down.</BODY></HTML>" into OutPut
  reply OutPut
end quithc

function encrypt ref
  put scramble(ref,"GuruHits") into source
  put the length of source into sourcelen
  put "" into thedestiny
  repeat with x = 1 to sourcelen
    put converttoHEX(chartonum(char x of source)) after thedestiny
  end repeat
  set itemdelimiter to tab
  return(thedestiny)
end encrypt

function decrypt source
  repeat with x = length(source) down to 1
    if x mod 2 ≠ 0 then next repeat
    put " " after char x of source
  end repeat
  put length(source) into sourcelen
  put "" into thedestiny
  put 1 into x
  repeat
    if char x to x+1 of source = "0x" then
      add 2 to x
      next repeat
    end if
    if char x of source is in " x" then
      add 1 to x
      next repeat
    end if
    put char x of source into a
    if a is in "abcdef" then put convertHEXtoAB(a) into a
    put char x+1 of source into b
    if it="" then put 0 into b
    if b is in "abcdef" then put convertHEXtoAB(b) into b
    put numtochar(a*16+b) after thedestiny
    add 2 to x
    if x ≥ sourcelen then exit repeat
  end repeat
  put scramble(thedestiny,"GuruHits",true) into thedestiny
  set itemdelimiter to tab
  return(thedestiny)
end decrypt

function converttoHEX symbol
  set itemdelimiter to ","
  put symbol div 16   into a
  put symbol - (a*16) into b
  if a>9 or b>9 then
    put "10,11,12,13,14,15" into them -- not 16 since that's 0x10
    put "ABCDEF" into those
    repeat with x = 1 to 6
      get item x of them
      if a < it and b<it then exit repeat
      if a is it then put char x of those into a
      if b is it then put char x of those into b
    end repeat
  end if
  return (a & b)
end converttoHEX

function convertHEXtoAB a,b
  set itemdelimiter to ","
  put "10,11,12,13,14,15" into them -- not 16 since that's 0x10
  put "ABCDEF" into those
  repeat with x = 1 to 6
    if a is char x of those then return item x of them
  end repeat
end convertHEXtoAB

function webdecode source
  if char 1 of source = "+" or source = "%20" then
    return (" ")
    exit webdecode
  end if
  if char 1 of source = "%" then delete char 1 of source
  repeat with x = length(source) down to 1
    if x mod 2 ≠ 0 then next repeat
    put " " after char x of source
  end repeat
  put length(source) into sourcelen
  put "" into thedestiny
  put 1 into x
  repeat
    if char x to x+1 of source = "0x" then
      add 2 to x
      next repeat
    end if
    if char x of source is in " x" then
      add 1 to x
      next repeat
    end if
    put char x of source into a
    if a is in "abcdef" then put convertHEXtoAB(a) into a
    put char x+1 of source into b
    if it="" then put 0 into b
    if b is in "abcdef" then put convertHEXtoAB(b) into b
    put numtochar(a*16+b) after thedestiny
    add 2 to x
    if x ≥ sourcelen then exit repeat
  end repeat
  set itemdelimiter to tab
  return(thedestiny)
end webdecode

function webencode source,form
  if form = "web" then
    put "&#" & chartonum(source) & ";" into thedestiny
    return(thedestiny)
  else if form = "url" then
    put the length of source into sourcelen
    put "" into thedestiny
    repeat with x = 1 to sourcelen
      put converttoHEX(chartonum(char x of source)) after thedestiny
    end repeat
    set itemdelimiter to tab
    put "%" & thedestiny into thedestiny
    return(thedestiny)
  end if
end webencode

on createEmail output,fromaddress,quizname,quizurl
  set itemdelimiter to tab
  put "DATA:WebSTAR:NetCloak Files:NetCloak Mail Queue:" into mailqueue
  put  item 4 of output into email
  get offset("&#64;",email)
  if it ≠ 0 then put "@" into char it to it+4 of email
  put "Server: relay.example.net" & return & "To:" && email & return & "From:" && fromaddress & return & "CC:" & return & "Subject: Your Certificate for playing" && quizname & return & "Message:" & return into outmsg
  put quizname && "- Certificate" & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) & item 5 of output & ", you participated in the" && quizname && "and achieved a score of" && item 8 of output && "points with an accuracy of" && item 10 of output && "percent attaining the status of a" && item 11 of output && "at quiz level" && item 13 of output & "." & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) & "If you'd like to play the" && quizname && "again, then please go to" && quizurl & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) & "Don't forget to forward your quiz results to your family and friends." & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) & "Have fun!" & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) & "Joe Savelberg" & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) & "QuizMaster@guruhits.com" & numtochar(13) & numtochar(10) & "http://www.guruhits.com" & numtochar(13) & numtochar(10) & numtochar(13) & numtochar(10) & "--------------------------------------------" & numtochar(13) & numtochar(10) & "X-URL:" && quizurl & numtochar(13) & numtochar(10) & "X-Originating-IP: [" & item 3 of output & "]" & numtochar(13) & numtochar(10) & "X-Address:" && email & numtochar(13) & numtochar(10) after outmsg
  put mailqueue & the seconds & "-" & the ticks into mailfile
  open file mailfile
  write outmsg to file mailfile
  close file mailfile
end createEmail

on otherQuizForm -- FormInformation --,customerTCPAddress
  global FormInformation,customerTCPAddress
  go cd "OtherQuiz"
  send otherQuizForm to cd "OtherQuiz"
end otherQuizForm

on otherQuizFormShort -- FormInformation --,customerTCPAddress
  global FormInformation,customerTCPAddress
  go cd "OtherQuizShort"
  send otherQuizForm to cd "OtherQuizShort"
end otherQuizFormShort

on otherQuizFormCalc -- FormInformation --,customerTCPAddress
  global FormInformation,customerTCPAddress
  go cd "OtherQuizCalc"
  send otherQuizForm to cd "OtherQuizCalc"
end otherQuizFormCalc


-- the score record written for each play (item-delimited):
-- 1. the short date
-- 2. the long time
-- 3. CustomerTCPAddress
-- 4. email
-- 5. firstname
-- 6. lastname
-- 7. player
-- 8. score
-- 9. quiznb
-- 10. mypercentage
-- 11. mystatus
-- 12. checksum
-- 13. level
-- 14. category into output
Script of Card script

The quiz engine — quizscreen builds a quiz from the song database and renders the page HTML.

on quizscreen
  global gGuruHitsDB,gDBrange,FormInformation,CustomerTCPAddress,partner,gHighScore
  
  put "" into songline
  put "" into level
  put "" into category
  
  set itemdelimiter to "&"
  repeat with x = 1 to number of items of FormInformation
    set itemdelimiter to "&"
    get item x of FormInformation
    if "songline" is in it then
      set itemdelimiter to "="
      put decrypt(last item of it) into songline
      next repeat
    end if
    if "category" is in it then
      set itemdelimiter to "="
      put last item of it into category
      next repeat
    end if
    if "level" is in it then
      set itemdelimiter to "="
      put last item of it into level
      next repeat
    end if
  end repeat
  
  if category = "" then put "1" into category
  if level = "" then put "1" into level
  
  set itemdelimiter to "*"
  if item 1 of songline = "0000" and item 2 of songline = "0000" and item 3 of songline = "0000" and item 4 of songline = "6823154" then
    put 0 into score
    put 1 into quiznb
    put 0 into goodanswers
  else
    put item 5 of songline into score
    put item 6 of songline + 1 into quiznb
    put item 7 of songline into goodanswers
  end if
  
  put "<SET_LOCAL score = " & quote & score & quote & ">" & return after output
  put "<SET_LOCAL quiznb = " & quote & quiznb & quote & ">" & return after output
  put "<SET_LOCAL category = " & quote & category & quote & ">" & return after output
  put "<SET_LOCAL level = " & quote & level & quote & ">" & return after output
  put "<SET_LOCAL goodanswers = " & quote & goodanswers & quote & ">" & return after output
  put "<SET_LOCAL topscore = " & quote & gHighScore & quote & ">" & return after output
  
  set itemdelimiter to tab
  
  put false into test
  put "" into randomlist
  if level < 3 then
    repeat until test
      get random(gDBrange)
      if it is in randomlist then next repeat
      put it & return after randomlist
      if number of lines of randomlist = 3 then put true into test
    end repeat
  else
    repeat until test
      get random(gDBrange)
      if it is in randomlist then next repeat
      put false into test2
      repeat with x = 1 to the number of lines of randomlist
        if item 2 of line it of gGuruHitsDB = item 2 of line (line x of randomlist) of gGuruHitsDB then
          put true into test2
          exit repeat
        end if
      end repeat
      if test2 then next repeat
      put it & return after randomlist
      if number of lines of randomlist = 3 then put true into test
    end repeat
  end if
  
  put line (line 1 of randomlist) of gGuruHitsDB into Choose1
  put line (line 2 of randomlist) of gGuruHitsDB into Choose2
  put line (line 3 of randomlist) of gGuruHitsDB into Choose3
  
  if level > 1 then
    put "00000000.gif" into item 5 of Choose1
    put "00000000.gif" into item 5 of Choose2
    put "00000000.gif" into item 5 of Choose3
    put "http://www.amazon.com/exec/obidos/redirect?tag=theoriginalfreei&path=subst/home/music.html" into item 4 of Choose1
    put "http://www.amazon.com/exec/obidos/redirect?tag=theoriginalfreei&path=subst/home/music.html" into item 4 of Choose2
    put "http://www.amazon.com/exec/obidos/redirect?tag=theoriginalfreei&path=subst/home/music.html" into item 4 of Choose3
    if level = 2 then
      put "" into item 2 of Choose1
      put "" into item 2 of Choose2
      put "" into item 2 of Choose3
    else
      put "" into item 3 of Choose1
      put "" into item 3 of Choose2
      put "" into item 3 of Choose3
    end if
  end if
  
  put item 1 of line (line random(3) of randomlist) of gGuruHitsDB into Song2Play
  put "<SET_LOCAL playsong = " & quote & Song2Play & quote & ">" & return after output
  put "<SET_LOCAL songline = " & quote & encrypt(line 1 of randomlist & "*" & line 2 of randomlist & "*" & line 3 of randomlist & "*" & the ticks & "*" & score & "*" & quiznb & "*" & goodanswers) & quote & ">" & return after output
  
  put "<SET_LOCAL ref1 = " & quote & encrypt(item 1 of Choose1) & quote & ">" & return after output
  put "<SET_LOCAL artist1 = " & quote & item 2 of Choose1 & quote & ">" & return after output
  put "<SET_LOCAL song1 = " & quote & item 3 of Choose1 & quote & ">" & return after output
  put "<SET_LOCAL url1 = " & quote & item 4 of Choose1 & quote & ">" & return after output
  put "<SET_LOCAL cover1 = " & quote & item 5 of Choose1 & quote & ">" & return after output
  
  put "<SET_LOCAL ref2 = " & quote & encrypt(item 1 of Choose2) & quote & ">" & return after output
  put "<SET_LOCAL artist2 = " & quote & item 2 of Choose2 & quote & ">" & return after output
  put "<SET_LOCAL song2 = " & quote & item 3 of Choose2 & quote & ">" & return after output
  put "<SET_LOCAL url2 = " & quote & item 4 of Choose2 & quote & ">" & return after output
  put "<SET_LOCAL cover2 = " & quote & item 5 of Choose2 & quote & ">" & return after output
  
  
  put "<SET_LOCAL ref3 = " & quote & encrypt(item 1 of Choose3) & quote & ">" & return after output
  put "<SET_LOCAL artist3 = " & quote & item 2 of Choose3 & quote & ">" & return after output
  put "<SET_LOCAL song3 = " & quote & item 3 of Choose3 & quote & ">" & return after output
  put "<SET_LOCAL url3 = " & quote & item 4 of Choose3 & quote & ">" & return after output
  put "<SET_LOCAL cover3 = " & quote & item 5 of Choose3 & quote & ">" & return after output
  
  if score > 9999 * level then put "<SET_LOCAL mystatus = " & quote & "Guru" & quote & ">" & return after output
  else if score > 4999 * level then put "<SET_LOCAL mystatus = " & quote & "Vice Guru" & quote & ">" & return after output
  else if score > 2499 * level then put "<SET_LOCAL mystatus = " & quote & "Senior Guru" & quote & ">" & return after output
  else if score > 999 * level then put "<SET_LOCAL mystatus = " & quote & "Junior Guru" & quote & ">" & return after output
  else if score > 499 * level then put "<SET_LOCAL mystatus = " & quote & "Music Buff" & quote & ">" & return after output
  else if score > 199 * level then put "<SET_LOCAL mystatus = " & quote & "Follower" & quote & ">" & return after output
  else if score > 99 * level then put "<SET_LOCAL mystatus = " & quote & "Fan" & quote & ">" & return after output
  else
    put "<SET_LOCAL mystatus = " & quote & "Novice" & quote & ">" & return after output
  end if
  
  reply(output)
end quizscreen

on quizresult
  global gGuruHitsDB,gDBrange,gChecksums,FormInformation,CustomerTCPAddress,partner,gHighScore
  
  put "" into Song2Play
  put "" into songline
  put "" into theanswer
  put "" into level
  put "" into category
  set itemdelimiter to "&"
  repeat with x = 1 to number of items of FormInformation
    set itemdelimiter to "&"
    get item x of FormInformation
    
    if "playsong" is in it then
      set itemdelimiter to "="
      put last item of it into Song2Play
      next repeat
    end if
    
    if "songline" is in it then
      set itemdelimiter to "="
      put decrypt(last item of it) into songline
      next repeat
    end if
    
    if "answer" is in it then
      set itemdelimiter to "="
      put decrypt(last item of it) into theanswer
      next repeat
    end if
    
    if "category" is in it then
      set itemdelimiter to "="
      put last item of it into category
      next repeat
    end if
    
    if "level" is in it then
      set itemdelimiter to "="
      put last item of it into level
      next repeat
    end if
    
  end repeat
  
  if category = "" then put "1" into category
  if level = "" then put "1" into level
  
  set itemdelimiter to "*"
  put line (item 1 of songline) of gGuruHitsDB into Choose1
  put line (item 2 of songline) of gGuruHitsDB into Choose2
  put line (item 3 of songline) of gGuruHitsDB into Choose3
  put item 4 of songline into checksum
  put item 5 of songline into score
  put item 6 of songline into quiznb
  put item 7 of songline into goodanswers
  
  if offset(checksum,gChecksums) ≠ 0 then
    get "<REDIRECT " & quote & "http://www.guruhits.com/login.html" & quote & ">"
    reply (it)
    exit quizresult
  else
    put return & checksum after gChecksums
    if number of lines of gChecksums > 2000 then delete line 1 - 1000 of gChecksums
  end if
  
  set itemdelimiter to tab
  
  if theanswer = Song2play then
    put "<SET_LOCAL answer = " & quote & "correct" & quote & ">" & return after output
    put score + (10 * level) into score
    put goodanswers + 1 into goodanswers
  else
    put "<SET_LOCAL answer = " & quote & "wrong" & quote & ">" & return after output
    if level = "4" then put score - 40 into score
  end if
  
  put "<SET_LOCAL score = " & quote & score & quote & ">" & return after output
  put "<SET_LOCAL quiznb = " & quote & quiznb & quote & ">" & return after output
  put "<SET_LOCAL songline = " & quote & encrypt("0000*0000*0000*" & the ticks & "*" & score & "*" & quiznb & "*" & goodanswers) & quote & ">" & return after output
  put "<SET_LOCAL level = " & quote & level & quote & ">" & return after output
  put "<SET_LOCAL goodanswers = " & quote & goodanswers & quote & ">" & return after output
  put "<SET_LOCAL topscore = " & quote & gHighScore & quote & ">" & return after output
  
  
  if Song2Play = item 1 of Choose1 then
    
    put "<SET_LOCAL artist1 = " & quote & item 2 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL song1 = " & quote & item 3 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL url1 = " & quote & item 4 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL cover1 = " & quote & item 5 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL audio1 = " & quote & item 6 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL playsong = " & quote & item 1 of Choose1 & quote & ">" & return after output
    
    put "<SET_LOCAL artist2 = " & quote & item 2 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL song2 = " & quote & item 3 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL url2 = " & quote & item 4 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL cover2 = " & quote & item 5 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL audio2 = " & quote & item 6 of Choose2 & quote & ">" & return after output
    
    
    put "<SET_LOCAL artist3 = " & quote & item 2 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL song3 = " & quote & item 3 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL url3 = " & quote & item 4 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL cover3 = " & quote & item 5 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL audio3 = " & quote & item 6 of Choose3 & quote & ">" & return after output
    
  else if Song2Play = item 1 of Choose2 then
    
    put "<SET_LOCAL artist1 = " & quote & item 2 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL song1 = " & quote & item 3 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL url1 = " & quote & item 4 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL cover1 = " & quote & item 5 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL audio1 = " & quote & item 6 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL playsong = " & quote & item 1 of Choose2 & quote & ">" & return after output
    
    put "<SET_LOCAL artist2 = " & quote & item 2 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL song2 = " & quote & item 3 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL url2 = " & quote & item 4 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL cover2 = " & quote & item 5 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL audio2 = " & quote & item 6 of Choose1 & quote & ">" & return after output
    
    
    put "<SET_LOCAL artist3 = " & quote & item 2 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL song3 = " & quote & item 3 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL url3 = " & quote & item 4 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL cover3 = " & quote & item 5 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL audio3 = " & quote & item 6 of Choose3 & quote & ">" & return after output
    
    
  else if Song2Play = item 1 of Choose3 then
    
    put "<SET_LOCAL artist1 = " & quote & item 2 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL song1 = " & quote & item 3 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL url1 = " & quote & item 4 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL cover1 = " & quote & item 5 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL audio1 = " & quote & item 6 of Choose3 & quote & ">" & return after output
    put "<SET_LOCAL playsong = " & quote & item 1 of Choose3 & quote & ">" & return after output
    
    put "<SET_LOCAL artist2 = " & quote & item 2 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL song2 = " & quote & item 3 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL url2 = " & quote & item 4 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL cover2 = " & quote & item 5 of Choose1 & quote & ">" & return after output
    put "<SET_LOCAL audio2 = " & quote & item 6 of Choose1 & quote & ">" & return after output
    
    put "<SET_LOCAL artist3 = " & quote & item 2 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL song3 = " & quote & item 3 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL url3 = " & quote & item 4 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL cover3 = " & quote & item 5 of Choose2 & quote & ">" & return after output
    put "<SET_LOCAL audio3 = " & quote & item 6 of Choose2 & quote & ">" & return after output
    
  end if
  
  if score > 9999 * level then put "<SET_LOCAL mystatus = " & quote & "Guru" & quote & ">" & return after output
  else if score > 4999 * level then put "<SET_LOCAL mystatus = " & quote & "Vice Guru" & quote & ">" & return after output
  else if score > 2499 * level then put "<SET_LOCAL mystatus = " & quote & "Senior Guru" & quote & ">" & return after output
  else if score > 999 * level then put "<SET_LOCAL mystatus = " & quote & "Junior Guru" & quote & ">" & return after output
  else if score > 499 * level then put "<SET_LOCAL mystatus = " & quote & "Music Buff" & quote & ">" & return after output
  else if score > 199 * level then put "<SET_LOCAL mystatus = " & quote & "Follower" & quote & ">" & return after output
  else if score > 99 * level then put "<SET_LOCAL mystatus = " & quote & "Fan" & quote & ">" & return after output
  else
    put "<SET_LOCAL mystatus = " & quote & "Novice" & quote & ">" & return after output
  end if
  
  reply(output)
  
end quizresult

on savescore
  global gGuruHitsDB,gDBrange,FormInformation,CustomerTCPAddress,partner,gHighScore
  put partner & ":guruhits.score" into scorefile
  put cd fld "highscore" into temp
  
  put "" into firstname
  put "" into lastname
  put "" into email
  put "" into player
  put "" into songline
  put "" into level
  put "" into category
  set itemdelimiter to "&"
  repeat with x = 1 to number of items of FormInformation
    set itemdelimiter to "&"
    get item x of FormInformation
    
    if "firstname" is in it then
      set itemdelimiter to "="
      put casechange(last item of it,"c") into firstname
      repeat until offset("+",firstname) = 0
        put " " into char offset("+",firstname) of firstname
      end repeat
      repeat until offset("%",firstname) = 0
        get offset("%",firstname)
        put webencode(webdecode(char it to it+2 of firstname),"web") into char it to it+2 of firstname
      end repeat
      next repeat
    end if
    
    if "lastname" is in it then
      set itemdelimiter to "="
      put casechange(last item of it,"c") into lastname
      repeat until offset("+",lastname) = 0
        put " " into char offset("+",lastname) of lastname
      end repeat
      repeat until offset("%",lastname) = 0
        get offset("%",lastname)
        put webencode(webdecode(char it to it+2 of lastname),"web") into char it to it+2 of lastname
      end repeat
      next repeat
    end if
    
    if "email" is in it then
      set itemdelimiter to "="
      put casechange(last item of it,"l") into email
      repeat until offset("%",email) = 0
        get offset("%",email)
        put webencode(webdecode(char it to it+2 of email),"web") into char it to it+2 of email
      end repeat
      next repeat
    end if
    
    if "player" is in it then
      set itemdelimiter to "="
      put last item of it into player
      next repeat
    end if
    
    if "songline" is in it then
      set itemdelimiter to "="
      put decrypt(last item of it) into songline
      next repeat
    end if
    
    if "category" is in it then
      set itemdelimiter to "="
      put last item of it into category
      next repeat
    end if
    
    if "level" is in it then
      set itemdelimiter to "="
      put last item of it into level
      next repeat
    end if
    
  end repeat
  
  if firstname = "" and lastname = "" and player = "" then
    reply ("<P>Your score was already saved!</P>")
    exit savescore
  end if
  
  if category = "" then put "1" into category
  if level = "" then put "1" into level
  
  set itemdelimiter to "*"
  put item 4 of songline into checksum
  put item 5 of songline into score
  put item 6 of songline into quiznb
  put item 7 of songline into goodanswers
  
  if checksum is in temp then
    reply ("<P>Your score was already saved!</P>")
    exit savescore
  end if
  
  put the numberformat into savenbformat
  set the numberformat to 0.##
  put goodanswers / quiznb * 100 into mypercentage
  
  if score > 9999 * level then put "Guru" into mystatus
  else if score > 4999 * level then put "Vice Guru" into mystatus
  else if score > 2499 * level then put "Senior Guru" into mystatus
  else if score > 999 * level then put "Junior Guru" into mystatus
  else if score > 499 * level then put "Music Buff" into mystatus
  else if score > 199 * level then put "Follower" into mystatus
  else if score > 99 * level then put "Fan" into mystatus
  else
    put "Novice" into mystatus
  end if
  set itemdelimiter to tab
  
  put tab into what
  put return & the short date & what & the long time & what & CustomerTCPAddress & what & email & what¬
  & firstname & what & lastname & what & player & what & score & what & quiznb & what & mypercentage & what¬
  & mystatus & what & checksum & what & level & what & category into output
  
  put temp & return & output into temp
  
  put FullSort(temp,"t=n","d=d","c=item 9") into temp
  put FullSort(temp,"t=n","d=d","c=item 10") into temp
  put FullSort(temp,"t=n","d=d","c=item 8") into temp
  
  -- put FullSort(temp,"t=n","d=d","c=item 8") into temp
  if return is last char of temp then delete last char of temp
  if number of lines of temp > 100 then delete line 101 to (number of lines of temp) of temp
  put temp into cd fld "highscore"
  put item 8 of line 1 of temp into gHighScore
  
  open file scorefile
  write output to file scorefile at eof
  close file scorefile
  set the numberformat to savenbformat
  
  highscore category
  
  if email ≠ "" then
    if "@" is in email or "&#64;" is in email then
      createEmail output,"webmaster@guruhits.com (Joe Savelberg)","GuruHits Music Quiz","http://www.guruhits.com"
    end if
  end if
  
  set itemdelimiter to comma
  put item 2 of FullFind(temp,output,"m=a") into rank
  if rank = "" then put "0" into rank else put rank + 1 into rank
  put "<SET_LOCAL rank = " & quote & rank & quote & ">" & return after NCoutput
  
  put "<SET_LOCAL score = " & quote & score & quote & ">" & return after NCoutput
  put "<SET_LOCAL quiznb = " & quote & quiznb & quote & ">" & return after NCoutput
  put "<SET_LOCAL songline = " & quote & "done" & quote & ">" & return after NCoutput
  put "<SET_LOCAL level = " & quote & level & quote & ">" & return after NCoutput
  put "<SET_LOCAL goodanswers = " & quote & goodanswers & quote & ">" & return after NCoutput
  put "<SET_LOCAL topscore = " & quote & gHighScore & quote & ">" & return after NCoutput
  put "<SET_LOCAL mystatus = " & quote & mystatus & quote & ">" & return after NCoutput
  -- put "<SET_LOCAL myscore = " & quote & encrypt(firstname & "-" & lastename & "-" & email & "-" & score & "-" & quiznb & "-" & goodanswers) & quote & ">" & return after NCoutput
  
  reply(NCoutput)
  
end savescore

on highscore category
  global gGuruHitsDB,gDBrange,FormInformation,CustomerTCPAddress,partner
  put partner & ":temphigh.score." & category into scorefile
  
  put cd fld "highscore" into temp
  set itemdelimiter to tab
  
  put "" into output
  repeat with x = 1 to the number of lines of temp
    put line x of temp into theline
    if x mod 2 = 0 then put "#A5CEF7" into bgcolor
    else put "#E6EFFF" into bgcolor
    put "<TR BGCOLOR=" & quote & bgcolor & quote && "STYLE=" & quote & "font-size: 11px ; font-family: sans-serif" & quote & ">" &¬
    "<TD ALIGN=RIGHT>" & x & ".</TD>" &¬
    "<TD>" & item 5 of theline && item 6 of theline & "</TD>" &¬
    "<TD>" & item 11 of theline & "</TD>" &¬
    "<TD ALIGN=RIGHT>" & item 13 of theline & "</TD>" &¬
    "<TD ALIGN=RIGHT>" & item 10 of theline & "%</TD>" &¬
    "<TD ALIGN=RIGHT>" & item 8 of theline & "</TD>" &¬
    "</TR>" & return after output
  end repeat
  --1   date
  --2   time
  --3   203.0.113.5
  --4   player@example.com
  --5   Jochen
  --6   Savelberg
  --7   mov
  --8   1040
  --9   109
  --10  95.41
  --11  Junior Guru
  --12  169301
  --13  1
  
  open file scorefile
  write output to file scorefile
  close file scorefile
  
end highscore
Script of btn "Start"

Picks three random songs from the database (no repeats) — a manual test run.

on mouseUp
  global gGuruHitsDB,gDBrange
  put "" into cd fld output
  put the ticks into myStart
  put false into test
  put "" into randomlist
  repeat until test
    get random(gDBrange)
    if it is in randomlist then next repeat
    put it & return after randomlist
    if number of lines of randomlist = 3 then put true into test
  end repeat
  
  set itemdelimiter to tab
  put line (line 1 of randomlist) of gGuruHitsDB into Choose1
  put line (line 2 of randomlist) of gGuruHitsDB into Choose2
  put line (line 3 of randomlist) of gGuruHitsDB into Choose3
  
  put item 1 of line (line random(3) of randomlist) of gGuruHitsDB into Song2Play
  put "<SET_LOCAL playsong = " & quote & Song2Play & quote & ">" & return after output
  put "<SET_LOCAL songline = " & quote & encrypt(line 1 of randomlist & "-" & line 2 of randomlist & "-" & line 3 of randomlist) & quote & ">" & return after output
  
  put "<SET_LOCAL ref1 = " & quote & encrypt(item 1 of Choose1) & quote & ">" & return after output
  put "<SET_LOCAL artist1 = " & quote & item 2 of Choose1 & quote & ">" & return after output
  put "<SET_LOCAL song1 = " & quote & item 3 of Choose1 & quote & ">" & return after output
  put "<SET_LOCAL url1 = " & quote & item 4 of Choose1 & quote & ">" & return after output
  put "<SET_LOCAL cover1 = " & quote & item 5 of Choose1 & quote & ">" & return after output
  -- put "<SET_LOCAL audio1 = " & quote & item 6 of Choose1 & quote & ">" & return after output
  
  put "<SET_LOCAL ref2 = " & quote & encrypt(item 1 of Choose2) & quote & ">" & return after output
  put "<SET_LOCAL artist2 = " & quote & item 2 of Choose2 & quote & ">" & return after output
  put "<SET_LOCAL song2 = " & quote & item 3 of Choose2 & quote & ">" & return after output
  put "<SET_LOCAL url2 = " & quote & item 4 of Choose2 & quote & ">" & return after output
  put "<SET_LOCAL cover2 = " & quote & item 5 of Choose2 & quote & ">" & return after output
  -- put "<SET_LOCAL audio2 = " & quote & item 6 of Choose2 & quote & ">" & return after output
  
  
  put "<SET_LOCAL ref3 = " & quote & encrypt(item 1 of Choose3) & quote & ">" & return after output
  put "<SET_LOCAL artist3 = " & quote & item 2 of Choose3 & quote & ">" & return after output
  put "<SET_LOCAL song3 = " & quote & item 3 of Choose3 & quote & ">" & return after output
  put "<SET_LOCAL url3 = " & quote & item 4 of Choose3 & quote & ">" & return after output
  put "<SET_LOCAL cover3 = " & quote & item 5 of Choose3 & quote & ">" & return after output
  -- put "<SET_LOCAL audio3 = " & quote & item 6 of Choose3 & quote & ">" & return after output
  
  put output into cd fld "output"
  put the ticks - myStart into msg
end mouseUp

-- on mouseUp
-- global gGuruHitsDB,gDBrange
-- put "" into cd fld output
-- put the ticks into myStart
-- put false into test
-- put "" into randomlist
-- repeat until test
-- get random(gDBrange)
-- if it is in randomlist then next repeat
-- put it & return after randomlist
-- if number of lines of randomlist = 3 then put true into test
-- end repeat

-- set itemdelimiter to tab
-- put line (line 1 of randomlist) of gGuruHitsDB into Choose1
-- put line (line 2 of randomlist) of gGuruHitsDB into Choose2
-- put line (line 3 of randomlist) of gGuruHitsDB into Choose3

-- put item 1 of line (line random(3) of randomlist) of gGuruHitsDB into Song2Play
-- put "<SET_LOCAL playsong = " & quote & Song2Play & quote & ">" & return after output
-- put "<SET_LOCAL songline = " & quote & encrypt(line 1 of randomlist & "-" & line 2 of randomlist & "-" & line 3 of randomlist) & quote & ">" & return after output

-- put "<SET_LOCAL ref1 = " & quote & encrypt(item 1 of Choose1) & quote & ">" & return after output
-- put "<SET_LOCAL artist1 = " & quote & item 2 of Choose1 & quote & ">" & return after output
-- put "<SET_LOCAL song1 = " & quote & item 3 of Choose1 & quote & ">" & return after output
-- put "<SET_LOCAL url1 = " & quote & item 4 of Choose1 & quote & ">" & return after output
-- put "<SET_LOCAL cover1 = " & quote & item 5 of Choose1 & quote & ">" & return after output
-- -- put "<SET_LOCAL audio1 = " & quote & item 6 of Choose1 & quote & ">" & return after output

-- put "<SET_LOCAL ref2 = " & quote & encrypt(item 1 of Choose2) & quote & ">" & return after output
-- put "<SET_LOCAL artist2 = " & quote & item 2 of Choose2 & quote & ">" & return after output
-- put "<SET_LOCAL song2 = " & quote & item 3 of Choose2 & quote & ">" & return after output
-- put "<SET_LOCAL url2 = " & quote & item 4 of Choose2 & quote & ">" & return after output
-- put "<SET_LOCAL cover2 = " & quote & item 5 of Choose2 & quote & ">" & return after output
-- -- put "<SET_LOCAL audio2 = " & quote & item 6 of Choose2 & quote & ">" & return after output


-- put "<SET_LOCAL ref3 = " & quote & encrypt(item 1 of Choose3) & quote & ">" & return after output
-- put "<SET_LOCAL artist3 = " & quote & item 2 of Choose3 & quote & ">" & return after output
-- put "<SET_LOCAL song3 = " & quote & item 3 of Choose3 & quote & ">" & return after output
-- put "<SET_LOCAL url3 = " & quote & item 4 of Choose3 & quote & ">" & return after output
-- put "<SET_LOCAL cover3 = " & quote & item 5 of Choose3 & quote & ">" & return after output
-- -- put "<SET_LOCAL audio3 = " & quote & item 6 of Choose3 & quote & ">" & return after output

-- put output into cd fld "output"
-- put the ticks - myStart into msg
-- end mouseUp
Script of btn "temp"

A test button: injects a sample quiz form and calls quizscreen.

on mouseUp
  global FormInformation
  put "3019" into siteid
  put "songline=C08103064B183060C06903060C182D6CE09133264D1A2D60B481&firstname=Jochen&lastname=Savelberg&email=player@example.com&category=1&level=1&pi-qt=true&pi-real=true&Player=mov&submit=Set+%26+Play" into FormInformation
  send quizscreen to cd id siteid
end mouseUp
Script of btn "Shrink"

Collapses the card window to 50×50 (or restores it).

on mouseUp
  if hilite of me then
    set the height of cd window to 50
    set the width of cd window to 50
  else
    set the height of cd window to 342
    set the width of cd window to 512
  end if
end mouseUp
Contents of fld "level"

The rank ladder: the point thresholds from Novice to Guru.

Novice  0
Fan  100
Follower       200
Music Buff 500
Junior Guru 1000
Senior Guru 2500
Vice Guru  5000
Guru 10000
Contents of fld "highscore"

Working high-score buffer (tab-separated rows). Player identifiers shown here are anonymized; the live archive is never published.

22/03/2003	9:19:34	[IP_GRTF_1]	[Email_GRTF_1]	[Person_GRTF_1]	&#246;[Person_GRTF_2]	rpm	38280	1045	95.79	Vice Guru	414578328	4	1
6/09/2002	9:41:39	[IP_GRTF_2]	[Email_GRTF_2]	[Person_GRTF_3]		rpm	35000	1229	85.6	Vice Guru	16273163	4	1
6/08/2002	22:08:17	[IP_GRTF_3]	[Email_GRTF_3]			rpm	33000	931	94.31	Vice Guru	13770742	4	1
26/02/2002	19:56:08	[IP_GRTF_4]	[Email_GRTF_4]	[Person_GRTF_4]		au	32520	981	91.44	Vice Guru	23028721	4	1
27/04/2003	21:40:17	[IP_GRTF_5]	[Email_GRTF_5]	[Person_GRTF_5]		rpm	30960	986	89.25	Vice Guru	602561598	4	1
2/12/2003	23:19:04	[IP_GRTF_6]	[Email_GRTF_6]	[Person_GRTF_6]		rpm	26960	932	86.16	Vice Guru	220083783	4	1
12/05/2002	13:28:19	[IP_GRTF_7]	[Email_GRTF_7]	[Person_GRTF_7]		au	22950	1094	69.93	Vice Guru	25923559	3	1
25/02/2003	4:46:13	[IP_GRTF_8]		[Person_GRTF_8]		au	19860	1041	63.59	Vice Guru	283640982	3	1
26/05/2003	15:39:25	[IP_GRTF_9]	[Email_GRTF_8]	[Account_GRTF_1]		aif	16440	455	95.16	Senior Guru	753604411	4	1
5/11/2002	21:08:52	[IP_GRTF_10]		[Person_GRTF_9]		rpm	16410	605	90.41	Vice Guru	27080264	3	1
4/09/2003	17:12:04	[IP_GRTF_11]	[Email_GRTF_9]	[Person_GRTF_10]		rpm	15980	928	86.1	Vice Guru	509632157	2	1
1/04/2002	1:48:29	[IP_GRTF_12]	[Person_GRTF_11][Email_GRTF_10]	[Person_GRTF_12]		au	15680	540	86.3	Senior Guru	12309108	4	1
30/12/2001	21:59:33	[IP_GRTF_13]	[Email_GRTF_11]	[Person_GRTF_13]		rpm	15520	388	100	Senior Guru	18403771	4	1
12/07/2001	9:16:50	[IP_GRTF_14]	[Email_GRTF_12]	[Person_GRTF_14]		rpm	15320	503	88.07	Senior Guru	3614146	4	1
14/11/2001	20:40:29	[IP_GRTF_15]	[Email_GRTF_13]	[Person_GRTF_15]		au	15200	742	75.61	Senior Guru	37502262	4	1
18/08/2002	1:00:24	[IP_GRTF_16]	[Email_GRTF_14]	[Person_GRTF_16]		au	14000	480	86.46	Senior Guru	3505539	4	1
4/04/2003	12:08:22	[IP_GRTF_17]	[Person_GRTF_17]			rpm	13800	627	77.51	Senior Guru	482762224	4	1
11/10/2001	17:38:38	[IP_GRTF_18]	[Email_GRTF_15]	[Person_GRTF_18]		rpm	13300	925	71.89	Vice Guru	16053461	2	1
28/04/2003	19:37:04	[IP_GRTF_19]	[Email_GRTF_16]			rpm	12520	387	90.44	Senior Guru	608917588	4	1
11/10/2001	16:51:43	[IP_GRTF_20]	[Email_GRTF_17]	[Person_GRTF_19]		rpm	12060	825	73.09	Vice Guru	15884464	2	1

The two fields on the card hold data rather than scripts: level is the rank ladder, and highscore is the working high-score buffer. The original buffer held players' names, email addresses and IPs, so the sample shown here has every identifier swapped for a typed placeholder ([Person_…], [Email_…], [IP_…]) — anonymized data produced with Privatim, a privacy-first tool that detects and redacts personal information before anything is published.

The same stack still runs today under the SheepShaver emulator:

The GuruHits stack running under SheepShaver, 2026