<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>decode_buffer.log</title>
        <link>https://velog.io/</link>
        <description>:)</description>
        <lastBuildDate>Fri, 09 Aug 2024 07:06:10 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>decode_buffer.log</title>
            <url>https://velog.velcdn.com/images/decode_buffer/profile/53b54bee-c80a-470a-ab7d-9ebbc2f031b0/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. decode_buffer.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/decode_buffer" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[✅ C# - SCUM FTP Server Cawling Discord Bot]]></title>
            <link>https://velog.io/@decode_buffer/C-SCUM-FTP-Server-Cawling-Discord-Bot</link>
            <guid>https://velog.io/@decode_buffer/C-SCUM-FTP-Server-Cawling-Discord-Bot</guid>
            <pubDate>Fri, 09 Aug 2024 07:06:10 GMT</pubDate>
            <description><![CDATA[<h1 id="🔸run-screen">🔸Run Screen</h1>
<h3 id="🔹composition-screen">🔹Composition Screen</h3>
<p><img src="https://velog.velcdn.com/images/decode_buffer/post/2464546e-baff-4bc8-9969-a82e75ae6048/image.png" alt="">
<img src="https://velog.velcdn.com/images/decode_buffer/post/3af14e17-aed9-4012-b351-e48b3bbe2654/image.png" alt=""></p>
<h3 id="🔹server-screen">🔹Server Screen</h3>
<p><img src="https://velog.velcdn.com/images/decode_buffer/post/305b93aa-d186-4a7a-b32e-5fe752d90cc5/image.png" alt="">
<img src="https://velog.velcdn.com/images/decode_buffer/post/e178ae81-22fe-4d72-bb5d-83350691caf4/image.png" alt=""></p>
<h3 id="🔹client-screen">🔹Client Screen</h3>
<p><img src="https://velog.velcdn.com/images/decode_buffer/post/628ff7ce-9783-47c3-a771-98dd75c7b890/image.png" alt="">
<img src="https://velog.velcdn.com/images/decode_buffer/post/0d7a70c3-2431-4d9c-9481-77cd60e629e8/image.png" alt=""></p>
<h1 id="🔸source-code">🔸Source Code</h1>
<h3 id="🔹programcs">🔹Program.cs</h3>
<pre><code>using Discord;
using Discord.Commands;
using Discord.WebSocket;

namespace Scum_Log_Bot
{
    class Program
    {
        static string BOT_TOKEN = &quot;YOUR_BOT_TOKEN&quot;;

        static ulong raidChannelId = 0000000000000000000; // YOUR CHANNEL ID
        static ulong chatChannelId = 0000000000000000000; // YOUR CHANNEL ID

        static DiscordSocketClient client;
        static CommandService commandService;

        Dictionary&lt;string, KeyValuePair&lt;string, ulong&gt;&gt; infos = new Dictionary&lt;string, KeyValuePair&lt;string, ulong&gt;&gt;();

        static Dictionary&lt;string, LoggingTask&gt; tasks = new Dictionary&lt;string, LoggingTask&gt;();

        static void Main(string[] args)
        {
            Console.WriteLine(&quot;SCUM BOT - ON (By. 4K)\r\n&quot;);

            new Program().BotMain().GetAwaiter().GetResult();
        }

        private async Task BotMain()
        {
            client = new DiscordSocketClient(new DiscordSocketConfig()
            {
                LogLevel = LogSeverity.Debug
            });
            commandService = new CommandService(new CommandServiceConfig()
            {
                LogLevel = LogSeverity.Debug
            });

            client.Log += OnClientLogReceived;
            client.Ready += OnReady;
            client.MessageReceived += OnClientMessage;
            client.MessageDeleted += OnClientMessageDeleted;
            commandService.Log += OnClientLogReceived;

            await client.LoginAsync(TokenType.Bot, BOT_TOKEN);
            await client.StartAsync();

            await Task.Delay(-1);
        }

        private Task OnClientLogReceived(LogMessage msg)
        {
            //Console.WriteLine(msg.ToString());

            return Task.CompletedTask;
        }

        private async Task OnReady()
        {
            infos.Add(&quot;admin&quot;, new KeyValuePair&lt;string, ulong&gt;(&quot;어드민 명령어&quot;, 0000000000000000000)); // YOUR CHANNEL ID
            infos.Add(&quot;login&quot;, new KeyValuePair&lt;string, ulong&gt;(&quot;유저 입장&quot;, 0000000000000000000)); // YOUR CHANNEL ID
            infos.Add(&quot;lockpick&quot;, new KeyValuePair&lt;string, ulong&gt;(&quot;락픽&quot;, 0000000000000000000)); // YOUR CHANNEL ID
            infos.Add(&quot;vehicle_destruction&quot;, new KeyValuePair&lt;string, ulong&gt;(&quot;차량 파괴&quot;, 0000000000000000000)); // YOUR CHANNEL ID
            infos.Add(&quot;kill&quot;, new KeyValuePair&lt;string, ulong&gt;(&quot;킬&quot;, 0000000000000000000)); // YOUR CHANNEL ID
            infos.Add(&quot;chat&quot;, new KeyValuePair&lt;string, ulong&gt;(&quot;인게임 채팅&quot;, chatChannelId)); // YOUR CHANNEL ID
            infos.Add(&quot;violations&quot;, new KeyValuePair&lt;string, ulong&gt;(&quot;벤&quot;, 0000000000000000000)); // YOUR CHANNEL ID

            foreach (KeyValuePair&lt;string, KeyValuePair&lt;string, ulong&gt;&gt; info in infos)
            {
                tasks.Add(info.Key, new LoggingTask(client, info));

                Console.WriteLine($&quot;● {info.Value.Key} 로그 활성화&quot;);
            }

            Console.WriteLine();
        }

        private async Task OnClientMessage(SocketMessage arg)
        {
            var message = arg as SocketUserMessage;

            if (message == null)
            {
                return;
            }

            if (message.Author.IsBot &amp;&amp; !message.Content.Contains(&quot;➡️ 채팅 타입 - Admin&quot;) &amp;&amp; message.Content.Contains(&quot;➡️ 채팅 내용 - !raid&quot;) &amp;&amp; message.Channel.Id == chatChannelId)
            {
                await ((IMessageChannel)client.GetChannel(raidChannelId)).SendMessageAsync(message.Content.Replace(&quot;📊 PK | &quot;, &quot;📊 FK | &quot;));
            }
        }

        private async Task OnClientMessageDeleted(Cacheable&lt;IMessage, ulong&gt; cachedMessage, Cacheable&lt;IMessageChannel, ulong&gt; channel)
        {
            foreach (var info in infos)
            {
                if (info.Value.Value == channel.Id)
                {
                    foreach (var task in tasks)
                    {
                        task.Value.run = false;
                    }

                    Console.WriteLine($&quot;\r\n- 디스코드 &#39;{channel.Value.Name.Substring(3)}&#39; 채널에서 로그 삭제 이벤트가 발생 되었습니다. 프로그램을 재실행 해주세요.&quot;);
                    Console.Write(&quot;이 창을 닫으려면 아무 키나 누르세요...&quot;);

                    Console.ReadKey();

                    Environment.Exit(0);
                }
            }
        }
    }
}</code></pre><h3 id="🔹loggingtaskcs">🔹LoggingTask.cs</h3>
<pre><code>using Discord;
using Discord.WebSocket;
using System.Globalization;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;

namespace Scum_Log_Bot
{
    class LoggingTask
    {
        public bool run = true;

        private DiscordSocketClient client;
        private IMessageChannel channel;
        private KeyValuePair&lt;string, KeyValuePair&lt;string, ulong&gt;&gt; info;

        private string programPath;

        private ulong newLogCount;

        private readonly string host = &quot;ftp://YOUR_FTP_IP:YOUR_FTP_PORT&quot;;
        private readonly string user = &quot;YOUR_FTP_USER_ID&quot;;
        private readonly string password = &quot;YOUR_FTP_USER_PASSWORD&quot;;
        private readonly string remote = &quot;/SCUM/Saved/SaveFiles/Logs/&quot;;

        private string[] logFiles = new string[0];

        private ulong lastFileDate;
        private ulong lastFileLine;

        public LoggingTask(DiscordSocketClient client, KeyValuePair&lt;string, KeyValuePair&lt;string, ulong&gt;&gt; info)
        {
            this.client = client;
            this.info = info;

            new Thread(Run).Start();
        }

        private async void Run()
        {
            channel = client.GetChannel(info.Value.Value) as IMessageChannel;

            programPath = Directory.GetCurrentDirectory() + &quot;/Logs/&quot; + info.Key;

            if (!Directory.Exists(programPath))
            {
                Directory.CreateDirectory(programPath);
            }

            await ReadLastFileAsync();

            while (run)
            {
                await PrintSpecificFilesFromFtp(host, user, password, remote);
            }
        }

        private async Task ReadLastFileAsync()
        {
            try
            {
                lastFileDate = ulong.Parse(File.ReadAllText(programPath + &quot;/LastProgramFileDate.log&quot;));
            }
            catch (Exception ex)
            {
                //Console.WriteLine($&quot;Error: {ex.StackTrace}&quot;);

                lastFileDate = 0;
            }

            try
            {
                lastFileLine = ulong.Parse(File.ReadAllText(programPath + &quot;/LastProgramFileLine.log&quot;));
            } catch (Exception ex)
            {
                //Console.WriteLine($&quot;Error: {ex.StackTrace}&quot;);

                lastFileLine = 0;
            }
        }

        private async Task WriteLastFileAsync()
        {
            try
            {
                File.WriteAllText(programPath + &quot;/LastProgramFileDate.log&quot;, lastFileDate + &quot;&quot;);
            }
            catch (Exception ex)
            {
                //Console.WriteLine($&quot;Error: {ex.StackTrace}&quot;);
            }

            try
            {
                File.WriteAllText(programPath + &quot;/LastProgramFileLine.log&quot;, lastFileLine + &quot;&quot;);
            }
            catch (Exception ex)
            {
                //Console.WriteLine($&quot;Error: {ex.StackTrace}&quot;);
            }
        }

        private async Task PrintSpecificFilesFromFtp(string ftpServer, string ftpUsername, string ftpPassword, string remotePath)
        {
            try
            {
                await ListFilesOnFtpServer(ftpServer, ftpUsername, ftpPassword, remotePath);

                newLogCount = 0;
                foreach (string file in logFiles)
                {
                    ulong currentFileDate = ulong.Parse(file.Substring(file.Length - 18, 14));

                    if (currentFileDate &lt; lastFileDate)
                    {
                        continue;
                    }

                    string remoteFilePath = $&quot;{remotePath}{file}&quot;;
                    await PrintFileFromFtp(ftpServer, ftpUsername, ftpPassword, remoteFilePath, currentFileDate);
                }

                if (newLogCount &gt; 0)
                {
                    Console.WriteLine($&quot;- 디스코드 &#39;{channel.Name.Substring(3)}&#39; 채널에 새로운 로그 {newLogCount}개가 갱신 되었습니다.&quot;);
                }
            }
            catch (Exception ex)
            {
                //Console.WriteLine($&quot;Error: {ex.StackTrace}&quot;);
            }
        }

        private async Task ListFilesOnFtpServer(string ftpServer, string ftpUsername, string ftpPassword, string remotePath)
        {
            string ftpUri = $&quot;{ftpServer}{remotePath}&quot;;
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(ftpUri);
            request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
            request.Credentials = new NetworkCredential(ftpUsername, ftpPassword);
            request.UsePassive = true;

            try
            {
                using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
                using (Stream responseStream = response.GetResponseStream())
                using (StreamReader reader = new StreamReader(responseStream))
                {
                    string line;
                    var files = new List&lt;string&gt;();
                    while ((line = reader.ReadLine()) != null)
                    {
                        string[] parts = line.Split(new[] { &#39; &#39; }, StringSplitOptions.RemoveEmptyEntries);
                        string fileName = parts[parts.Length - 1];
                        if (fileName.StartsWith(ExceptionKey()))
                        {
                            files.Add(fileName);
                        }
                    }

                    logFiles = files.ToArray();
                }
            }
            catch (WebException ex)
            {
                //Console.WriteLine($&quot;Error: {ex.StackTrace}&quot;);

                logFiles = new string[0];
            }
        }

        private async Task PrintFileFromFtp(string ftpServer, string ftpUsername, string ftpPassword, string remoteFilePath, ulong currentFileDate)
        {
            ulong currentFileLine = 1;

            string ftpUri = $&quot;{ftpServer}{remoteFilePath}&quot;;
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(ftpUri);
            request.Method = WebRequestMethods.Ftp.DownloadFile;
            request.Credentials = new NetworkCredential(ftpUsername, ftpPassword);
            request.UsePassive = true;

            try
            {
                using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
                using (Stream responseStream = response.GetResponseStream())
                using (StreamReader reader = new StreamReader(responseStream, Encoding.Unicode))
                {
                    string allData = reader.ReadToEnd();
                    string[] contents = allData.Split(&quot;\n&quot;);

                    int size = contents.Length;
                    for (int i = 0; i &lt; size; i++)
                    {
                        if (contents[i].Length &gt; 0 &amp;&amp; !contents[i].Contains(&quot;Game version&quot;))
                        {
                            if (!ExceptionConditionCheck(contents[i]))
                            {
                                continue;
                            }

                            if (currentFileDate &gt; lastFileDate)
                            {
                                lastFileLine = 0;
                            }

                            if (currentFileLine &lt;= lastFileLine)
                            {
                                currentFileLine++;
                                continue;
                            }

                            string data = ParseData(contents[i], currentFileLine);

                            await channel.SendMessageAsync($&quot;```{data}```&quot;);

                            lastFileDate = currentFileDate;
                            lastFileLine = currentFileLine;

                            await WriteLastFileAsync();

                            currentFileLine++;
                            newLogCount++;

                            await Task.Delay(1000);
                        }
                    }
                }
            }
            catch (WebException ex)
            {
                //Console.WriteLine($&quot;Error: {ex.StackTrace}&quot;);
            }
        }

        private string ParseData(string data, ulong currentFileLine)
        {
            string pData = &quot;📊 PK | &quot; + info.Value.Key + &quot; | &quot; + DateTime.ParseExact(data.Substring(0, 19), &quot;yyyy.MM.dd-HH.mm.ss&quot;, CultureInfo.InvariantCulture).AddHours(9).ToString(&quot;yyyy.MM.dd-HH:mm:ss&quot;, CultureInfo.InvariantCulture) + &quot; | &quot; + currentFileLine + &quot;\r\n&quot;;

            switch (info.Key)
            {
                case &quot;admin&quot;:
                    string userPattern = @&quot;&#39;(\d+):(.+?)\((\d+)\)&#39;&quot;;

                    string commandPattern = @&quot;Command:\s&#39;(.+?)&#39;&quot;;
                    string teleportPattern = @&quot;Target\sof\sTeleportTo:\s&#39;(\d+):(.+?)\((\d+)\)&#39;\sLocation:\sX=(-?\d+\.\d+)\sY=(-?\d+\.\d+)\sZ=(-?\d+\.\d+)&quot;;
                    string mapClickPattern = @&quot;Used\smap\sclick\steleport\sto\s(player|vehicle):\s(?:&#39;(\d+):(.+?)\((\d+)\)&#39;|(.+?))\sLocation:\sX=(-?\d+\.\d+)\sY=(-?\d+\.\d+)\sZ=(-?\d+\.\d+)&quot;;

                    var userMatch = Regex.Match(data, userPattern);

                    if (userMatch.Success)
                    {
                        pData += $&quot;➡️ 사용자 정보 - {userMatch.Groups[2].Value} ({userMatch.Groups[1].Value})\r\n&quot;;

                        var commandMatch = Regex.Match(data, commandPattern);
                        var teleportMatch = Regex.Match(data, teleportPattern);
                        var mapClickMatch = Regex.Match(data, mapClickPattern);

                        if (commandMatch.Success)
                        {
                            pData += $&quot;➡️ 명령어 타입 - 채팅\r\n&quot;;
                            pData += $&quot;➡️ 명령어 정보 - {commandMatch.Groups[1].Value}&quot;;
                        }
                        else if (teleportMatch.Success)
                        {
                            pData += $&quot;➡️ 명령어 타입 - 맵 클릭\r\n&quot;;
                            pData += $&quot;➡️ 텔레포트 대상 - {teleportMatch.Groups[2].Value} ({teleportMatch.Groups[1].Value})\r\n&quot;;
                            pData += $&quot;➡️ 텔레포트 좌표 - X={teleportMatch.Groups[4].Value} Y={teleportMatch.Groups[5].Value} Z={teleportMatch.Groups[6].Value}&quot;;
                        }
                        else if (mapClickMatch.Success)
                        {
                            string steamId = mapClickMatch.Groups[2].Value;

                            pData += $&quot;➡️ 명령어 타입 - 맵 클릭\r\n&quot;;

                            if (!string.IsNullOrEmpty(steamId))
                            {
                                pData += $&quot;➡️ 텔레포트 대상 - {mapClickMatch.Groups[3].Value} ({steamId})\r\n&quot;;
                            } else
                            {
                                pData += $&quot;➡️ 텔레포트 대상 - {mapClickMatch.Groups[5].Value.Replace(&quot;&#39;&quot;, &quot;&quot;)}\r\n&quot;;
                            }

                            pData += $&quot;➡️ 텔레포트 좌표 - X={mapClickMatch.Groups[6].Value} Y={mapClickMatch.Groups[7].Value} Z={mapClickMatch.Groups[8].Value}&quot;;
                        }
                        else
                        {
                            pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 2)&quot;;
                        }
                    }
                    else
                    {
                        pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 1)&quot;;
                    }
                    break;
                case &quot;login&quot;:
                    string loginPattern = @&quot;&#39;(\d+\.\d+\.\d+\.\d+)\s(\d+):(.+?)\((\d+)\)&#39; logged in at:\sX=(-?\d+\.\d+)\sY=(-?\d+\.\d+)\sZ=(-?\d+\.\d+)&quot;;

                    var loginMatch = Regex.Match(data, loginPattern);

                    if (loginMatch.Success)
                    {
                        pData += $&quot;➡️ 계정 정보 - {loginMatch.Groups[3].Value} ({loginMatch.Groups[2].Value})\r\n&quot;;
                        pData += $&quot;➡️ IP 정보 - {loginMatch.Groups[1].Value}\r\n&quot;;
                        pData += $&quot;➡️ 입장 좌표 - X={loginMatch.Groups[5].Value} Y={loginMatch.Groups[6].Value} Z={loginMatch.Groups[7].Value}&quot;;
                    }
                    else
                    {
                        pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 3)&quot;;
                    }
                    break;
                case &quot;lockpick&quot;:
                    string lockpickPattern = @&quot;\[LogMinigame\]\s\[(?:BP_DialLockMinigame_C|LockpickingMinigame_C)\]\sUser:\s(.+?)\s\((\d+),\s(\d+)\)\.\sSuccess:\s(\w+)\.\sElapsed\stime:\s([\d\.]+)\.\sFailed\sattempts:\s(\d+)\.\sTarget\sobject:\s(.+?)\(ID:\s(.+?)\)\.\sLock\stype:\s(.+?)\.\sUser\sowner:\s(.+?)\.\sLocation:\sX=(-?\d+\.\d+)\sY=(-?\d+\.\d+)\sZ=(-?\d+\.\d+)&quot;;

                    var lockpickMatch = Regex.Match(data, lockpickPattern);

                    if (lockpickMatch.Success)
                    {
                        string lockType = lockpickMatch.Groups[9].Value;

                        pData += $&quot;➡️ 계정 정보 - {lockpickMatch.Groups[1].Value} ({lockpickMatch.Groups[3].Value})\r\n&quot;;
                        pData += $&quot;➡️ 대상 정보 - {lockpickMatch.Groups[7].Value} ({lockpickMatch.Groups[8].Value})\r\n&quot;;
                        pData += $&quot;➡️ 대상 주인 - {lockpickMatch.Groups[10].Value}\r\n&quot;;
                        pData += $&quot;➡️ 대상 좌표 - X={lockpickMatch.Groups[11].Value} Y={lockpickMatch.Groups[12].Value} Z={lockpickMatch.Groups[13].Value}\r\n&quot;;
                        pData += $&quot;➡️ 잠금 종류 - {(lockType.Equals(&quot;DialLock&quot;) ? &quot;다이얼 락&quot; : lockType.Equals(&quot;Advanced&quot;) ? &quot;금장&quot; : lockType.Equals(&quot;Medium&quot;) ? &quot;은장&quot; : lockType.Equals(&quot;Basic&quot;) ? &quot;동장&quot; : &quot;똥장&quot;)}\r\n&quot;;
                        pData += $&quot;➡️ 성공 여부 - {(lockpickMatch.Groups[4].Value.Equals(&quot;Yes&quot;) ? &quot;⭕&quot; : &quot;❌&quot;)}\r\n&quot;;
                        pData += $&quot;➡️ 실패 횟수 - {lockpickMatch.Groups[6].Value}회\r\n&quot;;
                        pData += $&quot;➡️ 소요 시간 - {lockpickMatch.Groups[5].Value}초&quot;;
                    }
                    else
                    {
                        pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 4)&quot;;
                    }
                    break;
                case &quot;vehicle_destruction&quot;:
                    string vdPattern = @&quot;\[Destroyed\]\s([\w_]+)\.\sVehicleId:\s(\d+)\.\sOwner:\s([^\.]+)\.\sLocation:\sX=([-?\d.]+)\sY=([-?\d.]+)\sZ=([-?\d.]+)&quot;;

                    var vdMatch = Regex.Match(data, vdPattern);

                    if (vdMatch.Success)
                    {
                        pData += $&quot;➡️ 차량 정보 - {vdMatch.Groups[1].Value} ({vdMatch.Groups[2].Value})\r\n&quot;;
                        pData += $&quot;➡️ 차량 주인 - {vdMatch.Groups[3].Value}\r\n&quot;;
                        pData += $&quot;➡️ 파괴 좌표 - X={vdMatch.Groups[4].Value} Y={vdMatch.Groups[5].Value} Z={vdMatch.Groups[6].Value}&quot;;
                    }
                    else
                    {
                        pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 5)&quot;;
                    }
                    break;
                case &quot;kill&quot;:
                    string killerPattern = @&quot;Killer:\s(.+?)\s\((\d+)\)&quot;;
                    string diedPattern = @&quot;Died:\s(.+?)\s\((\d+)\)&quot;;
                    string weaponPattern = @&quot;Weapon:\s(.+?)\s\[(.+?)\]&quot;;
                    string locationsPattern = @&quot;KillerLoc\s:\s([\d\.\-]+),\s([\d\.\-]+),\s([\d\.\-]+)\sVictimLoc:\s([\d\.\-]+),\s([\d\.\-]+),\s([\d\.\-]+),\sDistance:\s([\d\.\-]+)\sm&quot;;

                    var killerMatch = Regex.Match(data, killerPattern);
                    var diedMatch = Regex.Match(data, diedPattern);
                    var weaponMatch = Regex.Match(data, weaponPattern);
                    var locationsMatch = Regex.Match(data, locationsPattern);

                    if (killerMatch.Success &amp;&amp; diedMatch.Success &amp;&amp; weaponMatch.Success &amp;&amp; locationsMatch.Success)
                    {
                        pData += $&quot;➡️ 공격자 정보 - {killerMatch.Groups[1].Value} ({killerMatch.Groups[2].Value})\r\n&quot;;
                        pData += $&quot;➡️ 공격자 좌표 - X={locationsMatch.Groups[1].Value} Y={locationsMatch.Groups[2].Value} Z={locationsMatch.Groups[3].Value}\r\n&quot;;
                        pData += $&quot;➡️ 피해자 정보 - {diedMatch.Groups[1].Value} ({diedMatch.Groups[2].Value})\r\n&quot;;
                        pData += $&quot;➡️ 피해자 좌표 - X={locationsMatch.Groups[4].Value} Y={locationsMatch.Groups[5].Value} Z={locationsMatch.Groups[6].Value}\r\n&quot;;
                        pData += $&quot;➡️ 무기 정보 - {weaponMatch.Groups[1].Value} ({weaponMatch.Groups[2].Value})\r\n&quot;;
                        pData += $&quot;➡️ 거리 정보 - {locationsMatch.Groups[7].Value}m&quot;;
                    }
                    else
                    {
                        pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 6)&quot;;
                    }
                    break;
                case &quot;chat&quot;:
                    string chatPattern = @&quot;&#39;(\d{17}):([^&#39;]*)&#39; &#39;([^:]*):\s(.*)&#39;&quot;;

                    var chatMatch = Regex.Match(data, chatPattern);

                    if (chatMatch.Success)
                    {
                        pData += $&quot;➡️ 계정 정보 - {chatMatch.Groups[2].Value} ({chatMatch.Groups[1].Value})\r\n&quot;;
                        pData += $&quot;➡️ 채팅 타입 - {chatMatch.Groups[3].Value}\r\n&quot;;
                        pData += $&quot;➡️ 채팅 내용 - {chatMatch.Groups[4].Value}&quot;;
                    }
                    else
                    {
                        pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 7)&quot;;
                    }
                    break;
                case &quot;violations&quot;:
                    string violationPattern = @&quot;AConZGameMode::BanPlayerById:\sUser id:\s&#39;(\d{17})&#39;&quot;;

                    var violationMatch = Regex.Match(data, violationPattern);

                    if (violationMatch.Success)
                    {
                        pData += $&quot;➡️ 계정 정보 - {violationMatch.Groups[1].Value}&quot;;
                    }
                    else
                    {
                        pData = $&quot;⚠️ 오류 정보 - 로그 기록 중 예기치 못한 오류가 발생 했습니다. (Error Code. 8)&quot;;
                    }
                    break;
            }

            return pData;
        }

        private string ExceptionKey()
        {
            return
                info.Key.Equals(&quot;lockpick&quot;) ? &quot;gameplay&quot; :
                info.Key;
        }

        private bool ExceptionConditionCheck(string data)
        {
            if (info.Key.Equals(&quot;admin&quot;) &amp;&amp; !data.Contains(&quot;Command&quot;) &amp;&amp; !data.Contains(&quot;Location&quot;))
            {
                return false;
            }

            if (info.Key.Equals(&quot;login&quot;) &amp;&amp; !data.Contains(&quot;logged in&quot;))
            {
                return false;
            }

            if (info.Key.Equals(&quot;lockpick&quot;) &amp;&amp; !data.Contains(&quot;LockpickingMinigame_C&quot;) &amp;&amp; !data.Contains(&quot;BP_DialLockMinigame_C&quot;))
            {
                return false;
            }

            if (info.Key.Equals(&quot;vehicle_destruction&quot;) &amp;&amp; !data.Contains(&quot;Destroyed&quot;))
            {
                return false;
            }

            if (info.Key.Equals(&quot;kill&quot;) &amp;&amp; !data.Contains(&quot;Died&quot;))
            {
                return false;
            }

            if (info.Key.Equals(&quot;violations&quot;) &amp;&amp; !data.Contains(&quot;BanPlayerById&quot;))
            {
                return false;
            }

            return true;
        }
    }
}</code></pre><h1 id="🔸comment">🔸Comment</h1>
<h3 id="🔹프로그램-목적">🔹프로그램 목적</h3>
<blockquote>
</blockquote>
<p>➡️ SCUM 게임 서버 관리자가 직접 GPORTAL 사의 FTP 서버에 연결하지 않고도 실시간으로 손쉽게 로그 확인을 할 수 있도록 하기 위해 제작</p>
<h3 id="🔹동작-원리">🔹동작 원리</h3>
<blockquote>
</blockquote>
<p>➡️ SCUM 이라는 게임의 서버 호스팅을 제공하는 GPORTAL 사의 FTP 서버와 연결해 실시간으로 로그 파일을 크롤링 하고 그 데이터를 디스코드 봇을 통해 메세지 형태로 시각화 하는 방식으로 동작</p>
<h3 id="🔹주요-기술">🔹주요 기술</h3>
<blockquote>
</blockquote>
<p>➡️ FTP 연결 라이브러리
➡️ Discord 봇 API
➡️ 데이터 크롤링 및 파싱
➡️ 데이터 캡슐화
➡️ 멀티 쓰레딩
➡️ 자원 비동기화
➡️ 파일 입출력</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[✅ Java - Spring Boot 팁 정리]]></title>
            <link>https://velog.io/@decode_buffer/Java-Spring-Boot-%ED%8C%81-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@decode_buffer/Java-Spring-Boot-%ED%8C%81-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Tue, 11 Jul 2023 12:50:49 GMT</pubDate>
            <description><![CDATA[<p>🔸 서버 재시작 없이 빌드만 하면 변경된 코드 바로 적용</p>
<p>🔹 build.gradle -&gt; dependencies -&gt; developmentOnly ‘org.springframework.boot:spring-boot-devtools’ 추가</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[✅ Java - Spring Boot Spring io 사이트]]></title>
            <link>https://velog.io/@decode_buffer/Java-Spring-Boot-Spring-io-%EC%82%AC%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@decode_buffer/Java-Spring-Boot-Spring-io-%EC%82%AC%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Tue, 11 Jul 2023 12:48:00 GMT</pubDate>
            <description><![CDATA[<p>🔸 <a href="https://spring.io/">https://spring.io/</a></p>
<p>🔹 위 사이트 접속
🔹 Project -&gt; Spring Boot 선택
🔹 Learn 선택
🔹 Reference Doc. 선택
🔹 필요한 Reference 참조</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[✅ Java - Spring Boot Spring Initializr 사이트]]></title>
            <link>https://velog.io/@decode_buffer/Java-Spring-Boot-Spring-Initializr-%EC%82%AC%EC%9D%B4%ED%8A%B8</link>
            <guid>https://velog.io/@decode_buffer/Java-Spring-Boot-Spring-Initializr-%EC%82%AC%EC%9D%B4%ED%8A%B8</guid>
            <pubDate>Tue, 11 Jul 2023 12:40:37 GMT</pubDate>
            <description><![CDATA[<p>🔸<a href="https://start.spring.io/">https://start.spring.io/</a></p>
<p>🔹 위 사이트 접속
🔹 Gradle - Groovy, Java, 3.1.1, com.overflow, Project, Project, Description, com.overflow.Project, jar, 17 선택
🔹 Dependencies 에서 Spring Web, Thymeleaf 추가
🔹 GENERATE 클릭
🔹 완성 된 .zip 다운로드 후 컴파일러에 Import</p>
]]></description>
        </item>
    </channel>
</rss>