Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Actions/Actions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Threading;
using System.Runtime.InteropServices;
using BuildSoft.VRChat.Osc;
using BuildSoft.VRChat.Osc.Avatar;
Expand Down Expand Up @@ -147,14 +146,14 @@ public class OSCAction : IAction

public string ParameterName { get; set; }

public OscType Type { get; set; }
public OscType OscTypeValue { get; set; }

public string Value { get; set; }

public OSCAction(string parameterName, OscType type, string value)
{
ParameterName = parameterName;
Type = type;
OscTypeValue = type;
Value = value;
}

Expand All @@ -167,7 +166,7 @@ public void PerformAction()
return;
}

switch (Type)
switch (OscTypeValue)
{
case OscType.Bool:
if (bool.TryParse(value, out bool boolValue))
Expand Down
4 changes: 2 additions & 2 deletions LineHandlers/AbstractLineHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Tailgrab.LineHandler
public interface ILineHandler
{
void AddAction(IAction action);

bool HandleLine(string line);

void LogColor( string color );
Expand All @@ -18,7 +18,7 @@ public abstract class AbstractLineHandler: ILineHandler
{
protected string Pattern { get; }
protected Regex regex;
protected List<IAction> Actions = new List<IAction>();
public List<IAction> Actions = new List<IAction>();
protected bool LogOutput { get; set; } = true;
protected string LogColor = "37m"; // Default to white
public string COLOR_PREFIX => $"\u001b[{LogColor}";
Expand Down
1 change: 0 additions & 1 deletion LineHandlers/PenNetworkIdHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ namespace Tailgrab.LineHandler;
using System.Text;
using System.Text.RegularExpressions;
using Tailgrab.PlayerManagement;
using NLog;

public class PenNetworkHandler : AbstractLineHandler
{
Expand Down
2 changes: 1 addition & 1 deletion NLog.config
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
throwConfigExceptions="true">

<targets>
<target xsi:type="File" name="logfile" fileName="logs/${shortdate}.log"
<target xsi:type="File" name="logfile" fileName="logs/tailgrab-${shortdate}.log"
layout="${longdate}|${level:uppercase=true}|${logger}|${message} ${exception:format=tostring}" />
<target xsi:type="Console" name="logconsole"
layout="${longdate}|${level:uppercase=true}|${message} ${exception:format=tostring}" />
Expand Down
67 changes: 13 additions & 54 deletions Program.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
using System.Reflection;
using System.Text;
using BuildSoft.VRChat.Osc;
using Tailgrab.Actions;
using Tailgrab.LineHandler;
using NLog;
using Tailgrab.Configuration;

public class FileTailer
{
/// <summary>
/// A list of the Regex Line Matchers that are used to process log lines.
/// </summary>
static List<ILineHandler> handlers = new List<ILineHandler>{};
static List<ILineHandler> HandlerList = new List<ILineHandler>{};

/// <summary>
/// A List of opened file paths to avoid opening the same file multiple times.
/// </summary>
static List<string> openedFiles = new List<string>{};
static List<string> OpenedFiles = new List<string>{};

/// <summary>
/// The path to the user's profile directory.
Expand All @@ -32,56 +31,16 @@ public class FileTailer
public static Logger logger = LogManager.GetCurrentClassLogger();


/// <summary>
/// Load and Initialize all the Line Handlers from the regular-expressions.txt file.
/// </summary>
public static void InitializeMatchsets()
{
using (FileStream fs = new FileStream("./regular-expressions.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader sr = new StreamReader(fs, Encoding.UTF8))
{
while (true && sr != null)
{
string? line = sr.ReadLine();
if (line == null)
{
break;
}

handlers.Add( new LoggingLineHandler(line));
}

handlers.Add( new OnPlayerJoinHandler(OnPlayerJoinHandler.LOG_PATTERN) );
handlers.Add( new OnPlayerNetworkHandler(OnPlayerNetworkHandler.LOG_PATTERN) );
handlers.Add( new StickerHandler(StickerHandler.LOG_PATTERN) );
handlers.Add( new PrintHandler(PrintHandler.LOG_PATTERN) );
handlers.Add( new AvatarChangeHandler(AvatarChangeHandler.LOG_PATTERN) );
handlers.Add( new AvatarUnpackHandler(AvatarUnpackHandler.LOG_PATTERN) );
handlers.Add( new WarnKickHandler(WarnKickHandler.LOG_PATTERN) );
handlers.Add( new PenNetworkHandler(PenNetworkHandler.LOG_PATTERN) );
handlers.Add( new QuitHandler(QuitHandler.LOG_PATTERN) );

//handlers.Add( new VTKHandler(VTKHandler.LOG_PATTERN) );
ILineHandler handler = new VTKHandler(VTKHandler.LOG_PATTERN);
handler.AddAction( new OSCAction("/avatar/parameters/Ear/Right_Angle", OscType.Float, "20.0" ));
handler.AddAction( new DelayAction(500) );
handler.AddAction( new OSCAction("/avatar/parameters/Ear/Right_Angle", OscType.Float, "0.0" ));
handler.LogColor("31;1m"); // Bright Red
handlers.Add( handler );

}
}

/// <summary>
/// Threaded tailing of a file, reading new lines as they are added.
/// </summary>
public static async Task TailFileAsync(string filePath)
{
if( openedFiles.Contains(filePath) )
if( OpenedFiles.Contains(filePath) )
{
return;
}
openedFiles.Add(filePath);
OpenedFiles.Add(filePath);

Console.WriteLine($"Tailing file: {filePath}. Press Ctrl+C to stop.");

Expand All @@ -105,7 +64,7 @@ public static async Task TailFileAsync(string filePath)
string? line;
while ((line = await sr.ReadLineAsync()) != null)
{
foreach (ILineHandler handler in handlers)
foreach (ILineHandler handler in HandlerList)
{
if (handler.HandleLine(line))
{
Expand Down Expand Up @@ -154,24 +113,24 @@ public static async Task Main(string[] args)

logger.Info($"Tailgrab Version: {BuildInfo.GetInformationalVersion()}");

string filePath = VRChatAppDataPath + @"\\";
string filePath = VRChatAppDataPath + Path.DirectorySeparatorChar;
if (args.Length == 0)
{
logger.Warn("No path argument provided, defaulting to VRChat log directory.");
Console.WriteLine("Usage: dotnet run <filePath>");
Console.WriteLine($"Running without arguments will watch the VRChat log directory at '{filePath}'");
} else
logger.Warn("No path argument provided, defaulting to VRChat log directory: {filePath}");
}
else
{
filePath = args[0];
}

if (!Directory.Exists(filePath))
{
logger.Info($"WatchZing VRChat log directory at '{filePath}'");
logger.Info($"Missing VRChat log directory at '{filePath}'");
return;
}

InitializeMatchsets();
ConfigurationManager.LoadLineHandlersFromConfig(HandlerList);
logger.Info($"Watching for log changes from: '{filePath}'");
await WatchPath(filePath);
}
}
Expand Down
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,32 @@
# TailGrab
VRChat Log Parser and Automation tool to help moderators manage trouble makers in VRChat since VRChat will not take moderation seriously.

# Usage
Open a Powershell or Command Line prompt in your windows host, change directory to where ```tailgrab``` has been extracted to and start it with:

```.\tailgrab.exe```

Or if you have moved where the VR Chat ```output_log_*.txt``` are located; then:

```.\tailgrab.exe {full path to VR Chat logs ending with a \}```


# Capabilities

The core concept of the TailGrab was to create a Windows friendly ```grep``` of VR Chat log events that would allow a group moderation team to review, get insights of bad actors and with the action framework to perform a scripted reaction to a log event.

EG:
A ```Vote To Kick``` is received from a patreon, the action sequence could:
- Send a OSC Avatar Parameter(s) that change the avatar's ear position
- Delay for a quarter of a second
- Send a keystroke to your soundboard application
- Send a keystroke to OBS to start recording


## POC Version
- Parse VRChat log files
- World ```Furry Hideout``` will record User Pen Interaction
- Record User's avatar usage while in the instance
- Record User's moderation while in the instance (Warn and final Kick)
- Partial work with OSC Triggered events to send to your avatar
- Partial work with Keystroke events sent to a application of your choice
108 changes: 99 additions & 9 deletions config.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,120 @@
{
"lineHandlers": [
{
"type": "VTKHandler",
"handlerTypeValue": "AvatarChange",
"enabled": true,
"logPatternType": "*default",
"logPattern": ".*\\[VTK\\].*Right_Ear_Angle.*",
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[Behaviour\\]\\WSwitching\\W([\\S\\W]+)\\Wto\\Wavatar\\W([\\S\\W]+)",
"logOutput": false,
"logOutputColor": "*default",
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "AvatarUnpack",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[AssetBundleDownloadManager\\]\\W\\[\\d+\\] Unpacking Avatar \\(([\\S\\W]+) by ([\\S\\W]+)\\)",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "OnPlayerJoin",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[Behaviour\\]\\WOnPlayer([\\d\\w]+)\\W([\\d\\w\\W]+)\\W\\((usr_[\\d\\w\\W]+)\\)",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "OnPlayerNetwork",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[AP\\]\\WPlayer\\W\\W([\\d\\w\\W]+)\\W joined with ID ([\\d]+)",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "PenNetwork",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[NetworkProcessing\\] Received ownership transfer of ([\\d]+) from ([\\d]+) to ([\\d]+)",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "Print",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[API\\]\\WRequesting\\WGet\\Wprints/(prnt_[\\d\\w\\W]+)\\W\\{\\{\\}\\}",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "Sticker",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W(?:[\\[Always\\]\\W]*)\\[StickersManager\\]\\WUser\\W(usr_[\\d\\w\\W]+)\\W\\(([\\d\\w\\W]+)\\)\\Wspawned\\Wsticker\\W(file_[\\d\\w\\W]+)",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "VTK",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[ModerationManager\\] A vote kick has been initiated against ([\\S\\W]+), do you agree",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
{
"type": "OSCAction",
"actionTypeValue": "OSCAction",
"parameterName": "/avatar/parameters/Ear/Right_Angle",
"oscType": "Float",
"oscValueType": "Float",
"value": "20.0"
},
{
"type": "DelayAction",
"actionTypeValue": "DelayAction",
"milliseconds": 500
},
{
"type": "OSCAction",
"actionTypeValue": "OSCAction",
"parameterName": "/avatar/parameters/Ear/Right_Angle",
"oscType": "Float",
"oscValueType": "Float",
"value": "0.0"
}
]
},
{
"handlerTypeValue": "WarnKick",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\W\\[ModerationManager\\]\\W([\\S\\W]+)\\Whas\\Wbeen\\W(warned|kicked)",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
},
{
"handlerTypeValue": "Quit",
"enabled": true,
"patternTypeValue": "Default",
"pattern": "([\\d]{4}.[\\d]{2}.[\\d]{2}\\W[\\d]{2}:[\\d]{2}:[\\d]{2})\\W(Log[\\W]{8}|Debug[\\W]{6})-\\W\\WVRCApplication: HandleApplicationQuit at ([\\d\\W]+)",
"logOutput": false,
"logOutputColor": "Default",
"actions": [
]
}
]
}
Loading
Loading