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