💾 Archived View for gemini.conman.org › extensions › port70 › handlers › blog.lua captured on 2021-12-03 at 14:04:38.

View Raw

More Information

⬅️ Previous capture (2020-10-31)

➡️ Next capture (2022-04-28)

🚧 View Differences

-=-=-=-=-=-=-

-- ***********************************************************************
--
-- Copyright 2020 by Sean Conner.
--
-- This program is free software: you can redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by the
-- Free Software Foundation, either version 3 of the License, or (at your
-- option) any later version.
--
-- This program is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
-- Public License for more details.
--
-- You should have received a copy of the GNU General Public License along
-- with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- Comments, questions and criticisms can be sent to: sean@conman.org
--
-- =======================================================================
--
-- Code to handle blog requests.
--
-- ***********************************************************************
-- luacheck: globals init handler last_link
-- luacheck: ignore 611 631

local syslog   = require "org.conman.syslog"
local fsys     = require "org.conman.fsys"
local magic    = require "org.conman.fsys.magic"
local date     = require "org.conman.date"
local wrapt    = require "org.conman.string".wrapt
local url      = require "org.conman.parsers.url.data"
               + require "org.conman.parsers.url.gopher"
               + require "org.conman.parsers.url"
local mimetype = require "org.conman.parsers.mimetype"
local gtypes   = require "org.conman.const.gopher-types"
local io       = require "io"
local os       = require "os"
local table    = require "table"
local string   = require "string"
local utf8     = require "utf8"
local mklink   = require "port70.mklink"
local readfile = require "port70.readfile"
local html     = require "org.conman.app.port70.handlers.blog.html"
local uurl     = require "org.conman.app.port70.handlers.blog.url-util"
local format   = require "org.conman.app.port70.handlers.blog.format"
local blog     = require "org.conman.app.mod_blog"

local require  = require
local ipairs   = ipairs
local tonumber = tonumber

_ENV = {}

-- ***********************************************************************
-- usage:       init()
-- desc:        Intialize the handler module
-- ***********************************************************************

function init(conf)
  conf.blog = blog.init(conf.config)
  return true
end

-- ***********************************************************************

function handler(config,request,ios)
  local CONF  = require "port70.CONF"
  local first = blog.bookend("first")
  local last  = blog.bookend("last")
  local port do
    if CONF.network.port == 70 or CONF.network.port == 'gopher' then
      port = ""
    else
      port = string.format(":%d",CONF.network.port)
    end
  end
  
  -- ----------------------------------------------------------------
  
  local function affiliate(location)
    for _,aff in ipairs(config.blog.affiliate) do
      if location.scheme == aff.proto then
        return string.format(aff.link,location.path)
      end
    end
    return uurl.toa(location)
  end
  
  -- ----------------------------------------------------------------
  
  local function xlat_links(when,links)
    local function filetype(filename)
      if fsys.access(filename,"r") then
        local mime = mimetype:match(magic(filename))
        local ftype
        
        if not mime then
          ftype = gtypes.binary
        else
          if mime.type:find("text/",1,true) then
            ftype = gtypes.file
          elseif mime.type:find("image/gif",1,true) then
            ftype = gtypes.gif
          elseif mime.type:find("image/",1,true) then
            ftype = gtypes.image
          elseif mime.type:find("audio/",1,true) then
            ftype = gtypes.sound
          elseif mime.type:find("video/",1,true) then
            ftype = gtypes.video
          else
            ftype = gtypes.binary
          end
        end
        return ftype
      end
      
      return gtypes.file
    end
    
    -- ----------------------------------------------------------------
    
    for i,link in ipairs(links) do
      local u = url:match(link)
      
      if u.scheme then
        ios:write(string.format("[%d] %s\n",i,affiliate(u)))
        
      else
        local t = blog.tumbler.new(link,first,last)
        if t then
          if t.ustart == 'part' or t.range
          or t.file and t.filename ~= "" then
            if t.file then
              local filename = config.basedir .. string.format("/%04d/%02d/%02d/%s",t.start.year,t.start.month,t.start.day,t.filename)
              ios:write(string.format("[%d] gopher://%s%s/%s%s%s\n",
                        i,
                        CONF.network.host,
                        port,
                        filetype(filename),
                        config.selector,
                        link
                ))
            else
              ios:write(string.format("[%d] gopher://%s%s/0%s%s\n",
                        i,
                        CONF.network.host,
                        port,
                        config.selector,
                        link
              ))
            end
          else
            ios:write(string.format("[%d] gopher://%s%s/1%s%s\n",
                        i,
                        CONF.network.host,
                        port,
                        config.selector,
                        link
            ))
          end
        else
          if link:match "^/" then
            if link:find("/thisday",1,true) then
              ios:write(string.format("[%d] gopher://%s%s/0%s%s\n",
                        i,
                        CONF.network.host,
                        port,
                        config.selector,
                        link:sub(2,-1)
              ))
            else
              ios:write(string.format("[%d] %s%s\n",i,config.blog.url,link:sub(2,-1)))
            end
          else
            if link:find("parallel/",1,true) then
              ios:write(string.format("[%d] %s%s\n",i,config.blog.url,link))
            else
              local filename = string.format(
                "%s/%04d/%02d/%02d/%s",
                config.blog.basedir,
                when.year,
                when.month,
                when.day,
                link
              )
              ios:write(string.format("{%d] gopher://%s%s/%s%s%04d/%02d/%02d/%s\n",
                        i,
                        CONF.network.host,
                        port,
                        filetype(filename),
                        config.selector,
                        when.year,
                        when.month,
                        when.day,
                        link
              ))
            end
          end
        end
      end
    end
  end
  
  -- ----------------------------------------------------------------
  
  local function display(tumbler)
    local function gopherlink(entry)
      return string.format("gopher://%s%s/%s%s%04d/%02d/%02d.%d",
                CONF.network.host,
                port,
                gtypes.file,
                config.selector,
                entry.when.year,
                entry.when.month,
                entry.when.day,
                entry.when.part
        )
    end
    
    for entry in blog.entries(tumbler) do
      local x = html(entry.body)
      local text,links = format(x)
      local t = wrapt(entry.title,77)
      table.insert(t,1,"")
      table.insert(t,2,"* * * * *")
      table.insert(t,3,"")
      table.insert(t,"")
      if tumbler.range then
        table.insert(t,os.date("%A, %B %d, %Y",os.time(tumbler.start)))
        table.insert(t,gopherlink(entry))
        table.insert(t,"")
      end
      
      for i = 1 , #t do
        t[i] = string.rep(" ",(80 - utf8.len(t[i])) // 2) .. t[i]
      end
      
      ios:write(table.concat(t,"\n"),'\n')
      ios:write(text)
      xlat_links(tumbler.start,links)
    end
    return true
  end
  
  -- ----------------------------------------------------------------
  
  local function thisday(req)
    local month,day = req:match("^thisday/?(%d+)/(%d+)")
    if not month then
      local now = os.date("*t")
      month = now.month
      day   = now.day
    else
      month = tonumber(month)
      day   = tonumber(day)
    end
    
    for year = first.year , last.year do
      local x = string.format("%04d/%02d/%02d",year,month,day)
      local tumbler = blog.tumbler.new(x,first,last)
      if tumbler then
        tumbler.range = true
        display(tumbler)
      end
    end
    
    if ios.__wbytes == 0 then
      ios:write("There are no entries for this date.\n")
    end
    
    return true
  end
  
  -- ----------------------------------------------------------------
  -- If the request is empty, display the year menu
  -- ----------------------------------------------------------------
  
  if request.rest == "" then
    for i = last.year , first.year , -1 do
      local itop = string.format("%04d",i)
      ios:write(mklink {
        type     = 'dir',
        display  = itop,
        selector = config.selector .. itop,
      })
    end
    return true
  end
  
  -- ----------------------------------------------------------------
  -- There's a tumbler.  Parse and handle
  -- ----------------------------------------------------------------
  
  local tumbler = blog.tumbler.new(request.rest,first,last)
  if not tumbler then
    if request.rest:match "^thisday" then
      return thisday(request.rest)
    else
      ios:write(mklink {
                type = 'error',
                display = 'Selector not found',
                selector = request.rest
        })
      return false
    end
  end
  
  if tumbler.file and tumbler.filename ~= "" then
    local filename = blog.filename(tumbler)
    if filename:match("%.x%-html$") then
      local f,err = io.open(filename,"rb")
      if not f then
        syslog('error',"%s: %s",filename,err)
        ios:write(mklink {
          type = 'error',
          display = 'Selector not found',
          selector = request
        })
        return false
      end
      
      local text,links = format(html(f:read("a")))
      ios:write(text)
      xlat_links(tumbler.start,links)
      f:close()
      return true
    else
      return readfile(blog.filename(tumbler),"\1",config,request,ios)
    end
    
  elseif tumbler.range then
    return display(tumbler)
    
  elseif tumbler.ustart == 'year' then
    local when = { year = 2021 , month = 1 , day = 1 }
    
    for i = 1 , 12 do
      if tumbler.start.year == first.year and i                  >= first.month
      or tumbler.start.year == last.year  and i                  <= last.month
      or tumbler.start.year >  first.year and tumbler.start.year <  last.year then
        when.month = i
        local d = os.time(when)
        ios:write(mklink {
                type = 'dir',
                display = os.date("%B",d),
                selector = string.format("%s%04d/%02d",config.selector,tumbler.start.year,i)
        })
      end
    end
    return true
    
  elseif tumbler.ustart == 'month' then
    local entries
    
    for day = 1 , date.daysinmonth(tumbler.start) do
      tumbler.start.day = day
      local list = blog.day_titles(tumbler.start)
      if #list > 0 then
        entries = true
        ios:write(mklink {
                type     = 'dir',
                display  = string.format("%04d/%02d/%02d",tumbler.start.year,tumbler.start.month,tumbler.start.day),
                selector = string.format("%s%04d/%02d/%02d",config.selector,tumbler.start.year,tumbler.start.month,tumbler.start.day),
        })
        
        for i,title in ipairs(list) do
          ios:write(mklink {
            type     = 'file',
            display  = title,
            selector = string.format("%s%04d/%02d/%02d.%d",config.selector,tumbler.start.year,tumbler.start.month,tumbler.start.day,i),
          })
        end
      end
    end
    
    if not entries then
      ios:write(mklink {
        type = 'info',
        display = "There are no entries this month"
      })
    end
    return true
    
  elseif tumbler.ustart == 'day' then
    local list = blog.day_titles(tumbler.start)
    for i,title in ipairs(list) do
      ios:write(mklink {
        type     = 'file',
        display  = title,
        selector = string.format("%s%04d/%02d/%02d.%d",config.selector,tumbler.start.year,tumbler.start.month,tumbler.start.day,i),
      })
    end
    return true
    
  else
    return display(tumbler)
  end
end

-- ***********************************************************************
-- usage:       link = last_link()
-- desc:        return a gopher link for the latest blog entry
-- return:      link (string) gopher link
-- ***********************************************************************

function last_link(selector)
  local last = blog.bookend("last")
  return string.format(
                "%s%d/%02d/%02d.%d",
                selector,
                last.year,
                last.month,
                last.day,
                last.part
        )
end

-- ***********************************************************************

return _ENV