mirror of
https://github.com/TheNetsky/Microsoft-Rewards-Script.git
synced 2026-01-18 14:03:58 +00:00
v3 init
Based of v3.0.0b10.
This commit is contained in:
50
src/logging/Discord.ts
Normal file
50
src/logging/Discord.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import PQueue from 'p-queue'
|
||||
import type { LogLevel } from './Logger'
|
||||
|
||||
const DISCORD_LIMIT = 2000
|
||||
|
||||
export interface DiscordConfig {
|
||||
enabled?: boolean
|
||||
url: string
|
||||
}
|
||||
|
||||
const discordQueue = new PQueue({
|
||||
interval: 1000,
|
||||
intervalCap: 2,
|
||||
carryoverConcurrencyCount: true
|
||||
})
|
||||
|
||||
function truncate(text: string) {
|
||||
return text.length <= DISCORD_LIMIT ? text : text.slice(0, DISCORD_LIMIT - 14) + ' …(truncated)'
|
||||
}
|
||||
|
||||
export async function sendDiscord(discordUrl: string, content: string, level: LogLevel): Promise<void> {
|
||||
if (!discordUrl) return
|
||||
|
||||
const request: AxiosRequestConfig = {
|
||||
method: 'POST',
|
||||
url: discordUrl,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
data: { content: truncate(content), allowed_mentions: { parse: [] } },
|
||||
timeout: 10000
|
||||
}
|
||||
|
||||
await discordQueue.add(async () => {
|
||||
try {
|
||||
await axios(request)
|
||||
} catch (err: any) {
|
||||
const status = err?.response?.status
|
||||
if (status === 429) return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function flushDiscordQueue(timeoutMs = 5000): Promise<void> {
|
||||
await Promise.race([
|
||||
(async () => {
|
||||
await discordQueue.onIdle()
|
||||
})(),
|
||||
new Promise<void>((_, reject) => setTimeout(() => reject(new Error('discord flush timeout')), timeoutMs))
|
||||
]).catch(() => {})
|
||||
}
|
||||
189
src/logging/Logger.ts
Normal file
189
src/logging/Logger.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
import chalk from 'chalk'
|
||||
import cluster from 'cluster'
|
||||
import { sendDiscord } from './Discord'
|
||||
import { sendNtfy } from './Ntfy'
|
||||
import type { MicrosoftRewardsBot } from '../index'
|
||||
import { errorDiagnostic } from '../util/ErrorDiagnostic'
|
||||
import type { LogFilter } from '../interface/Config'
|
||||
|
||||
export type Platform = boolean | 'main'
|
||||
export type LogLevel = 'info' | 'warn' | 'error' | 'debug'
|
||||
export type ColorKey = keyof typeof chalk
|
||||
export interface IpcLog {
|
||||
content: string
|
||||
level: LogLevel
|
||||
}
|
||||
|
||||
type ChalkFn = (msg: string) => string
|
||||
|
||||
function platformText(platform: Platform): string {
|
||||
return platform === 'main' ? 'MAIN' : platform ? 'MOBILE' : 'DESKTOP'
|
||||
}
|
||||
|
||||
function platformBadge(platform: Platform): string {
|
||||
return platform === 'main' ? chalk.bgCyan('MAIN') : platform ? chalk.bgBlue('MOBILE') : chalk.bgMagenta('DESKTOP')
|
||||
}
|
||||
|
||||
function getColorFn(color?: ColorKey): ChalkFn | null {
|
||||
return color && typeof chalk[color] === 'function' ? (chalk[color] as ChalkFn) : null
|
||||
}
|
||||
|
||||
function consoleOut(level: LogLevel, msg: string, chalkFn: ChalkFn | null): void {
|
||||
const out = chalkFn ? chalkFn(msg) : msg
|
||||
switch (level) {
|
||||
case 'warn':
|
||||
return console.warn(out)
|
||||
case 'error':
|
||||
return console.error(out)
|
||||
default:
|
||||
return console.log(out)
|
||||
}
|
||||
}
|
||||
|
||||
function formatMessage(message: string | Error): string {
|
||||
return message instanceof Error ? `${message.message}\n${message.stack || ''}` : message
|
||||
}
|
||||
|
||||
export class Logger {
|
||||
constructor(private bot: MicrosoftRewardsBot) {}
|
||||
|
||||
info(isMobile: Platform, title: string, message: string, color?: ColorKey) {
|
||||
return this.baseLog('info', isMobile, title, message, color)
|
||||
}
|
||||
|
||||
warn(isMobile: Platform, title: string, message: string | Error, color?: ColorKey) {
|
||||
return this.baseLog('warn', isMobile, title, message, color)
|
||||
}
|
||||
|
||||
error(isMobile: Platform, title: string, message: string | Error, color?: ColorKey) {
|
||||
return this.baseLog('error', isMobile, title, message, color)
|
||||
}
|
||||
|
||||
debug(isMobile: Platform, title: string, message: string | Error, color?: ColorKey) {
|
||||
return this.baseLog('debug', isMobile, title, message, color)
|
||||
}
|
||||
|
||||
private baseLog(
|
||||
level: LogLevel,
|
||||
isMobile: Platform,
|
||||
title: string,
|
||||
message: string | Error,
|
||||
color?: ColorKey
|
||||
): void {
|
||||
const now = new Date().toLocaleString()
|
||||
const formatted = formatMessage(message)
|
||||
|
||||
const levelTag = level.toUpperCase()
|
||||
const cleanMsg = `[${now}] [${this.bot.userData.userName}] [${levelTag}] ${platformText(
|
||||
isMobile
|
||||
)} [${title}] ${formatted}`
|
||||
|
||||
const config = this.bot.config
|
||||
|
||||
if (level === 'debug' && !config.debugLogs && !process.argv.includes('-dev')) {
|
||||
return
|
||||
}
|
||||
|
||||
const badge = platformBadge(isMobile)
|
||||
const consoleStr = `[${now}] [${this.bot.userData.userName}] [${levelTag}] ${badge} [${title}] ${formatted}`
|
||||
|
||||
let logColor: ColorKey | undefined = color
|
||||
|
||||
if (!logColor) {
|
||||
switch (level) {
|
||||
case 'error':
|
||||
logColor = 'red'
|
||||
break
|
||||
case 'warn':
|
||||
logColor = 'yellow'
|
||||
break
|
||||
case 'debug':
|
||||
logColor = 'magenta'
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (level === 'error' && config.errorDiagnostics) {
|
||||
const page = this.bot.isMobile ? this.bot.mainMobilePage : this.bot.mainDesktopPage
|
||||
const error = message instanceof Error ? message : new Error(String(message))
|
||||
errorDiagnostic(page, error)
|
||||
}
|
||||
|
||||
const consoleAllowed = this.shouldPassFilter(config.consoleLogFilter, level, cleanMsg)
|
||||
const webhookAllowed = this.shouldPassFilter(config.webhook.webhookLogFilter, level, cleanMsg)
|
||||
|
||||
if (consoleAllowed) {
|
||||
consoleOut(level, consoleStr, getColorFn(logColor))
|
||||
}
|
||||
|
||||
if (!webhookAllowed) {
|
||||
return
|
||||
}
|
||||
|
||||
if (cluster.isPrimary) {
|
||||
if (config.webhook.discord?.enabled && config.webhook.discord.url) {
|
||||
if (level === 'debug') return
|
||||
sendDiscord(config.webhook.discord.url, cleanMsg, level)
|
||||
}
|
||||
|
||||
if (config.webhook.ntfy?.enabled && config.webhook.ntfy.url) {
|
||||
if (level === 'debug') return
|
||||
sendNtfy(config.webhook.ntfy, cleanMsg, level)
|
||||
}
|
||||
} else {
|
||||
process.send?.({ __ipcLog: { content: cleanMsg, level } })
|
||||
}
|
||||
}
|
||||
|
||||
private shouldPassFilter(filter: LogFilter | undefined, level: LogLevel, message: string): boolean {
|
||||
// If disabled or not, let all logs pass
|
||||
if (!filter || !filter.enabled) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Always log error levelo logs, remove these lines to disable this!
|
||||
if (level === 'error') {
|
||||
return true
|
||||
}
|
||||
|
||||
const { mode, levels, keywords, regexPatterns } = filter
|
||||
|
||||
const hasLevelRule = Array.isArray(levels) && levels.length > 0
|
||||
const hasKeywordRule = Array.isArray(keywords) && keywords.length > 0
|
||||
const hasPatternRule = Array.isArray(regexPatterns) && regexPatterns.length > 0
|
||||
|
||||
if (!hasLevelRule && !hasKeywordRule && !hasPatternRule) {
|
||||
return mode === 'blacklist'
|
||||
}
|
||||
|
||||
const lowerMessage = message.toLowerCase()
|
||||
let isMatch = false
|
||||
|
||||
if (hasLevelRule && levels!.includes(level)) {
|
||||
isMatch = true
|
||||
}
|
||||
|
||||
if (!isMatch && hasKeywordRule) {
|
||||
if (keywords!.some(k => lowerMessage.includes(k.toLowerCase()))) {
|
||||
isMatch = true
|
||||
}
|
||||
}
|
||||
|
||||
// Fancy regex filtering if set!
|
||||
if (!isMatch && hasPatternRule) {
|
||||
for (const pattern of regexPatterns!) {
|
||||
try {
|
||||
const regex = new RegExp(pattern, 'i')
|
||||
if (regex.test(message)) {
|
||||
isMatch = true
|
||||
break
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
return mode === 'whitelist' ? isMatch : !isMatch
|
||||
}
|
||||
}
|
||||
61
src/logging/Ntfy.ts
Normal file
61
src/logging/Ntfy.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import PQueue from 'p-queue'
|
||||
import type { WebhookNtfyConfig } from '../interface/Config'
|
||||
import type { LogLevel } from './Logger'
|
||||
|
||||
const ntfyQueue = new PQueue({
|
||||
interval: 1000,
|
||||
intervalCap: 2,
|
||||
carryoverConcurrencyCount: true
|
||||
})
|
||||
|
||||
export async function sendNtfy(config: WebhookNtfyConfig, content: string, level: LogLevel): Promise<void> {
|
||||
if (!config?.url) return
|
||||
|
||||
switch (level) {
|
||||
case 'error':
|
||||
config.priority = 5 // Highest
|
||||
break
|
||||
|
||||
case 'warn':
|
||||
config.priority = 4
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = { 'Content-Type': 'text/plain' }
|
||||
if (config.title) headers['Title'] = config.title
|
||||
if (config.tags?.length) headers['Tags'] = config.tags.join(',')
|
||||
if (config.priority) headers['Priority'] = String(config.priority)
|
||||
if (config.token) headers['Authorization'] = `Bearer ${config.token}`
|
||||
|
||||
const url = config.topic ? `${config.url}/${config.topic}` : config.url
|
||||
|
||||
const request: AxiosRequestConfig = {
|
||||
method: 'POST',
|
||||
url: url,
|
||||
headers,
|
||||
data: content,
|
||||
timeout: 10000
|
||||
}
|
||||
|
||||
await ntfyQueue.add(async () => {
|
||||
try {
|
||||
await axios(request)
|
||||
} catch (err: any) {
|
||||
const status = err?.response?.status
|
||||
if (status === 429) return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function flushNtfyQueue(timeoutMs = 5000): Promise<void> {
|
||||
await Promise.race([
|
||||
(async () => {
|
||||
await ntfyQueue.onIdle()
|
||||
})(),
|
||||
new Promise<void>((_, reject) => setTimeout(() => reject(new Error('ntfy flush timeout')), timeoutMs))
|
||||
]).catch(() => {})
|
||||
}
|
||||
Reference in New Issue
Block a user