OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View File

@@ -0,0 +1,34 @@
<?php
namespace Leantime\Core\Support\String;
/**
* @mixin \Illuminate\Support\Stringable
*/
class AlphaNumeric
{
/**
* Cleans a string by removing special characters and optionally spaces.
*
* @param bool $removeSpaces Whether to remove spaces from the string.
* @return callable A function that cleans a string based on the given parameter.
*/
public function alphaNumeric($removeSpaces = false)
{
return function ($value) use ($removeSpaces) {
$cleaned = preg_replace('/[^A-Za-z0-9 ]/', '', (string) $value);
if ($removeSpaces) {
$cleaned = str_replace(' ', '', $cleaned);
} else {
// Step 2: Replace multiple spaces with a single space
$cleaned = preg_replace('/\s+/', ' ', $cleaned);
}
// Step 3: Trim leading and trailing spaces
$cleaned = trim($cleaned);
return $cleaned;
};
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Leantime\Core\Support\String;
/**
* @mixin \Illuminate\Support\Stringable
*/
class BeautifyFilename
{
/**
* Beautifies a filename by normalizing characters and formatting.
*
* @return callable A function that beautifies a filename
*/
public function beautifyFilename()
{
return function ($filename) {
// reduce consecutive characters
$filename = preg_replace([
// "file name.zip" becomes "file-name.zip"
'/ +/',
// "file___name.zip" becomes "file-name.zip"
'/_+/',
// "file---name.zip" becomes "file-name.zip"
'/-+/',
], '-', $filename);
$filename = preg_replace([
// "file--.--.-.--name.zip" becomes "file.name.zip"
'/-*\.-*/',
// "file...name..zip" becomes "file.name.zip"
'/\.{2,}/',
], '.', $filename);
// lowercase for windows/unix interoperability http://support.microsoft.com/kb/100625
$filename = mb_strtolower($filename, mb_detect_encoding($filename));
// ".file-name.-" becomes "file-name"
$filename = trim($filename, '.-');
return $filename;
};
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Leantime\Core\Support\String;
use Illuminate\Support\Str;
/**
* @mixin \Illuminate\Support\Stringable
*/
class SanitizeFilename
{
/**
* Sanitizes a filename by removing or replacing unsafe characters.
*
* @param bool $beautify Whether to beautify the filename
* @return callable A function that sanitizes a filename
*/
public function sanitizeFilename($beautify = true)
{
return function ($filename) use ($beautify) {
// sanitize filename
$filename = preg_replace(
'~
[<>:"/\\\|?*]| # file system reserved https://en.wikipedia.org/wiki/Filename#Reserved_characters_and_words
[\x00-\x1F]| # control characters http://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx
[\x7F\xA0\xAD]| # non-printing characters DEL, NO-BREAK SPACE, SOFT HYPHEN
[#\[\]@!$&\'()+,;=]| # URI reserved https://www.rfc-editor.org/rfc/rfc3986#section-2.2
[{}^\~`] # URL unsafe characters https://www.ietf.org/rfc/rfc1738.txt
~x',
'-',
$filename
);
// avoids ".", ".." or ".hiddenFiles"
$filename = ltrim($filename, '.-');
// optional beautification
if ($beautify) {
$filename = Str::beautifyFilename($filename);
}
// maximize filename length to 255 bytes http://serverfault.com/a/9548/44086
$ext = pathinfo($filename, PATHINFO_EXTENSION);
$filename = mb_strcut(
pathinfo($filename, PATHINFO_FILENAME),
0,
255 - ($ext ? strlen($ext) + 1 : 0),
mb_detect_encoding($filename)
).($ext ? '.'.$ext : '');
return $filename;
};
}
}

View File

@@ -0,0 +1,99 @@
<?php
namespace Leantime\Core\Support\String;
/**
* @mixin \Illuminate\Support\Stringable
*/
class SanitizeForLLM
{
/**
* Sanitizes string for safe use with LLM APIs by removing potential prompt injection patterns
* and other problematic characters that could interfere with JSON serialization or system prompts.
*
* @return callable A function that sanitizes a string for LLM processing
*/
public function sanitizeForLLM()
{
return function ($value, bool $removeNewlines = false) {
if (! is_string($value)) {
return $value ?? '';
}
// Step 1: Replace line breaks with space
$result = str_replace(["\r\n", "\r"], "\n", $value);
// Step 2: Escape JSON special characters except newlines
$result = str_replace(
['\\', '"', "\t", "\f", "\b"],
['\\\\', '\\"', ' ', ' ', ' '],
$result
);
// Step 3: Replace problematic characters with safe alternatives
$replacements = [
// Replace backslashes with forward slashes (for paths)
'\\' => '/',
// Replace double quotes with single quotes
'"' => "'",
// Replace special JSON characters with similar safe characters
'{' => '(',
'}' => ')',
// Collapse multiple spaces into single space
' ' => ' ',
];
$result = str_replace(array_keys($replacements), array_values($replacements), $result);
// Step 4: Remove any remaining potentially problematic characters
$result = preg_replace('/[\x80-\x9F]/u', '', $result);
// Remove common delimiters that might be used to "break out" of a system prompt
$attackPatterns = [
// System prompt break patterns
'/\<\/?system\>/', '/\<\/?assistant\>/', '/\<\/?user\>/', '/\<\/?human\>/',
// XML-like tags that might be used in exploits
'/\<\/?instructions\>/', '/\<\/?prompt\>/', '/\<\/?context\>/',
// Special command patterns
'/\[\[.*?\]\]/', '/\{\{.*?\}\}/',
// Common attack prefix/suffix patterns
'/ignore previous instructions/', '/ignore all previous commands/',
'/disregard (previous|prior|all|your) instructions?/',
'/forget (previous|prior|all|your) instructions?/',
// Additional boundary markers
'/```system/', '/```instructions/', '/```prompt/',
'/\$\$\$system/', '/\$\$\$instructions/', '/\$\$\$prompt/',
];
$result = preg_replace($attackPatterns, '', $result);
// Step 5: Handle potential JSON serialization issues
// Ensure the string is valid UTF-8
if (! mb_check_encoding($result, 'UTF-8')) {
$result = mb_convert_encoding($result, 'UTF-8', 'UTF-8');
}
// Step 6: Additional sanitization for special patterns
// Remove or replace specific problematic sequences
$result = str_replace(
['{{{', '}}}', '<<<', '>>>'],
['{ { {', '} } }', '< < <', '> > >'],
$result
);
// Step 7: Remove consecutive spaces (which can occur after other replacements)
$result = preg_replace('/ {2,}/', ' ', $result);
if ($removeNewlines) {
$result = str_replace("\n", ' ', $value);
}
return $result;
};
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace Leantime\Core\Support\String;
use Illuminate\Support\Str;
/**
* @mixin \Illuminate\Support\Stringable
*/
class ToMarkdown
{
/**
* Converts a PHP array into a formatted markdown string.
*
* @param int $headerLevel Starting header level (1-6)
* @return callable A function that converts data to markdown format
*/
public function toMarkdown($headerLevel = 2)
{
$sanitizeForMarkdown = function ($value) {
if ($value === null) {
return '*null*';
}
if (is_bool($value)) {
return $value ? 'true' : 'false';
}
$string = (string) $value;
// Use the sanitizeForLLM macro for consistent sanitization
$string = Str::sanitizeForLLM($string);
return $string;
};
return function ($data) use ($headerLevel, $sanitizeForMarkdown) {
if (! is_array($data)) {
if (is_bool($data)) {
return $data ? 'true' : 'false';
}
$string = (string) $data;
// Use the sanitizeForLLM macro for consistent sanitization
return Str::sanitizeForLLM($string);
}
$result = '';
$indentLevel = 0;
// Internal function to process array recursively
$processArray = function ($array, $level, $indent) use (&$processArray, &$result, $sanitizeForMarkdown) {
foreach ($array as $key => $value) {
// Skip numeric keys for sequential arrays if they're just indices
$skipKey = is_int($key) && $key === count($array) - count($array);
if (! $skipKey) {
$data = preg_split('/(?=[A-Z])/', $key);
$string = implode(' ', $data);
$string = ucwords($string);
$result .= str_repeat(' ', $indent).'**'.$sanitizeForMarkdown($string).':**';
}
if (is_array($value)) {
// Handle nested arrays
if (empty($value)) {
$result .= str_repeat(' ', $indent)."*Empty*\n\n";
} elseif (array_keys($array) !== range(0, count($array) - 1)) {
// Associative array - process recursively
$processArray($value, $level + 1, $indent + 1);
} else {
// Sequential array - create a list
foreach ($value as $item) {
if (is_array($item)) {
// Nested array item
$result .= str_repeat(' ', $indent).'- ';
$nestedResult = '';
$processArray($item, $level + 2, 0);
// Format the nested result as an indented block
$lines = explode("\n", trim($nestedResult));
$result .= array_shift($lines)."\n";
foreach ($lines as $line) {
$result .= str_repeat(' ', $indent + 1).$line."\n";
}
} else {
// Simple item
$result .= str_repeat(' ', $indent).'- '.$sanitizeForMarkdown($item)."\n";
}
}
$result .= "\n";
}
} elseif (is_bool($value)) {
// Handle boolean values
$result .= str_repeat(' ', $indent).($value ? '✅ Yes' : '❌ No')."\n\n";
} elseif ($value === null) {
// Handle null values
$result .= str_repeat(' ', $indent)."*Not provided*\n\n";
} else {
// Handle scalar values
$formattedValue = $sanitizeForMarkdown($value);
// Check if value is multi-line and format accordingly
if (strpos($formattedValue, "\n") !== false) {
$result .= str_repeat(' ', $indent)."```\n".$formattedValue."\n```\n\n";
} else {
$result .= str_repeat(' ', $indent).$formattedValue."\n\n";
}
}
}
return $result;
};
// Start processing
$processArray($data, $headerLevel, $indentLevel);
// Clean up and return result
return trim($result);
};
}
}