I believe this should work and I fixed a small security vulnerability related to $read (always use the n switch when using $read)

Code:
on *:TEXT:!fact:#: {
  if ($nick isop # ) {
    if ((%floodQUOTE) || ($($+(%,floodQUOTE.,$nick),2))) { return }
    set -u10 %floodQUOTE On
    set -u30 %floodQUOTE. $+ $nick On
    msg $chan $read(heartfacts.txt,n)
    facts $chan
  }
  else {
    msg $chan Mods will one of you fact command please if it hasn't been used too recently?
  }
}

alias facts {
  if ($timer([facts]).type == online) { return }
  elseif ($timer([facts]).type == offline) {
    .timer[facts] 0 1800 msg $1 $read(heartfacts.txt,n)
  }
}


There may be a better way, but this was all I could think of at the current time.

When you first do the !fact command, it'll check if a timer is active that outputs facts, if a timer is active, it'll return and not execute the alias, if one isn't active, it'll become active and start outputting a random fact every 30 minutes.

Note: This will activate only if a mod uses the command, if you want it so that if only a user is there, then just add the tweet $chan to the else part of the code.