hooks.go

  1package plugin
  2
  3import (
  4	"log"
  5	"time"
  6
  7	lua "github.com/yuin/gopher-lua"
  8)
  9
 10// Hook event names.
 11const (
 12	HookStartup         = "startup"
 13	HookShutdown        = "shutdown"
 14	HookEmailReceived   = "email_received"
 15	HookEmailSendBefore = "email_send_before"
 16	HookEmailSendAfter  = "email_send_after"
 17	HookEmailViewed     = "email_viewed"
 18	HookFolderChanged   = "folder_changed"
 19	HookComposerUpdated = "composer_updated"
 20	HookEmailBodyRender = "email_body_render"
 21)
 22
 23// Status area names.
 24const (
 25	StatusInbox     = "inbox"
 26	StatusComposer  = "composer"
 27	StatusEmailView = "email_view"
 28)
 29
 30// registerHook adds a callback for the given event.
 31func (m *Manager) registerHook(event string, fn *lua.LFunction) {
 32	m.hooks[event] = append(m.hooks[event], fn)
 33}
 34
 35// CallHook invokes all callbacks registered for the given event.
 36func (m *Manager) CallHook(event string, args ...lua.LValue) {
 37	callbacks, ok := m.hooks[event]
 38	if !ok {
 39		return
 40	}
 41
 42	for _, fn := range callbacks {
 43		if err := m.state.CallByParam(lua.P{
 44			Fn:      fn,
 45			NRet:    0,
 46			Protect: true,
 47		}, args...); err != nil {
 48			log.Printf("plugin hook %q error: %v", event, err)
 49		}
 50	}
 51}
 52
 53// CallSendHook calls a hook with email send metadata.
 54func (m *Manager) CallSendHook(event string, to, cc, subject, accountID string) {
 55	callbacks, ok := m.hooks[event]
 56	if !ok {
 57		return
 58	}
 59
 60	L := m.state
 61	t := L.NewTable()
 62	t.RawSetString("to", lua.LString(to))
 63	t.RawSetString("cc", lua.LString(cc))
 64	t.RawSetString("subject", lua.LString(subject))
 65	t.RawSetString("account_id", lua.LString(accountID))
 66
 67	for _, fn := range callbacks {
 68		if err := L.CallByParam(lua.P{
 69			Fn:      fn,
 70			NRet:    0,
 71			Protect: true,
 72		}, t); err != nil {
 73			log.Printf("plugin hook %q error: %v", event, err)
 74		}
 75	}
 76}
 77
 78// CallFolderHook calls a hook with a folder name.
 79func (m *Manager) CallFolderHook(event string, folderName string) {
 80	callbacks, ok := m.hooks[event]
 81	if !ok {
 82		return
 83	}
 84
 85	for _, fn := range callbacks {
 86		if err := m.state.CallByParam(lua.P{
 87			Fn:      fn,
 88			NRet:    0,
 89			Protect: true,
 90		}, lua.LString(folderName)); err != nil {
 91			log.Printf("plugin hook %q error: %v", event, err)
 92		}
 93	}
 94}
 95
 96// CallComposerHook calls a hook with composer state info.
 97func (m *Manager) CallComposerHook(event string, body, subject, to, cc, bcc string) {
 98	callbacks, ok := m.hooks[event]
 99	if !ok {
100		return
101	}
102
103	L := m.state
104	t := L.NewTable()
105	t.RawSetString("body_len", lua.LNumber(len(body)))
106	t.RawSetString("body", lua.LString(body))
107	t.RawSetString("subject", lua.LString(subject))
108	t.RawSetString("to", lua.LString(to))
109	t.RawSetString("cc", lua.LString(cc))
110	t.RawSetString("bcc", lua.LString(bcc))
111
112	for _, fn := range callbacks {
113		if err := L.CallByParam(lua.P{
114			Fn:      fn,
115			NRet:    0,
116			Protect: true,
117		}, t); err != nil {
118			log.Printf("plugin hook %q error: %v", event, err)
119		}
120	}
121}
122
123// CallBodyRenderHook runs all email_body_render callbacks, threading the body
124// string through each. Callbacks receive (email_table, rendered, raw):
125//   - rendered: the current display string (ANSI-styled, post-HTML→terminal)
126//   - raw: the original message body (HTML or plain text, same string fed to
127//     the renderer) — useful for parsing the source instead of the rendered
128//     output
129//
130// A callback may return a string to replace the rendered body, or nil to leave
131// it unchanged. Non-string returns are ignored. Multiple callbacks chain in
132// registration order; each subsequent callback sees the previous callback's
133// rendered output, but always the same raw source.
134func (m *Manager) CallBodyRenderHook(email *lua.LTable, rendered, raw string) string {
135	callbacks, ok := m.hooks[HookEmailBodyRender]
136	if !ok {
137		return rendered
138	}
139
140	L := m.state
141	for _, fn := range callbacks {
142		if err := L.CallByParam(lua.P{
143			Fn:      fn,
144			NRet:    1,
145			Protect: true,
146		}, email, lua.LString(rendered), lua.LString(raw)); err != nil {
147			log.Printf("plugin hook %q error: %v", HookEmailBodyRender, err)
148			continue
149		}
150		ret := L.Get(-1)
151		L.Pop(1)
152		if s, ok := ret.(lua.LString); ok {
153			rendered = string(s)
154		}
155	}
156	return rendered
157}
158
159// CallKeyBinding invokes a plugin key binding callback with the given arguments.
160func (m *Manager) CallKeyBinding(binding KeyBinding, args ...lua.LValue) {
161	if err := m.state.CallByParam(lua.P{
162		Fn:      binding.Fn,
163		NRet:    0,
164		Protect: true,
165	}, args...); err != nil {
166		log.Printf("plugin keybinding %q error: %v", binding.Key, err)
167	}
168}
169
170// EmailToTable converts email fields into a Lua table.
171func (m *Manager) EmailToTable(uid uint32, from string, to []string, subject string, date time.Time, isRead bool, accountID string, folder string) *lua.LTable {
172	L := m.state
173
174	t := L.NewTable()
175	t.RawSetString("uid", lua.LNumber(uid))
176	t.RawSetString("from", lua.LString(from))
177	t.RawSetString("subject", lua.LString(subject))
178	t.RawSetString("date", lua.LString(date.Format(time.RFC3339)))
179	t.RawSetString("is_read", lua.LBool(isRead))
180	t.RawSetString("account_id", lua.LString(accountID))
181	t.RawSetString("folder", lua.LString(folder))
182
183	toTable := L.NewTable()
184	for i, addr := range to {
185		toTable.RawSetInt(i+1, lua.LString(addr))
186	}
187	t.RawSetString("to", toTable)
188
189	return t
190}