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}