1 #ifdef RCS
2 static char rcsid[]="$Id: news.c,v 1.1.1.1 2000/11/13 02:42:45 holsta Exp $";
3 #endif
4 /******************************************************************************
5 * Internetting Cooperating Programmers
6 * ----------------------------------------------------------------------------
7 *
8 * ____ PROJECT
9 * | _ \ __ _ _ __ ___ ___ _ __
10 * | | | |/ _` | '_ \ / __/ _ \ '__|
11 * | |_| | (_| | | | | (_| __/ |
12 * |____/ \__,_|_| |_|\___\___|_| the IRC bot
13 *
14 * All files in this archive are subject to the GNU General Public License.
15 *
16 * $Source: /cvsroot/dancer/dancer/src/news.c,v $
17 * $Revision: 1.1.1.1 $
18 * $Date: 2000/11/13 02:42:45 $
19 * $Author: holsta $
20 * $State: Exp $
21 * $Locker: $
22 *
23 * ---------------------------------------------------------------------------
24 *****************************************************************************/
25
26 /******************************************************************************
27
28 Suggested way of a NEWS system for Dancer
29 =========================================
30
31 The NEWS system is a kind of mass-tell that shows information to persons with
32 a certain level or higher when they join. Each NEWS item is only showed once
33 to each user (if not otherwise explicitly requested). The NEWS items will
34 automatically be removed after a certain number of days in the list.
35 The system will of course prevent too many NEWS items to get displayed if a
36 user joins and X news items are to get displayed where X is larger than a
37 certain limit. The system will then fall back to say that "there are X news
38 items to get displayed, use NEWSREAD to read them".
39
40
41 Suggested commands:
42
43 NEWSADD [ -e expires] [-U] [-p match] <level> <text>
44 Adds a new NEWS item to the list.
45
46 -U marks the news item as... urgent. ;)
47
48 <match> is a wildcard match the receiver must match to receive the news.
49
50 <expires> is the number of days the news is valid. Default will be set in
51 the .config file controlling the bot (internal default 90 days).
52
53 <level> is the least level required for a user to get this info displayed.
54 Level 0 (or 'all') makes all registered users get the info.
55
56 When invoked, this command will output the 'newsid' which can be used to
57 remove it or explicitly read it with NEWSREAD.
58
59 NEWSDEL <id>
60 Removes the specified NEWS item from the list.
61
62 NEWSLIST [number of items]
63 Lists the X latest NEWS items from the list. Detailed with author, date, id
64 and level info.
65
66 NEWSREAD [id] (actually a TELLME alias)
67 With no argument, this presents the oldest unread NEWS entry. With argument
68 it presents the item associated with the specified id.
69 -a reads all items at once
70 -c clear all, make all items marked as read without showing them
71
72 User-data:
73
74 AUTONEWS
75 Controls how a user gets the news presented on join. Three different options:
76 'NEVER' - I don't want to see any news at all (at join)
77 'INFO' - I just want to be told there is news (and number of unread items)
78 '<number>' - I want to see the first (oldest) news item on joining, when the
79 news level is higher than this number (default is 0). If none
80 match but there are unmatched ones, the INFO way will be used
81
82 Info stored per NEWS item:
83
84 <id> - 32 bit number (unique number)
85 <date> - 32 bit number (time of entry, seconds since 1970)
86 <level> - 32 bit number (lowest level required to get this)
87 <author> - string (who wrote this)
88 <flags> - string (URGENT?)
89 <expire> - 32 bit number (days to keep the entry)
90 <match> - string (userhost match pattern)
91 <text> - text to end of line
92
93 NEW ONES
94 <count> - read counter
95 <last> - time of last reading
96
97
98 ******************************************************************************/
99
100 #include "dancer.h"
101 #include "trio.h"
102 #include "strio.h"
103 #include "list.h"
104 #include "user.h"
105 #include "news.h"
106
107 extern char newsfile[];
108 extern long levels[];
109 extern time_t now;
110 extern itemident *current;
111
112 int numNews = 0;
113 itemnews *newsHead = NULL;
114
115
116 static long newsId = 0; /* Use the function to access */
117
118 /* --- NewsAddItem ------------------------------------------------ */
119
FreeNews(void * v)120 void FreeNews(void *v)
121 {
122 itemnews *n;
123
124 snapshot;
125 n = (itemnews *)v;
126 if (n) {
127 if (n->author)
128 StrFree(n->author);
129 if (n->news)
130 StrFree(n->news);
131 if (n->match)
132 StrFree(n->match);
133 numNews--;
134 }
135 }
136
137 /* Returns the newly added newsitem on success or NULL on error */
NewsAddItem(char * author,long level,long id,time_t time,long flags,long expire,char * match,char * news)138 itemnews *NewsAddItem(char *author,
139 long level,
140 long id,
141 time_t time,
142 long flags,
143 long expire,
144 char *match,
145 char *news)
146 {
147 itemnews *n;
148
149 snapshot;
150 n = NewEntry(itemnews);
151 if (n) {
152 InsertLast(newsHead, n);
153 n->level = level;
154 n->id = id;
155 n->time = time;
156 n->flags = flags;
157 n->expire = expire;
158 n->author = StrDuplicate(author);
159 n->match = StrDuplicate(match ? match : "*");
160 n->news = StrDuplicate(news);
161 numNews++;
162 }
163 return n;
164 }
165
NewsSave(void)166 void NewsSave(void)
167 {
168 char tempfile[MIDBUFFER];
169 bool ok = TRUE;
170 itemnews *n, *next;
171 FILE *f;
172
173 snapshot;
174 if (NIL == newsfile[0])
175 return;
176
177 /* Remove expired news */
178 for (n = First(newsHead); n; n = next) {
179 next = Next(n);
180 if ((now - n->time) > (n->expire * SECINDAY))
181 DeleteEntry(newsHead, n, FreeNews);
182 }
183
184 StrFormatMax(tempfile, sizeof(tempfile), "%s~", newsfile);
185
186 f = fopen(tempfile, "w");
187 if (f) {
188 if (0 > fprintf(f, "News seqno: %d\n", newsId)) {
189 ok = FALSE;
190 }
191 else {
192 for (n = First(newsHead); n; n = Next(n)) {
193 if (0 > fprintf(f, "%s %d %d %d %d %d %s :%s\n",
194 n->author, n->level, n->id, n->time,
195 n->flags, n->expire, n->match, n->news)) {
196 ok = FALSE;
197 break;
198 }
199 }
200 }
201 fclose(f);
202
203 if (ok)
204 rename(tempfile, newsfile);
205 }
206 }
207
NewsInit(void)208 void NewsInit(void)
209 {
210 char buf[MAXLINE];
211 char news[BIGBUFFER];
212 char author[BIGBUFFER];
213 char match[MIDBUFFER];
214 long flags;
215 long expire;
216 long id;
217 long level;
218 time_t time;
219 FILE *f;
220
221 snapshot;
222 newsHead = NewList(itemnews);
223
224 if (newsfile[0] && (f = fopen(newsfile, "r"))) {
225 if (fgets(buf, MAXLINE, f)) {
226 StrScan(buf, "News seqno: %d", &newsId);
227 while (fgets(buf, MAXLINE, f)) {
228 news[0] = (char)0;
229 if (8 <= StrScan(buf, "%"BIGBUFFERTXT"s %d %d %d %d %d "
230 "%"MIDBUFFERTXT"s :%"BIGBUFFERTXT"[^\n]",
231 author, &level, &id, &time, &flags, &expire,
232 match, news)) {
233 NewsAddItem(author, level, id, time, flags, expire, match, news);
234 }
235 }
236 }
237 fclose(f);
238 }
239 }
240
NewsCleanup(void)241 void NewsCleanup(void)
242 {
243 snapshot;
244 NewsSave();
245 DeleteList(newsHead, FreeNews);
246 }
247
248 /* returns TRUE if something went wrong */
249
NewsDelete(long id,long flags)250 bool NewsDelete(long id, long flags)
251 {
252 itemnews *n;
253
254 snapshot;
255 for (n = First(newsHead); n; n = Next(n)) {
256 if (n->id == id) {
257 DeleteEntry(newsHead, n, FreeNews);
258 return FALSE;
259 }
260 }
261 return TRUE;
262 }
263
264 /***********************************************************************
265 *
266 * NewsRead()
267 * ==========
268 * Returns the number of unread news including the pointed one.
269 *
270 * The input ID is the high water mark to use.
271 *
272 * The newsp pointer points to a 'itemnews' pointer that will point out
273 * the current news item to read.
274 *
275 ***********************************************************************/
276
NewsRead(long id,itemnews ** newsp)277 long NewsRead(long id, itemnews **newsp)
278 {
279 long unread = 0;
280 itemnews *n;
281
282 snapshot;
283 if (newsp)
284 *newsp = NULL; /* default to nothing */
285
286 for (n = First(newsHead); n; n = Next(n)) {
287 if ((n->id > id) && (current->level >= n->level)) {
288 if (Match(current->host, n->match)) {
289 if (newsp && !*newsp)
290 *newsp = n;
291 unread++;
292 }
293 }
294 }
295 return unread;
296 }
297
NewsID(void)298 long NewsID(void)
299 {
300 return ++newsId;
301 }
302
303 /***********************************************************************
304 *
305 * NewsReport()
306 *
307 * Report news for a user at join.
308 *
309 **********************************************************************/
310
NewsReport(itemident * ident)311 void NewsReport(itemident *ident)
312 {
313 itemnews *n;
314
315 snapshot;
316 if (NewsRead(ident->user->newsid, &n)) {
317 Sendf(ident->nick, GetText(msg_news_report),
318 n->news,
319 n->author,
320 TimeAgo(n->time));
321 ident->user->newsid = n->id;
322 }
323 }
324
CounterNews(void)325 static long CounterNews(void)
326 {
327 long count = 0;
328 itemnews *n;
329
330 snapshot;
331 for (n = First(newsHead); n; n = Next(n)) {
332 if ((current->level < n->level) ||
333 ((current->level <= LEVELCHANOP) &&
334 !Match(current->host, n->match)))
335 continue;
336 count++;
337 }
338 return count;
339 }
340
NewsList(char * from,long lines)341 void NewsList(char *from, long lines)
342 {
343 int max;
344 long count = 0;
345 itemnews *n;
346
347 snapshot;
348 max = CounterNews();
349
350 for (n = First(newsHead); n; n = Next(n)) {
351 if ((n->level > current->level) ||
352 ((current->level <= LEVELCHANOP) && !Match(n->match, current->host)))
353 continue;
354
355 if (max <= (count + lines)) {
356 Sendf(from, GetText(msg_news_list),
357 (n->id > current->user->newsid) ? GetText(msg_unread) : "",
358 n->id, n->level, n->match, n->author, TimeAgo(n->time));
359 }
360
361 count++;
362 }
363
364 if (0 == count)
365 Sendf(from, GetText(msg_no_news));
366 }
367