diff --git a/src/bot/audioCaptureProcedure.ts b/src/bot/audioCaptureProcedure.ts index c0179c8..ddb7871 100644 --- a/src/bot/audioCaptureProcedure.ts +++ b/src/bot/audioCaptureProcedure.ts @@ -25,12 +25,12 @@ class AudioCaptureProcessor extends AudioWorkletProcessor { this.minRmsThreshold = 0.0003; this.preRollSamples = Math.ceil(this.nativeRate * 1.0); this.minFlushSamples = Math.ceil(this.nativeRate * 0.5); - this.silenceFlushCallbacks = 6; + this.silenceFlushSamples = Math.ceil(this.nativeRate * 1.0); this.ratio = this.nativeRate / this.targetRate; this.chunkBuffer = []; this.samplesCollected = 0; this.hasVoicedContent = false; - this.consecutiveSilentCallbacks = 0; + this.consecutiveSilentSamples = 0; } process(inputs, outputs, parameters) { @@ -45,9 +45,9 @@ class AudioCaptureProcessor extends AudioWorkletProcessor { if (cbRms >= this.minRmsThreshold) { this.hasVoicedContent = true; - this.consecutiveSilentCallbacks = 0; + this.consecutiveSilentSamples = 0; } else { - this.consecutiveSilentCallbacks++; + this.consecutiveSilentSamples += input.length; } this.chunkBuffer.push(new Float32Array(input)); @@ -56,7 +56,7 @@ class AudioCaptureProcessor extends AudioWorkletProcessor { const shouldFlush = ( this.samplesCollected >= this.maxSamplesPerChunk || (this.hasVoicedContent - && this.consecutiveSilentCallbacks >= this.silenceFlushCallbacks + && this.consecutiveSilentSamples >= this.silenceFlushSamples && this.samplesCollected > this.minFlushSamples) ); @@ -75,7 +75,7 @@ class AudioCaptureProcessor extends AudioWorkletProcessor { const rms = Math.sqrt(powerSum / Math.max(merged.length, 1)); this.hasVoicedContent = false; - this.consecutiveSilentCallbacks = 0; + this.consecutiveSilentSamples = 0; if (rms >= this.minRmsThreshold) { const outLen = Math.floor(merged.length / this.ratio); @@ -264,14 +264,14 @@ export class AudioCaptureProcedure { const maxSamplesPerChunk = nativeRate * 8; const preRollSamples = Math.ceil(nativeRate * 1.0); const minFlushSamples = Math.ceil(nativeRate * 0.5); - const silenceFlushCallbacks = 6; + const silenceFlushSamples = Math.ceil(nativeRate * 1.0); const ratio = nativeRate / targetRate; scriptProcessor = ctx.createScriptProcessor(8192, 1, 1); let chunkBuffer: Float32Array[] = []; let samplesCollected = 0; let hasVoicedContent = false; - let consecutiveSilentCallbacks = 0; + let consecutiveSilentSamples = 0; scriptProcessor.onaudioprocess = (e: AudioProcessingEvent) => { const input = e.inputBuffer.getChannelData(0); @@ -283,9 +283,9 @@ export class AudioCaptureProcedure { if (cbRms >= minRmsThreshold) { hasVoicedContent = true; - consecutiveSilentCallbacks = 0; + consecutiveSilentSamples = 0; } else { - consecutiveSilentCallbacks++; + consecutiveSilentSamples += input.length; } chunkBuffer.push(new Float32Array(input)); @@ -294,7 +294,7 @@ export class AudioCaptureProcedure { const shouldFlush = ( samplesCollected >= maxSamplesPerChunk || (hasVoicedContent - && consecutiveSilentCallbacks >= silenceFlushCallbacks + && consecutiveSilentSamples >= silenceFlushSamples && samplesCollected > minFlushSamples) ); @@ -313,7 +313,7 @@ export class AudioCaptureProcedure { const rms = Math.sqrt(powerSum / Math.max(merged.length, 1)); hasVoicedContent = false; - consecutiveSilentCallbacks = 0; + consecutiveSilentSamples = 0; if (rms >= minRmsThreshold) { const outLen = Math.floor(merged.length / ratio); diff --git a/src/bot/chatProcedure.ts b/src/bot/chatProcedure.ts index 8fa1cd6..6b2affc 100644 --- a/src/bot/chatProcedure.ts +++ b/src/bot/chatProcedure.ts @@ -27,6 +27,7 @@ export class ChatProcedure { private _consecutiveOpenFailures: number = 0; private static readonly _MAX_OPEN_FAILURES = 5; private _isAuthMode: boolean; + private _recentlySentTexts: Set = new Set(); constructor( page: Page, @@ -465,10 +466,14 @@ export class ChatProcedure { 'meeting ended', 'meeting started', 'was invited', 'left the chat', 'joined the meeting', 'left the meeting', 'doesn\'t have a teams account', 'verify their identity', 'new notification', 'last read', + 'sending...', 'sending…', 'gesendet', 'sent', ]; function _isNoise(text: string): boolean { const lower = text.toLowerCase(); - return noisePatterns.some(p => lower.includes(p)); + if (noisePatterns.some(p => lower.includes(p))) return true; + if (/^\d{1,2}:\d{2}(:\d{2})?\s*$/.test(text.trim())) return true; + if (/^8:/.test(text.trim())) return true; + return false; } function _extractTeamsTimestamp(el: HTMLElement): string | undefined { @@ -970,6 +975,22 @@ export class ChatProcedure { }): void { if (!this._isSubscribed || !msg.text) return; + // Strip leading timestamp prefixes like "22:51 " or "22:51:03 " + let cleanText = msg.text.replace(/^\d{1,2}:\d{2}(:\d{2})?\s+/, '').trim(); + // Strip "Sending..." prefix + cleanText = cleanText.replace(/^Sending\.\.\.\s*/i, '').trim(); + if (!cleanText) return; + msg.text = cleanText; + + // Filter bot's own sent messages (echoed back from the chat panel) + const normalisedIncoming = cleanText.replace(/\s+/g, ' ').trim().substring(0, 200); + if (this._recentlySentTexts.has(normalisedIncoming)) return; + + // Sanitize raw Teams MRI IDs as speaker (e.g. "8:teamsvisitor:cbb83be0...") + if (/^8:(orgid|teamsvisitor|live|guest):/.test(msg.speaker)) { + msg.speaker = 'Unknown'; + } + const nowMs = Date.now(); // Dedup @@ -1243,6 +1264,12 @@ export class ChatProcedure { await this._page.keyboard.press('Enter'); } this._logger.info(`Chat message sent (stage=${stageUsed}, via=${sent ? 'sendBtn' : 'enter'})`); + const normalised = text.replace(/\s+/g, ' ').trim().substring(0, 200); + this._recentlySentTexts.add(normalised); + if (this._recentlySentTexts.size > 50) { + const first = this._recentlySentTexts.values().next().value; + if (first) this._recentlySentTexts.delete(first); + } return true; }