├── LICENSE
├── README.md
├── VRChatActivityLogViewer
├── .gitignore
├── VRChatActivityLogViewer.sln
└── VRChatActivityLogViewer
│ ├── AboutDialog.xaml
│ ├── AboutDialog.xaml.cs
│ ├── ActivityLogGridModel.cs
│ ├── ActivityLogSearchParameter.cs
│ ├── App.xaml
│ ├── App.xaml.cs
│ ├── DetailWindow.xaml
│ ├── DetailWindow.xaml.cs
│ ├── LoggerErrorDialog.xaml
│ ├── LoggerErrorDialog.xaml.cs
│ ├── MainWindow.xaml
│ ├── MainWindow.xaml.cs
│ ├── Properties
│ └── PublishProfiles
│ │ ├── PublishProfile.pubxml
│ │ └── SelfContainedProfile.pubxml
│ ├── Utilities
│ └── SnakeCaseNamingPolicy.cs
│ ├── VRChatActivityLogModel.cs
│ ├── VRChatActivityLogViewer.csproj
│ ├── VRChatApi
│ ├── VRChatApiService.cs
│ └── World.cs
│ ├── WebService.cs
│ ├── YoutubeApi
│ ├── YoutubeApiService.cs
│ └── YoutubeEmbedResponse.cs
│ └── icon.ico
├── VRChatActivityLogger
├── .gitignore
├── VRChatActivityLogger.sln
└── VRChatActivityLogger
│ ├── Argument.cs
│ ├── Batch
│ ├── add-taskschedular.bat
│ ├── delete-taskschedular.bat
│ └── task.xml
│ ├── Logger.cs
│ ├── Program.cs
│ ├── Properties
│ ├── PublishProfiles
│ │ ├── PublishProfile.pubxml
│ │ └── SelfContainedProfile.pubxml
│ └── launchSettings.json
│ ├── RegexPatterns.cs
│ ├── VRChatActivityLogger.cs
│ └── VRChatActivityLogger.csproj
└── VRChatActivityToolsShared
├── .gitignore
├── VRChatActivityToolsShared.sln
└── VRChatActivityToolsShared
├── Database
├── ActivityLog.cs
├── ActivityType.cs
├── DatabaseContext.cs
├── DatabaseMigration.cs
└── Information.cs
└── VRChatActivityToolsShared.csproj
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 nukora
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | VRChatActivityTools
2 | ====
3 |
4 | VRChatのログを解析し、joinしたワールドや会った人の履歴などをデータベースに保存します。
5 | Analyzes VRChat logs and stores the history of worlds joined and people met in a database. (English documentation is available under the Japanese version)
6 |
7 | # 日本語
8 |
9 | ## インストール
10 |
11 | ### ダウンロード
12 |
13 | 以下のページからダウンロードする事ができます。
14 |
15 | https://github.com/nukora/VRChatActivityTools/releases
16 |
17 | Boothでの配布も行っていますので、そちらからもダウンロードできます。
18 |
19 | https://nukora.booth.pm/items/1690568
20 |
21 | もしくは自分でコンパイルしてください。
22 |
23 | ### VRChatActivityTools_v.0.0.zip
24 |
25 | 起動に別途 .NET Core 3.1 Runtime のインストールが必要になりますが、ファイルサイズが小さく、起動にかかる時間も短いです。
26 |
27 | ランタイムは以下の場所にあります。
28 |
29 | https://dotnet.microsoft.com/download/dotnet-core/3.1
30 |
31 | .NET Core RuntimeとDesktop Runtimeの最新版をインストールしてください。
32 |
33 | InstallersのWindowsのx64を選択すれば大丈夫だと思います。
34 |
35 | ### VRChatActivityTools_v.0.0_SelfContained.zip
36 |
37 | ランタイムのインストール無しで起動できますが、ファイルサイズが大きく、初回起動にかなりの時間がかかります。
38 |
39 | なるべくランタイムをインストールする事をおすすめします。
40 |
41 | ### インストール
42 |
43 | ダウンロードしたzipファイルを解凍して適当なフォルダに配置してください。
44 |
45 | ※Program Filesなどの書き込み制限のあるフォルダには置かないでください。
46 |
47 | このプログラムは64bit Windows専用です。
48 |
49 | ## 使い方
50 |
51 | ### VRChatActivityLogger.exe
52 | VRChatのログを解析し、活動履歴のデータベースを作成します。
53 |
54 | 既にデータベースが作成されている場合は追加登録されていきます。
55 |
56 | タスクスケジューラなどで定期実行されるようにすると便利かもしれません。
57 |
58 | コンソール画面を表示しないで実行したい場合は以下の起動オプションを使用してください。
59 |
60 | ```
61 | VRChatActivityLogger.exe -console false
62 | ```
63 |
64 | ### VRChatActivityLogViewer.exe
65 | データベースの内容をGUIで表示します。
66 |
67 | データベースをまだ作成していない場合は、先にVRChatActivityLogger.exeを実行してください。
68 |
69 | 画面上のLoggerボタンをクリックする事でも実行する事ができます。
70 |
71 | ### VRChatActivityLog.db
72 | VRChatActivityLogger.exeを実行すると作成されるデータベースファイルです。
73 |
74 | 中身はSQLite3のデータなので、他のアプリと連携したりもできると思います。
75 |
76 | ## 既知の問題
77 |
78 | inviteの送信履歴などから送信先となるユーザ名を表示する事はできません。VRChatのログにユーザ名が記録されない為です。
79 |
80 | ## ライセンス
81 |
82 | このプログラムにはMITライセンスが適用されます。
83 |
84 | # English
85 |
86 | ## Installation
87 |
88 | ### Download
89 |
90 | You can download it from the following page.
91 |
92 | https://github.com/nukora/VRChatActivityTools/releases
93 |
94 | You can also download it from Booth.
95 |
96 | https://nukora.booth.pm/items/1690568
97 |
98 | Or you can compile it by yourself.
99 |
100 | ### VRChatActivityTools_v.0.0.zip
101 |
102 | NET Core 3.1 Runtime must be installed separately to start, but the file size is small and the startup time is short.
103 |
104 | The runtime can be found at :
105 |
106 | https://dotnet.microsoft.com/download/dotnet-core/3.1
107 |
108 | Install the latest versions of .NET Core Runtime and Desktop Runtime.
109 |
110 | If you select Windows x64 for Installers, I think you will be fine.
111 |
112 | ### VRChatActivityTools_v.0.0_SelfContained.zip
113 |
114 | You can start the program without installing the runtime, but the file size is large and it will take a long time to start the first time.
115 |
116 | We recommend that you install the runtime if possible.
117 |
118 | ### Installation
119 |
120 | Unzip the downloaded zip file and place it in an appropriate folder.
121 |
122 | Do not place it in a write-restricted folder such as Program Files.
123 |
124 | This program is for 64bit Windows only.
125 |
126 | ## How to use :
127 |
128 | ### VRChatActivityLogger.exe
129 |
130 | Analyzes the VRChat log and creates a database of activity history.
131 |
132 | If a database has already been created, it will be added to the database.
133 |
134 | It may be useful to use a task scheduler or similar to run it periodically.
135 |
136 | If you want to run without displaying the console screen, use the following startup option.
137 |
138 | ```
139 | VRChatActivityLogger.exe -console false
140 | ```
141 |
142 | ### VRChatActivityLogViewer.exe
143 |
144 | Displays the contents of the database in GUI.
145 |
146 | If you have not yet created a database, run VRChatActivityLogger.exe first.
147 |
148 | You can also run it by clicking the Logger button on the screen.
149 |
150 | ### VRChatActivityLog.db
151 | This is the database file that is created when you run VRChatActivityLogger.exe.
152 |
153 | The contents are SQLite3 data, so it can be used in conjunction with other applications.
154 |
155 | ## Known issues
156 |
157 | It is not possible to display the name of the user to whom invitations are sent from the invitations sending history, because the user name is not recorded in the VRChat log.
158 |
159 | ## License
160 |
161 | This software is released under the MIT License, see LICENSE.
162 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29503.13
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VRChatActivityLogViewer", "VRChatActivityLogViewer\VRChatActivityLogViewer.csproj", "{2A0D35BA-6621-4B75-BF8D-C37D9858FED0}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VRChatActivityToolsShared", "..\VRChatActivityToolsShared\VRChatActivityToolsShared\VRChatActivityToolsShared.csproj", "{87F7193A-DFB6-4E46-9434-842E70E50472}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {2A0D35BA-6621-4B75-BF8D-C37D9858FED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {2A0D35BA-6621-4B75-BF8D-C37D9858FED0}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {2A0D35BA-6621-4B75-BF8D-C37D9858FED0}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {2A0D35BA-6621-4B75-BF8D-C37D9858FED0}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {87F7193A-DFB6-4E46-9434-842E70E50472}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {87F7193A-DFB6-4E46-9434-842E70E50472}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {87F7193A-DFB6-4E46-9434-842E70E50472}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {87F7193A-DFB6-4E46-9434-842E70E50472}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {C0D9EFA5-1439-4279-A0D4-FF9227743642}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/AboutDialog.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | VRChatActivityViewer
18 |
19 | v0.0.0
20 | Copyright (c) 2019 nukora
21 | GitHub: https://github.com/nukora/VRChatActivityTools
22 | Booth: https://nukora.booth.pm/items/1690568
23 | Twitter: @ilove_kemomimi
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/AboutDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Reflection;
5 | using System.Text;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Shapes;
14 |
15 | namespace VRChatActivityLogViewer
16 | {
17 | ///
18 | /// AboutDialog.xaml の相互作用ロジック
19 | ///
20 | public partial class AboutDialog : Window
21 | {
22 | public AboutDialog()
23 | {
24 | InitializeComponent();
25 |
26 | var version = Assembly.GetExecutingAssembly().GetName().Version;
27 |
28 | versionText.Text = $"v{version.Major}.{version.Minor}.{version.Build}";
29 | }
30 |
31 | private void closeButton_Click(object sender, RoutedEventArgs e)
32 | {
33 | this.Close();
34 | }
35 |
36 | private void Hyperlink_RequestNavigate(object sender, System.Windows.Navigation.RequestNavigateEventArgs e)
37 | {
38 | Process.Start(new ProcessStartInfo("cmd", $"/c start {e.Uri}") { CreateNoWindow = true });
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/ActivityLogGridModel.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using VRChatActivityToolsShared.Database;
3 |
4 | namespace VRChatActivityLogViewer
5 | {
6 | ///
7 | /// DataGridに表示するモデル
8 | ///
9 | public class ActivityLogGridModel
10 | {
11 | /// タイムスタンプ
12 | public DateTime TimeStamp { get; }
13 |
14 | /// アクティビティの種類の名前
15 | public string ActivityName { get; }
16 |
17 | /// アクティビティの種類
18 | public ActivityType Type { get; }
19 |
20 | /// アクティビティの内容
21 | public string Content { get; }
22 |
23 | /// ワールドIDがコピーできるか
24 | public bool IsCopyableWorldID { get; } = false;
25 |
26 | /// ユーザIDがコピーできるか
27 | public bool IsCopyableUserID { get; } = false;
28 |
29 | /// URLがコピーできるか
30 | public bool IsCopyableUrl { get; } = false;
31 |
32 | /// 詳細画面が有効かどうか
33 | public bool IsDetailWindowEnabled { get; } = false;
34 |
35 | /// ワールドID
36 | public string WorldID { get; }
37 |
38 | /// ユーザID
39 | public string UserID { get; }
40 |
41 | /// 元データ
42 | public ActivityLog Source { get; }
43 |
44 | ///
45 | /// コンストラクタ
46 | ///
47 | ///
48 | public ActivityLogGridModel(ActivityLog activityLog)
49 | {
50 | Source = activityLog;
51 |
52 | Type = activityLog.ActivityType;
53 | TimeStamp = activityLog.Timestamp ?? default;
54 |
55 | var addIcon = string.Empty;
56 | if (activityLog.Message != null || activityLog.Url != null)
57 | {
58 | addIcon += "✉";
59 | }
60 |
61 | if (activityLog.ActivityType == ActivityType.JoinedRoom)
62 | {
63 | ActivityName = "Join";
64 | Content = activityLog.WorldName;
65 | WorldID = activityLog.WorldID;
66 | IsCopyableWorldID = true;
67 | IsDetailWindowEnabled = true;
68 | }
69 | if (activityLog.ActivityType == ActivityType.MetPlayer)
70 | {
71 | ActivityName = "Meet";
72 | Content = activityLog.UserName;
73 | }
74 | if (activityLog.ActivityType == ActivityType.SendInvite)
75 | {
76 | ActivityName = "Send Invite";
77 | Content = addIcon + activityLog.WorldName;
78 | WorldID = activityLog.WorldID;
79 | IsCopyableWorldID = true;
80 | UserID = activityLog.UserID;
81 | IsCopyableUserID = true;
82 | IsDetailWindowEnabled = true;
83 | }
84 | if (activityLog.ActivityType == ActivityType.ReceivedInvite)
85 | {
86 | ActivityName = "Received Invite";
87 | Content = addIcon + activityLog.UserName + " -> " + activityLog.WorldName;
88 | WorldID = activityLog.WorldID;
89 | IsCopyableWorldID = true;
90 | UserID = activityLog.UserID;
91 | IsCopyableUserID = true;
92 | IsDetailWindowEnabled = true;
93 | }
94 | if (activityLog.ActivityType == ActivityType.SendRequestInvite)
95 | {
96 | ActivityName = "Send RequestInvite";
97 | UserID = addIcon + activityLog.UserID;
98 | IsCopyableUserID = true;
99 | IsDetailWindowEnabled = true;
100 | }
101 | if (activityLog.ActivityType == ActivityType.ReceivedRequestInvite)
102 | {
103 | ActivityName = "Received RequestInvite";
104 | Content = addIcon + activityLog.UserName;
105 | UserID = activityLog.UserID;
106 | IsCopyableUserID = true;
107 | IsDetailWindowEnabled = true;
108 | }
109 | if (activityLog.ActivityType == ActivityType.SendFriendRequest)
110 | {
111 | ActivityName = "Send FriendRequest";
112 | UserID = activityLog.UserID;
113 | IsCopyableUserID = true;
114 | IsDetailWindowEnabled = true;
115 | }
116 | if (activityLog.ActivityType == ActivityType.ReceivedFriendRequest)
117 | {
118 | ActivityName = "Received FriendRequest";
119 | Content = activityLog.UserName;
120 | UserID = activityLog.UserID;
121 | IsCopyableUserID = true;
122 | IsDetailWindowEnabled = true;
123 | }
124 | if (activityLog.ActivityType == ActivityType.AcceptFriendRequest)
125 | {
126 | ActivityName = "Accept FriendRequest";
127 | Content = activityLog.UserName;
128 | UserID = activityLog.UserID;
129 | IsCopyableUserID = true;
130 | IsDetailWindowEnabled = true;
131 | }
132 | if (activityLog.ActivityType == ActivityType.ReceivedInviteResponse)
133 | {
134 | ActivityName = "Received InviteResponse";
135 | Content = addIcon + activityLog.UserName;
136 | UserID = activityLog.UserID;
137 | IsCopyableUserID = true;
138 | IsDetailWindowEnabled = true;
139 | }
140 | if (activityLog.ActivityType == ActivityType.ReceivedRequestInviteResponse)
141 | {
142 | ActivityName = "Received RequestInviteResponse";
143 | Content = addIcon + activityLog.UserName;
144 | UserID = activityLog.UserID;
145 | IsCopyableUserID = true;
146 | IsDetailWindowEnabled = true;
147 | }
148 | if (activityLog.ActivityType == ActivityType.PlayedVideo)
149 | {
150 | ActivityName = "Video";
151 | Content = activityLog.Url;
152 | IsDetailWindowEnabled = true;
153 | IsCopyableUrl = true;
154 | }
155 | if (activityLog.ActivityType == ActivityType.AcceptInvite)
156 | {
157 | ActivityName = "Accept Invite";
158 | Content = addIcon + activityLog.UserName + " -> " + activityLog.WorldName;
159 | WorldID = activityLog.WorldID;
160 | IsCopyableWorldID = true;
161 | UserID = activityLog.UserID;
162 | IsCopyableUserID = true;
163 | IsDetailWindowEnabled = true;
164 | }
165 | if (activityLog.ActivityType == ActivityType.AcceptRequestInvite)
166 | {
167 | ActivityName = "Accept RequestInvite";
168 | Content = addIcon + activityLog.UserName;
169 | UserID = activityLog.UserID;
170 | IsCopyableUserID = true;
171 | IsDetailWindowEnabled = true;
172 | }
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/ActivityLogSearchParameter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace VRChatActivityLogViewer
4 | {
5 | ///
6 | /// アクティビティの検索条件
7 | ///
8 | class ActivityLogSearchParameter
9 | {
10 | /// ワールドにjoinした履歴を含める
11 | public bool IsJoinedRoom { get; set; } = true;
12 |
13 | /// プレイヤーと会った履歴を含める
14 | public bool IsMetPlayer { get; set; } = true;
15 |
16 | /// inviteを受け取った履歴を含める
17 | public bool IsReceivedInvite { get; set; } = true;
18 |
19 | /// reqInvを受け取った履歴を含める
20 | public bool IsReceivedRequestInvite { get; set; } = true;
21 |
22 | /// inviteを送った履歴を含める
23 | public bool IsSendInvite { get; set; } = true;
24 |
25 | /// reqInvを送った履歴を含める
26 | public bool IsSendRequestInvite { get; set; } = true;
27 |
28 | /// フレンドリクエストを送った履歴を含める
29 | public bool IsSendFriendRequest { get; set; } = true;
30 |
31 | /// フレンドリクエストを受け取った履歴を含める
32 | public bool IsReceivedFriendRequest { get; set; } = true;
33 |
34 | /// フレンドリクエストを承認した履歴を含める
35 | public bool IsAcceptFriendRequest { get; set; } = true;
36 |
37 | /// 検索する期間の始まり
38 | public DateTime? FromDateTime { get; set; } = null;
39 |
40 | /// 検索する期間の終わり
41 | public DateTime? UntilDateTime { get; set; } = null;
42 |
43 | /// inviteへの返信を受け取った履歴を含める
44 | public bool IsReceivedInviteResponse { get; set; } = true;
45 |
46 | /// reqInvへの返信を受け取った履歴を含める
47 | public bool IsReceivedRequestInviteResponse { get; set; } = true;
48 |
49 | /// 動画を再生した履歴を含める
50 | public bool IsPlayedVideo { get; set; } = true;
51 |
52 | /// inviteを承認した履歴を含める
53 | public bool IsAcceptInvite { get; set; } = true;
54 |
55 | /// reqInvを承認した履歴を含める
56 | public bool IsAcceptRequestInvite { get; set; } = true;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/App.xaml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | namespace VRChatActivityLogViewer
4 | {
5 | ///
6 | /// Interaction logic for App.xaml
7 | ///
8 | public partial class App : Application
9 | {
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/DetailWindow.xaml:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/DetailWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Text.RegularExpressions;
8 | using System.Threading.Tasks;
9 | using System.Windows;
10 | using System.Windows.Controls;
11 | using System.Windows.Data;
12 | using System.Windows.Documents;
13 | using System.Windows.Input;
14 | using System.Windows.Media;
15 | using System.Windows.Media.Imaging;
16 | using System.Windows.Shapes;
17 | using VRChatActivityLogViewer.VRChatApi;
18 | using VRChatActivityLogViewer.YoutubeApi;
19 | using VRChatActivityToolsShared.Database;
20 |
21 | namespace VRChatActivityLogViewer
22 | {
23 | ///
24 | /// DetailWindow.xaml の相互作用ロジック
25 | ///
26 | public partial class DetailWindow : Window
27 | {
28 | public ActivityLog ActivityLog { get; }
29 |
30 | private VRChatApiService vrchatApiService;
31 | private YoutubeApiService youtubeApiService;
32 | private WebService webService;
33 | private World world;
34 |
35 | ///
36 | /// コンストラクタ
37 | ///
38 | ///
39 | public DetailWindow(ActivityLog activityLog)
40 | {
41 | this.ActivityLog = activityLog;
42 |
43 | vrchatApiService = new VRChatApiService();
44 | youtubeApiService = new YoutubeApiService();
45 | webService = new WebService();
46 |
47 | InitializeComponent();
48 |
49 | InitializeView();
50 | }
51 |
52 | ///
53 | /// 表示する内容を初期化します
54 | ///
55 | private void InitializeView()
56 | {
57 | // ボタンの有効/無効
58 | JoinButton.Visibility = ActivityLog.WorldID != null ? Visibility.Visible : Visibility.Collapsed;
59 | CopyWorldIdButton.Visibility = ActivityLog.WorldID != null ? Visibility.Visible : Visibility.Collapsed;
60 | CopyWorldNameButton.Visibility = ActivityLog.WorldID != null ? Visibility.Visible : Visibility.Collapsed;
61 | CopyUserIdButton.Visibility = ActivityLog.UserID != null ? Visibility.Visible : Visibility.Collapsed;
62 | CopyUrlButton.Visibility = ActivityLog.ActivityType == ActivityType.PlayedVideo && ActivityLog.Url != null ? Visibility.Visible : Visibility.Collapsed;
63 |
64 | // アクティビティタイプとタイムスタンプ
65 | ActivityTypeText.Text = ActivityTypeToString(ActivityLog.ActivityType);
66 | TimestampText.Text = ActivityLog.Timestamp?.ToString("yyyy/MM/dd HH:mm:ss");
67 |
68 | // アクティビティタイプによってヘッダの表示内容を変更
69 | InitializeHeaderView();
70 |
71 | // Video以外の場合は共通の処理
72 | if (ActivityLog.ActivityType != ActivityType.PlayedVideo)
73 | {
74 | // ワールド名がある場合
75 | if (ActivityLog.WorldName != null)
76 | {
77 | WorldNameText.Text = ActivityLog.WorldName;
78 | ChangeWorldInfoButton.Visibility = Visibility.Visible;
79 | UnknownContentsGrid.Visibility = Visibility.Hidden;
80 | WorldInfoGrid.Visibility = Visibility.Visible;
81 | }
82 |
83 | // メッセージかURLがある場合
84 | if (ActivityLog.Message != null || ActivityLog.Url != null)
85 | {
86 | MessageText.Text = ActivityLog.Message;
87 | ChangeMessageInfoButton.Visibility = Visibility.Visible;
88 |
89 | if (ActivityLog.WorldName == null)
90 | {
91 | UnknownContentsGrid.Visibility = Visibility.Hidden;
92 | MessageInfoGrid.Visibility = Visibility.Visible;
93 | }
94 | }
95 | }
96 | }
97 |
98 | ///
99 | /// ヘッダを初期化します
100 | ///
101 | private void InitializeHeaderView()
102 | {
103 | if (ActivityLog.ActivityType == ActivityType.ReceivedInvite ||
104 | ActivityLog.ActivityType == ActivityType.ReceivedInviteResponse ||
105 | ActivityLog.ActivityType == ActivityType.ReceivedRequestInvite ||
106 | ActivityLog.ActivityType == ActivityType.ReceivedRequestInviteResponse ||
107 | ActivityLog.ActivityType == ActivityType.ReceivedFriendRequest ||
108 | ActivityLog.ActivityType == ActivityType.AcceptFriendRequest ||
109 | ActivityLog.ActivityType == ActivityType.AcceptInvite ||
110 | ActivityLog.ActivityType == ActivityType.AcceptRequestInvite)
111 | {
112 | FromUserName.Text = $"from {ActivityLog.UserName}";
113 | }
114 | }
115 |
116 | ///
117 | /// 詳細画面が読み込まれた後に呼び出されるイベントです
118 | ///
119 | ///
120 | ///
121 | private async void DetailWindow_Loaded(object sender, RoutedEventArgs e)
122 | {
123 | try
124 | {
125 | await GetWorldInformation();
126 |
127 | if (ActivityLog.WorldID != null)
128 | {
129 | if (world != null)
130 | {
131 | WorldImageContent.Source = await CreateBitmapImageFromUri(world.ThumbnailImageUrl);
132 | WorldImageContent.Visibility = Visibility.Visible;
133 |
134 | WorldAuthorText.Text = $"by {world.AuthorName}";
135 | }
136 | }
137 |
138 | if (ActivityLog.Url != null && ActivityLog.ActivityType != ActivityType.PlayedVideo)
139 | {
140 | MessageImageContent.Source = await CreateBitmapImageFromUri(ActivityLog.Url);
141 | MessageImageContent.Visibility = Visibility.Visible;
142 | }
143 |
144 | if (ActivityLog.ActivityType == ActivityType.PlayedVideo)
145 | {
146 | // Youtubeのみ特別扱いしてサムネイルを表示する
147 | var youtubeUrlRegex = @"^https?://(www\.)?youtube\.com/watch\?v=([^&]+).*$|^https?://youtu\.be/([^\?]+).*$";
148 | var match = Regex.Match(ActivityLog.Url, youtubeUrlRegex);
149 |
150 | if (match.Success)
151 | {
152 | var id = match.Groups[2].Success ? match.Groups[2].Value : match.Groups[3].Success ? match.Groups[3].Value : string.Empty;
153 | var oEmbed = await youtubeApiService.GetOEmbedAsync(id);
154 |
155 | if (oEmbed != null)
156 | {
157 | UnknownContentsGrid.Visibility = Visibility.Hidden;
158 | VideoGrid.Visibility = Visibility.Visible;
159 |
160 | VideoTitleText.Text = oEmbed.Title;
161 | VideoAuthorText.Text = $"{oEmbed.AuthorName}";
162 | VideoImageContent.Source = await CreateBitmapImageFromUri(oEmbed.ThumbnailUrl);
163 | VideoImageContent.Visibility = Visibility.Visible;
164 | }
165 | }
166 | }
167 | }
168 | catch (Exception ex) when (ex is InvalidOperationException || ex is HttpRequestException)
169 | {
170 | // 外部から情報を取得する要素はおまけのため、エラーを無視する
171 | }
172 | catch (Exception)
173 | {
174 | MessageBox.Show("エラーが発生しました。プログラムを終了します。", "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
175 | Application.Current.Shutdown();
176 | }
177 | }
178 |
179 | ///
180 | /// ワールドの追加情報をVRCAPIで取得します
181 | ///
182 | ///
183 | private async Task GetWorldInformation()
184 | {
185 | if (world != null)
186 | {
187 | return;
188 | }
189 |
190 | var id = ActivityLog?.WorldID?.Split(':')[0];
191 | if (!string.IsNullOrWhiteSpace(id))
192 | {
193 | world = await vrchatApiService.GetWorldAsync(id);
194 | }
195 | }
196 |
197 | ///
198 | /// ネットワークから画像を取得し、BitmapImageを作成します
199 | ///
200 | ///
201 | private async Task CreateBitmapImageFromUri(string uri)
202 | {
203 | using (var stream = await webService.GetStreamAsync(uri))
204 | {
205 | var bitmap = new BitmapImage();
206 |
207 | bitmap.BeginInit();
208 | bitmap.CacheOption = BitmapCacheOption.OnLoad;
209 | bitmap.CreateOptions = BitmapCreateOptions.None;
210 | bitmap.StreamSource = stream;
211 | bitmap.EndInit();
212 | bitmap.Freeze();
213 |
214 | return bitmap;
215 | }
216 | }
217 |
218 | ///
219 | /// アクティビティタイプからヘッダに表示する文字列を取得します
220 | ///
221 | ///
222 | ///
223 | private string ActivityTypeToString(ActivityType type) => type switch
224 | {
225 | ActivityType.JoinedRoom => "Join",
226 | ActivityType.MetPlayer => "Meet",
227 | ActivityType.SendInvite => "Send Invite",
228 | ActivityType.ReceivedInvite => "Received Invite",
229 | ActivityType.SendRequestInvite => "Send RequestInvite",
230 | ActivityType.ReceivedRequestInvite => "Received RequestInvite",
231 | ActivityType.SendFriendRequest => "Send FriendRequest",
232 | ActivityType.ReceivedFriendRequest => "Received FriendRequest",
233 | ActivityType.AcceptFriendRequest => "Accept FriendRequest",
234 | ActivityType.SendInviteResponse => "Send Invite Response",
235 | ActivityType.ReceivedInviteResponse => "Received Invite Response",
236 | ActivityType.SendRequestInviteResponse => "Send RequestInvite Response",
237 | ActivityType.ReceivedRequestInviteResponse => "Received RequestInvite Response",
238 | ActivityType.PlayedVideo => "Video",
239 | ActivityType.AcceptInvite => "Accept Invite",
240 | ActivityType.AcceptRequestInvite => "Accept RequestInvite",
241 | _ => "Unknown Activity",
242 | };
243 |
244 | ///
245 | /// Joinボタンクリック時のイベント
246 | ///
247 | ///
248 | ///
249 | private void JoinButton_Click(object sender, RoutedEventArgs e)
250 | {
251 | if(ActivityLog == null || ActivityLog.WorldID == null)
252 | {
253 | return;
254 | }
255 |
256 | var uri = "vrchat://launch?id=" + ActivityLog.WorldID;
257 | uri = uri.Replace("&", "^&");
258 | Process.Start(new ProcessStartInfo("cmd", $"/c start {uri}") { CreateNoWindow = true });
259 | }
260 |
261 | ///
262 | /// Copy World IDボタンクリック時のイベント
263 | ///
264 | ///
265 | ///
266 | private void CopyWorldIdButton_Click(object sender, RoutedEventArgs e)
267 | {
268 | if (ActivityLog == null || ActivityLog.WorldID == null)
269 | {
270 | return;
271 | }
272 |
273 | Clipboard.SetDataObject(ActivityLog.WorldID ?? "");
274 | }
275 |
276 | ///
277 | /// Copy World Nameボタンクリック時のイベント
278 | ///
279 | ///
280 | ///
281 | private void CopyWorldNameButton_Click(object sender, RoutedEventArgs e)
282 | {
283 | if (ActivityLog == null || ActivityLog.WorldName == null)
284 | {
285 | return;
286 | }
287 |
288 | Clipboard.SetDataObject(ActivityLog.WorldName ?? "");
289 | }
290 |
291 | ///
292 | /// Copy User IDボタンクリック時のイベント
293 | ///
294 | ///
295 | ///
296 | private void CopyUserIdButton_Click(object sender, RoutedEventArgs e)
297 | {
298 | if (ActivityLog == null || ActivityLog.UserID == null)
299 | {
300 | return;
301 | }
302 |
303 | Clipboard.SetDataObject(ActivityLog.UserID ?? "");
304 | }
305 |
306 | ///
307 | /// Copy Urlボタンクリック時のイベント
308 | ///
309 | ///
310 | ///
311 | private void CopyUrlButton_Click(object sender, RoutedEventArgs e)
312 | {
313 | if (ActivityLog == null || ActivityLog.Url == null)
314 | {
315 | return;
316 | }
317 |
318 | Clipboard.SetDataObject(ActivityLog.Url ?? "");
319 | }
320 |
321 | ///
322 | /// Show Message Infoボタンクリック時のイベント
323 | ///
324 | ///
325 | ///
326 | private void ChangeMessageInfoButton_Click(object sender, RoutedEventArgs e)
327 | {
328 | WorldInfoGrid.Visibility = Visibility.Hidden;
329 | MessageInfoGrid.Visibility = Visibility.Visible;
330 | }
331 |
332 | ///
333 | /// Show World Infoボタンクリック時のイベント
334 | ///
335 | ///
336 | ///
337 | private void ChangeWorldInfoButton_Click(object sender, RoutedEventArgs e)
338 | {
339 | WorldInfoGrid.Visibility = Visibility.Visible;
340 | MessageInfoGrid.Visibility = Visibility.Hidden;
341 | }
342 |
343 | ///
344 | /// ワールド名クリック時のイベント
345 | ///
346 | ///
347 | ///
348 | private void WorldHyperlink_Click(object sender, RoutedEventArgs e)
349 | {
350 | var id = ActivityLog?.WorldID?.Split(':')[0];
351 | var uri = $"https://vrchat.com/home/world/{id}";
352 |
353 | Process.Start(new ProcessStartInfo("cmd", $"/c start {uri}") { CreateNoWindow = true });
354 | }
355 |
356 | ///
357 | /// ビデオタイトルクリック時のイベント
358 | ///
359 | ///
360 | ///
361 | private void VideoHyperlink_Click(object sender, RoutedEventArgs e)
362 | {
363 | var uri = $"{ActivityLog.Url}";
364 |
365 | Process.Start(new ProcessStartInfo("cmd", $"/c start {uri}") { CreateNoWindow = true });
366 | }
367 |
368 | ///
369 | /// 名前を付けて画像を保存メニュークリック時のイベント
370 | ///
371 | ///
372 | ///
373 | private async void SaveMessageImageMenuItem_Click(object sender, RoutedEventArgs e)
374 | {
375 | try
376 | {
377 | var s = ActivityLog.Url.Split('/');
378 | var fileName = s[s.Length - 1];
379 | var dialog = new SaveFileDialog();
380 | dialog.FileName = fileName;
381 | dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
382 |
383 | if (dialog.ShowDialog() ?? false)
384 | {
385 | await webService.DownloadFile(ActivityLog.Url, dialog.FileName);
386 | }
387 | }
388 | catch (Exception)
389 | {
390 | MessageBox.Show("ファイルの保存に失敗しました", "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
391 | }
392 | }
393 | }
394 | }
395 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/LoggerErrorDialog.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 以下のVRChatログの解析に失敗しました。
16 | 該当のログファイルを削除、または別の場所に移動する事でスキップして取り込めます。
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/LoggerErrorDialog.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Text;
5 | using System.Windows;
6 | using System.Windows.Controls;
7 | using System.Windows.Data;
8 | using System.Windows.Documents;
9 | using System.Windows.Input;
10 | using System.Windows.Media;
11 | using System.Windows.Media.Imaging;
12 | using System.Windows.Shapes;
13 |
14 | namespace VRChatActivityLogViewer
15 | {
16 | ///
17 | /// LoggerErrorDialog.xaml の相互作用ロジック
18 | ///
19 | public partial class LoggerErrorDialog : Window
20 | {
21 | private string errorFilePath;
22 |
23 | public LoggerErrorDialog(string errorFilePath)
24 | {
25 | InitializeComponent();
26 | this.errorFilePath = errorFilePath;
27 | filePathLink.Text = errorFilePath;
28 | }
29 |
30 | private void filePathLink_Click(object sender, RoutedEventArgs e)
31 | {
32 | Process.Start("EXPLORER.EXE", @$"/select,""{errorFilePath}""");
33 | }
34 |
35 | private void closeButton_Click(object sender, RoutedEventArgs e)
36 | {
37 | this.Close();
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | Send
86 |
87 |
88 |
89 |
90 |
91 | Received
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | Accept
103 |
104 |
105 |
106 |
107 |
108 | Period
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 | Keyword
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
191 |
192 |
193 |
194 |
195 |
196 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.ObjectModel;
4 | using System.Diagnostics;
5 | using System.IO;
6 | using System.Linq;
7 | using System.Threading.Tasks;
8 | using System.Windows;
9 | using System.Windows.Controls;
10 | using System.Windows.Input;
11 | using VRChatActivityToolsShared.Database;
12 |
13 | namespace VRChatActivityLogViewer
14 | {
15 | ///
16 | /// Interaction logic for MainWindow.xaml
17 | ///
18 | public partial class MainWindow : Window
19 | {
20 | private ObservableCollection ActivityLogGridModelCollection = new ObservableCollection();
21 |
22 | private readonly string errorFilePath = "./Logs/VRChatActivityLogger/errorfile.txt";
23 |
24 | ///
25 | /// コンストラクタ
26 | ///
27 | public MainWindow()
28 | {
29 | InitializeComponent();
30 | }
31 |
32 | ///
33 | /// ウィンドウロード時の処理
34 | ///
35 | ///
36 | ///
37 | private void Window_Loaded(object sender, RoutedEventArgs e)
38 | {
39 | DisableProcessingMode();
40 |
41 | periodComboBox.SelectedIndex = 0;
42 |
43 | fromDatePicker.SelectedDate = DateTime.Today.AddDays(-1);
44 | untilDatePicker.SelectedDate = DateTime.Today;
45 |
46 | ActivityLogGrid.ItemsSource = ActivityLogGridModelCollection;
47 | }
48 |
49 | ///
50 | /// Searchボタンクリック時のイベント
51 | ///
52 | ///
53 | ///
54 | private async void searchButton_Click(object sender, RoutedEventArgs e)
55 | {
56 | await ExecuteSearch();
57 | }
58 |
59 | ///
60 | /// 検索処理の実行
61 | ///
62 | ///
63 | private async Task ExecuteSearch()
64 | {
65 | try
66 | {
67 | // 処理中モード開始
68 | EnableProcessingMode();
69 |
70 | // DBが古い場合はアップグレードする
71 | if (DatabaseMigration.GetCurrentVersion() < DatabaseContext.Version)
72 | {
73 | DatabaseMigration.UpgradeDatabase();
74 | }
75 |
76 | // 検索期間の計算
77 | DateTime? fromDate = default;
78 | DateTime? untilDate = default;
79 |
80 | if (periodComboBox.SelectedItem is ComboBoxItem periodItem)
81 | {
82 | if (periodItem != null)
83 | {
84 | var period = (SearchPeriod)periodItem.Tag;
85 |
86 | if (period == SearchPeriod.Recent)
87 | {
88 | fromDate = DateTime.Today.AddDays(-1);
89 | untilDate = DateTime.Today;
90 | }
91 | else if (period == SearchPeriod.OneWeek)
92 | {
93 | fromDate = DateTime.Today.AddDays(-7);
94 | untilDate = DateTime.Today;
95 | }
96 | else if (period == SearchPeriod.OneMonth)
97 | {
98 | fromDate = DateTime.Today.AddDays(-30);
99 | untilDate = DateTime.Today;
100 | }
101 | else if (period == SearchPeriod.OneYear)
102 | {
103 | fromDate = DateTime.Today.AddDays(-365);
104 | untilDate = DateTime.Today;
105 | }
106 | else if (period == SearchPeriod.All)
107 | {
108 | fromDate = DateTime.Parse("1970/01/01 00:00:00");
109 | untilDate = DateTime.Parse("3000/12/31 23:59:59");
110 | }
111 | else if (period == SearchPeriod.Custom)
112 | {
113 | fromDate = fromDatePicker.SelectedDate;
114 | untilDate = untilDatePicker.SelectedDate;
115 | }
116 | }
117 | }
118 |
119 | // ログの検索
120 | var parameter = new ActivityLogSearchParameter
121 | {
122 | IsJoinedRoom = joinCheckBox.IsChecked ?? false,
123 | IsMetPlayer = meetCheckBox.IsChecked ?? false,
124 | IsSendInvite = sendInvCheckBox.IsChecked ?? false,
125 | IsSendRequestInvite = sendReqInvCheckBox.IsChecked ?? false,
126 | IsReceivedInvite = recvInvCheckBox.IsChecked ?? false,
127 | IsReceivedRequestInvite = recvReqInvCheckBox.IsChecked ?? false,
128 | IsSendFriendRequest = sendFriendReqCheckBox.IsChecked ?? false,
129 | IsReceivedFriendRequest = recvFriendReqCheckBox.IsChecked ?? false,
130 | IsAcceptFriendRequest = acptFriendReqCheckBox.IsChecked ?? false,
131 | FromDateTime = fromDate,
132 | UntilDateTime = untilDate?.AddDays(1),
133 | IsReceivedInviteResponse = recvInvResCheckBox.IsChecked ?? false,
134 | IsReceivedRequestInviteResponse = recvReqInvResCheckBox.IsChecked ?? false,
135 | IsPlayedVideo = videoCheckBox.IsChecked ?? false,
136 | IsAcceptInvite = acptInvCheckBox.IsChecked ?? false,
137 | IsAcceptRequestInvite = acptReqInvCheckBox.IsChecked ?? false,
138 | };
139 | var activityLogs = await VRChatActivityLogModel.SearchActivityLogs(parameter);
140 |
141 | // 選択アイテムの保存
142 | var selectedItem = ActivityLogGrid.SelectedItem as ActivityLogGridModel;
143 |
144 | // グリッド作成
145 | ActivityLogGridModelCollection.Clear();
146 |
147 | var keywords = keywordBox.Text.Split(' ').Where(s => s != string.Empty).ToArray();
148 |
149 | if (smartSearchCheckBox.IsChecked)
150 | {
151 | // スマート検索
152 | var tmpList = new List();
153 |
154 | for (var i = 0; i < activityLogs.Count; i++)
155 | {
156 | var gridModel = new ActivityLogGridModel(activityLogs[i]);
157 |
158 | if (keywords.Any())
159 | {
160 | var contained = keywords.All(k => gridModel.Content?.Contains(k, StringComparison.CurrentCultureIgnoreCase) ?? false);
161 |
162 | if (!contained)
163 | {
164 | continue;
165 | }
166 | }
167 |
168 | if (gridModel.Type != ActivityType.JoinedRoom)
169 | {
170 | ActivityLog relatedJoin = null;
171 |
172 | for (var j = i; 0 <= j; j--)
173 | {
174 | if (activityLogs[j].ActivityType == ActivityType.JoinedRoom)
175 | {
176 | relatedJoin = activityLogs[j];
177 |
178 | break;
179 | }
180 | }
181 |
182 | ActivityLog latestJoin = null;
183 |
184 | for (var j = tmpList.Count - 1; 0 <= j; j--)
185 | {
186 | if (tmpList[j].Type == ActivityType.JoinedRoom)
187 | {
188 | latestJoin = tmpList[j].Source;
189 |
190 | break;
191 | }
192 | }
193 |
194 | if (relatedJoin != null && relatedJoin != latestJoin)
195 | {
196 | tmpList.Add(new ActivityLogGridModel(relatedJoin));
197 | }
198 | }
199 |
200 | tmpList.Add(gridModel);
201 | }
202 |
203 | foreach (var gridModel in tmpList.OrderByDescending(a => a.TimeStamp))
204 | {
205 | ActivityLogGridModelCollection.Add(gridModel);
206 | }
207 | }
208 | else
209 | {
210 | // 従来の検索
211 | foreach (var activityLog in activityLogs.OrderByDescending(a => a.Timestamp))
212 | {
213 | var gridModel = new ActivityLogGridModel(activityLog);
214 |
215 | if (keywords.Any())
216 | {
217 | var contained = keywords.All(k => gridModel.Content?.Contains(k, StringComparison.CurrentCultureIgnoreCase) ?? false);
218 |
219 | if (!contained)
220 | {
221 | continue;
222 | }
223 | }
224 |
225 | ActivityLogGridModelCollection.Add(gridModel);
226 | }
227 | }
228 |
229 | // 選択アイテムの復元
230 | if (selectedItem != null)
231 | {
232 | var newSelectedItem = ActivityLogGridModelCollection.FirstOrDefault(a => a.Source.ID == selectedItem.Source.ID);
233 |
234 | if (newSelectedItem != null)
235 | {
236 | ActivityLogGrid.SelectedItem = newSelectedItem;
237 | ActivityLogGrid.ScrollIntoView(ActivityLogGrid.SelectedItem);
238 | }
239 | }
240 |
241 | // 処理中モード終了
242 | DisableProcessingMode();
243 | }
244 | catch (Exception)
245 | {
246 | MessageBox.Show("エラーが発生しました。プログラムを終了します。", "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
247 | Application.Current.Shutdown();
248 | }
249 | }
250 |
251 | ///
252 | /// Loggerボタンクリック時のイベント
253 | ///
254 | ///
255 | ///
256 | private async void loggerButton_Click(object sender, RoutedEventArgs e)
257 | {
258 | try
259 | {
260 | EnableProcessingMode();
261 |
262 | var success = await Task.Run(() =>
263 | {
264 | var process = Process.Start("VRChatActivityLogger.exe");
265 | process.WaitForExit();
266 | return process.ExitCode == 0;
267 | });
268 |
269 | DisableProcessingMode();
270 |
271 | if (!success)
272 | {
273 | if (File.Exists(errorFilePath))
274 | {
275 | var errorFileLines = File.ReadAllLines(errorFilePath);
276 | if (0 < errorFileLines.Length)
277 | {
278 | var dialog = new LoggerErrorDialog(errorFileLines[0]);
279 | dialog.Owner = this;
280 | dialog.ShowDialog();
281 |
282 | return;
283 | }
284 | }
285 |
286 | MessageBox.Show("VRChatログの解析に失敗しました。", "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
287 | }
288 | }
289 | catch (System.ComponentModel.Win32Exception)
290 | {
291 | MessageBox.Show("VRChatActivityLogger.exeが見つかりませんでした。", "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
292 | DisableProcessingMode();
293 | }
294 | catch (Exception)
295 | {
296 | MessageBox.Show("エラーが発生しました。プログラムを終了します。", "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
297 | Application.Current.Shutdown();
298 | }
299 | }
300 |
301 | ///
302 | /// WorldIDコピーボタンクリック時のイベント
303 | ///
304 | ///
305 | ///
306 | private void CopyWorldIDButton_Click(object sender, RoutedEventArgs e)
307 | {
308 | if (sender is Button button)
309 | {
310 | if (button.Tag is ActivityLogGridModel tag)
311 | {
312 | Clipboard.SetDataObject(tag.WorldID ?? "");
313 | }
314 | }
315 | }
316 |
317 | ///
318 | /// UserIDコピーボタンクリック時のイベント
319 | ///
320 | ///
321 | ///
322 | private void CopyUserIDButton_Click(object sender, RoutedEventArgs e)
323 | {
324 | if (sender is Button button)
325 | {
326 | if (button.Tag is ActivityLogGridModel tag)
327 | {
328 | Clipboard.SetDataObject(tag.UserID ?? "");
329 | }
330 | }
331 | }
332 |
333 | ///
334 | /// Joinボタンクリック時のイベント
335 | ///
336 | ///
337 | ///
338 | private void JoinButton_Click(object sender, RoutedEventArgs e)
339 | {
340 | if (sender is Button button)
341 | {
342 | if (button.Tag is ActivityLogGridModel tag)
343 | {
344 | var uri = "vrchat://launch?id=" + tag.WorldID;
345 | uri = uri.Replace("&", "^&");
346 | Process.Start(new ProcessStartInfo("cmd", $"/c start {uri}") { CreateNoWindow = true });
347 | }
348 | }
349 | }
350 |
351 | ///
352 | /// Detailボタンクリック時のイベント
353 | ///
354 | ///
355 | ///
356 | private void DetailButton_Click(object sender, RoutedEventArgs e)
357 | {
358 | if (sender is Button button)
359 | {
360 | if (button.Tag is ActivityLogGridModel tag)
361 | {
362 | if (!tag.IsDetailWindowEnabled)
363 | {
364 | return;
365 | }
366 |
367 | ShowDetailWindow(tag.Source);
368 | }
369 | }
370 | }
371 |
372 | ///
373 | /// グリッドをダブルクリックした時のイベント
374 | ///
375 | ///
376 | ///
377 | private void ActivityLogGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e)
378 | {
379 | if (ActivityLogGrid.SelectedItem is ActivityLogGridModel gridModel)
380 | {
381 | if (!gridModel.IsDetailWindowEnabled)
382 | {
383 | return;
384 | }
385 |
386 | ShowDetailWindow(gridModel.Source);
387 | }
388 | }
389 |
390 | ///
391 | /// 詳細ダイアログを表示する
392 | ///
393 | ///
394 | private void ShowDetailWindow(ActivityLog activityLog)
395 | {
396 | var dialog = new DetailWindow(activityLog);
397 | dialog.WindowStartupLocation = WindowStartupLocation.CenterScreen;
398 | dialog.Show();
399 | }
400 |
401 | ///
402 | /// 処理中モードにする
403 | ///
404 | private void EnableProcessingMode()
405 | {
406 | taskbarInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.Indeterminate;
407 | Mouse.OverrideCursor = Cursors.Wait;
408 | loggerButton.IsEnabled = false;
409 | searchButton.IsEnabled = false;
410 | }
411 |
412 | ///
413 | /// 処理中モードを解除する
414 | ///
415 | private void DisableProcessingMode()
416 | {
417 | taskbarInfo.ProgressState = System.Windows.Shell.TaskbarItemProgressState.None;
418 | Mouse.OverrideCursor = null;
419 | loggerButton.IsEnabled = true;
420 |
421 | if (File.Exists(DatabaseContext.DBFilePath))
422 | {
423 | searchButton.IsEnabled = true;
424 | }
425 | else
426 | {
427 | searchButton.IsEnabled = false;
428 | }
429 |
430 | ActivityLogGrid.Focus();
431 | }
432 |
433 | ///
434 | /// キーワード入力時のイベント
435 | ///
436 | ///
437 | ///
438 | private async void keywordBox_KeyDown(object sender, KeyEventArgs e)
439 | {
440 | if (e.Key == Key.Enter)
441 | {
442 | await ExecuteSearch();
443 | }
444 | }
445 |
446 | ///
447 | /// Help/Aboutメニュークリック時のイベント
448 | ///
449 | ///
450 | ///
451 | private void AboutMenuItem_Click(object sender, RoutedEventArgs e)
452 | {
453 | var dialog = new AboutDialog();
454 | dialog.Owner = this;
455 | dialog.ShowDialog();
456 | }
457 |
458 | ///
459 | /// File/Exitメニュークリック時のイベント
460 | ///
461 | ///
462 | ///
463 | private void ExitMenuItem_Click(object sender, RoutedEventArgs e)
464 | {
465 | Application.Current.Shutdown();
466 | }
467 |
468 | ///
469 | /// Tools/TaskScheduler/Registerメニュークリック時のイベント
470 | ///
471 | ///
472 | ///
473 | private void RegisterTaskSchedulerMenuItem_Click(object sender, RoutedEventArgs e)
474 | {
475 | try
476 | {
477 | var result = MessageBox.Show("1時間毎にLoggerが実行されるようタスクスケジューラに登録しますか?", "VRChatActivityLogViewer", MessageBoxButton.YesNo, MessageBoxImage.Question);
478 | if(result == MessageBoxResult.No)
479 | {
480 | return;
481 | }
482 |
483 | var process = Process.Start(new ProcessStartInfo("add-taskschedular.bat", "/c") { RedirectStandardOutput = true, RedirectStandardError = true });
484 | process.WaitForExit();
485 | var stdo = process.StandardOutput.ReadToEnd();
486 | var stde = process.StandardError.ReadToEnd();
487 |
488 | if (string.IsNullOrWhiteSpace(stde))
489 | {
490 | MessageBox.Show(stdo, "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Information);
491 | }
492 | else
493 | {
494 | MessageBox.Show(stde, "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
495 | }
496 | }
497 | catch (System.ComponentModel.Win32Exception)
498 | {
499 | MessageBox.Show("add-taskschedular.batが見つかりませんでした。", "VRChatActivityLogViewer");
500 | DisableProcessingMode();
501 | }
502 | }
503 |
504 | ///
505 | /// Tools/TaskScheduler/Unregisterメニュークリック時のイベント
506 | ///
507 | ///
508 | ///
509 | private void UnregisterTaskSchedulerMenuItem_Click(object sender, RoutedEventArgs e)
510 | {
511 | try
512 | {
513 | var result = MessageBox.Show("タスクスケジューラに登録した設定を解除しますか?", "VRChatActivityLogViewer", MessageBoxButton.YesNo, MessageBoxImage.Question);
514 | if (result == MessageBoxResult.No)
515 | {
516 | return;
517 | }
518 |
519 | var process = Process.Start(new ProcessStartInfo("delete-taskschedular.bat", "/c") { RedirectStandardOutput = true, RedirectStandardError = true });
520 | process.WaitForExit();
521 | var stdo = process.StandardOutput.ReadToEnd();
522 | var stde = process.StandardError.ReadToEnd();
523 |
524 | if (string.IsNullOrWhiteSpace(stde))
525 | {
526 | MessageBox.Show(stdo, "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Information);
527 | }
528 | else
529 | {
530 | MessageBox.Show(stde, "VRChatActivityLogViewer", MessageBoxButton.OK, MessageBoxImage.Error);
531 | }
532 | }
533 | catch (System.ComponentModel.Win32Exception)
534 | {
535 | MessageBox.Show("delete-taskschedular.batが見つかりませんでした。", "VRChatActivityLogViewer");
536 | DisableProcessingMode();
537 | }
538 | }
539 |
540 | ///
541 | /// キーワードクリアボタン押下時のイベント
542 | ///
543 | ///
544 | ///
545 | private void KeywordClearButton_Click(object sender, RoutedEventArgs e)
546 | {
547 | keywordBox.Clear();
548 | }
549 |
550 | ///
551 | /// 検索期間コンボボックス選択時のイベント
552 | ///
553 | ///
554 | ///
555 | private void periodComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
556 | {
557 | if (sender is ComboBox comboBox)
558 | {
559 | if (comboBox.SelectedItem is ComboBoxItem item)
560 | {
561 | if (item.Tag != null && (SearchPeriod)item.Tag == SearchPeriod.Custom)
562 | {
563 | customRange.Visibility = Visibility.Visible;
564 | }
565 | else
566 | {
567 | customRange.Visibility = Visibility.Collapsed;
568 | }
569 | }
570 | }
571 | }
572 | }
573 | public enum SearchPeriod
574 | {
575 | Recent,
576 | OneWeek,
577 | OneMonth,
578 | OneYear,
579 | All,
580 | Custom,
581 | }
582 | }
583 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/Properties/PublishProfiles/PublishProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | Release
9 | Any CPU
10 | netcoreapp3.1
11 | bin\Publish\netcoreapp3.1\
12 | win-x64
13 | false
14 | true
15 | true
16 |
17 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/Properties/PublishProfiles/SelfContainedProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | Release
9 | Any CPU
10 | netcoreapp3.1
11 | bin\SelfContainedPublish\netcoreapp3.1\
12 | win-x64
13 | true
14 | true
15 | true
16 | false
17 |
18 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/Utilities/SnakeCaseNamingPolicy.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Text.Json;
5 | using System.Text.RegularExpressions;
6 |
7 | namespace VRChatActivityLogViewer.Utilities
8 | {
9 | public class SnakeCaseNamingPolicy : JsonNamingPolicy
10 | {
11 | public static SnakeCaseNamingPolicy Instance = new SnakeCaseNamingPolicy();
12 |
13 | private SnakeCaseNamingPolicy() { }
14 |
15 | public override string ConvertName(string name) =>
16 | Regex.Replace(name, @"([a-z])([A-Z])", @"$1_$2").ToLowerInvariant();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogModel.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using System.Threading.Tasks;
4 | using VRChatActivityToolsShared.Database;
5 |
6 | namespace VRChatActivityLogViewer
7 | {
8 | ///
9 | /// VRChatでのアクティビティのモデル
10 | ///
11 | class VRChatActivityLogModel
12 | {
13 | ///
14 | /// アクティビティを検索する
15 | ///
16 | ///
17 | ///
18 | public static async Task> SearchActivityLogs(ActivityLogSearchParameter parameter)
19 | {
20 | return await Task.Run(() =>
21 | {
22 | var searchActivityTypes = new List();
23 | if (parameter.IsJoinedRoom)
24 | searchActivityTypes.Add(ActivityType.JoinedRoom);
25 | if (parameter.IsMetPlayer)
26 | searchActivityTypes.Add(ActivityType.MetPlayer);
27 | if (parameter.IsSendInvite)
28 | searchActivityTypes.Add(ActivityType.SendInvite);
29 | if (parameter.IsSendRequestInvite)
30 | searchActivityTypes.Add(ActivityType.SendRequestInvite);
31 | if (parameter.IsReceivedInvite)
32 | searchActivityTypes.Add(ActivityType.ReceivedInvite);
33 | if (parameter.IsReceivedRequestInvite)
34 | searchActivityTypes.Add(ActivityType.ReceivedRequestInvite);
35 | if (parameter.IsSendFriendRequest)
36 | searchActivityTypes.Add(ActivityType.SendFriendRequest);
37 | if (parameter.IsReceivedFriendRequest)
38 | searchActivityTypes.Add(ActivityType.ReceivedFriendRequest);
39 | if (parameter.IsAcceptFriendRequest)
40 | searchActivityTypes.Add(ActivityType.AcceptFriendRequest);
41 | if (parameter.IsReceivedInviteResponse)
42 | searchActivityTypes.Add(ActivityType.ReceivedInviteResponse);
43 | if (parameter.IsReceivedRequestInviteResponse)
44 | searchActivityTypes.Add(ActivityType.ReceivedRequestInviteResponse);
45 | if (parameter.IsPlayedVideo)
46 | searchActivityTypes.Add(ActivityType.PlayedVideo);
47 | if (parameter.IsAcceptInvite)
48 | searchActivityTypes.Add(ActivityType.AcceptInvite);
49 | if (parameter.IsAcceptRequestInvite)
50 | searchActivityTypes.Add(ActivityType.AcceptRequestInvite);
51 |
52 | List activityLogs = new List();
53 | using (var db = new DatabaseContext())
54 | {
55 | activityLogs = db.ActivityLogs
56 | .Where(a => searchActivityTypes.Contains(a.ActivityType))
57 | .Where(a => a.Timestamp >= parameter.FromDateTime)
58 | .Where(a => a.Timestamp < parameter.UntilDateTime)
59 | .ToList();
60 | }
61 | return activityLogs;
62 | });
63 |
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatActivityLogViewer.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | netcoreapp3.1
6 | true
7 | icon.ico
8 | 1.4.0
9 |
10 |
11 |
12 | none
13 | false
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatApi/VRChatApiService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net.Http;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Text.Json;
7 | using System.Reflection;
8 |
9 | namespace VRChatActivityLogViewer.VRChatApi
10 | {
11 | ///
12 | /// VRChatAPIへのアクセスを提供するサービス
13 | ///
14 | public class VRChatApiService
15 | {
16 | ///
17 | /// VRCAPIのキー
18 | ///
19 | private readonly string apiKey = "JlE5Jldo5Jibnk5O5hTx6XVqsJu4WJ26";
20 |
21 | ///
22 | /// HttpClient
23 | ///
24 | private static HttpClient client = new HttpClient();
25 |
26 | ///
27 | /// JsonSerializerOptions
28 | ///
29 | private static JsonSerializerOptions jsonSerializerOptions;
30 |
31 | ///
32 | /// ユーザーエージェント文字列
33 | ///
34 | private const string UserAgent = "VRChatActivityLogViewer/{Version} nukora";
35 |
36 | static VRChatApiService()
37 | {
38 | jsonSerializerOptions = new JsonSerializerOptions
39 | {
40 | PropertyNameCaseInsensitive = true,
41 | };
42 |
43 | var version = Assembly.GetExecutingAssembly().GetName().Version;
44 | var replacedUserAgent = UserAgent.Replace("{Version}", $"{version.Major}.{version.Minor}.{version.Build}");
45 |
46 | client.DefaultRequestHeaders.Add("User-Agent", replacedUserAgent);
47 | }
48 |
49 | ///
50 | /// ワールド情報を取得する
51 | ///
52 | ///
53 | ///
54 | public async Task GetWorldAsync(string id)
55 | {
56 | var uri = $"https://api.vrchat.cloud/api/1/worlds/{id}?apiKey={apiKey}";
57 |
58 | var response = await client.GetAsync(uri);
59 |
60 | if (response.IsSuccessStatusCode)
61 | {
62 | var json = await response.Content.ReadAsStringAsync();
63 | var world = JsonSerializer.Deserialize(json, jsonSerializerOptions);
64 |
65 | return world;
66 | }
67 |
68 | return null;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/VRChatApi/World.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace VRChatActivityLogViewer.VRChatApi
6 | {
7 | ///
8 | /// VRChatAPI World object
9 | ///
10 | public class World
11 | {
12 | public string ID { get; set; }
13 | public string Name { get; set; }
14 | public string Description { get; set; }
15 | public bool Featured { get; set; }
16 | public string AuthorID { get; set; }
17 | public string AuthorName { get; set; }
18 | public int Capacity { get; set; }
19 | public string[] Tags { get; set; }
20 | public string ReleaseStatus { get; set; }
21 | public string ImageUrl { get; set; }
22 | public string ThumbnailImageUrl { get; set; }
23 | public int Version { get; set; }
24 | public string Organization { get; set; }
25 | public string PreviewYoutubeId { get; set; }
26 | public int Favorites { get; set; }
27 | public string CreatedAt { get; set; }
28 | public string UpdatedAt { get; set; }
29 | public string PublicationDate { get; set; }
30 | public string LabsPublicationDate { get; set; }
31 | public int Visits { get; set; }
32 | public int Popularity { get; set; }
33 | public int Heat { get; set; }
34 | public int PublicOccupants { get; set; }
35 | public int PrivateOccupants { get; set; }
36 | public int Occupants { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/WebService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Net.Http;
5 | using System.Reflection;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 | using System.Windows.Media.Imaging;
9 |
10 | namespace VRChatActivityLogViewer
11 | {
12 | ///
13 | /// Webへのアクセスを提供するサービス
14 | ///
15 | public class WebService
16 | {
17 | ///
18 | /// HttpClient
19 | ///
20 | private static HttpClient client = new HttpClient();
21 |
22 | ///
23 | /// ユーザーエージェント文字列
24 | ///
25 | private const string UserAgent = "VRChatActivityLogViewer/{Version} nukora";
26 |
27 | static WebService()
28 | {
29 | var version = Assembly.GetExecutingAssembly().GetName().Version;
30 | var replacedUserAgent = UserAgent.Replace("{Version}", $"{version.Major}.{version.Minor}.{version.Build}");
31 |
32 | client.DefaultRequestHeaders.Add("User-Agent", replacedUserAgent);
33 | }
34 |
35 | ///
36 | /// URIを指定して画像をダウンロードする
37 | ///
38 | ///
39 | ///
40 | public async Task GetStreamAsync(string uri)
41 | {
42 | var byteArray = await client.GetByteArrayAsync(uri);
43 | return new WrappingStream(new MemoryStream(byteArray));
44 | }
45 |
46 | ///
47 | /// URIを指定してファイルをストレージにダウンロードする
48 | ///
49 | ///
50 | ///
51 | public async Task DownloadFile(string uri, string filePath)
52 | {
53 | using var response = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
54 | if (response.StatusCode == System.Net.HttpStatusCode.OK)
55 | {
56 | using var stream = await response.Content.ReadAsStreamAsync();
57 | using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None);
58 | await stream.CopyToAsync(fileStream);
59 | fileStream.Flush();
60 | }
61 | }
62 | }
63 |
64 | ///
65 | /// Dispose後にキャッシュを破棄するStreamのラッパー
66 | ///
67 | public class WrappingStream : Stream
68 | {
69 | Stream innerStream;
70 |
71 | public WrappingStream(Stream stream)
72 | {
73 | if (stream == null)
74 | {
75 | throw new ArgumentNullException(nameof(stream));
76 | }
77 |
78 | innerStream = stream;
79 | }
80 |
81 | public override bool CanRead
82 | {
83 | get
84 | {
85 | ThrowIfDisposed();
86 | return innerStream.CanRead;
87 | }
88 | }
89 |
90 | public override bool CanSeek
91 | {
92 | get
93 | {
94 | ThrowIfDisposed();
95 | return innerStream.CanSeek;
96 | }
97 | }
98 |
99 | public override bool CanWrite
100 | {
101 | get
102 | {
103 | ThrowIfDisposed();
104 | return innerStream.CanWrite;
105 | }
106 | }
107 |
108 | public override long Length
109 | {
110 | get
111 | {
112 | ThrowIfDisposed();
113 | return innerStream.Length;
114 | }
115 | }
116 |
117 | public override long Position
118 | {
119 | get
120 | {
121 | ThrowIfDisposed();
122 | return innerStream.Position;
123 | }
124 | set
125 | {
126 | ThrowIfDisposed();
127 | innerStream.Position = value;
128 | }
129 | }
130 |
131 | public override void Flush()
132 | {
133 | ThrowIfDisposed();
134 | innerStream.Flush();
135 | }
136 |
137 | public override int Read(byte[] buffer, int offset, int count)
138 | {
139 | ThrowIfDisposed();
140 | return innerStream.Read(buffer, offset, count);
141 | }
142 |
143 | public override long Seek(long offset, SeekOrigin origin)
144 | {
145 | ThrowIfDisposed();
146 | return innerStream.Seek(offset, origin);
147 | }
148 |
149 | public override void SetLength(long value)
150 | {
151 | ThrowIfDisposed();
152 | innerStream.SetLength(value);
153 | }
154 |
155 | public override void Write(byte[] buffer, int offset, int count)
156 | {
157 | ThrowIfDisposed();
158 | innerStream.Write(buffer, offset, count);
159 | }
160 |
161 | public override Task ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken)
162 | {
163 | ThrowIfDisposed();
164 | return innerStream.ReadAsync(buffer, offset, count, cancellationToken);
165 | }
166 |
167 | public new Task ReadAsync(byte[] buffer, int offset, int count)
168 | {
169 | ThrowIfDisposed();
170 | return innerStream.ReadAsync(buffer, offset, count);
171 | }
172 |
173 | protected override void Dispose(bool disposing)
174 | {
175 | if (disposing)
176 | {
177 | innerStream.Dispose();
178 | innerStream = null;
179 | }
180 |
181 | base.Dispose(disposing);
182 | }
183 |
184 | private void ThrowIfDisposed()
185 | {
186 | if (innerStream == null)
187 | {
188 | throw new ObjectDisposedException(GetType().Name);
189 | }
190 | }
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/YoutubeApi/YoutubeApiService.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Net.Http;
4 | using System.Text;
5 | using System.Text.Json;
6 | using System.Threading.Tasks;
7 | using VRChatActivityLogViewer.Utilities;
8 |
9 | namespace VRChatActivityLogViewer.YoutubeApi
10 | {
11 | ///
12 | /// Youtube oEmbed APIへのアクセスを提供するサービス
13 | ///
14 | public class YoutubeApiService
15 | {
16 | ///
17 | /// HttpClient
18 | ///
19 | private static HttpClient client = new HttpClient();
20 |
21 | ///
22 | /// JsonSerializerOptions
23 | ///
24 | private static JsonSerializerOptions jsonSerializerOptions;
25 |
26 | ///
27 | /// ユーザーエージェント文字列
28 | ///
29 | private static string userAgent = "Wget/1.20.3";
30 |
31 | static YoutubeApiService()
32 | {
33 | jsonSerializerOptions = new JsonSerializerOptions
34 | {
35 | PropertyNameCaseInsensitive = true,
36 | PropertyNamingPolicy = SnakeCaseNamingPolicy.Instance,
37 |
38 | };
39 |
40 | client.DefaultRequestHeaders.Add("User-Agent", userAgent);
41 | }
42 |
43 | ///
44 | /// 動画のoEmbed情報を取得する
45 | ///
46 | ///
47 | ///
48 | public async Task GetOEmbedAsync(string id)
49 | {
50 | var uri = $"https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v={id}&format=json";
51 |
52 | var response = await client.GetAsync(uri);
53 |
54 | if (response.IsSuccessStatusCode)
55 | {
56 | var json = await response.Content.ReadAsStringAsync();
57 | var oEmbedResponse = JsonSerializer.Deserialize(json, jsonSerializerOptions);
58 |
59 | return oEmbedResponse;
60 | }
61 |
62 | return null;
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/YoutubeApi/YoutubeEmbedResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace VRChatActivityLogViewer.YoutubeApi
6 | {
7 | ///
8 | /// YoutubeのoEmbedレスポンス
9 | ///
10 | public class YoutubeEmbedResponse
11 | {
12 | public string Title { get; set; }
13 | public string AuthorName { get; set; }
14 | public string AuthorUrl { get; set; }
15 | public string Type { get; set; }
16 | public int Height { get; set; }
17 | public int Width { get; set; }
18 | public string Version { get; set; }
19 | public string ProviderName { get; set; }
20 | public string ProviderUrl { get; set; }
21 | public int ThumbnailHeight { get; set; }
22 | public int ThumbnailWidth { get; set; }
23 | public string ThumbnailUrl { get; set; }
24 | public string Html { get; set; }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/VRChatActivityLogViewer/VRChatActivityLogViewer/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nukora/VRChatActivityTools/96e6474e71bc65bf6a7be0ad90c5888753926cc6/VRChatActivityLogViewer/VRChatActivityLogViewer/icon.ico
--------------------------------------------------------------------------------
/VRChatActivityLogger/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.29503.13
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VRChatActivityLogger", "VRChatActivityLogger\VRChatActivityLogger.csproj", "{9AA67FBA-9964-4FC2-8894-79786EF81F36}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VRChatActivityToolsShared", "..\VRChatActivityToolsShared\VRChatActivityToolsShared\VRChatActivityToolsShared.csproj", "{7DF3B522-F999-4B58-BC05-C75E2A9DB1E9}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {9AA67FBA-9964-4FC2-8894-79786EF81F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {9AA67FBA-9964-4FC2-8894-79786EF81F36}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {9AA67FBA-9964-4FC2-8894-79786EF81F36}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {9AA67FBA-9964-4FC2-8894-79786EF81F36}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {7DF3B522-F999-4B58-BC05-C75E2A9DB1E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {7DF3B522-F999-4B58-BC05-C75E2A9DB1E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {7DF3B522-F999-4B58-BC05-C75E2A9DB1E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {7DF3B522-F999-4B58-BC05-C75E2A9DB1E9}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {C2D16B47-A9B3-40FC-B97C-9B63E11202DF}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Argument.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace VRChatActivityLogger
6 | {
7 | ///
8 | /// コマンドライン引数のラップクラス
9 | ///
10 | class Argument
11 | {
12 | private string[] args;
13 | private Dictionary namedParameters = new Dictionary();
14 |
15 | ///
16 | /// 名前なし引数の一覧
17 | ///
18 | public List NamelessParameters { get; set; } = new List();
19 |
20 | ///
21 | /// 名前あり引数の一覧
22 | ///
23 | public Dictionary NamedParameters
24 | {
25 | get
26 | {
27 | return namedParameters;
28 | }
29 | set
30 | {
31 | namedParameters = value;
32 | Initialize();
33 | }
34 | }
35 |
36 | ///
37 | /// 引数を解析します。
38 | ///
39 | ///
40 | public Argument(string[] args)
41 | {
42 | this.args = args;
43 | Initialize();
44 | }
45 |
46 | private void Initialize()
47 | {
48 | NamelessParameters.Clear();
49 | for (int i = 0; i < args.Length; i++)
50 | {
51 | if (args[i].Length > 1 && args[i].StartsWith("-"))
52 | {
53 | var key = args[i].Substring(1);
54 | var value = "";
55 | while (i + 1 < args.Length && !args[i + 1].StartsWith("-"))
56 | {
57 | i++;
58 | value += " " + args[i];
59 | }
60 | NamedParameters[key] = value.Trim();
61 | }
62 | else
63 | {
64 | NamelessParameters.Add(args[i]);
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Batch/add-taskschedular.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | cd "%~dp0"
3 | if exist task_actual.xml del task_actual.xml
4 | setlocal enabledelayedexpansion
5 | for /f "delims=" %%a in (task.xml) do (
6 | set line=%%a
7 | echo !line:__DIR__=%~dp0! >> task_actual.xml
8 | )
9 |
10 | schtasks /Create /F /XML task_actual.xml /TN VRChatActivityLogger
11 | del task_actual.xml
12 |
13 | if not "%1" == "/c" (
14 | PAUSE
15 | )
16 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Batch/delete-taskschedular.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | schtasks /Delete /F /TN VRChatActivityLogger
3 | if not "%1" == "/c" (
4 | PAUSE
5 | )
6 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Batch/task.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | false
5 | false
6 | IgnoreNew
7 | true
8 |
9 | false
10 | false
11 |
12 |
13 |
14 |
15 | 2021-02-03T00:00:00
16 |
17 | PT1H
18 |
19 |
20 |
21 |
22 |
23 | "__DIR__VRChatActivityLogger.exe"
24 | -console false
25 | __DIR__
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Logger.cs:
--------------------------------------------------------------------------------
1 | using NLog;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace VRChatActivityLogger
7 | {
8 | ///
9 | /// Loggerクラス
10 | ///
11 | static class Logger
12 | {
13 | static private bool isInitialized = false;
14 | static private NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();
15 |
16 | ///
17 | /// Loggerを取得します。
18 | ///
19 | ///
20 | static public NLog.Logger GetLogger()
21 | {
22 | if (!isInitialized) Initialize();
23 | return logger;
24 | }
25 |
26 | ///
27 | /// Loggerの初期化
28 | ///
29 | static private void Initialize()
30 | {
31 | var config = new NLog.Config.LoggingConfiguration();
32 | var fileTarget = new NLog.Targets.FileTarget()
33 | {
34 | FileName = System.Environment.CurrentDirectory + "/Logs/VRChatActivityLogger/${shortdate}.log",
35 | Layout = "${longdate} [${uppercase:${level}}] ${message}",
36 | MaxArchiveFiles = 5,
37 | };
38 | var consoleTarget = new NLog.Targets.ConsoleTarget()
39 | {
40 | Layout = "${message}",
41 | };
42 | config.AddRule(LogLevel.Debug, LogLevel.Fatal, fileTarget);
43 | config.AddRule(LogLevel.Debug, LogLevel.Fatal, consoleTarget);
44 | NLog.LogManager.Configuration = config;
45 | isInitialized = true;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace VRChatActivityLogger
5 | {
6 | class Program
7 | {
8 | [System.Runtime.InteropServices.DllImport("kernel32.dll")]
9 | private static extern bool AllocConsole();
10 |
11 | ///
12 | /// エントリポイント
13 | ///
14 | ///
15 | static int Main(string[] rawArgs)
16 | {
17 |
18 | var args = new Argument(rawArgs)
19 | {
20 | NamedParameters = new Dictionary {
21 | { "console", "true" },
22 | }
23 | };
24 | if (args.NamedParameters["console"].ToLower() != "false")
25 | {
26 | AllocConsole();
27 | }
28 |
29 | var logger = Logger.GetLogger();
30 | logger.Info("VRChatActivityLoggerを実行します。");
31 |
32 | var app = new VRChatActivityLogger();
33 | var returnCode = app.Run();
34 |
35 | logger.Info("VRChatActivityLoggerを終了します。");
36 |
37 | return returnCode;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Properties/PublishProfiles/PublishProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | Release
9 | Any CPU
10 | netcoreapp3.1
11 | bin\Publish\netcoreapp3.1\
12 | win-x64
13 | false
14 | true
15 | true
16 |
17 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Properties/PublishProfiles/SelfContainedProfile.pubxml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 | FileSystem
8 | Release
9 | Any CPU
10 | netcoreapp3.1
11 | bin\SelfContainedPublish\netcoreapp3.1\
12 | win-x64
13 | true
14 | true
15 | true
16 | false
17 |
18 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/Properties/launchSettings.json:
--------------------------------------------------------------------------------
1 | {
2 | "profiles": {
3 | "VRChatActivityLogger": {
4 | "commandName": "Project",
5 | "commandLineArgs": "-console true"
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/RegexPatterns.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Text.RegularExpressions;
5 |
6 | namespace VRChatActivityLogger
7 | {
8 | ///
9 | /// 正規表現のグループ名定義
10 | ///
11 | static public class PatternType
12 | {
13 | public static readonly string ReceivedInvite = "ReceivedInvite";
14 | public static readonly string ReceivedRequestInvite = "ReceivedRequestInvite";
15 | public static readonly string SendInvite = "SendInvite";
16 | public static readonly string SendRequestInvite = "SendRequestInvite";
17 | public static readonly string JoinedRoom1 = "JoinedRoom1";
18 | public static readonly string JoinedRoom2 = "JoinedRoom2";
19 | public static readonly string MetPlayer = "MetPlayer";
20 | public static readonly string SendFriendRequest = "SendFriendRequest";
21 | public static readonly string ReceivedFriendRequest = "ReceivedFriendRequest";
22 | public static readonly string AcceptFriendRequest = "AcceptFriendRequest";
23 | public static readonly string ReceivedInviteResponse = "ReceivedInviteResponse";
24 | public static readonly string ReceivedRequestInviteResponse = "ReceivedRequestInviteResponse";
25 | public static readonly string PlayedVideo1 = "PlayedVideo1";
26 | public static readonly string PlayedVideo2 = "PlayedVideo2";
27 | public static readonly string AcceptInvite = "AcceptInvite";
28 | public static readonly string AcceptRequestInvite = "AcceptRequestInvite";
29 | }
30 |
31 | ///
32 | /// 正規表現を定義するクラス
33 | ///
34 | public static class RegexPatterns
35 | {
36 | public static Regex ReceivedInviteDetail { get; }
37 | public static Regex ReceivedRequestInviteDetail { get; }
38 | public static Regex SendInviteDetail { get; }
39 | public static Regex SendRequestInviteDetail { get; }
40 | public static Regex JoinedRoom1Detail { get; }
41 | public static Regex JoinedRoom2Detail { get; }
42 | public static Regex MetPlayerDetail { get; }
43 | public static Regex SendFriendRequestDetail { get; }
44 | public static Regex ReceivedFriendRequestDetail { get; }
45 | public static Regex AcceptFriendRequestDetail { get; }
46 | public static Regex ReceivedInviteResponseDetail { get; }
47 | public static Regex ReceivedRequestInviteResponseDetail { get; }
48 | public static Regex PlayedVideo1Detail { get; }
49 | public static Regex PlayedVideo2Detail { get; }
50 | public static Regex AcceptInviteDetail { get; }
51 | public static Regex AcceptRequestInviteDetail { get; }
52 | public static Regex All { get; }
53 |
54 | ///
55 | /// コンストラクタ
56 | ///
57 | static RegexPatterns()
58 | {
59 | //ログの種類判別(個別)
60 | string header = @"^\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2}\s.{11}-\s{2}";
61 |
62 | string receivedInvite = header + @"Received Notification:.+type:invite,.+$";
63 | string receivedRequestInvite = header + @"Received Notification:.+type:requestInvite,.+$";
64 | string sendInvite = header + @"Send notification:.+type:invite,.+$";
65 | string sendRequestInvite = header + @"Send notification:.+type:requestInvite,.+$";
66 | string joinedRoom1 = header + @"\[(RoomManager|[DŽDž]*|Behaviour)\] Joining w.+$";
67 | string joinedRoom2 = header + @"\[(RoomManager|[DŽDž]*|Behaviour)\] Joining or Creating Room:.+$";
68 | string metPlayer = header + @"\[(Player|[DŽDž]*|Behaviour)\] Initialized PlayerAPI.+$";
69 | string sendFriendRequest = header + @"Send notification:.+type:friendRequest,.+$";
70 | string receivedFriendRequest = header + @"Received Notification:.+type:friendRequest,.+$";
71 | string acceptFriendRequest = header + @"AcceptNotification for notification:.+type:friendRequest,.+$";
72 | string receivedInviteResponse = header + @"Received Notification:.+type:inviteResponse,.+$";
73 | string receivedRequestInviteResponse = header + @"Received Notification:.+type:requestInviteResponse,.+$";
74 | string playedVideo1 = header + @"User .+ added URL .+$";
75 | string playedVideo2 = header + @"\[Video Playback\] Attempting to resolve URL '.+'$";
76 | string acceptInvite = header + @"AcceptNotification for notification:.+type:invite,.+$";
77 | string acceptRequestInvite = header + @"AcceptNotification for notification:.+type:requestInvite,.+$";
78 |
79 | //ログの種類判別(一括)
80 | string all = "";
81 | all += $@"(?{receivedInvite})|";
82 | all += $@"(?{receivedRequestInvite})|";
83 | all += $@"(?{sendInvite})|";
84 | all += $@"(?{sendRequestInvite})|";
85 | all += $@"(?{joinedRoom1})|";
86 | all += $@"(?{joinedRoom2})|";
87 | all += $@"(?{metPlayer})|";
88 | all += $@"(?{sendFriendRequest})|";
89 | all += $@"(?{receivedFriendRequest})|";
90 | all += $@"(?{acceptFriendRequest})|";
91 | all += $@"(?{receivedInviteResponse})|";
92 | all += $@"(?{receivedRequestInviteResponse})|";
93 | all += $@"(?{playedVideo1})|";
94 | all += $@"(?{playedVideo2})|";
95 | all += $@"(?{acceptInvite})|";
96 | all += $@"(?{acceptRequestInvite})";
97 | All = new Regex(all, RegexOptions.Compiled);
98 |
99 | //ログの詳細を解析
100 | string detailHeader = @"^(\d{4}\.\d{2}\.\d{2}\s\d{2}:\d{2}:\d{2})\s.{11}-\s{2}";
101 |
102 | string receivedInviteDetail = detailHeader + @"Received Notification:
13 | /// VRChatでの活動履歴をログから取得し、データベースに保存するクラス
14 | ///
15 | class VRChatActivityLogger
16 | {
17 | ///
18 | /// ロガー
19 | ///
20 | private NLog.Logger logger = Logger.GetLogger();
21 |
22 | ///
23 | /// VRChatのログの保存場所
24 | ///
25 | public string VRChatLogFilePath { get; set; } =
26 | Regex.Replace(GetFolderPath(SpecialFolder.LocalApplicationData), @"\\[^\\]+$", "") + @"\LocalLow\VRChat\VRChat\";
27 |
28 | ///
29 | /// 処理を実行します。
30 | ///
31 | ///
32 | public int Run()
33 | {
34 | var logger = Logger.GetLogger();
35 | try
36 | {
37 | ClearErrorInfoFile();
38 |
39 | // ログ解析
40 | var activityLogs = new List();
41 | foreach (var file in Directory.EnumerateFiles(VRChatLogFilePath, "output_log_*"))
42 | {
43 | logger.Debug("ログを解析中 " + file);
44 | activityLogs.AddRange(ParseVRChatLog(file));
45 | }
46 | activityLogs = activityLogs.OrderBy(a => a.Timestamp).ToList();
47 |
48 | // DBファイルチェック
49 | if (!File.Exists(DatabaseContext.DBFilePath))
50 | {
51 | logger.Info("データベースファイルが見つかりませんでした。新しく作成します。");
52 |
53 | DatabaseMigration.CreateDatabase();
54 |
55 | logger.Info("データベースファイルを作成しました。");
56 | }
57 |
58 | // DBバージョンチェック
59 | var currentVersion = DatabaseMigration.GetCurrentVersion();
60 |
61 | if (currentVersion < DatabaseContext.Version)
62 | {
63 | logger.Info("古いバージョンのデータベースを使用しています。データベースのアップグレードを行います。");
64 |
65 | DatabaseMigration.UpgradeDatabase();
66 |
67 | logger.Info("データベースをアップグレードしました。");
68 | }
69 | else if (DatabaseContext.Version < currentVersion)
70 | {
71 | throw new InvalidOperationException("新しいバージョンのアプリで作成されたデータベースが存在するため、処理を中断します。");
72 | }
73 |
74 | // DB更新
75 | using (var db = new DatabaseContext())
76 | {
77 | // 既にDBに登録されているログは登録対象から削除する
78 | var lastActivity = db.ActivityLogs.Find(db.ActivityLogs.Max(a => a.ID));
79 | if (lastActivity != null)
80 | {
81 | var idBackup = lastActivity.ID;
82 | lastActivity.ID = null;
83 | for (int i = 0; i < activityLogs.Count; i++)
84 | {
85 | if (activityLogs[i].Timestamp == lastActivity.Timestamp)
86 | {
87 | if (activityLogs[i].Equals(lastActivity))
88 | {
89 | activityLogs.RemoveRange(0, i + 1);
90 | break;
91 | }
92 | }
93 | }
94 | lastActivity.ID = idBackup;
95 | }
96 |
97 | // IDの同じフレンドリクエストは登録対象から削除する
98 | foreach (var log in activityLogs.Where(l => l.ActivityType == ActivityType.ReceivedFriendRequest).ToArray())
99 | {
100 | var isDuplicated =
101 |
102 | // 登録対象の中に重複がある
103 | activityLogs.Any(x =>
104 | x.ActivityType == ActivityType.ReceivedFriendRequest &&
105 | x.NotificationID == log.NotificationID &&
106 | x.Timestamp < log.Timestamp) ||
107 |
108 | // DB上に重複がある
109 | db.ActivityLogs.Any(x =>
110 | x.ActivityType == ActivityType.ReceivedFriendRequest &&
111 | x.NotificationID == log.NotificationID);
112 |
113 | if (isDuplicated)
114 | {
115 | activityLogs.Remove(log);
116 | }
117 | }
118 |
119 | // DBに登録する
120 | using (var transaction = db.Database.BeginTransaction())
121 | {
122 | try
123 | {
124 | foreach (var activityLog in activityLogs)
125 | {
126 | db.Add(activityLog);
127 | db.SaveChanges();
128 | }
129 | transaction.Commit();
130 | }
131 | catch (Exception)
132 | {
133 | transaction.Rollback();
134 | throw;
135 | }
136 |
137 | }
138 |
139 | }
140 |
141 | logger.Info(activityLogs.Count + "件追加しました。");
142 |
143 | }
144 | catch (Exception ex)
145 | {
146 | logger.Error(ex);
147 | WriteErrorInfoFile();
148 | return -1;
149 | }
150 | return 0;
151 | }
152 |
153 | ///
154 | /// VRChatのログを解析します。
155 | ///
156 | ///
157 | ///
158 | private List ParseVRChatLog(string filePath)
159 | {
160 | string rawData = "";
161 | var activityLogs = new List();
162 | using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
163 | using (var sr = new StreamReader(fs, Encoding.GetEncoding("UTF-8")))
164 | {
165 | processingFilePath = filePath;
166 | processingLineNumber = 0;
167 |
168 | while ((rawData = sr.ReadLine()) != null)
169 | {
170 | processingLineNumber++;
171 |
172 | if (rawData.Length > 25 && rawData.Substring(20, 5) == "Error")
173 | {
174 | continue;
175 | }
176 |
177 | Match match = RegexPatterns.All.Match(rawData);
178 |
179 | if (!match.Success)
180 | {
181 | continue;
182 | }
183 |
184 | processingLine = match.Value;
185 |
186 | if (match.Groups[PatternType.ReceivedInvite].Value.Length != 0)
187 | {
188 | var m = RegexPatterns.ReceivedInviteDetail.Match(match.ToString());
189 | var activityLog = new ActivityLog
190 | {
191 | ActivityType = ActivityType.ReceivedInvite,
192 | Timestamp = DateTime.Parse(m.Groups[1].Value),
193 | NotificationID = m.Groups[4].Value,
194 | UserID = m.Groups[3].Value,
195 | UserName = m.Groups[2].Value,
196 | WorldID = m.Groups[5].Value,
197 | WorldName = m.Groups[6].Value,
198 | };
199 |
200 | if (m.Groups[8].Success)
201 | {
202 | activityLog.Message = m.Groups[8].Value;
203 | }
204 |
205 | if (m.Groups[10].Success)
206 | {
207 | activityLog.Url = m.Groups[10].Value;
208 | }
209 |
210 | activityLogs.Add(activityLog);
211 | }
212 | else if (match.Groups[PatternType.ReceivedRequestInvite].Value.Length != 0)
213 | {
214 | var m = RegexPatterns.ReceivedRequestInviteDetail.Match(match.ToString());
215 | var activityLog = new ActivityLog
216 | {
217 | ActivityType = ActivityType.ReceivedRequestInvite,
218 | Timestamp = DateTime.Parse(m.Groups[1].Value),
219 | NotificationID = m.Groups[4].Value,
220 | UserID = m.Groups[3].Value,
221 | UserName = m.Groups[2].Value,
222 | };
223 |
224 | if (m.Groups[6].Success)
225 | {
226 | activityLog.Message = m.Groups[6].Value;
227 | }
228 |
229 | if (m.Groups[8].Success)
230 | {
231 | activityLog.Url = m.Groups[8].Value;
232 | }
233 |
234 | activityLogs.Add(activityLog);
235 | }
236 | else if (match.Groups[PatternType.SendInvite].Value.Length != 0)
237 | {
238 | var m = RegexPatterns.SendInviteDetail.Match(match.ToString());
239 | var activityLog = new ActivityLog
240 | {
241 | ActivityType = ActivityType.SendInvite,
242 | Timestamp = DateTime.Parse(m.Groups[1].Value),
243 | UserID = m.Groups[2].Value,
244 | WorldID = m.Groups[3].Value,
245 | WorldName = m.Groups[4].Value,
246 | };
247 |
248 | if (m.Groups[6].Success)
249 | {
250 | activityLog.Message = m.Groups[6].Value;
251 | }
252 |
253 | activityLogs.Add(activityLog);
254 | }
255 | else if (match.Groups[PatternType.SendRequestInvite].Value.Length != 0)
256 | {
257 | var m = RegexPatterns.SendRequestInviteDetail.Match(match.ToString());
258 | var activityLog = new ActivityLog
259 | {
260 | ActivityType = ActivityType.SendRequestInvite,
261 | Timestamp = DateTime.Parse(m.Groups[1].Value),
262 | UserID = m.Groups[2].Value,
263 | };
264 |
265 | if (m.Groups[3].Success)
266 | {
267 | activityLog.Message = m.Groups[3].Value;
268 | }
269 |
270 | activityLogs.Add(activityLog);
271 | }
272 | else if (match.Groups[PatternType.JoinedRoom1].Value.Length != 0)
273 | {
274 | var m = RegexPatterns.JoinedRoom1Detail.Match(match.ToString());
275 | activityLogs.Add(new ActivityLog
276 | {
277 | ActivityType = ActivityType.JoinedRoom,
278 | Timestamp = DateTime.Parse(m.Groups[1].Value),
279 | WorldID = m.Groups[3].Value,
280 | });
281 | }
282 | else if (match.Groups[PatternType.JoinedRoom2].Value.Length != 0)
283 | {
284 | var m = RegexPatterns.JoinedRoom2Detail.Match(match.ToString());
285 | if (activityLogs.Any() && activityLogs[activityLogs.Count - 1].ActivityType == ActivityType.JoinedRoom)
286 | {
287 | activityLogs[activityLogs.Count - 1].WorldName = m.Groups[3].Value;
288 | }
289 | else
290 | {
291 | activityLogs.Add(new ActivityLog
292 | {
293 | ActivityType = ActivityType.JoinedRoom,
294 | Timestamp = DateTime.Parse(m.Groups[1].Value),
295 | WorldName = m.Groups[3].Value,
296 | });
297 | }
298 | }
299 | else if (match.Groups[PatternType.MetPlayer].Value.Length != 0)
300 | {
301 | var m = RegexPatterns.MetPlayerDetail.Match(match.ToString());
302 | activityLogs.Add(new ActivityLog
303 | {
304 | ActivityType = ActivityType.MetPlayer,
305 | Timestamp = DateTime.Parse(m.Groups[1].Value),
306 | UserName = m.Groups[3].Value,
307 | });
308 | }
309 | else if (match.Groups[PatternType.SendFriendRequest].Value.Length != 0)
310 | {
311 | var m = RegexPatterns.SendFriendRequestDetail.Match(match.ToString());
312 | activityLogs.Add(new ActivityLog
313 | {
314 | ActivityType = ActivityType.SendFriendRequest,
315 | Timestamp = DateTime.Parse(m.Groups[1].Value),
316 | UserID = m.Groups[2].Value,
317 | });
318 | }
319 | else if (match.Groups[PatternType.ReceivedFriendRequest].Value.Length != 0)
320 | {
321 | var m = RegexPatterns.ReceivedFriendRequestDetail.Match(match.ToString());
322 | var activityLog = new ActivityLog
323 | {
324 | ActivityType = ActivityType.ReceivedFriendRequest,
325 | Timestamp = DateTime.Parse(m.Groups[1].Value),
326 | NotificationID = m.Groups[4].Value,
327 | UserID = m.Groups[3].Value,
328 | UserName = m.Groups[2].Value,
329 | };
330 |
331 | activityLogs.Add(activityLog);
332 | }
333 | else if (match.Groups[PatternType.AcceptFriendRequest].Value.Length != 0)
334 | {
335 | var m = RegexPatterns.AcceptFriendRequestDetail.Match(match.ToString());
336 | activityLogs.Add(new ActivityLog
337 | {
338 | ActivityType = ActivityType.AcceptFriendRequest,
339 | Timestamp = DateTime.Parse(m.Groups[1].Value),
340 | UserName = m.Groups[2].Value,
341 | UserID = m.Groups[3].Value,
342 | NotificationID = m.Groups[4].Value,
343 | });
344 | }
345 | else if (match.Groups[PatternType.ReceivedInviteResponse].Value.Length != 0)
346 | {
347 | var m = RegexPatterns.ReceivedInviteResponseDetail.Match(match.ToString());
348 | var activityLog = new ActivityLog
349 | {
350 | ActivityType = ActivityType.ReceivedInviteResponse,
351 | Timestamp = DateTime.Parse(m.Groups[1].Value),
352 | NotificationID = m.Groups[4].Value,
353 | UserName = m.Groups[2].Value,
354 | UserID = m.Groups[3].Value,
355 | };
356 |
357 | if (m.Groups[6].Success)
358 | {
359 | activityLog.Message = m.Groups[6].Value;
360 | }
361 |
362 | if (m.Groups[8].Success)
363 | {
364 | activityLog.Url = m.Groups[8].Value;
365 | }
366 |
367 | activityLogs.Add(activityLog);
368 | }
369 | else if (match.Groups[PatternType.ReceivedRequestInviteResponse].Value.Length != 0)
370 | {
371 | var m = RegexPatterns.ReceivedRequestInviteResponseDetail.Match(match.ToString());
372 | var activityLog = new ActivityLog
373 | {
374 | ActivityType = ActivityType.ReceivedRequestInviteResponse,
375 | Timestamp = DateTime.Parse(m.Groups[1].Value),
376 | NotificationID = m.Groups[4].Value,
377 | UserName = m.Groups[2].Value,
378 | UserID = m.Groups[3].Value,
379 | };
380 |
381 | if (m.Groups[6].Success)
382 | {
383 | activityLog.Message = m.Groups[6].Value;
384 | }
385 |
386 | if (m.Groups[8].Success)
387 | {
388 | activityLog.Url = m.Groups[8].Value;
389 | }
390 |
391 | activityLogs.Add(activityLog);
392 | }
393 | else if (match.Groups[PatternType.PlayedVideo1].Value.Length != 0)
394 | {
395 | // VRCSDK2で作成したワールドの場合
396 |
397 | var m = RegexPatterns.PlayedVideo1Detail.Match(match.ToString());
398 | var activityLog = new ActivityLog
399 | {
400 | ActivityType = ActivityType.PlayedVideo,
401 | Timestamp = DateTime.Parse(m.Groups[1].Value),
402 | UserName = m.Groups[2].Value,
403 | Url = m.Groups[3].Value,
404 | };
405 |
406 | // 1回のアクションにつき2行のログが出力される事がある
407 | if (activityLogs[activityLogs.Count - 1] == activityLog)
408 | {
409 | continue;
410 | }
411 |
412 | activityLogs.Add(activityLog);
413 | }
414 | else if (match.Groups[PatternType.PlayedVideo2].Value.Length != 0)
415 | {
416 | // VRCSDK3で作成したワールドの場合
417 |
418 | var m = RegexPatterns.PlayedVideo2Detail.Match(match.ToString());
419 | var activityLog = new ActivityLog
420 | {
421 | ActivityType = ActivityType.PlayedVideo,
422 | Timestamp = DateTime.Parse(m.Groups[1].Value),
423 | Url = m.Groups[2].Value,
424 | };
425 |
426 | activityLogs.Add(activityLog);
427 | }
428 | else if (match.Groups[PatternType.AcceptInvite].Value.Length != 0)
429 | {
430 | var m = RegexPatterns.AcceptInviteDetail.Match(match.ToString());
431 | var activityLog = new ActivityLog
432 | {
433 | ActivityType = ActivityType.AcceptInvite,
434 | Timestamp = DateTime.Parse(m.Groups[1].Value),
435 | NotificationID = m.Groups[4].Value,
436 | UserID = m.Groups[3].Value,
437 | UserName = m.Groups[2].Value,
438 | WorldID = m.Groups[5].Value,
439 | WorldName = m.Groups[6].Value,
440 | };
441 |
442 | if (m.Groups[8].Success)
443 | {
444 | activityLog.Message = m.Groups[8].Value;
445 | }
446 |
447 | if (m.Groups[10].Success)
448 | {
449 | activityLog.Url = m.Groups[10].Value;
450 | }
451 |
452 | activityLogs.Add(activityLog);
453 | }
454 | else if (match.Groups[PatternType.AcceptRequestInvite].Value.Length != 0)
455 | {
456 | var m = RegexPatterns.AcceptRequestInviteDetail.Match(match.ToString());
457 | var activityLog = new ActivityLog
458 | {
459 | ActivityType = ActivityType.AcceptRequestInvite,
460 | Timestamp = DateTime.Parse(m.Groups[1].Value),
461 | NotificationID = m.Groups[4].Value,
462 | UserID = m.Groups[3].Value,
463 | UserName = m.Groups[2].Value,
464 | };
465 |
466 | if (m.Groups[6].Success)
467 | {
468 | activityLog.Message = m.Groups[6].Value;
469 | }
470 |
471 | if (m.Groups[8].Success)
472 | {
473 | activityLog.Url = m.Groups[8].Value;
474 | }
475 |
476 | activityLogs.Add(activityLog);
477 | }
478 | else
479 | {
480 | continue;
481 | }
482 |
483 | processingLine = string.Empty;
484 | }
485 |
486 | processingLineNumber = 0;
487 | processingFilePath = string.Empty;
488 | }
489 |
490 | return activityLogs;
491 | }
492 |
493 | private string processingFilePath = string.Empty;
494 |
495 | private int processingLineNumber = 0;
496 |
497 | private string processingLine = string.Empty;
498 |
499 | private readonly string errorFilePath = "./Logs/VRChatActivityLogger/errorfile.txt";
500 |
501 | ///
502 | /// エラーファイルをクリアします。
503 | ///
504 | private void ClearErrorInfoFile()
505 | {
506 | if (File.Exists(errorFilePath))
507 | {
508 | File.Delete(errorFilePath);
509 | }
510 | }
511 |
512 | ///
513 | /// エラーファイルを書き出します。
514 | ///
515 | private void WriteErrorInfoFile()
516 | {
517 | if (!string.IsNullOrEmpty(processingFilePath))
518 | {
519 | var body =
520 | Path.GetFullPath(processingFilePath) + Environment.NewLine +
521 | processingLineNumber + Environment.NewLine +
522 | processingLine + Environment.NewLine;
523 | File.WriteAllText(errorFilePath, body);
524 |
525 | logger.Error($"{processingFilePath}#{processingLineNumber}");
526 | logger.Error($"{processingLine}");
527 | }
528 | }
529 | }
530 | }
531 |
--------------------------------------------------------------------------------
/VRChatActivityLogger/VRChatActivityLogger/VRChatActivityLogger.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | netcoreapp3.1
6 | true
7 |
8 |
9 | 1.4.0
10 |
11 |
12 |
13 | none
14 | false
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | add-taskschedular.bat
32 | PreserveNewest
33 | Always
34 | true
35 |
36 |
37 | delete-taskschedular.bat
38 | PreserveNewest
39 | Always
40 | true
41 |
42 |
43 | task.xml
44 | PreserveNewest
45 | Always
46 | true
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Aa][Rr][Mm]/
27 | [Aa][Rr][Mm]64/
28 | bld/
29 | [Bb]in/
30 | [Oo]bj/
31 | [Ll]og/
32 | [Ll]ogs/
33 |
34 | # Visual Studio 2015/2017 cache/options directory
35 | .vs/
36 | # Uncomment if you have tasks that create the project's static files in wwwroot
37 | #wwwroot/
38 |
39 | # Visual Studio 2017 auto generated files
40 | Generated\ Files/
41 |
42 | # MSTest test Results
43 | [Tt]est[Rr]esult*/
44 | [Bb]uild[Ll]og.*
45 |
46 | # NUnit
47 | *.VisualState.xml
48 | TestResult.xml
49 | nunit-*.xml
50 |
51 | # Build Results of an ATL Project
52 | [Dd]ebugPS/
53 | [Rr]eleasePS/
54 | dlldata.c
55 |
56 | # Benchmark Results
57 | BenchmarkDotNet.Artifacts/
58 |
59 | # .NET Core
60 | project.lock.json
61 | project.fragment.lock.json
62 | artifacts/
63 |
64 | # StyleCop
65 | StyleCopReport.xml
66 |
67 | # Files built by Visual Studio
68 | *_i.c
69 | *_p.c
70 | *_h.h
71 | *.ilk
72 | *.meta
73 | *.obj
74 | *.iobj
75 | *.pch
76 | *.pdb
77 | *.ipdb
78 | *.pgc
79 | *.pgd
80 | *.rsp
81 | *.sbr
82 | *.tlb
83 | *.tli
84 | *.tlh
85 | *.tmp
86 | *.tmp_proj
87 | *_wpftmp.csproj
88 | *.log
89 | *.vspscc
90 | *.vssscc
91 | .builds
92 | *.pidb
93 | *.svclog
94 | *.scc
95 |
96 | # Chutzpah Test files
97 | _Chutzpah*
98 |
99 | # Visual C++ cache files
100 | ipch/
101 | *.aps
102 | *.ncb
103 | *.opendb
104 | *.opensdf
105 | *.sdf
106 | *.cachefile
107 | *.VC.db
108 | *.VC.VC.opendb
109 |
110 | # Visual Studio profiler
111 | *.psess
112 | *.vsp
113 | *.vspx
114 | *.sap
115 |
116 | # Visual Studio Trace Files
117 | *.e2e
118 |
119 | # TFS 2012 Local Workspace
120 | $tf/
121 |
122 | # Guidance Automation Toolkit
123 | *.gpState
124 |
125 | # ReSharper is a .NET coding add-in
126 | _ReSharper*/
127 | *.[Rr]e[Ss]harper
128 | *.DotSettings.user
129 |
130 | # TeamCity is a build add-in
131 | _TeamCity*
132 |
133 | # DotCover is a Code Coverage Tool
134 | *.dotCover
135 |
136 | # AxoCover is a Code Coverage Tool
137 | .axoCover/*
138 | !.axoCover/settings.json
139 |
140 | # Visual Studio code coverage results
141 | *.coverage
142 | *.coveragexml
143 |
144 | # NCrunch
145 | _NCrunch_*
146 | .*crunch*.local.xml
147 | nCrunchTemp_*
148 |
149 | # MightyMoose
150 | *.mm.*
151 | AutoTest.Net/
152 |
153 | # Web workbench (sass)
154 | .sass-cache/
155 |
156 | # Installshield output folder
157 | [Ee]xpress/
158 |
159 | # DocProject is a documentation generator add-in
160 | DocProject/buildhelp/
161 | DocProject/Help/*.HxT
162 | DocProject/Help/*.HxC
163 | DocProject/Help/*.hhc
164 | DocProject/Help/*.hhk
165 | DocProject/Help/*.hhp
166 | DocProject/Help/Html2
167 | DocProject/Help/html
168 |
169 | # Click-Once directory
170 | publish/
171 |
172 | # Publish Web Output
173 | *.[Pp]ublish.xml
174 | *.azurePubxml
175 | # Note: Comment the next line if you want to checkin your web deploy settings,
176 | # but database connection strings (with potential passwords) will be unencrypted
177 | *.pubxml
178 | *.publishproj
179 |
180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
181 | # checkin your Azure Web App publish settings, but sensitive information contained
182 | # in these scripts will be unencrypted
183 | PublishScripts/
184 |
185 | # NuGet Packages
186 | *.nupkg
187 | # NuGet Symbol Packages
188 | *.snupkg
189 | # The packages folder can be ignored because of Package Restore
190 | **/[Pp]ackages/*
191 | # except build/, which is used as an MSBuild target.
192 | !**/[Pp]ackages/build/
193 | # Uncomment if necessary however generally it will be regenerated when needed
194 | #!**/[Pp]ackages/repositories.config
195 | # NuGet v3's project.json files produces more ignorable files
196 | *.nuget.props
197 | *.nuget.targets
198 |
199 | # Microsoft Azure Build Output
200 | csx/
201 | *.build.csdef
202 |
203 | # Microsoft Azure Emulator
204 | ecf/
205 | rcf/
206 |
207 | # Windows Store app package directories and files
208 | AppPackages/
209 | BundleArtifacts/
210 | Package.StoreAssociation.xml
211 | _pkginfo.txt
212 | *.appx
213 | *.appxbundle
214 | *.appxupload
215 |
216 | # Visual Studio cache files
217 | # files ending in .cache can be ignored
218 | *.[Cc]ache
219 | # but keep track of directories ending in .cache
220 | !?*.[Cc]ache/
221 |
222 | # Others
223 | ClientBin/
224 | ~$*
225 | *~
226 | *.dbmdl
227 | *.dbproj.schemaview
228 | *.jfm
229 | *.pfx
230 | *.publishsettings
231 | orleans.codegen.cs
232 |
233 | # Including strong name files can present a security risk
234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
235 | #*.snk
236 |
237 | # Since there are multiple workflows, uncomment next line to ignore bower_components
238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
239 | #bower_components/
240 |
241 | # RIA/Silverlight projects
242 | Generated_Code/
243 |
244 | # Backup & report files from converting an old project file
245 | # to a newer Visual Studio version. Backup files are not needed,
246 | # because we have git ;-)
247 | _UpgradeReport_Files/
248 | Backup*/
249 | UpgradeLog*.XML
250 | UpgradeLog*.htm
251 | ServiceFabricBackup/
252 | *.rptproj.bak
253 |
254 | # SQL Server files
255 | *.mdf
256 | *.ldf
257 | *.ndf
258 |
259 | # Business Intelligence projects
260 | *.rdl.data
261 | *.bim.layout
262 | *.bim_*.settings
263 | *.rptproj.rsuser
264 | *- [Bb]ackup.rdl
265 | *- [Bb]ackup ([0-9]).rdl
266 | *- [Bb]ackup ([0-9][0-9]).rdl
267 |
268 | # Microsoft Fakes
269 | FakesAssemblies/
270 |
271 | # GhostDoc plugin setting file
272 | *.GhostDoc.xml
273 |
274 | # Node.js Tools for Visual Studio
275 | .ntvs_analysis.dat
276 | node_modules/
277 |
278 | # Visual Studio 6 build log
279 | *.plg
280 |
281 | # Visual Studio 6 workspace options file
282 | *.opt
283 |
284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
285 | *.vbw
286 |
287 | # Visual Studio LightSwitch build output
288 | **/*.HTMLClient/GeneratedArtifacts
289 | **/*.DesktopClient/GeneratedArtifacts
290 | **/*.DesktopClient/ModelManifest.xml
291 | **/*.Server/GeneratedArtifacts
292 | **/*.Server/ModelManifest.xml
293 | _Pvt_Extensions
294 |
295 | # Paket dependency manager
296 | .paket/paket.exe
297 | paket-files/
298 |
299 | # FAKE - F# Make
300 | .fake/
301 |
302 | # CodeRush personal settings
303 | .cr/personal
304 |
305 | # Python Tools for Visual Studio (PTVS)
306 | __pycache__/
307 | *.pyc
308 |
309 | # Cake - Uncomment if you are using it
310 | # tools/**
311 | # !tools/packages.config
312 |
313 | # Tabs Studio
314 | *.tss
315 |
316 | # Telerik's JustMock configuration file
317 | *.jmconfig
318 |
319 | # BizTalk build output
320 | *.btp.cs
321 | *.btm.cs
322 | *.odx.cs
323 | *.xsd.cs
324 |
325 | # OpenCover UI analysis results
326 | OpenCover/
327 |
328 | # Azure Stream Analytics local run output
329 | ASALocalRun/
330 |
331 | # MSBuild Binary and Structured Log
332 | *.binlog
333 |
334 | # NVidia Nsight GPU debugger configuration file
335 | *.nvuser
336 |
337 | # MFractors (Xamarin productivity tool) working folder
338 | .mfractor/
339 |
340 | # Local History for Visual Studio
341 | .localhistory/
342 |
343 | # BeatPulse healthcheck temp database
344 | healthchecksdb
345 |
346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
347 | MigrationBackup/
348 |
349 | # Ionide (cross platform F# VS Code tools) working folder
350 | .ionide/
351 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/VRChatActivityToolsShared.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.30804.86
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRChatActivityToolsShared", "VRChatActivityToolsShared\VRChatActivityToolsShared.csproj", "{9760E49C-ECCF-45F0-98A1-158FE4A1DA8F}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {9760E49C-ECCF-45F0-98A1-158FE4A1DA8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {9760E49C-ECCF-45F0-98A1-158FE4A1DA8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {9760E49C-ECCF-45F0-98A1-158FE4A1DA8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {9760E49C-ECCF-45F0-98A1-158FE4A1DA8F}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {9FCDB403-DD6E-44B0-8BBB-13A4C669A925}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityLog.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Text;
5 |
6 | namespace VRChatActivityToolsShared.Database
7 | {
8 | public class ActivityLog : IEquatable
9 | {
10 | /// アクティビティで一意のID
11 | public int? ID { get; set; }
12 | /// アクティビティの種類
13 | public ActivityType ActivityType { get; set; }
14 | /// そのアクティビティを行った時刻
15 | public DateTime? Timestamp { get; set; }
16 | /// 関連する通知ID
17 | public string NotificationID { get; set; }
18 | /// 関連するユーザID
19 | public string UserID { get; set; }
20 | /// 関連するユーザ名
21 | public string UserName { get; set; }
22 | /// 関連するワールドID
23 | public string WorldID { get; set; }
24 | /// 関連するワールド名
25 | public string WorldName { get; set; }
26 | /// 関連するメッセージ
27 | public string Message { get; set; }
28 | /// 関連するURL
29 | public string Url { get; set; }
30 |
31 | public bool Equals(ActivityLog other)
32 | {
33 | return other is ActivityLog log &&
34 | EqualityComparer.Default.Equals(ID, log.ID) &&
35 | ActivityType == log.ActivityType &&
36 | EqualityComparer.Default.Equals(Timestamp, log.Timestamp) &&
37 | NotificationID == log.NotificationID &&
38 | UserID == log.UserID &&
39 | UserName == log.UserName &&
40 | WorldID == log.WorldID &&
41 | WorldName == log.WorldName &&
42 | Message == log.Message &&
43 | Url == log.Url;
44 | }
45 |
46 | public override bool Equals(object obj)
47 | {
48 | return Equals(obj as ActivityLog);
49 | }
50 |
51 | public override int GetHashCode()
52 | {
53 | HashCode hash = new HashCode();
54 |
55 | hash.Add(ID);
56 | hash.Add(ActivityType);
57 | hash.Add(Timestamp);
58 | hash.Add(NotificationID);
59 | hash.Add(UserID);
60 | hash.Add(UserName);
61 | hash.Add(WorldID);
62 | hash.Add(WorldName);
63 | hash.Add(Message);
64 | hash.Add(Url);
65 |
66 | return hash.ToHashCode();
67 | }
68 |
69 | public static bool operator ==(ActivityLog l, ActivityLog r)
70 | {
71 | if (l is null && r is null)
72 | {
73 | return true;
74 | }
75 |
76 | return Equals(l, r);
77 | }
78 |
79 | public static bool operator !=(ActivityLog l, ActivityLog r) => !(l == r);
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/ActivityType.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace VRChatActivityToolsShared.Database
6 | {
7 | ///
8 | /// アクティビティの種類
9 | ///
10 | public enum ActivityType : int
11 | {
12 | /// ワールドにjoinした
13 | JoinedRoom = 0,
14 |
15 | /// プレイヤーと会った
16 | MetPlayer = 1,
17 |
18 | /// inviteを送った
19 | SendInvite = 2,
20 |
21 | /// inviteを受け取った
22 | ReceivedInvite = 3,
23 |
24 | /// reqInvを送った
25 | SendRequestInvite = 4,
26 |
27 | /// reqInvを受け取った
28 | ReceivedRequestInvite = 5,
29 |
30 | /// フレンドリクエストを送った
31 | SendFriendRequest = 6,
32 |
33 | /// フレンドリクエストを受け取った
34 | ReceivedFriendRequest = 7,
35 |
36 | /// フレンドリクエストを承認した
37 | AcceptFriendRequest = 8,
38 |
39 | /// inviteへの返信を送った
40 | SendInviteResponse = 9,
41 |
42 | /// inviteへの返信を受け取った
43 | ReceivedInviteResponse = 10,
44 |
45 | /// reqInvへの返信を送った
46 | SendRequestInviteResponse = 11,
47 |
48 | /// reqInvへの返信を受け取った
49 | ReceivedRequestInviteResponse = 12,
50 |
51 | /// 動画を再生した
52 | PlayedVideo = 13,
53 |
54 | /// inviteを承認した
55 | AcceptInvite = 14,
56 |
57 | /// reqInvを承認した
58 | AcceptRequestInvite = 15,
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseContext.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using Microsoft.Data.Sqlite;
5 | using Microsoft.EntityFrameworkCore;
6 |
7 | namespace VRChatActivityToolsShared.Database
8 | {
9 | ///
10 | /// データベースコンテキスト
11 | ///
12 | public class DatabaseContext : DbContext
13 | {
14 | ///
15 | /// データベースのバージョン
16 | ///
17 | public static int Version { get; } = 3;
18 |
19 | ///
20 | /// データベースのファイルパス
21 | ///
22 | public static string DBFilePath { get; set; } = @"VRChatActivityLog.db";
23 |
24 | ///
25 | /// ActivityLogsテーブル
26 | ///
27 | public DbSet ActivityLogs { get; set; }
28 |
29 | ///
30 | /// Informationテーブル
31 | ///
32 | public DbSet Information { get; set; }
33 |
34 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
35 | {
36 | var connectionString = new SqliteConnectionStringBuilder { DataSource = DBFilePath }.ToString();
37 | optionsBuilder.UseSqlite(new SqliteConnection(connectionString));
38 | }
39 |
40 | protected override void OnModelCreating(ModelBuilder modelBuilder)
41 | {
42 | modelBuilder.Entity().HasKey(a => new { a.ID });
43 | modelBuilder.Entity().HasKey(a => new { a.ID });
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/DatabaseMigration.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Data.Sqlite;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace VRChatActivityToolsShared.Database
7 | {
8 | ///
9 | /// DBのマイグレーションを行うクラスです。
10 | ///
11 | public static class DatabaseMigration
12 | {
13 | ///
14 | /// データベースを新規作成します。
15 | ///
16 | ///
17 | public static void CreateDatabase()
18 | {
19 | using var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}");
20 |
21 | db.Open();
22 |
23 | #region var sql = "...";
24 | var sql =
25 | @"
26 | CREATE TABLE ""Information"" (
27 | ""ID"" INTEGER NOT NULL CONSTRAINT ""PK_Information"" PRIMARY KEY AUTOINCREMENT,
28 | ""Version"" INTEGER NOT NULL
29 | );
30 |
31 | INSERT INTO ""Information"" (
32 | ""Version""
33 | )
34 | VALUES (
35 | @Version
36 | );
37 |
38 | CREATE TABLE ""ActivityLogs"" (
39 | ""ID"" INTEGER NOT NULL CONSTRAINT ""PK_ActivityLogs"" PRIMARY KEY AUTOINCREMENT,
40 | ""ActivityType"" INTEGER NOT NULL,
41 | ""Timestamp"" TEXT NULL,
42 | ""NotificationID"" TEXT NULL,
43 | ""UserID"" TEXT NULL,
44 | ""UserName"" TEXT NULL,
45 | ""WorldID"" TEXT NULL,
46 | ""WorldName"" TEXT NULL,
47 | ""Message"" TEXT NULL,
48 | ""Url"" TEXT NULL
49 | );
50 | ";
51 | #endregion
52 |
53 | using var command = new SqliteCommand(sql, db);
54 |
55 | command.Parameters.Add(new SqliteParameter("@Version", DatabaseContext.Version));
56 |
57 | command.ExecuteNonQuery();
58 | }
59 |
60 | ///
61 | /// 現在のデータベースがどのバージョンで作成されたかを取得します。
62 | ///
63 | ///
64 | public static int GetCurrentVersion()
65 | {
66 | using (var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}"))
67 | {
68 | db.Open();
69 |
70 | #region var existsTableSql = "...";
71 | var existsTableSql =
72 | @"
73 | SELECT
74 | COUNT(*)
75 | FROM
76 | sqlite_master
77 | WHERE
78 | TYPE = 'table' AND
79 | name = 'Information';
80 | ";
81 | #endregion
82 |
83 | using (var command = new SqliteCommand(existsTableSql, db))
84 | {
85 | using var reader = command.ExecuteReader();
86 | reader.Read();
87 |
88 | var hasTable = 0 < reader.GetInt32(0);
89 |
90 | if (!hasTable)
91 | {
92 | return 1;
93 | }
94 | }
95 |
96 | #region var versionSql = "...";
97 | var versionSql =
98 | @"
99 | SELECT
100 | Version
101 | FROM
102 | Information;
103 | ";
104 | #endregion
105 |
106 | using (var command = new SqliteCommand(versionSql, db))
107 | {
108 | using var reader = command.ExecuteReader();
109 | reader.Read();
110 |
111 | var version = reader.GetInt32(0);
112 |
113 | return version;
114 | }
115 | }
116 | }
117 |
118 | ///
119 | /// データベースを更新します。
120 | ///
121 | public static void UpgradeDatabase()
122 | {
123 | var currentVersion = GetCurrentVersion();
124 |
125 | if (currentVersion < 2)
126 | {
127 | UpgradeDatabaseVersion2();
128 | }
129 |
130 | if (currentVersion < 3)
131 | {
132 | UpgradeDatabaseVersion3();
133 | }
134 | }
135 |
136 | ///
137 | /// データベースをver2へ更新します。
138 | ///
139 | private static void UpgradeDatabaseVersion2()
140 | {
141 | using var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}");
142 |
143 | db.Open();
144 |
145 | #region var sql = "...";
146 | var sql =
147 | @"
148 | CREATE TABLE ""Information"" (
149 | ""ID"" INTEGER NOT NULL CONSTRAINT ""PK_Information"" PRIMARY KEY AUTOINCREMENT,
150 | ""Version"" INTEGER NOT NULL
151 | );
152 |
153 | INSERT INTO ""Information"" (
154 | ""Version""
155 | )
156 | VALUES (
157 | @Version
158 | );
159 |
160 | ALTER TABLE
161 | ""ActivityLogs""
162 | ADD COLUMN
163 | ""Message"" TEXT NULL;
164 |
165 | ALTER TABLE
166 | ""ActivityLogs""
167 | ADD COLUMN
168 | ""Url"" TEXT NULL;
169 | ";
170 | #endregion
171 |
172 | using var command = new SqliteCommand(sql, db);
173 |
174 | command.Parameters.Add(new SqliteParameter("@Version", 2));
175 |
176 | command.ExecuteNonQuery();
177 | }
178 |
179 | ///
180 | /// データベースをver3へ更新します。
181 | ///
182 | private static void UpgradeDatabaseVersion3()
183 | {
184 | using var db = new SqliteConnection($"Filename={DatabaseContext.DBFilePath}");
185 |
186 | db.Open();
187 |
188 | #region var sql = "...";
189 | var sql =
190 | @"
191 | UPDATE
192 | ""Information""
193 | SET
194 | ""Version"" = @Version;
195 | ";
196 | #endregion
197 |
198 | using var command = new SqliteCommand(sql, db);
199 |
200 | command.Parameters.Add(new SqliteParameter("@Version", 3));
201 |
202 | command.ExecuteNonQuery();
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/VRChatActivityToolsShared/Database/Information.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace VRChatActivityToolsShared.Database
6 | {
7 | public class Information
8 | {
9 | /// ID
10 | public int ID { get; set; }
11 |
12 | /// DBバージョン
13 | public int Version { get; set; }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/VRChatActivityToolsShared/VRChatActivityToolsShared/VRChatActivityToolsShared.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netcoreapp3.1
5 |
6 |
7 |
8 | none
9 | false
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------