Fossil SCM

Add the ability to designate a timeline robot user. If such a user exists, and if chat is enabled, then notifications of all timeline events appear in chat, from the robot user.

drh 2022-09-13 20:11 trunk
Commit e9d7cf3e92a3d11fafd83a0ba200e4e66fc17fdd7e2b490362f310f413dcc4cf
3 files changed +34 -13 +37 -5 +8
+34 -13
--- src/alerts.c
+++ src/alerts.c
@@ -143,36 +143,57 @@
143143
);
144144
}
145145
146146
/*
147147
** Enable triggers that automatically populate the pending_alert
148
-** table.
148
+** table. (Later:) Also add triggers that automatically relay timeline
149
+** events to chat, if chat is configured for that.
149150
*/
150151
void alert_create_trigger(void){
151
- if( !db_table_exists("repository","pending_alert") ) return;
152
- db_multi_exec(
153
- "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */
154
- /* "DROP TRIGGER IF EXISTS repository.email_trigger1;\n" Very old legacy */
155
- "CREATE TRIGGER temp.alert_trigger1\n"
156
- "AFTER INSERT ON repository.event BEGIN\n"
157
- " INSERT INTO pending_alert(eventid)\n"
158
- " SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n"
159
- " ON CONFLICT(eventId) DO NOTHING;\n"
160
- "END;"
161
- );
152
+ if( db_table_exists("repository","pending_alert") ){
153
+ db_multi_exec(
154
+ "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */
155
+ "CREATE TRIGGER temp.alert_trigger1\n"
156
+ "AFTER INSERT ON repository.event BEGIN\n"
157
+ " INSERT INTO pending_alert(eventid)\n"
158
+ " SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n"
159
+ " ON CONFLICT(eventId) DO NOTHING;\n"
160
+ "END;"
161
+ );
162
+ }
163
+ if( db_table_exists("repository","chat") ){
164
+ const char *zChatUser = db_get("chat-timeline-user", 0);
165
+ char *zChatCaps;
166
+ if( zChatUser==0 || zChatUser[0]==0 ) return;
167
+ zChatCaps = db_text(0, "SELECT cap FROM user WHERE login=%Q", zChatUser);
168
+ if( zChatCaps && strchr(zChatCaps,'C')!=0 ){
169
+ db_multi_exec(
170
+ "CREATE TRIGGER temp.chat_trigger1\n"
171
+ "AFTER INSERT ON repository.event BEGIN\n"
172
+ " INSERT INTO chat(mtime,xfrom,xmsg)"
173
+ " SELECT julianday(), %Q,"
174
+ " format('[%%.12s]: %%s', blob.uuid, new.comment)"
175
+ " FROM blob WHERE rid=new.objid;\n"
176
+ "END;\n",
177
+ zChatUser
178
+ );
179
+ }
180
+ fossil_free(zChatCaps);
181
+ }
162182
}
163183
164184
/*
165
-** Disable triggers the event_pending triggers.
185
+** Disable triggers the event_pending and chat triggers.
166186
**
167187
** This must be called before rebuilding the EVENT table, for example
168188
** via the "fossil rebuild" command.
169189
*/
170190
void alert_drop_trigger(void){
171191
db_multi_exec(
172192
"DROP TRIGGER IF EXISTS temp.alert_trigger1;\n"
173193
"DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */
194
+ "DROP TRIGGER IF EXISTS temp.chat_trigger1;\n"
174195
);
175196
}
176197
177198
/*
178199
** Return true if email alerts are active.
179200
--- src/alerts.c
+++ src/alerts.c
@@ -143,36 +143,57 @@
143 );
144 }
145
146 /*
147 ** Enable triggers that automatically populate the pending_alert
148 ** table.
 
149 */
150 void alert_create_trigger(void){
151 if( !db_table_exists("repository","pending_alert") ) return;
152 db_multi_exec(
153 "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */
154 /* "DROP TRIGGER IF EXISTS repository.email_trigger1;\n" Very old legacy */
155 "CREATE TRIGGER temp.alert_trigger1\n"
156 "AFTER INSERT ON repository.event BEGIN\n"
157 " INSERT INTO pending_alert(eventid)\n"
158 " SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n"
159 " ON CONFLICT(eventId) DO NOTHING;\n"
160 "END;"
161 );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162 }
163
164 /*
165 ** Disable triggers the event_pending triggers.
166 **
167 ** This must be called before rebuilding the EVENT table, for example
168 ** via the "fossil rebuild" command.
169 */
170 void alert_drop_trigger(void){
171 db_multi_exec(
172 "DROP TRIGGER IF EXISTS temp.alert_trigger1;\n"
173 "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */
 
174 );
175 }
176
177 /*
178 ** Return true if email alerts are active.
179
--- src/alerts.c
+++ src/alerts.c
@@ -143,36 +143,57 @@
143 );
144 }
145
146 /*
147 ** Enable triggers that automatically populate the pending_alert
148 ** table. (Later:) Also add triggers that automatically relay timeline
149 ** events to chat, if chat is configured for that.
150 */
151 void alert_create_trigger(void){
152 if( db_table_exists("repository","pending_alert") ){
153 db_multi_exec(
154 "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */
155 "CREATE TRIGGER temp.alert_trigger1\n"
156 "AFTER INSERT ON repository.event BEGIN\n"
157 " INSERT INTO pending_alert(eventid)\n"
158 " SELECT printf('%%.1c%%d',new.type,new.objid) WHERE true\n"
159 " ON CONFLICT(eventId) DO NOTHING;\n"
160 "END;"
161 );
162 }
163 if( db_table_exists("repository","chat") ){
164 const char *zChatUser = db_get("chat-timeline-user", 0);
165 char *zChatCaps;
166 if( zChatUser==0 || zChatUser[0]==0 ) return;
167 zChatCaps = db_text(0, "SELECT cap FROM user WHERE login=%Q", zChatUser);
168 if( zChatCaps && strchr(zChatCaps,'C')!=0 ){
169 db_multi_exec(
170 "CREATE TRIGGER temp.chat_trigger1\n"
171 "AFTER INSERT ON repository.event BEGIN\n"
172 " INSERT INTO chat(mtime,xfrom,xmsg)"
173 " SELECT julianday(), %Q,"
174 " format('[%%.12s]: %%s', blob.uuid, new.comment)"
175 " FROM blob WHERE rid=new.objid;\n"
176 "END;\n",
177 zChatUser
178 );
179 }
180 fossil_free(zChatCaps);
181 }
182 }
183
184 /*
185 ** Disable triggers the event_pending and chat triggers.
186 **
187 ** This must be called before rebuilding the EVENT table, for example
188 ** via the "fossil rebuild" command.
189 */
190 void alert_drop_trigger(void){
191 db_multi_exec(
192 "DROP TRIGGER IF EXISTS temp.alert_trigger1;\n"
193 "DROP TRIGGER IF EXISTS repository.alert_trigger1;\n" /* Purge legacy */
194 "DROP TRIGGER IF EXISTS temp.chat_trigger1;\n"
195 );
196 }
197
198 /*
199 ** Return true if email alerts are active.
200
+37 -5
--- src/chat.c
+++ src/chat.c
@@ -127,10 +127,23 @@
127127
** SETTING: chat-alert-sound width=10
128128
**
129129
** This is the name of the builtin sound file to use for the alert tone.
130130
** The value must be the name of a builtin WAV file.
131131
*/
132
+/*
133
+** SETTING: chat-timeline-user width=10
134
+**
135
+** If this setting is defined and is not an empty string and its value is
136
+** the name of a user that hash chat privilege (privilege letter "C"), then
137
+** timeline events are posted to the chat as they arrive. The synthesized
138
+** chat messages appear to come from the user identified by this setting,
139
+** not the user on the timeline event.
140
+**
141
+** All chat messages that come from the chat-timeline-user are interpreted
142
+** as text/x-fossil-wiki instead of as text/markdown. For this reason,
143
+** the chat-timeline-user name should probably not be a real user.
144
+*/
132145
/*
133146
** WEBPAGE: chat loadavg-exempt
134147
**
135148
** Start up a browser-based chat session.
136149
**
@@ -415,14 +428,22 @@
415428
** zMsg to HTML.
416429
**
417430
** Space to hold the returned string is obtained from fossil_malloc()
418431
** and must be freed by the caller.
419432
*/
420
-static char *chat_format_to_html(const char *zMsg){
433
+static char *chat_format_to_html(const char *zMsg, int isWiki){
421434
Blob out;
422435
blob_init(&out, "", 0);
423
- if(*zMsg){
436
+ if( zMsg==0 || zMsg[0]==0 ){
437
+ /* No-op */
438
+ }else if( isWiki ){
439
+ /* Used for chat-timeline-user. The zMsg is text/x-fossil-wiki. */
440
+ Blob bIn;
441
+ blob_init(&bIn, zMsg, (int)strlen(zMsg));
442
+ wiki_convert(&bIn, &out, WIKI_INLINE);
443
+ }else{
444
+ /* The common case: zMsg is text/markdown */
424445
Blob bIn;
425446
blob_init(&bIn, zMsg, (int)strlen(zMsg));
426447
markdown_to_html(&bIn, NULL, &out);
427448
}
428449
return blob_str(&out);
@@ -442,11 +463,11 @@
442463
int i;
443464
char *zOut;
444465
db_find_and_open_repository(0,0);
445466
g.perm.Hyperlink = 1;
446467
for(i=0; i<g.argc; i++){
447
- zOut = chat_format_to_html(g.argv[i]);
468
+ zOut = chat_format_to_html(g.argv[i], 0);
448469
fossil_print("[%d]: %s\n", i, zOut);
449470
fossil_free(zOut);
450471
}
451472
}
452473
@@ -545,22 +566,26 @@
545566
void chat_poll_webpage(void){
546567
Blob json; /* The json to be constructed and returned */
547568
sqlite3_int64 dataVersion; /* Data version. Used for polling. */
548569
const int iDelay = 1000; /* Delay until next poll (milliseconds) */
549570
int nDelay; /* Maximum delay.*/
571
+ const char *zChatUser; /* chat-timeline-user */
572
+ int isWiki = 0; /* True if chat message is x-fossil-wiki */
550573
int msgid = atoi(PD("name","0"));
551574
const int msgBefore = atoi(PD("before","0"));
552575
int nLimit = msgBefore>0 ? atoi(PD("n","0")) : 0;
553576
const int bRaw = P("raw")!=0;
577
+
554578
Blob sql = empty_blob;
555579
Stmt q1;
556580
nDelay = db_get_int("chat-poll-timeout",420); /* Default about 7 minutes */
557581
login_check_credentials();
558582
if( !g.perm.Chat ) {
559583
chat_emit_permissions_error(1);
560584
return;
561585
}
586
+ zChatUser = db_get("chat-timeline-user",0);
562587
chat_create_tables();
563588
cgi_set_content_type("application/json");
564589
dataVersion = db_int64(0, "PRAGMA data_version");
565590
blob_append_sql(&sql,
566591
"SELECT msgid, datetime(mtime), xfrom, xmsg, length(file),"
@@ -619,21 +644,23 @@
619644
blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
620645
}
621646
blob_append(&json, "\"xfrom\":", -1);
622647
if(zFrom){
623648
blob_appendf(&json, "%!j,", zFrom);
649
+ isWiki = fossil_strcmp(zFrom,zChatUser)==0;
624650
}else{
625651
/* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */
626652
blob_appendf(&json, "null,");
653
+ isWiki = 0;
627654
}
628655
blob_appendf(&json, "\"uclr\":%!j,",
629656
user_color(zFrom ? zFrom : "nobody"));
630657
631658
if(bRaw){
632659
blob_appendf(&json, "\"xmsg\":%!j,", zRawMsg);
633660
}else{
634
- zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
661
+ zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "", isWiki);
635662
blob_appendf(&json, "\"xmsg\":%!j,", zMsg);
636663
fossil_free(zMsg);
637664
}
638665
639666
if( nByte==0 ){
@@ -686,16 +713,19 @@
686713
*/
687714
void chat_fetch_one(void){
688715
Blob json = empty_blob; /* The json to be constructed and returned */
689716
const int fRaw = PD("raw",0)!=0;
690717
const int msgid = atoi(PD("name","0"));
718
+ const char *zChatUser;
719
+ int isWiki;
691720
Stmt q;
692721
login_check_credentials();
693722
if( !g.perm.Chat ) {
694723
chat_emit_permissions_error(0);
695724
return;
696725
}
726
+ zChatUser = db_get("chat-timeline-user",0);
697727
chat_create_tables();
698728
cgi_set_content_type("application/json");
699729
db_prepare(&q,
700730
"SELECT datetime(mtime), xfrom, xmsg, length(file),"
701731
" fname, fmime, lmtime"
@@ -716,21 +746,23 @@
716746
blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
717747
}
718748
blob_append(&json, "\"xfrom\":", -1);
719749
if(zFrom){
720750
blob_appendf(&json, "%!j,", zFrom);
751
+ isWiki = fossil_strcmp(zFrom, zChatUser);
721752
}else{
722753
/* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */
723754
blob_appendf(&json, "null,");
755
+ isWiki = 0;
724756
}
725757
blob_appendf(&json, "\"uclr\":%!j,",
726758
user_color(zFrom ? zFrom : "nobody"));
727759
blob_append(&json,"\"xmsg\":", 7);
728760
if(fRaw){
729761
blob_appendf(&json, "%!j,", zRawMsg);
730762
}else{
731
- char * zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
763
+ char * zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "", isWiki);
732764
blob_appendf(&json, "%!j,", zMsg);
733765
fossil_free(zMsg);
734766
}
735767
if( nByte==0 ){
736768
blob_appendf(&json, "\"fsize\":0");
737769
--- src/chat.c
+++ src/chat.c
@@ -127,10 +127,23 @@
127 ** SETTING: chat-alert-sound width=10
128 **
129 ** This is the name of the builtin sound file to use for the alert tone.
130 ** The value must be the name of a builtin WAV file.
131 */
 
 
 
 
 
 
 
 
 
 
 
 
 
132 /*
133 ** WEBPAGE: chat loadavg-exempt
134 **
135 ** Start up a browser-based chat session.
136 **
@@ -415,14 +428,22 @@
415 ** zMsg to HTML.
416 **
417 ** Space to hold the returned string is obtained from fossil_malloc()
418 ** and must be freed by the caller.
419 */
420 static char *chat_format_to_html(const char *zMsg){
421 Blob out;
422 blob_init(&out, "", 0);
423 if(*zMsg){
 
 
 
 
 
 
 
 
424 Blob bIn;
425 blob_init(&bIn, zMsg, (int)strlen(zMsg));
426 markdown_to_html(&bIn, NULL, &out);
427 }
428 return blob_str(&out);
@@ -442,11 +463,11 @@
442 int i;
443 char *zOut;
444 db_find_and_open_repository(0,0);
445 g.perm.Hyperlink = 1;
446 for(i=0; i<g.argc; i++){
447 zOut = chat_format_to_html(g.argv[i]);
448 fossil_print("[%d]: %s\n", i, zOut);
449 fossil_free(zOut);
450 }
451 }
452
@@ -545,22 +566,26 @@
545 void chat_poll_webpage(void){
546 Blob json; /* The json to be constructed and returned */
547 sqlite3_int64 dataVersion; /* Data version. Used for polling. */
548 const int iDelay = 1000; /* Delay until next poll (milliseconds) */
549 int nDelay; /* Maximum delay.*/
 
 
550 int msgid = atoi(PD("name","0"));
551 const int msgBefore = atoi(PD("before","0"));
552 int nLimit = msgBefore>0 ? atoi(PD("n","0")) : 0;
553 const int bRaw = P("raw")!=0;
 
554 Blob sql = empty_blob;
555 Stmt q1;
556 nDelay = db_get_int("chat-poll-timeout",420); /* Default about 7 minutes */
557 login_check_credentials();
558 if( !g.perm.Chat ) {
559 chat_emit_permissions_error(1);
560 return;
561 }
 
562 chat_create_tables();
563 cgi_set_content_type("application/json");
564 dataVersion = db_int64(0, "PRAGMA data_version");
565 blob_append_sql(&sql,
566 "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file),"
@@ -619,21 +644,23 @@
619 blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
620 }
621 blob_append(&json, "\"xfrom\":", -1);
622 if(zFrom){
623 blob_appendf(&json, "%!j,", zFrom);
 
624 }else{
625 /* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */
626 blob_appendf(&json, "null,");
 
627 }
628 blob_appendf(&json, "\"uclr\":%!j,",
629 user_color(zFrom ? zFrom : "nobody"));
630
631 if(bRaw){
632 blob_appendf(&json, "\"xmsg\":%!j,", zRawMsg);
633 }else{
634 zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
635 blob_appendf(&json, "\"xmsg\":%!j,", zMsg);
636 fossil_free(zMsg);
637 }
638
639 if( nByte==0 ){
@@ -686,16 +713,19 @@
686 */
687 void chat_fetch_one(void){
688 Blob json = empty_blob; /* The json to be constructed and returned */
689 const int fRaw = PD("raw",0)!=0;
690 const int msgid = atoi(PD("name","0"));
 
 
691 Stmt q;
692 login_check_credentials();
693 if( !g.perm.Chat ) {
694 chat_emit_permissions_error(0);
695 return;
696 }
 
697 chat_create_tables();
698 cgi_set_content_type("application/json");
699 db_prepare(&q,
700 "SELECT datetime(mtime), xfrom, xmsg, length(file),"
701 " fname, fmime, lmtime"
@@ -716,21 +746,23 @@
716 blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
717 }
718 blob_append(&json, "\"xfrom\":", -1);
719 if(zFrom){
720 blob_appendf(&json, "%!j,", zFrom);
 
721 }else{
722 /* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */
723 blob_appendf(&json, "null,");
 
724 }
725 blob_appendf(&json, "\"uclr\":%!j,",
726 user_color(zFrom ? zFrom : "nobody"));
727 blob_append(&json,"\"xmsg\":", 7);
728 if(fRaw){
729 blob_appendf(&json, "%!j,", zRawMsg);
730 }else{
731 char * zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "");
732 blob_appendf(&json, "%!j,", zMsg);
733 fossil_free(zMsg);
734 }
735 if( nByte==0 ){
736 blob_appendf(&json, "\"fsize\":0");
737
--- src/chat.c
+++ src/chat.c
@@ -127,10 +127,23 @@
127 ** SETTING: chat-alert-sound width=10
128 **
129 ** This is the name of the builtin sound file to use for the alert tone.
130 ** The value must be the name of a builtin WAV file.
131 */
132 /*
133 ** SETTING: chat-timeline-user width=10
134 **
135 ** If this setting is defined and is not an empty string and its value is
136 ** the name of a user that hash chat privilege (privilege letter "C"), then
137 ** timeline events are posted to the chat as they arrive. The synthesized
138 ** chat messages appear to come from the user identified by this setting,
139 ** not the user on the timeline event.
140 **
141 ** All chat messages that come from the chat-timeline-user are interpreted
142 ** as text/x-fossil-wiki instead of as text/markdown. For this reason,
143 ** the chat-timeline-user name should probably not be a real user.
144 */
145 /*
146 ** WEBPAGE: chat loadavg-exempt
147 **
148 ** Start up a browser-based chat session.
149 **
@@ -415,14 +428,22 @@
428 ** zMsg to HTML.
429 **
430 ** Space to hold the returned string is obtained from fossil_malloc()
431 ** and must be freed by the caller.
432 */
433 static char *chat_format_to_html(const char *zMsg, int isWiki){
434 Blob out;
435 blob_init(&out, "", 0);
436 if( zMsg==0 || zMsg[0]==0 ){
437 /* No-op */
438 }else if( isWiki ){
439 /* Used for chat-timeline-user. The zMsg is text/x-fossil-wiki. */
440 Blob bIn;
441 blob_init(&bIn, zMsg, (int)strlen(zMsg));
442 wiki_convert(&bIn, &out, WIKI_INLINE);
443 }else{
444 /* The common case: zMsg is text/markdown */
445 Blob bIn;
446 blob_init(&bIn, zMsg, (int)strlen(zMsg));
447 markdown_to_html(&bIn, NULL, &out);
448 }
449 return blob_str(&out);
@@ -442,11 +463,11 @@
463 int i;
464 char *zOut;
465 db_find_and_open_repository(0,0);
466 g.perm.Hyperlink = 1;
467 for(i=0; i<g.argc; i++){
468 zOut = chat_format_to_html(g.argv[i], 0);
469 fossil_print("[%d]: %s\n", i, zOut);
470 fossil_free(zOut);
471 }
472 }
473
@@ -545,22 +566,26 @@
566 void chat_poll_webpage(void){
567 Blob json; /* The json to be constructed and returned */
568 sqlite3_int64 dataVersion; /* Data version. Used for polling. */
569 const int iDelay = 1000; /* Delay until next poll (milliseconds) */
570 int nDelay; /* Maximum delay.*/
571 const char *zChatUser; /* chat-timeline-user */
572 int isWiki = 0; /* True if chat message is x-fossil-wiki */
573 int msgid = atoi(PD("name","0"));
574 const int msgBefore = atoi(PD("before","0"));
575 int nLimit = msgBefore>0 ? atoi(PD("n","0")) : 0;
576 const int bRaw = P("raw")!=0;
577
578 Blob sql = empty_blob;
579 Stmt q1;
580 nDelay = db_get_int("chat-poll-timeout",420); /* Default about 7 minutes */
581 login_check_credentials();
582 if( !g.perm.Chat ) {
583 chat_emit_permissions_error(1);
584 return;
585 }
586 zChatUser = db_get("chat-timeline-user",0);
587 chat_create_tables();
588 cgi_set_content_type("application/json");
589 dataVersion = db_int64(0, "PRAGMA data_version");
590 blob_append_sql(&sql,
591 "SELECT msgid, datetime(mtime), xfrom, xmsg, length(file),"
@@ -619,21 +644,23 @@
644 blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
645 }
646 blob_append(&json, "\"xfrom\":", -1);
647 if(zFrom){
648 blob_appendf(&json, "%!j,", zFrom);
649 isWiki = fossil_strcmp(zFrom,zChatUser)==0;
650 }else{
651 /* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */
652 blob_appendf(&json, "null,");
653 isWiki = 0;
654 }
655 blob_appendf(&json, "\"uclr\":%!j,",
656 user_color(zFrom ? zFrom : "nobody"));
657
658 if(bRaw){
659 blob_appendf(&json, "\"xmsg\":%!j,", zRawMsg);
660 }else{
661 zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "", isWiki);
662 blob_appendf(&json, "\"xmsg\":%!j,", zMsg);
663 fossil_free(zMsg);
664 }
665
666 if( nByte==0 ){
@@ -686,16 +713,19 @@
713 */
714 void chat_fetch_one(void){
715 Blob json = empty_blob; /* The json to be constructed and returned */
716 const int fRaw = PD("raw",0)!=0;
717 const int msgid = atoi(PD("name","0"));
718 const char *zChatUser;
719 int isWiki;
720 Stmt q;
721 login_check_credentials();
722 if( !g.perm.Chat ) {
723 chat_emit_permissions_error(0);
724 return;
725 }
726 zChatUser = db_get("chat-timeline-user",0);
727 chat_create_tables();
728 cgi_set_content_type("application/json");
729 db_prepare(&q,
730 "SELECT datetime(mtime), xfrom, xmsg, length(file),"
731 " fname, fmime, lmtime"
@@ -716,21 +746,23 @@
746 blob_appendf(&json, "\"lmtime\":%!j,", zLMtime);
747 }
748 blob_append(&json, "\"xfrom\":", -1);
749 if(zFrom){
750 blob_appendf(&json, "%!j,", zFrom);
751 isWiki = fossil_strcmp(zFrom, zChatUser);
752 }else{
753 /* see https://fossil-scm.org/forum/forumpost/e0be0eeb4c */
754 blob_appendf(&json, "null,");
755 isWiki = 0;
756 }
757 blob_appendf(&json, "\"uclr\":%!j,",
758 user_color(zFrom ? zFrom : "nobody"));
759 blob_append(&json,"\"xmsg\":", 7);
760 if(fRaw){
761 blob_appendf(&json, "%!j,", zRawMsg);
762 }else{
763 char * zMsg = chat_format_to_html(zRawMsg ? zRawMsg : "", isWiki);
764 blob_appendf(&json, "%!j,", zMsg);
765 fossil_free(zMsg);
766 }
767 if( nByte==0 ){
768 blob_appendf(&json, "\"fsize\":0");
769
--- src/setup.c
+++ src/setup.c
@@ -1351,10 +1351,18 @@
13511351
@ default setting of approximately 7 minutes works well on many systems.
13521352
@ Shorter delays might be required on installations that use proxies
13531353
@ or web-servers with short timeouts. For best efficiency, this value
13541354
@ should be larger rather than smaller.
13551355
@ (Property: "chat-poll-timeout")</p>
1356
+ @ <hr />
1357
+ entry_attribute("Chat Timeline Robot Username", 15,
1358
+ "chat-timeout-user", "chatrobot", "", 0);
1359
+ @ <p>If this setting is the name of a user that has "C" privilege, then
1360
+ @ all changes to the timeline will result in a new chat message from this
1361
+ @ user. Leave this blank to omit notifications of check-ins and other
1362
+ @ timeline events from appearing in chat.
1363
+ @ (Property: "chat-timeout-user")</p>
13561364
@ <hr />
13571365
13581366
multiple_choice_attribute("Alert sound",
13591367
"chat-alert-sound", "snd", azAlerts[0],
13601368
count(azAlerts)/2, azAlerts);
13611369
--- src/setup.c
+++ src/setup.c
@@ -1351,10 +1351,18 @@
1351 @ default setting of approximately 7 minutes works well on many systems.
1352 @ Shorter delays might be required on installations that use proxies
1353 @ or web-servers with short timeouts. For best efficiency, this value
1354 @ should be larger rather than smaller.
1355 @ (Property: "chat-poll-timeout")</p>
 
 
 
 
 
 
 
 
1356 @ <hr />
1357
1358 multiple_choice_attribute("Alert sound",
1359 "chat-alert-sound", "snd", azAlerts[0],
1360 count(azAlerts)/2, azAlerts);
1361
--- src/setup.c
+++ src/setup.c
@@ -1351,10 +1351,18 @@
1351 @ default setting of approximately 7 minutes works well on many systems.
1352 @ Shorter delays might be required on installations that use proxies
1353 @ or web-servers with short timeouts. For best efficiency, this value
1354 @ should be larger rather than smaller.
1355 @ (Property: "chat-poll-timeout")</p>
1356 @ <hr />
1357 entry_attribute("Chat Timeline Robot Username", 15,
1358 "chat-timeout-user", "chatrobot", "", 0);
1359 @ <p>If this setting is the name of a user that has "C" privilege, then
1360 @ all changes to the timeline will result in a new chat message from this
1361 @ user. Leave this blank to omit notifications of check-ins and other
1362 @ timeline events from appearing in chat.
1363 @ (Property: "chat-timeout-user")</p>
1364 @ <hr />
1365
1366 multiple_choice_attribute("Alert sound",
1367 "chat-alert-sound", "snd", azAlerts[0],
1368 count(azAlerts)/2, azAlerts);
1369

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button