feat: integrate chat voice and diagnostics client
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
/// Alignment requested by a TeamSpeak spacer channel tag.
|
||||
enum SpacerAlignment {
|
||||
/// Left-aligned spacer content.
|
||||
left,
|
||||
|
||||
/// Right-aligned spacer content.
|
||||
right,
|
||||
|
||||
/// Center-aligned spacer content.
|
||||
center,
|
||||
}
|
||||
|
||||
/// Built-in TeamSpeak spacer separator patterns.
|
||||
enum SpacerSpecialType {
|
||||
/// `___`
|
||||
solidLine,
|
||||
|
||||
/// `---`
|
||||
dashLine,
|
||||
|
||||
/// `...`
|
||||
dotLine,
|
||||
|
||||
/// `-.-`
|
||||
dashDotLine,
|
||||
|
||||
/// `-..`
|
||||
dashDotDotLine,
|
||||
}
|
||||
|
||||
/// Parsed form of a TeamSpeak spacer channel name.
|
||||
class SpacerChannelNameParseResult {
|
||||
/// Construct a spacer parse result.
|
||||
const SpacerChannelNameParseResult({
|
||||
required this.isSpacer,
|
||||
required this.isValid,
|
||||
required this.alignment,
|
||||
required this.isRepeating,
|
||||
required this.uniqueSuffix,
|
||||
required this.text,
|
||||
required this.specialType,
|
||||
required this.isBlankSpacer,
|
||||
this.reason,
|
||||
});
|
||||
|
||||
/// True only when the name begins with a valid bracketed spacer tag.
|
||||
final bool isSpacer;
|
||||
|
||||
/// True when the bracketed spacer tag follows the supported syntax.
|
||||
final bool isValid;
|
||||
|
||||
/// Optional alignment flag. Null means the server/client default.
|
||||
final SpacerAlignment? alignment;
|
||||
|
||||
/// True when `*` appears in the tag and the text should repeat.
|
||||
final bool isRepeating;
|
||||
|
||||
/// The exact suffix after `Spacer` and before `]`.
|
||||
final String uniqueSuffix;
|
||||
|
||||
/// The exact text after the closing `]`.
|
||||
final String text;
|
||||
|
||||
/// Built-in separator type for special text values.
|
||||
final SpacerSpecialType? specialType;
|
||||
|
||||
/// True for the known blank-looking right-aligned dot spacer.
|
||||
final bool isBlankSpacer;
|
||||
|
||||
/// Parse error/reason for non-spacer or malformed names.
|
||||
final String? reason;
|
||||
}
|
||||
|
||||
/// Options for formatting a TeamSpeak spacer channel name.
|
||||
class SpacerChannelNameFormatOptions {
|
||||
/// Construct spacer formatting options.
|
||||
const SpacerChannelNameFormatOptions({
|
||||
this.alignment,
|
||||
this.isRepeating = false,
|
||||
this.uniqueSuffix = '',
|
||||
this.text = '',
|
||||
});
|
||||
|
||||
/// Optional alignment flag. Null means omit the alignment prefix.
|
||||
final SpacerAlignment? alignment;
|
||||
|
||||
/// Whether to include the repeating `*` tag flag.
|
||||
final bool isRepeating;
|
||||
|
||||
/// Uniqueness suffix to place after `Spacer`.
|
||||
final String uniqueSuffix;
|
||||
|
||||
/// Text to place after the closing `]`.
|
||||
final String text;
|
||||
}
|
||||
|
||||
const _notSpacer = SpacerChannelNameParseResult(
|
||||
isSpacer: false,
|
||||
isValid: false,
|
||||
alignment: null,
|
||||
isRepeating: false,
|
||||
uniqueSuffix: '',
|
||||
text: '',
|
||||
specialType: null,
|
||||
isBlankSpacer: false,
|
||||
reason: 'not a spacer channel name',
|
||||
);
|
||||
|
||||
/// Return true when [name] begins with a valid bracketed spacer tag.
|
||||
bool isSpacerChannelName(String name) => parseSpacerChannelName(name).isSpacer;
|
||||
|
||||
/// Parse a TeamSpeak spacer channel name.
|
||||
///
|
||||
/// Supported tag form is `[?Spacer#]Text`, parsed case-insensitively.
|
||||
/// `?` may be `l`, `r`, or `c`; `*` may also appear in the tag to
|
||||
/// mark repeating spacer content. The suffix and text are preserved
|
||||
/// exactly as written.
|
||||
SpacerChannelNameParseResult parseSpacerChannelName(String name) {
|
||||
if (!name.startsWith('[')) return _notSpacer;
|
||||
|
||||
final close = name.indexOf(']');
|
||||
if (close < 0) {
|
||||
return _invalidSpacerName('missing closing bracket');
|
||||
}
|
||||
|
||||
final tag = name.substring(1, close);
|
||||
final text = name.substring(close + 1);
|
||||
final match = RegExp(
|
||||
r'^([lrc*]*)(spacer)(.*)$',
|
||||
caseSensitive: false,
|
||||
).firstMatch(tag);
|
||||
|
||||
if (match == null) {
|
||||
if (tag.toLowerCase().contains('spacer')) {
|
||||
return _invalidSpacerName('invalid spacer tag');
|
||||
}
|
||||
return _notSpacer;
|
||||
}
|
||||
|
||||
final flags = match.group(1) ?? '';
|
||||
final uniqueSuffix = match.group(3) ?? '';
|
||||
final alignmentFlags = flags
|
||||
.toLowerCase()
|
||||
.split('')
|
||||
.where((flag) => flag == 'l' || flag == 'r' || flag == 'c')
|
||||
.toList();
|
||||
final repeatingFlags = flags.split('').where((flag) => flag == '*').length;
|
||||
|
||||
if (alignmentFlags.length > 1) {
|
||||
return _invalidSpacerName('multiple alignment flags');
|
||||
}
|
||||
if (repeatingFlags > 1) {
|
||||
return _invalidSpacerName('multiple repeating flags');
|
||||
}
|
||||
|
||||
final alignmentFlag = alignmentFlags.isEmpty ? null : alignmentFlags.first;
|
||||
final alignment = switch (alignmentFlag) {
|
||||
'l' => SpacerAlignment.left,
|
||||
'r' => SpacerAlignment.right,
|
||||
'c' => SpacerAlignment.center,
|
||||
_ => null,
|
||||
};
|
||||
final specialType = _specialTypeForText(text);
|
||||
return SpacerChannelNameParseResult(
|
||||
isSpacer: true,
|
||||
isValid: true,
|
||||
alignment: alignment,
|
||||
isRepeating: repeatingFlags == 1,
|
||||
uniqueSuffix: uniqueSuffix,
|
||||
text: text,
|
||||
specialType: specialType,
|
||||
isBlankSpacer: alignment == SpacerAlignment.right && text == '.',
|
||||
);
|
||||
}
|
||||
|
||||
/// Format a TeamSpeak spacer channel name deterministically.
|
||||
///
|
||||
/// The output uses canonical `Spacer` casing and places `*` before
|
||||
/// the alignment flag when both are present.
|
||||
String formatSpacerChannelName(SpacerChannelNameFormatOptions options) {
|
||||
final repeatFlag = options.isRepeating ? '*' : '';
|
||||
final alignFlag = switch (options.alignment) {
|
||||
SpacerAlignment.left => 'l',
|
||||
SpacerAlignment.right => 'r',
|
||||
SpacerAlignment.center => 'c',
|
||||
null => '',
|
||||
};
|
||||
return '[$repeatFlag${alignFlag}Spacer${options.uniqueSuffix}]${options.text}';
|
||||
}
|
||||
|
||||
/// Convert a channel name into display text while preserving the
|
||||
/// underlying channel entity and behavior.
|
||||
String channelSpacerLabel(String raw, {int repeatColumns = 32}) {
|
||||
final parsed = parseSpacerChannelName(raw);
|
||||
if (!parsed.isValid) return raw;
|
||||
if (parsed.isBlankSpacer) return '';
|
||||
if (parsed.isRepeating) {
|
||||
return _repeatSpacerText(parsed.text, repeatColumns);
|
||||
}
|
||||
|
||||
return switch (parsed.specialType) {
|
||||
SpacerSpecialType.solidLine => '────────',
|
||||
SpacerSpecialType.dashLine => '╌╌╌╌╌╌╌╌',
|
||||
SpacerSpecialType.dotLine => '········',
|
||||
SpacerSpecialType.dashDotLine => '─╶─╶─╶─╶─╶─╶─╶─╶',
|
||||
SpacerSpecialType.dashDotDotLine => '─╶╶─╶╶─╶╶─╶╶',
|
||||
null => parsed.text,
|
||||
};
|
||||
}
|
||||
|
||||
SpacerChannelNameParseResult _invalidSpacerName(String reason) {
|
||||
return SpacerChannelNameParseResult(
|
||||
isSpacer: false,
|
||||
isValid: false,
|
||||
alignment: null,
|
||||
isRepeating: false,
|
||||
uniqueSuffix: '',
|
||||
text: '',
|
||||
specialType: null,
|
||||
isBlankSpacer: false,
|
||||
reason: reason,
|
||||
);
|
||||
}
|
||||
|
||||
SpacerSpecialType? _specialTypeForText(String text) {
|
||||
return switch (text) {
|
||||
'___' => SpacerSpecialType.solidLine,
|
||||
'---' => SpacerSpecialType.dashLine,
|
||||
'...' => SpacerSpecialType.dotLine,
|
||||
'-.-' => SpacerSpecialType.dashDotLine,
|
||||
'-..' => SpacerSpecialType.dashDotDotLine,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
String _repeatSpacerText(String pattern, int repeatColumns) {
|
||||
if (pattern.isEmpty) return '────────';
|
||||
final buffer = StringBuffer();
|
||||
while (buffer.length < repeatColumns) {
|
||||
buffer.write(pattern);
|
||||
}
|
||||
return buffer.toString().substring(0, repeatColumns);
|
||||
}
|
||||
Reference in New Issue
Block a user