view quotedb.tcl @ 466:a8eb80bbffa5

quotedb: Add 'stats' command.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 09 May 2018 20:04:13 +0300
parents ba7ed8f6d2ab
children 32f0aba3ab9b
line wrap: on
line source

##########################################################################
#
# QuoteDB v2.5 by Matti 'ccr' Hamalainen <ccr@tnsp.org>
# (C) Copyright 2003-2017 Tecnic Software productions (TNSP)
#
# Not for public use or distribution. If you happen to find this,
# send your questions and/or problems to /dev/null, thank you.
#
##########################################################################

### The configuration should be in config.quotedb in same directory
### as this script. Or change the line below to point where ever
### you wish. See "config.quotedb.example" for an example config file.
source [file dirname [info script]]/config.quotedb

### Required utillib.tcl
source [file dirname [info script]]/utillib.tcl


##########################################################################
# No need to look below this line
##########################################################################
set qdb_name "QuoteDB"
set qdb_version "2.5"

#-------------------------------------------------------------------------
### Initialization messages
set qdb_message "$qdb_name v$qdb_version by ccr/TNSP"
putlog "$qdb_message"

### Require packages
package require sqlite3

### SQLite database initialization
if {[catch {sqlite3 qdb_handle $qdb_file} uerrmsg]} {
  putlog " Could not open SQLite3 database '$qdb_file': $uerrmsg"
  exit 2
}


#-------------------------------------------------------------------------
### Utility functions
proc qdb_log {jarg} {
  global qdb_logmsg qdb_name

  if {$qdb_logmsg != 0} {
    putlog "$qdb_name: $jarg"
  }
}

proc qdb_msg {apublic anick achan amsg {aargs {}}} {
  global qdb_preferredmsg
  set narg 1
  foreach marg $aargs {
    set amsg [string map [list "%$narg" $marg] $amsg]
    incr narg
  }
  utl_msg_do $qdb_preferredmsg $apublic $anick $achan $amsg
}


proc qdb_sql_exec { upublic unick uchan usql } {
  global qdb_handle qdb_msg_sql_error
  if {[catch {qdb_handle eval $usql} uerrmsg]} {
    qdb_log "$uerrmsg on SQL:\n$usql"
    qdb_msg $upublic $unick $uchan $qdb_msg_sql_error
    return 0
  }
  return 1
}

proc qdb_valid_user {upublic unick uchan uhand} {
  global qdb_msg_invalid_user
  if {$uhand == "" || $uhand == {}} {
    qdb_msg $upublic $unick $uchan $qdb_msg_invalid_user
    return 0
  }
  return 1
}

#-------------------------------------------------------------------------
proc qdb_add {ucmd utable unick uhost uhand uchan utext upublic} {
  global qdb_handle qdb_msg_add_success

  if {![qdb_valid_user $upublic $unick $uchan $uhand]} {
    return 0
  }

  if {$utext == "" || $utext == {}} {
    qdb_msg $upublic $unick $uchan "$ucmd add <teksti>"
    return 0
  }

  set usql "INSERT INTO $utable (utime,utext,user,host,chan) VALUES ([unixtime], '[utl_escape $utext]', '[utl_escape $uhand]', '[utl_escape $uhost]', '[utl_escape $uchan]')"
  if {![qdb_sql_exec $upublic $unick $uchan $usql]} {
    return 0
  }

  set quoteID [qdb_handle last_insert_rowid]
#  set numQuotes 0
#  set usql "SELECT count(*) AS numQuotes FROM $utable"
#  if {![qdb_sql_exec $upublic $unick $uchan $usql]} { return 0 }

  ### Log some data
  qdb_log "Added $utable #$quoteID ($unick/$uhand@$uchan): $utext"

  ### Report success to user
  qdb_msg $upublic $unick $uchan $qdb_msg_add_success [list $utable $quoteID]
  return 1
}


#-------------------------------------------------------------------------
proc qdb_delete {ucmd utable unick uhand uchan utext upublic} {
  global qdb_handle qdb_max_deltime qdb_msg_not_found

  set maxdiff [expr $qdb_max_deltime * 60]

  if {![qdb_valid_user $upublic $unick $uchan $uhand]} {
    return 0
  }

  if {![regexp {^\s*([0-9]+)$} $utext umatch unum]} {
    qdb_msg $upublic $unick $uchan "$ucmd del <id>"
    return 0
  }

  set qoverride [matchattr $uhand n]
  set usql "SELECT utime AS utime FROM $utable WHERE id=$unum"
  if {$qoverride == 0} {
    append usql " AND user='[utl_escape $uhand]'"
    set qextra ""
  } else {
    set qextra " (owner/time override)"
  }

  qdb_handle eval $usql {
    set udiff [expr [unixtime] - $utime]
    if {$udiff < $maxdiff || $qoverride} {
      set usql "DELETE FROM $utable WHERE id=$unum"
      if {![qdb_sql_exec $upublic $unick $uchan $usql]} {
        return 0
      } else {
        set usql "DELETE FROM ${utable}_votes WHERE urlid=$unum"
        if {![qdb_sql_exec $upublic $unick $uchan $usql]} {
          return 0
        }
        qdb_msg $upublic $unick $uchan "$utable #$unum poistettu$qextra."
        return 1
      }
    } else {
      qdb_msg $upublic $unick $uchan "$utable quote #$unum vanhempi kuin $qdb_max_deltime minuuttia, ei poisteta."
      return 0
    }
  }

  qdb_msg $upublic $unick $uchan $qdb_msg_not_found [list $utable $unum $uhand]
  return 0
}


#-------------------------------------------------------------------------
proc qdb_update {ucmd utable unick uhand uchan utext upublic} {
  global qdb_handle qdb_max_modtime qdb_msg_update_override
  global qdb_msg_update_ok qdb_msg_update_too_old qdb_msg_not_found

  set maxdiff [expr $qdb_max_modtime * 60]

  if {![qdb_valid_user $upublic $unick $uchan $uhand]} {
    return 0
  }

  if {![regexp {^\s*([0-9]+)\s+(.+)$} $utext umatch unum uquote]} {
    qdb_msg $upublic $unick $uchan "$ucmd update <id> <teksti>"
    return 0
  }

  set qoverride [matchattr $uhand n]
  set usql "SELECT utime AS utime FROM $utable WHERE id=$unum"
  if {$qoverride == 0} {
    append usql " AND user='[utl_escape $uhand]'"
    set qextra ""
  } else {
    set qextra $qdb_msg_update_override
  }

  qdb_handle eval $usql {
    set udiff [expr [unixtime] - $utime]
    if {$udiff < $maxdiff || $qoverride} {
      set usql "UPDATE $utable SET utext='[utl_escape $uquote]' WHERE id=$unum"
      if {![qdb_sql_exec $upublic $unick $uchan $usql]} {
        return 0
      } else {
        qdb_msg $upublic $unick $uchan $qdb_msg_update_ok [list $utable $unum $qextra]
        return 1
      }
    } else {
      qdb_msg $upublic $unick $uchan $qdb_msg_update_too_old [list $utable $unum $qdb_max_modtime]
      return 0
    }
  }

  qdb_msg $upublic $unick $uchan $qdb_msg_not_found [list $utable $unum $uhand]
  return 0
}


#-------------------------------------------------------------------------
proc qdb_toplist {ucmd utable unick uchan unum upublic} {
  global qdb_handle qdb_msg_toplist_limit qdb_msg_toplist_fmt

  if {$unum < 2 || $unum > 5} {
    qdb_msg $upublic $unick $uchan $qdb_msg_toplist_limit [list 2 5]
    return 0
  }

  set uresults 0
  set usql "SELECT total(${utable}_votes.vote) AS rating, ${utable}.id AS quoteID, ${utable}.utext AS utext, ${utable}.utime AS utime,${utable}.user AS uuser, ${utable}.id AS uid FROM ${utable}_votes INNER JOIN $utable ON ${utable}_votes.urlid = ${utable}.id GROUP BY ${utable}.id ORDER BY rating DESC LIMIT $unum"
  qdb_handle eval $usql {
    incr uresults
    qdb_msg $upublic $unick $uchan $qdb_msg_toplist_fmt [list $uresults $quoteID $utext $uuser $rating]
  }

  return 0
}


#-------------------------------------------------------------------------
proc qdb_get_rating_for_id {utable uid} {
  set usql "SELECT total(${utable}_votes.vote) AS qrating FROM ${utable}_votes WHERE urlid=${uid}"
  set qrating 0
  qdb_handle eval $usql { set urating $qrating }
  return $qrating
}


#-------------------------------------------------------------------------
proc qdb_vote {ucmd utable unick uhand uchan utext upublic} {
  global qdb_handle qdb_msg_you_voted qdb_msg_quote_does_not_exist qdb_msg_vote_updated

  if {![qdb_valid_user $upublic $unick $uchan $uhand]} {
    return 0
  }

  set uvote 1
  if {$utext == "" || [regexp {^\s*-1$} $utext umatch uvote]} {
    set usql "SELECT max(id) AS qid FROM ${utable}"
    set uid 0
    qdb_handle eval $usql { set uid $qid }
  } elseif {[regexp {^\s*([0-9]+)$} $utext umatch uid]} {
  } elseif {![regexp {^\s*([0-9]+)\s+(-1|1)$} $utext umatch uid uvote]} {
    qdb_msg $upublic $unick $uchan "$ucmd vote \[<id> \[1|-1\]\]"
    return 0
  }

  # Check if given quote ID exists.
  set uresults 0
  set usql "SELECT id AS qid FROM ${utable} WHERE id=$uid"
  qdb_handle eval $usql { incr uresults }
  if {$uresults == 0} {
    qdb_msg $upublic $unick $uchan $qdb_msg_quote_does_not_exist [list $uid]
    return 0
  }

  # Check if user has previously voted this item
  set usql "SELECT id AS qid FROM ${utable}_votes WHERE user='[utl_escape $uhand]' AND urlid=$uid"
  qdb_handle eval $usql {
    # Yes, update the previous vote
    set usql "UPDATE ${utable}_votes SET vote=$uvote WHERE id=$qid"
    if {[qdb_sql_exec $upublic $unick $uchan $usql]} {
      set urating [qdb_get_rating_for_id $utable $uid]
      qdb_log "$uhand changed vote to $uvote on #$uid / $utable, total $urating"
      qdb_msg $upublic $unick $uchan $qdb_msg_vote_updated [list $uid $uvote $urating]
      return 1
    } else {
      return 0
    }
  }

  # No previous votes, insert new
  set usql "INSERT INTO ${utable}_votes (user,urlid,vote) VALUES ('[utl_escape $uhand]',$uid,$uvote)"
  if {[qdb_sql_exec $upublic $unick $uchan $usql]} {
    set urating [qdb_get_rating_for_id $utable $uid]
    qdb_log "$uhand voted $uvote on #$uid / $utable, total $urating"
    qdb_msg $upublic $unick $uchan $qdb_msg_you_voted [list $uid $uvote $urating]
  }
}


#-------------------------------------------------------------------------
proc qdb_get {ucmd utable unick uchan uindex upublic} {
  global qdb_handle qdb_msg_no_matches

  set usql "SELECT total(${utable}_votes.vote) AS uvote, ${utable}.id AS quoteID, ${utable}.utext AS utext, ${utable}.utime AS utime, ${utable}.user AS uuser FROM ${utable} LEFT JOIN ${utable}_votes ON ${utable}_votes.urlid = ${utable}.id"
  if {$uindex >= 0} {
    append usql " WHERE ${utable}.id=$uindex GROUP BY ${utable}.id"
    qdb_log "$unick searched $utable #$uindex"
  } elseif {$uindex == -1} {
    append usql " GROUP BY ${utable}.id ORDER BY RANDOM() LIMIT 1"
    qdb_log "$unick get random $utable"
  } elseif {$uindex == -2} {
    append usql " GROUP BY ${utable}.id ORDER BY ${utable}.id DESC LIMIT 1"
    qdb_log "$unick get latest $utable"
  } else {
    qdb_log "$unick invalid qdb_get $utable index value."
    return 0
  }

  qdb_handle eval $usql {
    qdb_msg $upublic $unick $uchan "#${quoteID}: $utext ($uuser@[utl_ctime $utime], $uvote)"
    return 1
  }

  qdb_msg $upublic $unick $uchan $qdb_msg_no_matches [list $unick]
  return 0
}


#-------------------------------------------------------------------------
proc qdb_stats {ucmd utable unick uchan upublic} {
  global qdb_handle

  set usql "SELECT count(*) AS nvotes FROM ${utable}_votes"
  qdb_handle eval $usql {}

  set usql "SELECT count(*) AS nquotes FROM ${utable}"
  qdb_handle eval $usql {}

  qdb_msg $upublic $unick $uchan "${utable}-tilastot: ${nquotes} quotea, ${nvotes} annettua ääntä."
  return 0
}


#-------------------------------------------------------------------------
proc qdb_find {ucmd utable unick uhand uchan utext upublic} {
  global qdb_handle qdb_showmax_pub qdb_showmax_priv qdb_msg_no_matches qdb_msg_search_fmt

  if {$upublic == 0} {
    set ulimit $qdb_showmax_priv
  } else {
    set ulimit $qdb_showmax_pub
  }

  ### Parse the given command
  qdb_log "$unick/$uhand/$uchan searched $utable: $utext"

  set ftokens [split $utext " "]
  set fpatlist ""
  foreach ftoken $ftokens {
    set fprefix [string range $ftoken 0 0]
    set fpattern [string range $ftoken 1 end]

    if {$fprefix == "-"} {
      lappend fpatlist "utext NOT LIKE '%[utl_escape $fpattern]%'"
    } elseif {$fprefix == "%"} {
      lappend fpatlist "user LIKE '[utl_escape $fpattern]'"
    } elseif {$fprefix == "@"} {
      # foo
    } elseif {$fprefix == "+"} {
      lappend fpatlist "utext LIKE '%[utl_escape $fpattern]%'"
    } else {
      lappend fpatlist "utext LIKE '%[utl_escape $ftoken]%'"
    }
  }
  if {[llength $fpatlist] > 0} {
    set fquery "WHERE [join $fpatlist " AND "]"
  } else {
    set fquery ""
  }

  ### Query the database and output results
  set uresults 0
  set usql "SELECT id AS quoteID, utime AS utime, utext AS utext, user AS uuser FROM $utable $fquery ORDER BY utime DESC LIMIT $ulimit"
  qdb_handle eval $usql {
    incr uresults
    qdb_msg $upublic $unick $uchan $qdb_msg_search_fmt [list $quoteID $utext $uuser $utime [utl_ctime $utime]]
  }

  if {$uresults == 0} {
    qdb_msg $upublic $unick $uchan $qdb_msg_no_matches [list $unick]
    return 0
  }
  return 1
}


#-------------------------------------------------------------------------
proc qdb_command {ucmd utable unick uhost uhand uchan utext upublic} {
  global qdb_msg_help qdb_channels

  if {$upublic && ![utl_match_delim_list $qdb_channels $uchan]} {
    return 0
  }

  set utext [string trim $utext]

  if {$utext == "" || $utext == {}} {
    # No arguments, assume random query
    qdb_get $ucmd $utable $unick $uchan -1 $upublic
  } elseif {[regexp {^(stat|stats|tilasto|tilastot)$} $utext umatch]} {
    # Statistics
    qdb_stats $ucmd $utable $unick $uchan $upublic
  } elseif {[regexp {^(last|latest|uusin|viimeisin)$} $utext umatch]} {
    # Latest added
    qdb_get $ucmd $utable $unick $uchan -2 $upublic
  } elseif {[regexp {^([0-9]+)$} $utext umatch unum]} {
    # Numeric argument, assume index query
    qdb_get $ucmd $utable $unick $uchan $unum $upublic
  } elseif {[regexp {^top\s*([0-9]+)$} $utext umatch unum]} {
    # Toplist of quotes
    qdb_toplist $ucmd $utable $unick $uchan $unum $upublic
  } elseif {[regexp {^add\s*(.*)$} $utext umatch unum]} {
    # Add quote
    qdb_add $ucmd $utable $unick $uhost $uhand $uchan $unum $upublic
  } elseif {[regexp {^del\s*([0-9]*)$} $utext umatch unum]} {
    # Delete quote
    qdb_delete $ucmd $utable $unick $uhand $uchan $unum $upublic
  } elseif {[regexp {^update\s*(.*)$} $utext umatch unum]} {
    # Modify/update quote
    qdb_update $ucmd $utable $unick $uhand $uchan $unum $upublic
  } elseif {[regexp {^find\s*(.*)$} $utext umatch unum]} {
    # Find quote(s)
    qdb_find $ucmd $utable $unick $uhand $uchan $unum $upublic
  } elseif {[regexp {^vote\s*(.*)$} $utext umatch unum]} {
    # Vote
    qdb_vote $ucmd $utable $unick $uhand $uchan $unum $upublic
  } else {
    # Help/usage
    qdb_msg $upublic $unick $uchan $qdb_msg_help [list $ucmd]
  }
}

#-------------------------------------------------------------------------
# end of script