<commit_before>/**
 * Roomlogs
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * This handles data storage for rooms.
 *
 * @license MIT license
 */

'use strict';

const FS = require('./lib/fs');

/**
 * Most rooms have three logs:
 * - scrollback
 * - roomlog
 * - modlog
 * This class keeps track of all three.
 *
 * The scrollback is stored in memory, and is the log you get when you
 * join the room. It does not get moderator messages.
 *
 * The modlog is stored in
 * `logs/modlog/modlog_<ROOMID>.txt`
 * It contains moderator messages, formatted for ease of search.
 *
 * The roomlog is stored in
 * `logs/chat/<ROOMID>/<YEAR>-<MONTH>/<YEAR>-<MONTH>-<DAY>.txt`
 * It contains (nearly) everything.
 */
class Roomlog {
	/**
	 * @param {Room} room
	 */
	constructor(room, options = {}) {
		this.id = room.id;
		/**
		 * Scrollback log
		 * @type {string[]}
		 */
		this.log = [];
		this.broadcastBuffer = '';

		/**
		 * Battle rooms are multichannel, which means their logs are split
		 * into four channels, public, p1, p2, full.
		 */
		this.isMultichannel = !!options.isMultichannel;
		/**
		 * Chat rooms auto-truncate, which means it only stores the recent
		 * messages, if there are more.
		 */
		this.autoTruncate = !!options.autoTruncate;
		/**
		 * Chat rooms include timestamps.
		 */
		this.logTimes = !!options.logTimes;

		/**
		 * undefined = uninitialized,
		 * null = disabled
		 * @type {NodeJS.WritableStream? | undefined}
		 */
		this.modlogStream = undefined;
		/**
		 * undefined = uninitialized,
		 * null = disabled
		 * @type {NodeJS.WritableStream? | undefined}
		 */
		this.roomlogStream = undefined;

		// modlog/roomlog state
		this.sharedModlog = false;
		// TypeScript bug: can't infer
		/** @type {string} */
		this.roomlogFilename = '';

		this.setupModlogStream();
		this.setupRoomlogStream(true);
	}
	getScrollback(channel = 0) {
		let log = this.log;
		if (this.logTimes) log = [`|:|${~~(Date.now() / 1000)}`].concat(log);
		if (!this.isMultichannel) {
			return log.join('\n') + '\n';
		}
		log = [];
		for (let i = 0; i < this.log.length; ++i) {
			let line = this.log[i];
			if (line === '|split') {
				log.push(this.log[i + channel + 1]);
				i += 4;
			} else {
				log.push(line);
			}
		}
		let textLog = log.join('\n') + '\n';
		if (channel === 0) {
			return textLog.replace(/\n\|choice\|\|\n/g, '').replace(/\n\|seed\|\n/g, '');
		}
		return textLog;
	}
	setupModlogStream() {
		if (this.modlogStream !== undefined) return;
		if (!this.id.includes('-')) {
			this.modlogStream = FS(`logs/modlog/modlog_${this.id}.txt`).createAppendStream();
			return;
		}
		const sharedStreamId = this.id.split('-')[0];
		let stream = Roomlogs.sharedModlogs.get(sharedStreamId);
		if (!stream) {
			stream = FS(`logs/modlog/modlog_${sharedStreamId}.txt`).createAppendStream();
			Roomlogs.sharedModlogs.set(sharedStreamId, stream);
		}
		this.modlogStream = stream;
		this.sharedModlog = true;
	}<commit_msg>Roomlogs: don't remove too many newlines (#4307)<commit_after>/**
 * Roomlogs
 * Pokemon Showdown - http://pokemonshowdown.com/
 *
 * This handles data storage for rooms.
 *
 * @license MIT license
 */

'use strict';

const FS = require('./lib/fs');

/**
 * Most rooms have three logs:
 * - scrollback
 * - roomlog
 * - modlog
 * This class keeps track of all three.
 *
 * The scrollback is stored in memory, and is the log you get when you
 * join the room. It does not get moderator messages.
 *
 * The modlog is stored in
 * `logs/modlog/modlog_<ROOMID>.txt`
 * It contains moderator messages, formatted for ease of search.
 *
 * The roomlog is stored in
 * `logs/chat/<ROOMID>/<YEAR>-<MONTH>/<YEAR>-<MONTH>-<DAY>.txt`
 * It contains (nearly) everything.
 */
class Roomlog {
	/**
	 * @param {Room} room
	 */
	constructor(room, options = {}) {
		this.id = room.id;
		/**
		 * Scrollback log
		 * @type {string[]}
		 */
		this.log = [];
		this.broadcastBuffer = '';

		/**
		 * Battle rooms are multichannel, which means their logs are split
		 * into four channels, public, p1, p2, full.
		 */
		this.isMultichannel = !!options.isMultichannel;
		/**
		 * Chat rooms auto-truncate, which means it only stores the recent
		 * messages, if there are more.
		 */
		this.autoTruncate = !!options.autoTruncate;
		/**
		 * Chat rooms include timestamps.
		 */
		this.logTimes = !!options.logTimes;

		/**
		 * undefined = uninitialized,
		 * null = disabled
		 * @type {NodeJS.WritableStream? | undefined}
		 */
		this.modlogStream = undefined;
		/**
		 * undefined = uninitialized,
		 * null = disabled
		 * @type {NodeJS.WritableStream? | undefined}
		 */
		this.roomlogStream = undefined;

		// modlog/roomlog state
		this.sharedModlog = false;
		// TypeScript bug: can't infer
		/** @type {string} */
		this.roomlogFilename = '';

		this.setupModlogStream();
		this.setupRoomlogStream(true);
	}
	getScrollback(channel = 0) {
		let log = this.log;
		if (this.logTimes) log = [`|:|${~~(Date.now() / 1000)}`].concat(log);
		if (!this.isMultichannel) {
			return log.join('\n') + '\n';
		}
		log = [];
		for (let i = 0; i < this.log.length; ++i) {
			let line = this.log[i];
			if (line === '|split') {
				log.push(this.log[i + channel + 1]);
				i += 4;
			} else {
				log.push(line);
			}
		}
		let textLog = log.join('\n') + '\n';
		if (channel === 0) {
			return textLog.replace(/\n\|choice\|\|\n/g, '\n').replace(/\n\|seed\|\n/g, '\n');
		}
		return textLog;
	}
	setupModlogStream() {
		if (this.modlogStream !== undefined) return;
		if (!this.id.includes('-')) {
			this.modlogStream = FS(`logs/modlog/modlog_${this.id}.txt`).createAppendStream();
			return;
		}
		const sharedStreamId = this.id.split('-')[0];
		let stream = Roomlogs.sharedModlogs.get(sharedStreamId);
		if (!stream) {
			stream = FS(`logs/modlog/modlog_${sharedStreamId}.txt`).createAppendStream();
			Roomlogs.sharedModlogs.set(sharedStreamId, stream);
		}
		this.modlogStream = stream;
		this.sharedModlog = true;
	}
{'fraction_non_alphanumeric': 0.17017337148628178, 'fraction_numerical': 0.0047130112775627, 'mean_word_length': 3.0670773442847366, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3974329', 'n_tokens_mistral': 2310, 'n_tokens_neox': 2039, 'n_words': 495}