Skip to content
Snippets Groups Projects
Select Git revision
  • dailybuild_linux-x64
  • dailybuild_win32
  • master default protected
  • dd_msg_reader_use_dd_msg_area_chooser_and_area_sort_update
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

001-forum.ssjs

Blame
  • 001-forum.ssjs 25.21 KiB
    //Forum
    
    require('sbbsdefs.js', 'SYS_CLOSED');
    load(settings.web_lib + 'forum.js');
    load(settings.web_lib + 'avatars.js');
    
    var strings = {
    	script : {
    		open : '<script type="text/javascript">',
    		thread_navigation : 'threadNav();',
    		interval : 'setInterval(function () { %s }, %s);',
    		vote_functions : 'enableVoteButtonHandlers("%s");',
    		vote_refresh_thread : 'getVotesInThread("%s", %s)',
    		vote_refresh_threads : 'getVotesInThreads("%s")',
    		get_group_unread : 'getGroupUnreadCounts()',
    		get_sub_unread : 'getSubUnreadCounts("%s")',
    		poll_control : 'pollControl("%s", %s)',
    		get_poll_data : 'getPollData("%s", %s)',
    		close : '</script>'
    	},
    	notice_box : '<div id="noticebox" class="alert alert-warning">%s<script type="text/javascript">$("#noticebox").fadeOut(3000,function(){$("#noticebox").remove();});</script></div>',
    	group_list : {
    		breadcrumb : '<ol class="breadcrumb"><li><a href="./?page=%s">Forum</a></li></ol>',
    		container : {
    			open : '<div id="forum-list-container" class="list-group">',
    			close : '</div>'
    		},
    		item : '<a href="./?page=%s&amp;group=%s" class="list-group-item striped"><h3><strong>%s</strong></h3><span title="Unread messages (other)" class="badge ignored" id="badge-ignored-%s"></span><span title="Unread messages (scanned subs)" class="badge scanned" id="badge-scanned-%s"></span><p>%s : %s sub-boards</p></a>'
    	},
    	sub_list : {
    		breadcrumb : '<ol class="breadcrumb"><li><a href="./?page=%s">Forum</a></li><li><a href="./?page=%s&amp;group=%s">%s</a></li></ol>',
    		container : {
    			open : '<div id="forum-list-container" class="list-group">',
    			close : '</div>'
    		},
    		item : '<a href="./?page=%s&amp;sub=%s" class="list-group-item striped%s"><h4><strong>%s</strong></h4><span title="Unread messages" class="badge %s" id="badge-%s"></span><p>%s</p></a>'
    	},
    	thread_list : {
    		breadcrumb : '<ol class="breadcrumb"><li><a href="./?page=%s">Forum</a></li><li><a href="./?page=%s&amp;group=%s">%s</a></li><li><a href="./?page=%s&amp;sub=%s">%s</a></li></ol>',
    		container : {
    			open : '<div id="forum-list-container" class="list-group">',
    			close : '</div>'
    		},
    		item : {
    			open : '<a href="./?page=%s&amp;sub=%s&amp;thread=%s" class="list-group-item striped"><div class="row">',
    			details : {
    				open : '<div class="col-sm-9">',
    				info : '<strong>%s</strong><p>By <strong>%s</strong> on %s</p><p>Latest reply by <strong>%s</strong> on %s</p>',
    				close : '</div>'
    			},
    			badges : {
    				open : '<div class="col-sm-3"><div class="pull-right">',
    				unread : '<span title="Unread messages" class="badge%s" id="badge-%s">%s</span>',
    				votes : {
    					up : '<span id="uv-%s" title="Upvotes - Parent / Thread Total" class="badge upvote-bg"><span class="glyphicon glyphicon-arrow-up"></span><span id="uv-count-%s">%s / %s</span></span>&nbsp;',
    					down : '<span id="dv-%s" title="Downvotes - Parent / Thread Total" class="badge downvote-bg"><span class="glyphicon glyphicon-arrow-down"></span><span id="dv-count-%s">%s / %s</span></span>',
    				},
    				poll : '<span class="badge">POLL</span>',
    				close : '</div></div>'
    			},
    			close : '</div></a>'
    		},
    		controls : {
    			post : '<button class="btn btn-default icon" aria-label="Post a new message" title="Post a new message" onclick="addNew(\'%s\')"><span class="glyphicon glyphicon-pencil"></span></button>',
    			post_poll : '<button class="btn btn-default icon" aria-label="Post a new poll" title="Post a new poll" onclick="addPoll(\'%s\')"><span class="glyphicon glyphicon-list-alt"></span></button>',
    			scan_new : '<div class="pull-right"><button id="scan-cfg-new" class="btn %s icon" aria-label="Scan for new messages" title="Scan for new messages" onclick="setScanCfg(\'%s\', 1)"><span class="glyphicon glyphicon-ok-sign"></span></button>',
    			scan_you : '<button id="scan-cfg-youonly" class="btn %s icon" aria-label="Scan for new messages to you only" title="Scan for new messages to you only" onclick="setScanCfg(\'%s\', 2)"><span class="glyphicon glyphicon-user"></span></button>',
    			scan_off : '<button id="scan-cfg-off" class="btn %s icon" aria-label="Do not scan this sub" title="Do not scan this sub" onclick="setScanCfg(\'%s\', 0)"><span class="glyphicon glyphicon-ban-circle"></span></button></div>'
    		}
    	},
    	thread_view : {
    		breadcrumb : '<ol class="breadcrumb"><li><a href="./?page=%s">Forum</a></li><li><a href="./?page=%s&amp;group=%s">%s</a></li><li><a href="./?page=%s&amp;sub=%s">%s</a></li></ol>',
    		jump_unread : '<div id="jump-unread-container" style="margin-bottom:1em;" hidden><a class="btn btn-default" id="jump-unread" title="Jump to first unread message" href="#"><span class="glyphicon glyphicon-star"></span></a></div>',
    		set_unread : '$("#jump-unread").attr("href", "#%s");$("#jump-unread-container").attr("hidden", false);',
    		container : {
    			open : '<ul id="forum-list-container" class="list-group">',
    			close : '</ul>'
    		},
    		subject : '<h4><strong>%s</strong></h4>'
    	},
    	message : {
    		anchor : '<a id="%s"></a>',
    		open : '<li class="list-group-item striped" id="li-%s">',
    		header : {
    			open : '<div class="row">',
    			details : {
    				open : '<div class="col-sm-9">',
    				unread : '<span title="Unread" class="glyphicon glyphicon-star"></span>',
                    avatar : '<div name="avatar-%s" class="pull-left" style="padding-right:1em;"></div>',
    				info : 'From <strong>%s</strong>%s to <strong>%s</strong> on %s',
    				close : '</div>'
    			},
    			voting : {
    				open : '<div class="col-sm-3"><div class="pull-right">',
    				poll : '<span class="badge">POLL</span>',
    				buttons : {
    					up : '<button id="uv-%s" class="btn-uv btn btn-default icon %s"><span title="Upvotes" class="glyphicon glyphicon-arrow-up"></span><span id="uv-count-%s" title="Upvotes">%s</span></button>',
    					down : '<button id="dv-%s" class="btn-dv btn btn-default icon %s"><span title="Downvotes" class="glyphicon glyphicon-arrow-down"></span><span id="dv-count-%s" title="Downvotes">%s</span></button>'
    				},
    				close : '</div></div>'
    			},
    			close : '</div>'
    		},
    		body : {
    			open : '<div class="message" id="message-%s">',
    			poll : {
    				container : {
    					open : '<ul class="list-group">',
    					close : '</ul>'
    				},
    				title : '<li class="list-group-item"><strong>%s</strong></li>',
    				comment : '<li class="list-group-item">%s</li>',
    				answer : {
    					open : '<li class="list-group-item %s"><label><input type="%s" name="poll-%s" value="%s">%s</label> <div id="poll-count-%s-%s" class="pull-right">%s</div></li>',
    					closed : '<li class="list-group-item checkbox%s">%s) %s <div id="poll-count-%s-%s" class="pull-right">%s</div></li>'
    				},
    				last : '<li class="clearfix list-group-item">&nbsp;<div class="pull-right">%s</div></li>',
    				button : '<button id="submit-poll-%s" class="btn btn-primary" onclick="submitPollAnswers(\'%s\', %s)">Vote</button>',
    				closed : 'This poll has been closed.',
    				disallowed : 'You cannot vote on this poll.',
    				voted : 'You already voted on this poll.'
    			},
    			close : '</div>'
    		},
    		controls : {
    			oldest : '<a class="btn btn-default icon" title="Jump to oldest message" aria-label="Jump to oldest message" href="#%s"><span class="glyphicon glyphicon-fast-backward"></span></a>',
    			previous : '<a class="btn btn-default icon" title="Jump to previous message" aria-label="Jump to previous message" href="#%s" id="pm-%s"><span class="glyphicon glyphicon-step-backward"></span></a>',
    			link : '<a class="btn btn-default icon" title="Direct link to this message" aria-label="Direct link to this message" href="#%s"><span class="glyphicon glyphicon-link"></span></a>',
    			next : '<a class="btn btn-default icon" title="Jump to next message" aria-label="Jump to next message" href="#%s" id="nm-%s"><span class="glyphicon glyphicon-step-forward"></span></a>',
    			newest : '<a class="btn btn-default icon" title="Jump to newest message" aria-label="Jump to newest message" href="#%s"><span class="glyphicon glyphicon-fast-forward"></span></a>',
    			reply : '<button class="btn btn-default icon" title="Reply to this message" aria-label="Reply to this message" name="reply-%s" onclick="addReply(\'%s\', \'%s\')"><span class="glyphicon glyphicon-comment"></span></button>',
    			remove : '<button class="btn btn-default icon" title="Delete this message" aria-label="Delete this message" onclick="deleteMessage(\'%s\',\'%s\')"><span class="glyphicon glyphicon-trash"></span></button>'
    		},
    		close : '</li>'
    	}
    };
    
    var avatars = new Avatars();
    
    writeln('<script type="text/javascript" src="./js/forum.js"></script>');
    writeln('<script type="text/javascript" src="./js/graphics-converter.js"></script>');
    writeln('<script type="text/javascript" src="./js/avatars.js"></script>');
    
    if (typeof http_request.query.notice !== 'undefined') {
    	writeln(format(strings.notice_box, http_request.query.notice[0]));
    }
    
    if (typeof http_request.query.sub !== 'undefined' &&
    	typeof msg_area.sub[http_request.query.sub[0]] !== 'undefined' &&
    	!msg_area.sub[http_request.query.sub[0]].can_read
    ) {
    	writeln('<div class="alert alert-danger">Sorry, you do not have access to the requested sub.</div>');
    }
    
    if (typeof http_request.query.sub !== 'undefined' &&
    	typeof msg_area.sub[http_request.query.sub[0]] !== 'undefined' &&
    	typeof http_request.query.thread !== 'undefined' &&
    	msg_area.sub[http_request.query.sub[0]].can_read
    ) {
    
    	// Thread view
    	var msgBase = new MsgBase(http_request.query.sub[0]);
    
    	var firstUnread = '';
    
    	function writeMessage(thread, keys, key, index) {
    
    		var header = thread.messages[key];
    		if (header.attr&MSG_POLL &&
    			(msgBase.cfg.settings&SUB_NOVOTING || !settings.vote_functions)
    		) {
    			return;
    		}
    
    		var body = msgBase.get_msg_body(header.number);
    		if (body === null) return;
    
    		writeln(format(strings.message.anchor, header.number));
    		writeln(format(strings.message.open, header.number));
    
    		// Show subject if first message
    		if (index === 0) writeln(format(strings.thread_view.subject, thread.subject));
    
    		// Header
    		writeln(strings.message.header.open);
    
    		writeln(strings.message.header.details.open);
    		if (user.alias != settings.guest &&
    			header.number > msg_area.sub[http_request.query.sub[0]].scan_ptr
    		) {
    			writeln(strings.message.header.details.unread);
    			if (firstUnread === '') firstUnread += header.number;
    		}
    
            if (typeof header.from_net_addr != 'undefined') {
                avatars.get_netuser(header.from, header.from_net_addr);
                writeln(format(strings.message.header.details.avatar, header.from + '@' + header.from_net_addr));
            } else {
                avatars.get_localuser(header.from_ext);
                writeln(format(strings.message.header.details.avatar, header.from_ext));
            }
    
    		writeln(
    			format(
    				strings.message.header.details.info,
    				header.from,
    				typeof header.from_net_addr === 'undefined' ? '' : '@' + header.from_net_addr,
    				header.to || 'All',
    				(new Date(header.when_written_time * 1000)).toLocaleString()
    			)
    		);
    		writeln(strings.message.header.details.close);
    
    		writeln(strings.message.header.voting.open);
    		if (settings.vote_functions &&
    			!(msgBase.cfg.settings&SUB_NOVOTING) &&
    			!(header.attr&MSG_POLL)
    		) {
    			writeln(
    				format(
    					strings.message.header.voting.buttons.up,
    					header.number,
    					user.alias == settings.guest || user.security.restrictions&UFLAG_V || msgBase.cfg.settings&SUB_NOVOTING ? 'disabled' : '',
    					header.number,
    					header.upvotes
    				)
    			);
    			writeln(
    				format(
    					strings.message.header.voting.buttons.down,
    					header.number,
    					user.alias == settings.guest || user.security.restrictions&UFLAG_V || msgBase.cfg.settings&SUB_NOVOTING ? 'disabled' : '',
    					header.number,
    					header.downvotes
    				)
    			);
    		} else if (
    			settings.vote_functions &&
    			!(msgBase.cfg.settings&SUB_NOVOTING) &&
    			header.attr&MSG_POLL
    		) {
    			writeln(strings.message.header.voting.poll);
    		}
    		writeln(strings.message.header.voting.close);
    
    		writeln(strings.message.header.close);
    
    		// Body
    		writeln(format(strings.message.body.open, header.number));
    		// If this is a poll message
    		if (settings.vote_functions &&
    			!(msgBase.cfg.settings&SUB_NOVOTING) &&
    			header.attr&MSG_POLL
    		) {
    
    			var pollData = getUserPollData(http_request.query.sub[0], header.number);
    
    			writeln(strings.message.body.poll.container.open);
    
    			writeln(format(strings.message.body.poll.title, header.subject));
    
    			header.poll_comments.forEach(
    				function (e) {
    					writeln(format(strings.message.body.poll.comment, e.data));
    				}
    			);
    
    			// Poll is closed or user has voted
    			if (header.auxattr&POLL_CLOSED ||
    				pollData.answers > 0 ||
    				user.alias == settings.guest ||
    				user.security.restrictions&UFLAG_V
    			) {
    				header.poll_answers.forEach(
    					function (e, i) {
    						writeln(
    							format(
    								strings.message.body.poll.answer.closed,
    								pollData.answers&(1<<i) ? ' upvote-bg' : '',
    								i + 1, e.data, header.number, i,
    								pollData.show_results ? header.tally[i] || 0 : ''
    							)
    						);
    					}
    				);
    			// Poll is open and user hasn't voted
    			} else {
    				header.poll_answers.forEach(
    					function (e, i) {
    						writeln(
    							format(
    								strings.message.body.poll.answer.open,
    								header.votes < 2 ? 'radio' : 'checkbox',
    								header.votes < 2 ? 'radio' : 'checkbox',
    								header.number, i, e.data, header.number, i,
    								pollData.show_results ? header.tally[i] || 0 : ''
    							)
    						);
    					}
    				);
    			}
    
    			if (header.auxattr&POLL_CLOSED) {
    				writeln(format(strings.message.body.poll.last, strings.message.body.poll.closed));
    			} else if (pollData.answers > 0) {
    				writeln(format(strings.message.body.poll.last, strings.message.body.poll.voted));
    			} else if (user.alias == settings.guest || user.security.restrictions&UFLAG_V || msgBase.cfg.settings&SUB_NOVOTING) {
    				writeln(format(strings.message.body.poll.last, strings.message.body.poll.disallowed));
    			} else {
    				writeln(
    					format (
    						strings.message.body.poll.last,
    						format(
    							strings.message.body.poll.button,
    							header.number, http_request.query.sub[0], header.number
    						)
    					)
    				);
    			}
    
    			writeln(strings.message.body.poll.container.close);
    			writeln(strings.message.body.close);
    
    			writeln(strings.script.open);
    			if (header.votes > 1) {
    				writeln(format(strings.script.poll_control, header.number, header.votes));
    			}
    			// Refresh poll results every so often
    			writeln(
    				format(
    					strings.script.interval,
    					format(
    						strings.script.get_poll_data,
    						http_request.query.sub[0], header.number
    					),
    					settings.refresh_interval || 60000
    				)
    			);
    			writeln(strings.script.close);
    
    		// This is a normal message
    		} else {
    			writeln(formatMessage(body, false, settings.forum_extended_ascii && !header.is_utf8));
    			writeln(strings.message.body.close);
    		}
    
    		var prev = index === 0 ? thread.messages[thread.__first].number : thread.messages[keys[index - 1]].number;
    		var next = key === thread.__last ? thread.messages[thread.__last].number : thread.messages[keys[index + 1]].number;
    
    		// Standard controls
    		writeln(format(strings.message.controls.oldest, thread.messages[thread.__first].number));
    		writeln(format(strings.message.controls.previous, prev, header.number));
    		writeln(format(strings.message.controls.link, header.number));
    		writeln(format(strings.message.controls.next, next, header.number));
    		writeln(format(strings.message.controls.newest, thread.messages[thread.__last].number));
    
    		// User can post
    		if (user.alias !== settings.guest && msg_area.sub[msgBase.cfg.code].can_post) {
    			writeln(format(strings.message.controls.reply, header.number, msgBase.cfg.code, header.number));
    		}
    
    		// User is operator
    		if (user.alias != settings.guest && msg_area.sub[msgBase.cfg.code].is_operator) {
    			writeln(format(strings.message.controls.remove, msgBase.cfg.code, header.number));
    		}
    
    		writeln(strings.message.close);
    
    	}
    
    	writeln(
    		format(
    			strings.thread_view.breadcrumb,
    			http_request.query.page[0],
    			http_request.query.page[0],
    			msg_area.sub[http_request.query.sub[0]].grp_index,
    			msg_area.sub[http_request.query.sub[0]].grp_name,
    			http_request.query.page[0],
    			http_request.query.sub[0],
    			msg_area.sub[http_request.query.sub[0]].name
    		)
    	);
    	writeln(strings.thread_view.jump_unread);
    
    	try {
    		msgBase.open();
    		var thread = getMessageThreads(
    			http_request.query.sub[0],
    			settings.max_messages
    		).thread[
    			http_request.query.thread[0]
    		];
    		if(!thread) {
    			writeln("Error: No such message.");
    			exit(0);
    		}
    		var keys = Object.keys(thread.messages);
    		thread.__first = keys[0];
    		thread.__last = keys[keys.length - 1];
    		writeln(strings.thread_view.container.open);
    		keys.forEach(function (key, index) { writeMessage(thread, keys, key, index); });
    		writeln(strings.thread_view.container.close);
    		if (keys.length > 1 && firstUnread !== '') {
    			writeln(strings.script.open);
    			writeln(format(strings.thread_view.set_unread, firstUnread));
    			writeln(strings.script.close);
    		}
    		// Update scan pointer
    		if (user.alias !== settings.guest &&
    			!isNaN(thread.messages[thread.__last].number) &&
    			thread.messages[thread.__last].number < 0xFFFFFFFF &&
    			thread.messages[thread.__last].number > msg_area.sub[http_request.query.sub[0]].scan_ptr &&
    			thread.messages[thread.__last].number <= msgBase.last_msg
    		) {
    			msg_area.sub[http_request.query.sub[0]].scan_ptr =
    			thread.messages[thread.__last].number;
    		}
    		msgBase.close();
    
    		writeln(strings.script.open);
    		if (settings.keyboard_navigation) writeln(strings.script.thread_navigation);
    		if (settings.vote_functions) {
    			if (user.alias != settings.guest || user.security.restrictions&UFLAG_V) {
    				writeln(
    					format(strings.script.vote_functions, http_request.query.sub[0])
    				);
    			}
    			writeln(
    				format(
    					strings.script.vote_refresh_thread,
    					http_request.query.sub[0], thread.__first
    				)
    			);
    			writeln(
    				format(
    					strings.script.interval,
    					format(
    						strings.script.vote_refresh_thread,
    						http_request.query.sub[0], thread.__first
    					),
    					settings.refresh_interval || 60000
    				)
    			);
    		}
            writeln('const avatarizer = new Avatarizer();');
            Object.keys(avatars.cache.local).forEach(
                function (e) {
                    const bin = avatars.get_localuser(e);
                    if (!bin) return;
                    writeln('avatarizer.get_localuser("' + e + '", "' + bin + '");');
                }
            );
            Object.keys(avatars.cache.network).forEach(
                function (e) {
                    Object.keys(avatars.cache.network[e]).forEach(
                        function (ee) {
                            const bin = avatars.get_netuser(ee, e);
                            if (!bin) return;
                            writeln('avatarizer.get_netuser("' + ee + '", "' + e + '", "' + bin + '");');
                        }
                    )
                }
            );
            writeln(strings.script.close);
    
    
    	} catch (err) {
    		log(LOG_WARNING, format("%s:%u %s", err.fileName, err.lineNumber, err.message));
    	}
    
    } else if (
    	typeof http_request.query.sub !== 'undefined' &&
    	typeof msg_area.sub[http_request.query.sub[0]] !== 'undefined' &&
    	msg_area.sub[http_request.query.sub[0]].can_read
    ) {
    
    	// Thread list
    	function writeThread(thread) {
    
    		if (user.number > 0 && user.alias != settings.guest) {
    			var unread = getUnreadInThread(http_request.query.sub[0], thread);
    		} else {
    			var unread = 0;
    		}
    		var keys = Object.keys(thread.messages);
    		var first = keys[0];
    		var last = keys[keys.length - 1];
    		writeln(
    			format(
    				strings.thread_list.item.open,
    				http_request.query.page[0],
    				http_request.query.sub[0],
    				thread.id
    			)
    		);
    
    		writeln(strings.thread_list.item.details.open);
    		writeln(
    			format(
    				strings.thread_list.item.details.info,
    				thread.subject,
    				thread.messages[first].from,
    				(new Date(thread.messages[first].when_written_time * 1000)).toLocaleString(),
    				thread.messages[last].from,
    				(new Date(thread.messages[last].when_written_time * 1000)).toLocaleString()
    			)
    		);
    		writeln(strings.thread_list.item.details.close);
    
    		writeln(strings.thread_list.item.badges.open);
    		if (settings.vote_functions &&
    			!(msg_area.sub[http_request.query.sub[0]].settings&SUB_NOVOTING) &&
    			(thread.votes.up > 0 || thread.votes.down > 0)
    		) {
    			writeln(
    				format(
    					strings.thread_list.item.badges.votes.up,
    					thread.messages[first].number,
    					thread.messages[first].number,
    					thread.messages[first].upvotes,
    					thread.votes.up
    				)
    			);
    			writeln(
    				format(
    					strings.thread_list.item.badges.votes.down,
    					thread.messages[first].number,
    					thread.messages[first].number,
    					thread.messages[first].downvotes,
    					thread.votes.down
    				)
    			);
    		}
    		writeln(
    			format(
    				strings.thread_list.item.badges.unread,
    				msg_area.sub[http_request.query.sub[0]].scan_cfg&SCAN_CFG_NEW ? ' scanned' : '',
    				thread.messages[first].number,
    				(unread == 0 ? '' : unread)
    			)
    		);
    		if (settings.vote_functions &&
    			!(msg_area.sub[http_request.query.sub[0]].settings&SUB_NOVOTING) &&
    			thread.messages[first].attr&MSG_POLL
    		) {
    			writeln(strings.thread_list.item.badges.poll);
    		}
    		writeln(strings.thread_list.item.badges.close);
    
    		writeln(strings.thread_list.item.close);
    
    	}
    
    	writeln(
    		format(
    			strings.thread_list.breadcrumb,
    			http_request.query.page[0],
    			http_request.query.page[0],
    			msg_area.sub[http_request.query.sub[0]].grp_index,
    			msg_area.sub[http_request.query.sub[0]].grp_name,
    			http_request.query.page[0],
    			http_request.query.sub[0],
    			msg_area.sub[http_request.query.sub[0]].name
    		)
    	);
    
    	if (user.alias !== settings.guest && msg_area.sub[http_request.query.sub[0]].can_post) {
    		writeln(format(strings.thread_list.controls.post, http_request.query.sub[0]));
    		if (settings.vote_functions &&
    			!(msg_area.sub[http_request.query.sub[0]].settings&SUB_NOVOTING) &&
    			!(user.security.restrictions&UFLAG_V)
    		) {
    			writeln(format(strings.thread_list.controls.post_poll, http_request.query.sub[0]));
    		}
    	}
    
    	if (user.alias !== settings.guest) {
    		writeln(
    			format(
    				strings.thread_list.controls.scan_new,
    				!(msg_area.sub[http_request.query.sub[0]].scan_cfg&SCAN_CFG_YONLY) && msg_area.sub[http_request.query.sub[0]].scan_cfg&SCAN_CFG_NEW ? 'btn-primary' : 'btn-default',
    				http_request.query.sub[0]
    			)
    		);
    		writeln(
    			format(
    				strings.thread_list.controls.scan_you,
    				msg_area.sub[http_request.query.sub[0]].scan_cfg&SCAN_CFG_YONLY ? 'btn-primary' : 'btn-default',
    				http_request.query.sub[0]
    			)
    		);
    		writeln(
    			format(
    				strings.thread_list.controls.scan_off,
    				!(msg_area.sub[http_request.query.sub[0]].scan_cfg&SCAN_CFG_NEW) && !(msg_area.sub[http_request.query.sub[0]].scan_cfg&SCAN_CFG_YONLY) ? 'btn-primary' : 'btn-default',
    				http_request.query.sub[0]
    			)
    		);
    	}
    
    	try {
    		var threads = getMessageThreads(http_request.query.sub[0], settings.max_messages);
    		writeln(strings.thread_list.container.open);
    		threads.order.forEach(function(t){writeThread(threads.thread[t]);});
    		writeln(strings.thread_list.container.close);
    	} catch (err) {
    		log(LOG_WARNING, format("%s:%u %s", err.fileName, err.lineNumber, err.message));
    	}
    
    	if (settings.vote_functions) {
    		writeln(strings.script.open);
    		writeln(
    			format(
    				strings.script.interval,
    				format(strings.script.vote_refresh_threads, http_request.query.sub[0]),
    				settings.refresh_interval || 60000
    			)
    		);
    		writeln(strings.script.close);
    	}
    
    } else if (
    	typeof http_request.query.group !== 'undefined' &&
    	typeof msg_area.grp_list[http_request.query.group[0]] !== 'undefined'
    ) {
    
    	// Sub list
    	function writeSub(sub) {
    		writeln(
    			format(
    				strings.sub_list.item,
    				http_request.query.page[0],
    				sub.code,
    				sub.scan_cfg&SCAN_CFG_NEW || sub.scan_cfg&SCAN_CFG_YONLY ? ' scanned' : '',
    				sub.name,
    				sub.scan_cfg&SCAN_CFG_NEW || sub.scan_cfg&SCAN_CFG_YONLY ? 'scanned' : 'total',
    				sub.code,
    				sub.description
    			)
    		);
    	}
    
    	function writeApiCall(subs) {
    		writeln(strings.script.open);
    		writeln(format(strings.script.get_sub_unread, http_request.query.group[0]));
    		writeln(
    			format(
    				strings.script.interval,
    				format(strings.script.get_sub_unread, http_request.query.group[0]),
    				settings.refresh_interval || 60000
    			)
    		);
    		writeln(strings.script.close);
    	}
    
    	writeln(
    		format(
    			strings.sub_list.breadcrumb,
    			http_request.query.page[0],
    			http_request.query.page[0],
    			http_request.query.group[0],
    			msg_area.grp_list[http_request.query.group[0]].name
    		)
    	);
    
    	try {
    		var subs = listSubs(http_request.query.group[0]);
    		writeln(strings.sub_list.container.open);
    		subs.forEach(writeSub);
    		writeln(strings.sub_list.container.close);
    		if (user.number > 0 && user.alias !== settings.guest) writeApiCall(subs);
    	} catch (err) {
    		log(LOG_WARNING, format("%s:%u %s", err.fileName, err.lineNumber, err.message));
    	}
    
    } else {
    
    	// Group list
    	function writeGroup(group) {
    		writeln(
    			format(
    				strings.group_list.item,
    				http_request.query.page[0],
    				group.index,
    				group.name,
    				group.index,
    				group.index,
    				group.description,
    				group.sub_count
    			)
    		);
    	}
    
    	function writeApiCall(groups) {
    		writeln(strings.script.open);
    		writeln(strings.script.get_group_unread);
    		writeln(
    			format(
    				strings.script.interval,
    				strings.script.get_group_unread,
    				settings.refresh_interval || 60000
    			)
    		);
    		writeln(strings.script.close);
    	}
    
    	writeln(format(strings.group_list.breadcrumb, http_request.query.page[0]));
    
    	try {
    		var groups = listGroups();
    		writeln(strings.group_list.container.open);
    		groups.forEach(writeGroup);
    		writeln(strings.group_list.container.close);
    		if (user.number > 0 && user.alias !== settings.guest) writeApiCall(groups);
    	} catch (err) {
    		log(LOG_WARNING, format("%s:%u %s", err.fileName, err.lineNumber, err.message));
    	}
    
    }