mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2025-04-12 23:07:13 +03:00
Build a DHT app for desktop
This commit is contained in:
parent
36286dc8cf
commit
ab7b3532fc
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@ -1,5 +1,9 @@
|
|||||||
/inspectionProfiles/
|
/codeStyles
|
||||||
|
/dataSources.local.xml
|
||||||
|
/deployment.xml
|
||||||
/httpRequests/
|
/httpRequests/
|
||||||
/shelf/
|
/inspectionProfiles
|
||||||
/misc.xml
|
/misc.xml
|
||||||
|
/shelf/
|
||||||
|
/webServers.xml
|
||||||
/workspace.xml
|
/workspace.xml
|
||||||
|
4
.idea/php.xml
generated
Normal file
4
.idea/php.xml
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
|
||||||
|
</project>
|
7
app/.gitignore
vendored
Normal file
7
app/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.vscode/
|
||||||
|
.vs/
|
||||||
|
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
*.user
|
12
app/.idea/.idea.DiscordHistoryTracker/.idea/.gitignore
generated
vendored
Normal file
12
app/.idea/.idea.DiscordHistoryTracker/.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/.idea.DiscordHistoryTracker.iml
|
||||||
|
/contentModel.xml
|
||||||
|
/dataSources
|
||||||
|
/dataSources.local.xml
|
||||||
|
/dataSources.xml
|
||||||
|
/dictionaries
|
||||||
|
/httpRequests/
|
||||||
|
/misc.xml
|
||||||
|
/modules.xml
|
||||||
|
/projectSettingsUpdater.xml
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
1
app/.idea/.idea.DiscordHistoryTracker/.idea/.name
generated
Normal file
1
app/.idea/.idea.DiscordHistoryTracker/.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
|||||||
|
DiscordHistoryTracker
|
19
app/.idea/.idea.DiscordHistoryTracker/.idea/avalonia.xml
generated
Normal file
19
app/.idea/.idea.DiscordHistoryTracker/.idea/avalonia.xml
generated
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="AvaloniaProject">
|
||||||
|
<option name="projectPerEditor">
|
||||||
|
<map>
|
||||||
|
<entry key="Desktop/App.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Dialogs/MessageDialog.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/AboutWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/Controls/StatusBar.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/MainContentScreen.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/MainWindow.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/Pages/DatabasePage.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/Pages/TrackingPage.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/Pages/ViewerPage.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
<entry key="Desktop/Main/WelcomeScreen.axaml" value="Desktop/Desktop.csproj" />
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
445
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/Project.xml
generated
Normal file
445
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/Project.xml
generated
Normal file
@ -0,0 +1,445 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<code_scheme name="Project" version="173">
|
||||||
|
<option name="AUTODETECT_INDENTS" value="false" />
|
||||||
|
<option name="OTHER_INDENT_OPTIONS">
|
||||||
|
<value>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
|
<option name="RIGHT_MARGIN" value="999" />
|
||||||
|
<option name="FORMATTER_TAGS_ENABLED" value="true" />
|
||||||
|
<CssCodeStyleSettings>
|
||||||
|
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||||
|
</CssCodeStyleSettings>
|
||||||
|
<DB2CodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</DB2CodeStyleSettings>
|
||||||
|
<DerbyCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</DerbyCodeStyleSettings>
|
||||||
|
<GoCodeStyleSettings>
|
||||||
|
<option name="MOVE_ALL_STDLIB_IMPORTS_IN_ONE_GROUP" value="true" />
|
||||||
|
<option name="GROUP_STDLIB_IMPORTS" value="true" />
|
||||||
|
<option name="WRAP_COMP_LIT" value="5" />
|
||||||
|
<option name="WRAP_FUNC_PARAMS" value="5" />
|
||||||
|
<option name="WRAP_FUNC_RESULT" value="5" />
|
||||||
|
</GoCodeStyleSettings>
|
||||||
|
<H2CodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</H2CodeStyleSettings>
|
||||||
|
<HSQLCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</HSQLCodeStyleSettings>
|
||||||
|
<HTMLCodeStyleSettings>
|
||||||
|
<option name="HTML_ALIGN_TEXT" value="true" />
|
||||||
|
<option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
<option name="HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE" value="" />
|
||||||
|
<option name="HTML_DO_NOT_INDENT_CHILDREN_OF" value="" />
|
||||||
|
</HTMLCodeStyleSettings>
|
||||||
|
<JSCodeStyleSettings version="0">
|
||||||
|
<option name="FORCE_SEMICOLON_STYLE" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACKETS" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="FORCE_QUOTE_STYlE" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
|
||||||
|
</JSCodeStyleSettings>
|
||||||
|
<JSON>
|
||||||
|
<option name="OBJECT_WRAPPING" value="5" />
|
||||||
|
<option name="ARRAY_WRAPPING" value="5" />
|
||||||
|
</JSON>
|
||||||
|
<JavaCodeStyleSettings>
|
||||||
|
<option name="INSERT_INNER_CLASS_IMPORTS" value="true" />
|
||||||
|
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||||
|
<option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="99" />
|
||||||
|
<option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="IMPORT_LAYOUT_TABLE">
|
||||||
|
<value>
|
||||||
|
<package name="" withSubpackages="true" static="false" />
|
||||||
|
<package name="javax" withSubpackages="true" static="false" />
|
||||||
|
<package name="java" withSubpackages="true" static="false" />
|
||||||
|
<package name="" withSubpackages="true" static="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</JavaCodeStyleSettings>
|
||||||
|
<JetCodeStyleSettings>
|
||||||
|
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||||
|
<value>
|
||||||
|
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="ALIGN_IN_COLUMNS_CASE_BRANCH" value="true" />
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
|
||||||
|
<option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
|
||||||
|
<option name="IMPORT_NESTED_CLASSES" value="true" />
|
||||||
|
<option name="WRAP_ELVIS_EXPRESSIONS" value="0" />
|
||||||
|
<option name="ALLOW_TRAILING_COMMA" value="true" />
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
</JetCodeStyleSettings>
|
||||||
|
<LessCodeStyleSettings>
|
||||||
|
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||||
|
</LessCodeStyleSettings>
|
||||||
|
<MSSQLCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</MSSQLCodeStyleSettings>
|
||||||
|
<MySQLCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</MySQLCodeStyleSettings>
|
||||||
|
<Objective-C>
|
||||||
|
<option name="INDENT_DIRECTIVE_AS_CODE" value="true" />
|
||||||
|
<option name="KEEP_STRUCTURES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_CASE_EXPRESSIONS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_INIT_LIST" value="true" />
|
||||||
|
<option name="SPACE_AFTER_DICTIONARY_LITERAL_COLON" value="false" />
|
||||||
|
</Objective-C>
|
||||||
|
<OracleCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</OracleCodeStyleSettings>
|
||||||
|
<PHPCodeStyleSettings>
|
||||||
|
<option name="ALIGN_KEY_VALUE_PAIRS" value="true" />
|
||||||
|
<option name="CONCAT_SPACES" value="false" />
|
||||||
|
<option name="COMMA_AFTER_LAST_ARRAY_ELEMENT" value="true" />
|
||||||
|
<option name="PHPDOC_BLANK_LINE_BEFORE_TAGS" value="true" />
|
||||||
|
<option name="LOWER_CASE_BOOLEAN_CONST" value="true" />
|
||||||
|
<option name="LOWER_CASE_NULL_CONST" value="true" />
|
||||||
|
<option name="ELSE_IF_STYLE" value="COMBINE" />
|
||||||
|
<option name="VARIABLE_NAMING_STYLE" value="SNAKE_CASE" />
|
||||||
|
<option name="KEEP_BLANK_LINES_AFTER_LBRACE" value="0" />
|
||||||
|
<option name="SPACE_BEFORE_CLOSURE_LEFT_PARENTHESIS" value="false" />
|
||||||
|
<option name="FORCE_SHORT_DECLARATION_ARRAY_STYLE" value="true" />
|
||||||
|
<option name="NEW_LINE_AFTER_PHP_OPENING_TAG" value="true" />
|
||||||
|
<option name="SPACE_AROUND_ASSIGNMENT_IN_DECLARE" value="true" />
|
||||||
|
</PHPCodeStyleSettings>
|
||||||
|
<PostgresCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</PostgresCodeStyleSettings>
|
||||||
|
<Properties>
|
||||||
|
<option name="KEEP_BLANK_LINES" value="true" />
|
||||||
|
</Properties>
|
||||||
|
<Python>
|
||||||
|
<option name="SPACE_AROUND_EQ_IN_NAMED_PARAMETER" value="true" />
|
||||||
|
<option name="SPACE_AROUND_EQ_IN_KEYWORD_ARGUMENT" value="true" />
|
||||||
|
<option name="NEW_LINE_AFTER_COLON" value="true" />
|
||||||
|
<option name="DICT_WRAPPING" value="5" />
|
||||||
|
<option name="DICT_NEW_LINE_AFTER_LEFT_BRACE" value="true" />
|
||||||
|
<option name="DICT_NEW_LINE_BEFORE_RIGHT_BRACE" value="true" />
|
||||||
|
</Python>
|
||||||
|
<RsCodeStyleSettings>
|
||||||
|
<option name="ALIGN_RET_TYPE" value="false" />
|
||||||
|
<option name="ALIGN_TYPE_PARAMS" value="true" />
|
||||||
|
<option name="ALLOW_ONE_LINE_MATCH" value="true" />
|
||||||
|
<option name="SPACE_AROUND_ASSOC_TYPE_BINDING" value="true" />
|
||||||
|
</RsCodeStyleSettings>
|
||||||
|
<Ruby>
|
||||||
|
<option name="INDENT_PRIVATE_METHODS" value="true" />
|
||||||
|
<option name="INDENT_PROTECTED_METHODS" value="true" />
|
||||||
|
<option name="INDENT_PUBLIC_METHODS" value="true" />
|
||||||
|
<option name="INDENT_WHEN_CASES" value="true" />
|
||||||
|
<option name="CHAIN_CALLS_ALIGNMENT" value="2" />
|
||||||
|
</Ruby>
|
||||||
|
<SQLiteCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</SQLiteCodeStyleSettings>
|
||||||
|
<ScssCodeStyleSettings>
|
||||||
|
<option name="HEX_COLOR_LOWER_CASE" value="true" />
|
||||||
|
</ScssCodeStyleSettings>
|
||||||
|
<SqlCodeStyleSettings version="5">
|
||||||
|
<option name="KEYWORD_CASE" value="2" />
|
||||||
|
<option name="TYPE_CASE" value="2" />
|
||||||
|
<option name="CUSTOM_TYPE_CASE" value="2" />
|
||||||
|
<option name="SUBQUERY_CONTENT" value="1" />
|
||||||
|
<option name="SUBQUERY_CLOSING" value="1" />
|
||||||
|
<option name="INSERT_TABLE_EL_LINE" value="0" />
|
||||||
|
<option name="INSERT_EL_WRAP" value="2" />
|
||||||
|
<option name="SET_EL_WRAP" value="2" />
|
||||||
|
<option name="SET_ALIGN_EQUAL_SIGN" value="false" />
|
||||||
|
<option name="FROM_EL_WRAP" value="2" />
|
||||||
|
<option name="FROM_ALIGN_JOIN_TABLES" value="true" />
|
||||||
|
<option name="FROM_INDENT_JOIN" value="false" />
|
||||||
|
<option name="FROM_ONLY_JOIN_INDENT" value="2" />
|
||||||
|
<option name="WHERE_EL_WRAP" value="2" />
|
||||||
|
<option name="TABLE_OPENING" value="1" />
|
||||||
|
<option name="TABLE_CONTENT" value="2" />
|
||||||
|
<option name="TABLE_CLOSING" value="3" />
|
||||||
|
<option name="TABLE_DEFAULTS_ALIGN" value="false" />
|
||||||
|
<option name="TABLE_NULLABILITIES_ALIGN" value="false" />
|
||||||
|
<option name="CONSTRAINT_WRAP_1" value="false" />
|
||||||
|
<option name="CONSTRAINT_WRAP_3" value="true" />
|
||||||
|
<option name="CONSTRAINT_WRAP_4" value="true" />
|
||||||
|
<option name="VIEW_INDENT_QUERY" value="true" />
|
||||||
|
<option name="EXPR_CASE_WHEN_WRAP" value="false" />
|
||||||
|
</SqlCodeStyleSettings>
|
||||||
|
<SybaseCodeStyleSettings version="5">
|
||||||
|
<option name="USE_GENERIC_STYLE" value="true" />
|
||||||
|
</SybaseCodeStyleSettings>
|
||||||
|
<TypeScriptCodeStyleSettings version="0">
|
||||||
|
<option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" />
|
||||||
|
<option name="ENFORCE_TRAILING_COMMA" value="WhenMultiline" />
|
||||||
|
<option name="SPACES_WITHIN_IMPORTS" value="true" />
|
||||||
|
<option name="USE_CHAINED_CALLS_GROUP_INDENTS" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_ASYNC_ARROW_LPAREN" value="false" />
|
||||||
|
</TypeScriptCodeStyleSettings>
|
||||||
|
<XML>
|
||||||
|
<option name="XML_SPACE_INSIDE_EMPTY_TAG" value="true" />
|
||||||
|
</XML>
|
||||||
|
<codeStyleSettings language="CMake">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="CSS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Groovy">
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="HTML">
|
||||||
|
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JAVA">
|
||||||
|
<option name="RIGHT_MARGIN" value="999" />
|
||||||
|
<option name="BLANK_LINES_AFTER_PACKAGE" value="0" />
|
||||||
|
<option name="BLANK_LINES_BEFORE_IMPORTS" value="0" />
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||||
|
<option name="SPACE_AFTER_TYPE_CAST" value="false" />
|
||||||
|
<option name="SPACE_BEFORE_SYNCHRONIZED_PARENTHESES" value="false" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_LAMBDAS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JSON">
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="SPACE_WITHIN_BRACKETS" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_BRACES" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="JavaScript">
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="LESS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Lua">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="ObjectiveC">
|
||||||
|
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="SPACE_WITHIN_ARRAY_INITIALIZER_BRACES" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="PHP">
|
||||||
|
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
|
||||||
|
<option name="KEEP_CONTROL_STATEMENT_IN_ONE_LINE" value="false" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
|
||||||
|
<option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
|
||||||
|
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
|
||||||
|
<option name="CLASS_BRACE_STYLE" value="1" />
|
||||||
|
<option name="METHOD_BRACE_STYLE" value="1" />
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="SPECIAL_ELSE_IF_TREATMENT" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_PARAMETERS_IN_CALLS" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||||
|
<option name="SPACE_BEFORE_ARRAY_INITIALIZER_LBRACE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_METHODS_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="KEEP_SIMPLE_CLASSES_IN_ONE_LINE" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Puppet">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Python">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="RHTML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Rust">
|
||||||
|
<option name="RIGHT_MARGIN" value="140" />
|
||||||
|
<option name="ALIGN_MULTILINE_CHAINED_METHODS" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="SASS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="SCSS">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="SQL">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="Shell Script">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="4" />
|
||||||
|
<option name="TAB_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TOML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="TypeScript">
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_TERNARY_OPERATION" value="true" />
|
||||||
|
<option name="ALIGN_MULTILINE_ARRAY_INITIALIZER_EXPRESSION" value="true" />
|
||||||
|
<option name="IF_BRACE_FORCE" value="3" />
|
||||||
|
<option name="DOWHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="WHILE_BRACE_FORCE" value="3" />
|
||||||
|
<option name="FOR_BRACE_FORCE" value="3" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="XML">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="INDENT_SIZE" value="2" />
|
||||||
|
<option name="TAB_SIZE" value="2" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="go">
|
||||||
|
<option name="CALL_PARAMETERS_WRAP" value="5" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="kotlin">
|
||||||
|
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||||
|
<option name="LINE_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<option name="BLOCK_COMMENT_AT_FIRST_COLUMN" value="false" />
|
||||||
|
<option name="LINE_COMMENT_ADD_SPACE" value="true" />
|
||||||
|
<option name="ELSE_ON_NEW_LINE" value="true" />
|
||||||
|
<option name="METHOD_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="CLASS_ANNOTATION_WRAP" value="0" />
|
||||||
|
<option name="FIELD_ANNOTATION_WRAP" value="0" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
|
<option name="USE_TAB_CHARACTER" value="true" />
|
||||||
|
<option name="SMART_TABS" value="true" />
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="liquid">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="ruby">
|
||||||
|
<option name="SPACE_WITHIN_BRACES" value="true" />
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
<codeStyleSettings language="yaml">
|
||||||
|
<indentOptions>
|
||||||
|
<option name="KEEP_INDENTS_ON_EMPTY_LINES" value="true" />
|
||||||
|
</indentOptions>
|
||||||
|
</codeStyleSettings>
|
||||||
|
</code_scheme>
|
||||||
|
</component>
|
5
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
5
app/.idea/.idea.DiscordHistoryTracker/.idea/codeStyles/codeStyleConfig.xml
generated
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<component name="ProjectCodeStyleConfiguration">
|
||||||
|
<state>
|
||||||
|
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||||
|
</state>
|
||||||
|
</component>
|
6
app/.idea/.idea.DiscordHistoryTracker/.idea/encodings.xml
generated
Normal file
6
app/.idea/.idea.DiscordHistoryTracker/.idea/encodings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Encoding" defaultCharsetForPropertiesFiles="UTF-8">
|
||||||
|
<file url="PROJECT" charset="UTF-8" />
|
||||||
|
</component>
|
||||||
|
</project>
|
10
app/.idea/.idea.DiscordHistoryTracker/.idea/indexLayout.xml
generated
Normal file
10
app/.idea/.idea.DiscordHistoryTracker/.idea/indexLayout.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="UserContentModel">
|
||||||
|
<attachedFolders>
|
||||||
|
<Path>Resources</Path>
|
||||||
|
</attachedFolders>
|
||||||
|
<explicitIncludes />
|
||||||
|
<explicitExcludes />
|
||||||
|
</component>
|
||||||
|
</project>
|
850
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project.xml
generated
Normal file
850
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project.xml
generated
Normal file
@ -0,0 +1,850 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project" />
|
||||||
|
<inspection_tool class="AbstractMethodCallInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AccessToNonThreadSafeStaticFieldFromInstance" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="nonThreadSafeClasses">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="nonThreadSafeTypes" value="" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="AccessToStaticFieldLockedOnInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AddOperatorModifier" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AmbiguousFieldAccess" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AmbiguousMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AnonymousInnerClassMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ArrayEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssertEqualsCalledOnArray" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssertsWithoutMessagesTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssignmentOrReturnOfFieldWithMutableType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssignmentToCatchBlockParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssignmentToLambdaParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssignmentToMethodParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreTransformationOfOriginalParameter" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="AssignmentToStaticFieldFromInstanceMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssignmentToSuperclassField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AssignmentUsedAsCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AutoBoxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreAddedToCollection" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="AutoCloseableResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredTypes" value="java.util.stream.Stream,java.util.stream.IntStream,java.util.stream.LongStream,java.util.stream.DoubleStream,net.minecraft.client.Minecraft,net.minecraft.client.MainWindow" />
|
||||||
|
<option name="METHOD_MATCHER_CONFIG" value="java.util.Formatter,format,java.io.Writer,append,com.google.common.base.Preconditions,checkNotNull,org.hibernate.Session,close,java.io.PrintWriter,printf,net.minecraft.client.MinecraftClient,getInstance|getWindow" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="AutoUnboxing" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AwaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="AwaitWithoutCorrespondingSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="BadOddness" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="BigDecimalEquals" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="BigDecimalLegacyMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="BooleanExpressionMayBeConditional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CallToNativeMethodWhileLocked" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CallToSimpleGetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreGetterCallsOnOtherObjects" value="false" />
|
||||||
|
<option name="onlyReportPrivateGetter" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="CallToSimpleSetterInClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreSetterCallsOnOtherObjects" value="false" />
|
||||||
|
<option name="onlyReportPrivateSetter" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="CallToStringConcatCanBeReplacedByOperator" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CallableParameterUseCaseInTypeContextInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="CascadeStringReplacementInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="USE_SHORT_ARRAYS_SYNTAX" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="CastConflictsWithInstanceof" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CastToIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ChainedEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ChannelResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="insideTryAllowed" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ClassIndependentOfModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassLoaderInstantiation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassMayBeInterface" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="reportClassesWithNonAbstractMethods" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ClassMethodNameMatchesFieldNameInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ClassNameDiffersFromFileName" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassNestingDepth" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_limit" value="1" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ClassNewInstance" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassOnlyUsedInOneModule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassOnlyUsedInOnePackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassOverridesFieldOfSuperClassInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="REPORT_PRIVATE_REDEFINITION" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ClassReferencesSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassUnconnectedToPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ClassWithOnlyPrivateConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CloneCallsConstructors" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CloneInNonCloneableClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CloneReturnsClassType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CloneableClassInSecureContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CloneableImplementsClone" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreCloneableDueToInheritance" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="CollectionContainsUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CollectionsFieldAccessReplaceableByMethodCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ComparableImplementedButEqualsNotOverridden" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ComparatorNotSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CompareToUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ComparisonOfShortAndChar" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ComposeMissingKeys" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConditionSignal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConditionalExpression" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||||
|
<option name="ignoreSimpleAssignmentsAndReturns" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ConditionalExpressionWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConfusingElse" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||||
|
<option name="reportWhenNoStatementFollow" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ConfusingMainMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConfusingOctalEscape" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConfusingPlusesOrMinusesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConstantJUnitAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConstantMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConstantTestNGAssertArgument" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConstructorCount" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreDeprecatedConstructors" value="true" />
|
||||||
|
<option name="m_limit" value="5" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ControlFlowStatementWithoutBraces" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConvertJavadoc" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConvertLambdaToReference" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ConvertOldAnnotations" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CssConvertColorToHexInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CssConvertColorToRgbInspection" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CssMissingSemicolon" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CssReplaceWithShorthandUnsafely" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CustomClassloader" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CustomSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CyclicClassDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="CyclicPackageDependency" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DateToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DebuggerStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DeclareCollectionAsInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreLocalVariables" value="false" />
|
||||||
|
<option name="ignorePrivateMethodsAndFields" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="DefaultNotLastCaseInSwitch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DisallowWritingIntoStaticPropertiesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DisconnectedForeachInstructionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="DisjointPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DivideByZeroJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DocumentWriteJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DoubleBraceInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DoubleCheckedLocking" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreOnVolatileVariables" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="DriverManagerGetConnection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DuplicateBooleanBranch" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DuplicateConditionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="DynamicallyGeneratedCodeJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ES6ConvertIndexedForToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ES6ConvertLetToConst" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ES6ConvertToForOf" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ES6ShorthandObjectProperty" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ES6TopLevelAwaitExpression" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="EmptyDirectory" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="EmptyStatementBody" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_reportEmptyBlocks" value="true" />
|
||||||
|
<option name="commentsAreContent" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="EmptySynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="EnumSwitchStatementWhichMissesCases" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreSwitchStatementsWithDefault" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="EnumerationCanBeIteration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="EqualsCalledOnEnumConstant" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="EqualsHashCodeCalledOnUrl" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="EqualsUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ErrorRethrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ExceptionNameDoesntEndWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ExceptionPackage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ExpectedExceptionNeverThrownTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ExplicitArgumentCanBeLambda" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ExtendsThread" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ExtendsThrowable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ExternalizableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="FallthruInSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="FieldAccessedSynchronizedAndUnsynchronized" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="countGettersAndSetters" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="FieldDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="FieldHidesSuperclassField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreInvisibleFields" value="false" />
|
||||||
|
<option name="ignoreStaticFields" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="FieldMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="Finalize" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreTrivialFinalizers" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="FinalizeNotProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="FixedTimeStartWithInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="FloatingPointEquality" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="FoldInitializerAndIfToElvis" enabled="false" level="INFO" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ForLoopThatDoesntUseLoopVariableJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ForgottenDebugOutputInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="configuration">
|
||||||
|
<list>
|
||||||
|
<option value="\Codeception\Util\Debug::debug" />
|
||||||
|
<option value="\Codeception\Util\Debug::pause" />
|
||||||
|
<option value="\Doctrine\Common\Util\Debug::dump" />
|
||||||
|
<option value="\Doctrine\Common\Util\Debug::export" />
|
||||||
|
<option value="\Illuminate\Support\Debug\Dumper::dump" />
|
||||||
|
<option value="\Symfony\Component\Debug\Debug::enable" />
|
||||||
|
<option value="\Symfony\Component\Debug\DebugClassLoader::enable" />
|
||||||
|
<option value="\Symfony\Component\Debug\ErrorHandler::register" />
|
||||||
|
<option value="\Symfony\Component\Debug\ExceptionHandler::register" />
|
||||||
|
<option value="\TYPO3\CMS\Core\Utility\DebugUtility::debug" />
|
||||||
|
<option value="\Zend\Debug\Debug::dump" />
|
||||||
|
<option value="\Zend\Di\Display\Console::export" />
|
||||||
|
<option value="dd" />
|
||||||
|
<option value="debug_print_backtrace" />
|
||||||
|
<option value="debug_zval_dump" />
|
||||||
|
<option value="dpm" />
|
||||||
|
<option value="dpq" />
|
||||||
|
<option value="dsm" />
|
||||||
|
<option value="dump" />
|
||||||
|
<option value="dvm" />
|
||||||
|
<option value="error_log" />
|
||||||
|
<option value="kpr" />
|
||||||
|
<option value="phpinfo" />
|
||||||
|
<option value="print_r" />
|
||||||
|
<option value="var_dump" />
|
||||||
|
<option value="var_export" />
|
||||||
|
<option value="wp_die" />
|
||||||
|
<option value="xdebug_break" />
|
||||||
|
<option value="xdebug_call_class" />
|
||||||
|
<option value="xdebug_call_file" />
|
||||||
|
<option value="xdebug_call_function" />
|
||||||
|
<option value="xdebug_call_line" />
|
||||||
|
<option value="xdebug_code_coverage_started" />
|
||||||
|
<option value="xdebug_debug_zval" />
|
||||||
|
<option value="xdebug_debug_zval_stdout" />
|
||||||
|
<option value="xdebug_dump_superglobals" />
|
||||||
|
<option value="xdebug_enable" />
|
||||||
|
<option value="xdebug_get_code_coverage" />
|
||||||
|
<option value="xdebug_get_collected_errors" />
|
||||||
|
<option value="xdebug_get_declared_vars" />
|
||||||
|
<option value="xdebug_get_function_stack" />
|
||||||
|
<option value="xdebug_get_headers" />
|
||||||
|
<option value="xdebug_get_monitored_functions" />
|
||||||
|
<option value="xdebug_get_profiler_filename" />
|
||||||
|
<option value="xdebug_get_stack_depth" />
|
||||||
|
<option value="xdebug_get_tracefile_name" />
|
||||||
|
<option value="xdebug_is_enabled" />
|
||||||
|
<option value="xdebug_memory_usage" />
|
||||||
|
<option value="xdebug_peak_memory_usage" />
|
||||||
|
<option value="xdebug_print_function_stack" />
|
||||||
|
<option value="xdebug_start_code_coverage" />
|
||||||
|
<option value="xdebug_start_error_collection" />
|
||||||
|
<option value="xdebug_start_function_monitor" />
|
||||||
|
<option value="xdebug_start_trace" />
|
||||||
|
<option value="xdebug_stop_code_coverage" />
|
||||||
|
<option value="xdebug_stop_error_collection" />
|
||||||
|
<option value="xdebug_stop_function_monitor" />
|
||||||
|
<option value="xdebug_stop_trace" />
|
||||||
|
<option value="xdebug_time_index" />
|
||||||
|
<option value="xdebug_var_dump" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
<option name="migratedIntoUserSpace" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="FunctionNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="99" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="FunctionWithInconsistentReturnsJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="HashCodeUsesNonFinalVariable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="HibernateResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="insideTryAllowed" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="HtmlFormInputWithoutLabel" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="HtmlMissingClosingTag" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="HtmlPresentationalElement" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="HtmlRequiredTitleAttribute" enabled="true" level="INFORMATION" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="HtmlUnknownTag" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myValues">
|
||||||
|
<value>
|
||||||
|
<list size="7">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="nobr" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="noembed" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="comment" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="noscript" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="embed" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="script" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="foreignobject" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
<option name="myCustomValuesEnabled" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="IOResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredTypesString" value="java.io.ByteArrayOutputStream,java.io.ByteArrayInputStream,java.io.StringBufferInputStream,java.io.CharArrayWriter,java.io.CharArrayReader,java.io.StringWriter,java.io.StringReader" />
|
||||||
|
<option name="insideTryAllowed" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="IfStatementWithIdenticalBranchesJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ImplicitDefaultCharsetUsage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="IncompleteDestructuring" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="InconsistentLanguageLevel" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="InconsistentLineSeparators" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="IncrementDecrementOperationEquivalentInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="InnerClassOnInterface" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreInnerInterfaces" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="InnerClassReferencedViaSubclass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="InnerClassVariableHidesOuterClassVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreInvisibleFields" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="InsertLiteralUnderscores" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="InstanceofCatchParameter" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="InstanceofIncompatibleInterface" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="InstanceofThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="InterfaceMayBeAnnotatedFunctional" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="IsEmptyFunctionUsageInspection" enabled="false" level="WARNING" enabled_by_default="false">
|
||||||
|
<option name="SUGGEST_TO_USE_COUNT_CHECK" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="IsNullFunctionUsageInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="IteratorNextDoesNotThrowNoSuchElementException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JDBCExecuteWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JDBCPrepareStatementWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JDBCResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="insideTryAllowed" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JNDIResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="insideTryAllowed" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JSArrowFunctionBracesCanBeRemoved" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="JSClassNamingConvention" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="99" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JSConstructorReturnsPrimitive" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JSEqualityComparisonWithCoercion.TS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="mySeverity" value="Always" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JSJoinVariableDeclarationAndAssignment" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JSMissingSwitchBranches" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JSNonASCIINames" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myAllowOnlyAscii" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JSNonStrictModeUsed" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JSOctalInteger" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="myReportNonStrictEs5" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JSUndeclaredVariable" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="myCheckGlobalDefinitions" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JSUnusedGlobalSymbols" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myReportUnusedDefinitions" value="true" />
|
||||||
|
<option name="myReportUnusedProperties" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="JUnitDatapoint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JUnitRule" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JUnitTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JavadocHtmlLint" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="JoinDeclarationAndAssignmentJava" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="LambdaCanBeMethodCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="LambdaUnfriendlyMethodOverload" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="LengthOneStringInIndexOf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="LengthOneStringsInConcatenation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ListenerMayUseAdapter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="checkForEmptyMethods" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="LoadLibraryWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="LocalCanBeFinal" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="REPORT_VARIABLES" value="true" />
|
||||||
|
<option name="REPORT_PARAMETERS" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="LocalVariableDeclarationSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="LocalVariableNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="99" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="MalformedSetUpTearDown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MapReplaceableByEnumMap" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MethodCallSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MethodMayBeStatic" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_onlyPrivateOrFinal" value="false" />
|
||||||
|
<option name="m_ignoreEmptyMethods" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="MethodMayBeSynchronized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MethodOverloadsParentMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="reportIncompatibleParameters" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="MethodOverridesInaccessibleMethodOfSuper" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MethodOverridesStaticMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MethodSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MisorderedAssertEqualsArguments" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MisorderedAssertEqualsArgumentsTestNG" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MisorderedAssertEqualsParameters" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="MisorderedModifiersInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="MissingOverrideAnnotation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreObjectMethods" value="false" />
|
||||||
|
<option name="ignoreAnonymousClassMethods" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="MissortedModifiers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_requireAnnotationsFirst" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="MultipleTopLevelClassesInFile" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NakedNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NegatedConditional" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreNegatedNullComparison" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="NestedAssignment" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NestedClassSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NestedSwitchStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NestedSynchronizedStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NestedTernaryOperatorInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="NewExpressionSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonBlockStatementBodyJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonExceptionNameEndsWithException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonFinalClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonFinalFieldInEnum" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonFinalFieldOfException" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonFinalStaticVariableUsedInClassInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonFinalUtilityClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonPublicClone" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonReproducibleMathCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonSerializableFieldInSerializableClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignorableAnnotations">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||||
|
<option name="superClassString" value="java.awt.Component" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="NonSerializableObjectBoundToHttpSession" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonSerializableObjectPassedToObjectStream" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonSerializableWithSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonSerializableWithSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonShortCircuitBoolean" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonSynchronizedMethodOverridesSynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NonThreadSafeLazyInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NotOptimalIfConditionsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="REPORT_DUPLICATE_CONDITIONS" value="false" />
|
||||||
|
<option name="SUGGEST_OPTIMIZING_CONDITIONS" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="NotifyCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NotifyWithoutCorrespondingWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NullThrown" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="NumericToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="OCInconsistentNaming" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ObjectAllocationIgnoredJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ObjectInstantiationInEqualsHashCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ObjectNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ObjectToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ObsoleteCollection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreRequiredObsoleteCollectionTypes" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="OctalAndDecimalIntegersMixed" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="OffsetOperationsInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="OneTimeUseVariablesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ALLOW_LONG_STATEMENTS" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="OverloadedVarargsMethod" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="OverridableMethodCallDuringObjectConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PackageInMultipleModules" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ParameterHidingMemberVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreInvisibleFields" value="true" />
|
||||||
|
<option name="m_ignoreStaticMethodParametersHidingInstanceFields" value="true" />
|
||||||
|
<option name="m_ignoreForConstructors" value="true" />
|
||||||
|
<option name="m_ignoreForPropertySetters" value="true" />
|
||||||
|
<option name="m_ignoreForAbstractMethods" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ParameterNamingConventionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_regex" value="[a-z][A-Za-z]*" />
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="99" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ParameterizedParametersStaticCollection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpAssignmentInConditionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpAssignmentReplaceableWithOperatorAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpAssignmentReplaceableWithPrefixExpressionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpCSValidationInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||||
|
<option name="EXTENSIONS" value="php,js,css,inc" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpClassNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="0" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpClosureCanBeConvertedToShortArrowFunctionInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpCompoundNamespaceDepthInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpConstantNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="0" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpConstantReassignmentInspection" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpDivisionByZeroInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpDocMissingThrowsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="SKIP_ON_EMPTY_PHPDOC" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpFunctionNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="0" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpInconsistentReturnPointsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ALLOW_RETURN_NULL_IN_VOID" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpLongTypeFormInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpLoopCanBeConvertedToArrayFillInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpLoopCanBeConvertedToArrayFilterInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpLoopCanBeConvertedToArrayMapInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpMethodNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="0" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpMethodOrClassCallIsNotCaseSensitiveInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpMissingParentCallMagicInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpMissingStrictTypesDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpMissingVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpNewClassMissingParameterListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpNonCanonicalElementsOrderInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpOverridingMethodVisibilityInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpPropertyNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_regex" value="[a-z][_a-z\d]*" />
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="0" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpRedundantClosingTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="PhpSeparateElseIfInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpShortOpenTagInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpSingleStatementWithBracesInspection" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="PhpStatementHasEmptyBodyInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myCommentsCountAsContent" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpStatementWithoutBracesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpStaticAsDynamicMethodCallInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="SHOW_FOR_MAGIC" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpTraditionalSyntaxArrayLiteralInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpTraitsUseListInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpUndefinedCallbackInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpUndefinedClassConstantInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="DOWNGRADE_SEVERITY" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpUndefinedMethodInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="DOWNGRADE_SEVERITY" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpUnnecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpUnused" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="SHOW_UNUSED_BY_ENTRIES" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpUnusedParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="DONT_REPORT_ANONYMOUS" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpVarUsageInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PhpVariableNamingConventionInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_minLength" value="0" />
|
||||||
|
<option name="m_maxLength" value="0" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PhpVariableVariableInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PluginXmlDynamicPlugin" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="highlightNonDynamicEPUsages" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PointlessBitwiseExpressionJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreExpressionsContainingConstants" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ProblematicVarargsMethodOverride" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PropertyCanBeStaticInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ProtectedMemberInFinalClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PublicFieldAccessedInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PublicStaticArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PublicStaticCollectionField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PyAugmentAssignmentInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PyClassicStyleClassInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="PyMissingTypeHintsInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_onlyWhenTypesAreKnown" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="RawTypeCanBeGeneric" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReadObjectAndWriteObjectPrivate" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReadObjectInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReadResolveAndWriteReplaceProtected" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="RedundantElseClauseInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="RedundantFieldInitialization" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="onlyWarnOnNull" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="RedundantImplements" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreSerializable" value="true" />
|
||||||
|
<option name="ignoreCloneable" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="RedundantInnerClassModifier" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="RedundantObjectTypeCheck" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="RedundantSuppression" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="IGNORE_ALL" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ReferencingObjectsInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="RegExpOctalEscape" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceAssignmentWithOperatorAssignment" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreLazyOperators" value="true" />
|
||||||
|
<option name="ignoreObscureOperators" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ReplaceAssignmentWithOperatorAssignmentJS" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceCollectionCountWithSize" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceGuardClauseWithFunctionCall" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceStringFormatWithLiteral" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceSubstringWithDropLast" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceSubstringWithIndexingOperation" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceSubstringWithSubstringAfter" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceSubstringWithSubstringBefore" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReplaceSubstringWithTake" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ResultOfObjectAllocationIgnored" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ResultSetIndexZero" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ReturnOfInnerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="RsSimplifyBooleanExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="RuntimeExec" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="RuntimeExecWithNonConstantString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SafeLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SecurityAdvisoriesInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="optionConfiguration">
|
||||||
|
<list>
|
||||||
|
<option value="barryvdh/laravel-debugbar" />
|
||||||
|
<option value="behat/behat" />
|
||||||
|
<option value="brianium/paratest" />
|
||||||
|
<option value="codeception/codeception" />
|
||||||
|
<option value="codedungeon/phpunit-result-printer" />
|
||||||
|
<option value="composer/composer" />
|
||||||
|
<option value="doctrine/coding-standard" />
|
||||||
|
<option value="filp/whoops" />
|
||||||
|
<option value="friendsofphp/php-cs-fixer" />
|
||||||
|
<option value="humbug/humbug" />
|
||||||
|
<option value="infection/infection" />
|
||||||
|
<option value="jakub-onderka/php-parallel-lint" />
|
||||||
|
<option value="johnkary/phpunit-speedtrap" />
|
||||||
|
<option value="kalessil/production-dependencies-guard" />
|
||||||
|
<option value="mikey179/vfsStream" />
|
||||||
|
<option value="mockery/mockery" />
|
||||||
|
<option value="mybuilder/phpunit-accelerator" />
|
||||||
|
<option value="orchestra/testbench" />
|
||||||
|
<option value="pdepend/pdepend" />
|
||||||
|
<option value="phan/phan" />
|
||||||
|
<option value="phing/phing" />
|
||||||
|
<option value="phpcompatibility/php-compatibility" />
|
||||||
|
<option value="phpmd/phpmd" />
|
||||||
|
<option value="phpro/grumphp" />
|
||||||
|
<option value="phpspec/phpspec" />
|
||||||
|
<option value="phpspec/prophecy" />
|
||||||
|
<option value="phpstan/phpstan" />
|
||||||
|
<option value="phpunit/phpunit" />
|
||||||
|
<option value="povils/phpmnd" />
|
||||||
|
<option value="roave/security-advisories" />
|
||||||
|
<option value="satooshi/php-coveralls" />
|
||||||
|
<option value="sebastian/phpcpd" />
|
||||||
|
<option value="slevomat/coding-standard" />
|
||||||
|
<option value="spatie/phpunit-watcher" />
|
||||||
|
<option value="squizlabs/php_codesniffer" />
|
||||||
|
<option value="sstalle/php7cc" />
|
||||||
|
<option value="symfony/debug" />
|
||||||
|
<option value="symfony/maker-bundle" />
|
||||||
|
<option value="symfony/phpunit-bridge" />
|
||||||
|
<option value="symfony/var-dumper" />
|
||||||
|
<option value="vimeo/psalm" />
|
||||||
|
<option value="wimg/php-compatibility" />
|
||||||
|
<option value="wp-coding-standards/wpcs" />
|
||||||
|
<option value="yiisoft/yii2-coding-standards" />
|
||||||
|
<option value="yiisoft/yii2-debug" />
|
||||||
|
<option value="yiisoft/yii2-gii" />
|
||||||
|
<option value="zendframework/zend-coding-standard" />
|
||||||
|
<option value="zendframework/zend-debug" />
|
||||||
|
<option value="zendframework/zend-test" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SerialPersistentFieldsWithWrongSignature" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SerialVersionUIDNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SerializableHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||||
|
<option name="superClassString" value="java.awt.Component" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SerializableHasSerializationMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||||
|
<option name="superClassString" value="java.awt.Component" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SerializableInnerClassHasSerialVersionUIDField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||||
|
<option name="superClassString" value="java.awt.Component" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SerializableInnerClassWithNonSerializableOuterClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreAnonymousInnerClasses" value="false" />
|
||||||
|
<option name="superClassString" value="java.awt.Component" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SerializableStoresNonSerializable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SerializableWithUnconstructableAncestor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SetReplaceableByEnumSet" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SeveralTargetsMessage" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SharedThreadLocalRandom" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ShortEchoTagCanBeUsedInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SignalWithoutCorrespondingAwait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SimpleDateFormatWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SimplifiableAnnotation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SimplifiableIfStatement" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SingleStatementInBlock" enabled="false" level="INFORMATION" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SizeReplaceableByIsEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SleepWhileHoldingLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SocketResource" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="insideTryAllowed" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SqlGotoInspection" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SqlRedundantOrderingDirectionInspection" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SqlWithoutWhereInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myDontWarnForLimit" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="StackEmpty" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="StaticMethodOnlyUsedInOneClass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreTestClasses" value="true" />
|
||||||
|
<option name="ignoreOnConflicts" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="StrTrUsageAsStrReplaceInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="StringBufferToStringInConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="StringConcatenationInFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="StringConcatenationInMessageFormatCall" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="StringConcatenationMissingWhitespace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="StringEqualsEmptyString" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="SUPPRESS_FOR_VALUES_WHICH_COULD_BE_NULL" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="StringLiteralBreaksHTMLJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="StringReplaceableByStringBuffer" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="onlyWarnOnLoop" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="StringToUpperWithoutLocale" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SubStrUsedAsStrPosInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="SubtractionInCompareTo" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SuspiciousArrayCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SuspiciousIndentAfterControlStatement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SuspiciousLiteralUnderscore" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SwitchStatementWithConfusingDeclaration" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SynchronizationOnStaticField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SynchronizeOnLock" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SynchronizeOnThis" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SynchronizedMethod" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_includeNativeMethods" value="true" />
|
||||||
|
<option name="ignoreSynchronizedSuperMethods" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SynchronizedOnLiteralObject" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SystemGC" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SystemGetenv" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SystemProperties" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SystemRunFinalizersOnExit" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="SystemSetSecurityManager" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TailRecursionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TestCaseInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TestCaseWithConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TestCaseWithNoTestMethods" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreSupers" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="TestMethodInProductCode" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TestMethodWithoutAssertion" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TestOnlyProblems" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TextLabelInSwitchStatementJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThisEscapedInConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThreadDumpStack" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThreadLocalNotStaticFinal" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThreadPriority" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThreadStartInConstruction" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThreadStopSuspendResume" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThreadYield" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="ThrowCaughtLocally" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreRethrownExceptions" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ThrowRawExceptionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="ThrowablePrintStackTrace" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TimeToString" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TooBroadScope" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_allowConstructorAsInitializer" value="false" />
|
||||||
|
<option name="m_onlyLookAtBlocks" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="TransientFieldInNonSerializableClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TransientFieldNotInitialized" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="TrivialStringConcatenation" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnNecessaryDoubleQuotesInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnaryPlus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnclearBinaryExpression" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnconditionalWait" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnconstrainedVariableType" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UndeclaredTests" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnknownInspectionInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnnecessarilyQualifiedStaticUsage" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreStaticFieldAccesses" value="false" />
|
||||||
|
<option name="m_ignoreStaticMethodCalls" value="false" />
|
||||||
|
<option name="m_ignoreStaticAccessFromStaticContext" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnnecessarilyQualifiedStaticallyImportedElement" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnnecessaryBoxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="onlyReportSuperfluouslyBoxed" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnnecessaryConstantArrayCreationExpression" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnnecessaryConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreAnnotations" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnnecessaryExplicitNumericCast" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnnecessaryLocalVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
|
||||||
|
<option name="m_ignoreAnnotatedVariables" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnnecessaryLocalVariableJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreImmediatelyReturnedVariables" value="true" />
|
||||||
|
<option name="m_ignoreAnnotatedVariables" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnnecessaryParentheses" enabled="true" level="INFORMATION" enabled_by_default="true">
|
||||||
|
<option name="ignoreClarifyingParentheses" value="true" />
|
||||||
|
<option name="ignoreParenthesesOnConditionals" value="false" />
|
||||||
|
<option name="ignoreParenthesesOnLambdaParameter" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnnecessarySuperConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnnecessaryUnaryMinus" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UnnecessaryUnboxing" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="onlyReportSuperfluouslyUnboxed" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnsetConstructsCanBeMergedInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||||
|
<inspection_tool class="UnterminatedStatementJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreSemicolonAtEndOfBlock" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UnusedCatchParameterJS" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="m_ignoreCatchBlocksWithComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="UseCouple" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UseOfAWTPeerClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UseOfJDBCDriverClass" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UseOfObsoleteAssert" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UseOfObsoleteDateTimeApi" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UseOfProcessBuilder" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UseOfPropertiesAsHashtable" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UseOfSunClasses" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UsingInclusionReturnValueInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UtilityClassWithPublicConstructor" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="UtilityClassWithoutPrivateConstructor" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignorableAnnotations">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="ignoreClassesWithOnlyMain" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="VariableNotUsedInsideIf" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="VariableUseSideOnly" enabled="true" level="ERROR" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="VoidExpressionJS" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="VolatileArrayField" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="W3CssValidation" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="myCssVersion" value="css3svg" />
|
||||||
|
<option name="myIgnoreVendorSpecificProperties" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="WaitCalledOnCondition" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="WaitNotInLoop" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="WaitNotifyNotInSynchronizedContext" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="WaitOrAwaitWithoutTimeout" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="WaitWhileHoldingTwoLocks" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="WaitWithoutCorrespondingNotify" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="WhenWithOnlyElse" enabled="true" level="WEAK WARNING" enabled_by_default="true" />
|
||||||
|
<inspection_tool class="WhileLoopSpinsOnField" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoreNonEmtpyLoops" value="false" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="ZeroLengthArrayInitialization" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||||
|
</profile>
|
||||||
|
</component>
|
10
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
10
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
6
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
app/.idea/.idea.DiscordHistoryTracker/.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="PROJECT_PROFILE" value="Project" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
36
app/.idea/.idea.DiscordHistoryTracker/.idea/jsonSchemas.xml
generated
Normal file
36
app/.idea/.idea.DiscordHistoryTracker/.idea/jsonSchemas.xml
generated
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JsonSchemaMappingsProjectConfiguration">
|
||||||
|
<state>
|
||||||
|
<map>
|
||||||
|
<entry key="track-channel">
|
||||||
|
<value>
|
||||||
|
<SchemaInfo>
|
||||||
|
<option name="name" value="track-channel" />
|
||||||
|
<option name="relativePathToSchema" value="Resources/Schemas/track-channel.yml" />
|
||||||
|
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||||
|
</SchemaInfo>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="track-messages">
|
||||||
|
<value>
|
||||||
|
<SchemaInfo>
|
||||||
|
<option name="name" value="track-messages" />
|
||||||
|
<option name="relativePathToSchema" value="Resources/Schemas/track-messages.yml" />
|
||||||
|
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||||
|
</SchemaInfo>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
<entry key="track-users">
|
||||||
|
<value>
|
||||||
|
<SchemaInfo>
|
||||||
|
<option name="name" value="track-users" />
|
||||||
|
<option name="relativePathToSchema" value="Resources/Schemas/track-users.yml" />
|
||||||
|
<option name="schemaVersion" value="JSON Schema version 7" />
|
||||||
|
</SchemaInfo>
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</state>
|
||||||
|
</component>
|
||||||
|
</project>
|
22
app/.idea/.idea.DiscordHistoryTracker/.idea/libraries/Generated_files.xml
generated
Normal file
22
app/.idea/.idea.DiscordHistoryTracker/.idea/libraries/Generated_files.xml
generated
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="Generated files" type="javaScript">
|
||||||
|
<properties>
|
||||||
|
<sourceFilesUrls>
|
||||||
|
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/discord.js" />
|
||||||
|
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/dom.js" />
|
||||||
|
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/gui.js" />
|
||||||
|
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/settings.js" />
|
||||||
|
<item url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/state.js" />
|
||||||
|
</sourceFilesUrls>
|
||||||
|
</properties>
|
||||||
|
<CLASSES>
|
||||||
|
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/discord.js" />
|
||||||
|
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/dom.js" />
|
||||||
|
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/gui.js" />
|
||||||
|
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/settings.js" />
|
||||||
|
<root url="file://$PROJECT_DIR$/Resources/Tracker/scripts.min/state.js" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
20
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Desktop.xml
generated
Normal file
20
app/.idea/.idea.DiscordHistoryTracker/.idea/runConfigurations/Desktop.xml
generated
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Desktop" type="DotNetProject" factoryName=".NET Project">
|
||||||
|
<option name="EXE_PATH" value="$PROJECT_DIR$/Desktop/bin/Debug/net5.0/Desktop.exe" />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/Desktop/bin/Debug/net5.0" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="1" />
|
||||||
|
<option name="USE_EXTERNAL_CONSOLE" value="0" />
|
||||||
|
<option name="USE_MONO" value="0" />
|
||||||
|
<option name="RUNTIME_ARGUMENTS" value="" />
|
||||||
|
<option name="PROJECT_PATH" value="$PROJECT_DIR$/Desktop/Desktop.csproj" />
|
||||||
|
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||||
|
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||||
|
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||||
|
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||||
|
<option name="PROJECT_TFM" value="net5.0" />
|
||||||
|
<method v="2">
|
||||||
|
<option name="Build" />
|
||||||
|
</method>
|
||||||
|
</configuration>
|
||||||
|
</component>
|
3
app/.idea/.idea.DiscordHistoryTracker/.idea/scopes/Resources.xml
generated
Normal file
3
app/.idea/.idea.DiscordHistoryTracker/.idea/scopes/Resources.xml
generated
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<component name="DependencyValidationManager">
|
||||||
|
<scope name="Resources" pattern="file:Tracker/scripts/*" />
|
||||||
|
</component>
|
6
app/.idea/.idea.DiscordHistoryTracker/.idea/sqldialects.xml
generated
Normal file
6
app/.idea/.idea.DiscordHistoryTracker/.idea/sqldialects.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="PROJECT" dialect="SQLite" />
|
||||||
|
</component>
|
||||||
|
</project>
|
11
app/.idea/.idea.DiscordHistoryTracker/.idea/vcs.xml
generated
Normal file
11
app/.idea/.idea.DiscordHistoryTracker/.idea/vcs.xml
generated
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GitSharedSettings">
|
||||||
|
<option name="FORCE_PUSH_PROHIBITED_PATTERNS">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
app/.idea/.idea.DiscordHistoryTracker/.idea/watcherTasks.xml
generated
Normal file
8
app/.idea/.idea.DiscordHistoryTracker/.idea/watcherTasks.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectTasksOptions">
|
||||||
|
<enabled-global>
|
||||||
|
<option value="UglifyJS (Tracker)" />
|
||||||
|
</enabled-global>
|
||||||
|
</component>
|
||||||
|
</project>
|
73
app/Desktop/App.axaml
Normal file
73
app/Desktop/App.axaml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<Application xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
x:Class="DHT.Desktop.App">
|
||||||
|
|
||||||
|
<Application.Styles>
|
||||||
|
|
||||||
|
<FluentTheme Mode="Light" />
|
||||||
|
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Padding" Value="10 7 10 6" />
|
||||||
|
<Setter Property="Background" Value="#D2D2D2" />
|
||||||
|
<Setter Property="BorderBrush" Value="#999999" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="TextBlock.Foreground" Value="#070707" />
|
||||||
|
<Setter Property="Background" Value="#D2D2D2" />
|
||||||
|
<Setter Property="BorderBrush" Value="#999999" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:pointerover /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#DEDEDE" />
|
||||||
|
<Setter Property="BorderBrush" Value="#A2A2A2" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:pressed">
|
||||||
|
<Setter Property="RenderTransform" Value="none" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:pressed /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Background" Value="#E5E5E5" />
|
||||||
|
<Setter Property="BorderBrush" Value="#A5A5A5" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button:disabled /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="TextBlock.Foreground" Value="#909090" />
|
||||||
|
<Setter Property="Background" Value="#E9E9E9" />
|
||||||
|
<Setter Property="BorderBrush" Value="#BFBFBF" />
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style Selector="TextBox">
|
||||||
|
<Setter Property="BorderBrush" Value="#999999" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="SelectionBrush" Value="#72C0FF" />
|
||||||
|
<Setter Property="Background" Value="#F4F4F4" />
|
||||||
|
<Setter Property="Padding" Value="6 0" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox /template/ Border#PART_BorderElement">
|
||||||
|
<Setter Property="CornerRadius" Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox:pointerover /template/ Border#PART_BorderElement">
|
||||||
|
<Setter Property="BorderBrush" Value="#999999" />
|
||||||
|
<Setter Property="Background" Value="#F8F8F8" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox:focus /template/ Border#PART_BorderElement">
|
||||||
|
<Setter Property="BorderBrush" Value="#546A9F" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="Background" Value="#FBFBFB" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox:disabled /template/ Border#PART_BorderElement">
|
||||||
|
<Setter Property="BorderBrush" Value="#999999" />
|
||||||
|
<Setter Property="Background" Value="#BBBBBB" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBox /template/ TextBlock#PART_Watermark">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</Application.Styles>
|
||||||
|
|
||||||
|
</Application>
|
20
app/Desktop/App.axaml.cs
Normal file
20
app/Desktop/App.axaml.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using DHT.Desktop.Main;
|
||||||
|
|
||||||
|
namespace DHT.Desktop {
|
||||||
|
public class App : Application {
|
||||||
|
public override void Initialize() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnFrameworkInitializationCompleted() {
|
||||||
|
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) {
|
||||||
|
desktop.MainWindow = new MainWindow(new Arguments(desktop.Args));
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnFrameworkInitializationCompleted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
app/Desktop/Arguments.cs
Normal file
50
app/Desktop/Arguments.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using System;
|
||||||
|
using DHT.Server.Logging;
|
||||||
|
|
||||||
|
namespace DHT.Desktop {
|
||||||
|
public class Arguments {
|
||||||
|
public static Arguments Empty => new(Array.Empty<string>());
|
||||||
|
|
||||||
|
public string? DatabaseFile { get; }
|
||||||
|
public ushort? ServerPort { get; }
|
||||||
|
public string? ServerToken { get; }
|
||||||
|
|
||||||
|
public Arguments(string[] args) {
|
||||||
|
for (int i = 0; i < args.Length; i++) {
|
||||||
|
string key = args[i];
|
||||||
|
|
||||||
|
if (i >= args.Length - 1) {
|
||||||
|
Log.Warn("Missing value for command line argument: " + key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
string value = args[++i];
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case "-db":
|
||||||
|
DatabaseFile = value;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case "-port": {
|
||||||
|
if (ushort.TryParse(value, out var port)) {
|
||||||
|
ServerPort = port;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Log.Warn("Invalid port number: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
case "-token":
|
||||||
|
ServerToken = value;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Log.Warn("Unknown command line argument: " + key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
66
app/Desktop/Desktop.csproj
Normal file
66
app/Desktop/Desktop.csproj
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net5.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<RootNamespace>DHT.Desktop</RootNamespace>
|
||||||
|
<PackageId>DiscordHistoryTracker</PackageId>
|
||||||
|
<Authors>chylex</Authors>
|
||||||
|
<Company>DiscordHistoryTracker</Company>
|
||||||
|
<Product>DiscordHistoryTracker</Product>
|
||||||
|
<ApplicationIcon>./Resources/icon.ico</ApplicationIcon>
|
||||||
|
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
|
||||||
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
|
<AssemblyName>DiscordHistoryTracker</AssemblyName>
|
||||||
|
<Version>31.0.0.0</Version>
|
||||||
|
<AssemblyVersion>$(Version)</AssemblyVersion>
|
||||||
|
<FileVersion>$(Version)</FileVersion>
|
||||||
|
<PackageVersion>$(Version)</PackageVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>none</DebugType>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Avalonia" Version="0.10.3" />
|
||||||
|
<PackageReference Include="Avalonia.Desktop" Version="0.10.3" />
|
||||||
|
<ProjectReference Include="..\Server\Server.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
|
||||||
|
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Windows\MainWindow.axaml.cs">
|
||||||
|
<DependentUpon>MainWindow.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Dialogs\MessageDialog.axaml.cs">
|
||||||
|
<DependentUpon>MessageDialog.axaml</DependentUpon>
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<UpToDateCheckInput Remove="Pages\DatabasePage.axaml" />
|
||||||
|
<UpToDateCheckInput Remove="Pages\TrackingPage.axaml" />
|
||||||
|
<UpToDateCheckInput Remove="Pages\ViewerPage.axaml" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<EmbeddedResource Include="../Resources/Tracker/bootstrap.js">
|
||||||
|
<LogicalName>Tracker\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||||
|
<Link>Resources/Tracker/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="../Resources/Tracker/scripts.min/**">
|
||||||
|
<LogicalName>Tracker\scripts\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||||
|
<Link>Resources/Tracker/scripts/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="../Resources/Tracker/styles/**">
|
||||||
|
<LogicalName>Tracker\styles\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||||
|
<Link>Resources/Tracker/styles/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<EmbeddedResource Include="../Resources/Viewer/**">
|
||||||
|
<LogicalName>Viewer\%(RecursiveDir)%(Filename)%(Extension)</LogicalName>
|
||||||
|
<Link>Resources/Viewer/%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||||
|
</EmbeddedResource>
|
||||||
|
<AvaloniaResource Include="Resources/icon.ico" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
56
app/Desktop/Dialogs/Dialog.cs
Normal file
56
app/Desktop/Dialogs/Dialog.cs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Dialogs {
|
||||||
|
public static class Dialog {
|
||||||
|
public static async Task ShowOk(Window owner, string title, string message) {
|
||||||
|
await new MessageDialog {
|
||||||
|
DataContext = new MessageDialogModel {
|
||||||
|
Title = title,
|
||||||
|
Message = message,
|
||||||
|
IsOkVisible = true
|
||||||
|
}
|
||||||
|
}.ShowDialog<DialogResult.All>(owner);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<DialogResult.OkCancel> ShowOkCancel(Window owner, string title, string message) {
|
||||||
|
var result = await new MessageDialog {
|
||||||
|
DataContext = new MessageDialogModel {
|
||||||
|
Title = title,
|
||||||
|
Message = message,
|
||||||
|
IsOkVisible = true,
|
||||||
|
IsCancelVisible = true
|
||||||
|
}
|
||||||
|
}.ShowDialog<DialogResult.All?>(owner);
|
||||||
|
|
||||||
|
return result.ToOkCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<DialogResult.YesNo> ShowYesNo(Window owner, string title, string message) {
|
||||||
|
var result = await new MessageDialog {
|
||||||
|
DataContext = new MessageDialogModel {
|
||||||
|
Title = title,
|
||||||
|
Message = message,
|
||||||
|
IsYesVisible = true,
|
||||||
|
IsNoVisible = true
|
||||||
|
}
|
||||||
|
}.ShowDialog<DialogResult.All?>(owner);
|
||||||
|
|
||||||
|
return result.ToYesNo();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<DialogResult.YesNoCancel> ShowYesNoCancel(Window owner, string title, string message) {
|
||||||
|
var result = await new MessageDialog {
|
||||||
|
DataContext = new MessageDialogModel {
|
||||||
|
Title = title,
|
||||||
|
Message = message,
|
||||||
|
IsYesVisible = true,
|
||||||
|
IsNoVisible = true,
|
||||||
|
IsCancelVisible = true
|
||||||
|
}
|
||||||
|
}.ShowDialog<DialogResult.All?>(owner);
|
||||||
|
|
||||||
|
return result.ToYesNoCancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
49
app/Desktop/Dialogs/DialogResult.cs
Normal file
49
app/Desktop/Dialogs/DialogResult.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Dialogs {
|
||||||
|
public static class DialogResult {
|
||||||
|
public enum All {
|
||||||
|
Ok, Yes, No, Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum OkCancel {
|
||||||
|
Closed, Ok, Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum YesNo {
|
||||||
|
Closed, Yes, No
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum YesNoCancel {
|
||||||
|
Closed, Yes, No, Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OkCancel ToOkCancel(this All? result) {
|
||||||
|
return result switch {
|
||||||
|
null => OkCancel.Closed,
|
||||||
|
All.Ok => OkCancel.Ok,
|
||||||
|
All.Cancel => OkCancel.Cancel,
|
||||||
|
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to ok/cancel.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static YesNo ToYesNo(this All? result) {
|
||||||
|
return result switch {
|
||||||
|
null => YesNo.Closed,
|
||||||
|
All.Yes => YesNo.Yes,
|
||||||
|
All.No => YesNo.No,
|
||||||
|
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static YesNoCancel ToYesNoCancel(this All? result) {
|
||||||
|
return result switch {
|
||||||
|
null => YesNoCancel.Closed,
|
||||||
|
All.Yes => YesNoCancel.Yes,
|
||||||
|
All.No => YesNoCancel.No,
|
||||||
|
All.Cancel => YesNoCancel.Cancel,
|
||||||
|
_ => throw new ArgumentException("Cannot convert dialog result " + result + " to yes/no/cancel.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
41
app/Desktop/Dialogs/MessageDialog.axaml
Normal file
41
app/Desktop/Dialogs/MessageDialog.axaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:dialogs="clr-namespace:DHT.Desktop.Dialogs"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="500"
|
||||||
|
x:Class="DHT.Desktop.Dialogs.MessageDialog"
|
||||||
|
Title="{Binding Title}"
|
||||||
|
Width="500" SizeToContent="Height"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
|
||||||
|
<Window.DataContext>
|
||||||
|
<dialogs:MessageDialogModel />
|
||||||
|
</Window.DataContext>
|
||||||
|
|
||||||
|
<Window.Styles>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="FontSize" Value="15" />
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="WrapPanel">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Right" />
|
||||||
|
<Setter Property="Margin" Value="0 8 0 0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Margin" Value="8 0 0 0" />
|
||||||
|
<Setter Property="MinWidth" Value="80" />
|
||||||
|
</Style>
|
||||||
|
</Window.Styles>
|
||||||
|
|
||||||
|
<StackPanel Margin="20">
|
||||||
|
<TextBlock Text="{Binding Message}" />
|
||||||
|
<WrapPanel>
|
||||||
|
<Button Click="ClickOk" IsVisible="{Binding IsOkVisible}">OK</Button>
|
||||||
|
<Button Click="ClickYes" IsVisible="{Binding IsYesVisible}">Yes</Button>
|
||||||
|
<Button Click="ClickNo" IsVisible="{Binding IsNoVisible}">No</Button>
|
||||||
|
<Button Click="ClickCancel" IsVisible="{Binding IsCancelVisible}">Cancel</Button>
|
||||||
|
</WrapPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</Window>
|
35
app/Desktop/Dialogs/MessageDialog.axaml.cs
Normal file
35
app/Desktop/Dialogs/MessageDialog.axaml.cs
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Dialogs {
|
||||||
|
public class MessageDialog : Window {
|
||||||
|
public MessageDialog() {
|
||||||
|
InitializeComponent();
|
||||||
|
#if DEBUG
|
||||||
|
this.AttachDevTools();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClickOk(object? sender, RoutedEventArgs e) {
|
||||||
|
Close(DialogResult.All.Ok);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClickYes(object? sender, RoutedEventArgs e) {
|
||||||
|
Close(DialogResult.All.Yes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClickNo(object? sender, RoutedEventArgs e) {
|
||||||
|
Close(DialogResult.All.No);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClickCancel(object? sender, RoutedEventArgs e) {
|
||||||
|
Close(DialogResult.All.Cancel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
11
app/Desktop/Dialogs/MessageDialogModel.cs
Normal file
11
app/Desktop/Dialogs/MessageDialogModel.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
namespace DHT.Desktop.Dialogs {
|
||||||
|
public class MessageDialogModel {
|
||||||
|
public string Title { get; init; } = "";
|
||||||
|
public string Message { get; init; } = "";
|
||||||
|
|
||||||
|
public bool IsOkVisible { get; init; } = false;
|
||||||
|
public bool IsYesVisible { get; init; } = false;
|
||||||
|
public bool IsNoVisible { get; init; } = false;
|
||||||
|
public bool IsCancelVisible { get; init; } = false;
|
||||||
|
}
|
||||||
|
}
|
67
app/Desktop/Main/AboutWindow.axaml
Normal file
67
app/Desktop/Main/AboutWindow.axaml
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="480" d:DesignHeight="295"
|
||||||
|
x:Class="DHT.Desktop.Main.AboutWindow"
|
||||||
|
Title="About Discord History Tracker"
|
||||||
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
|
Width="480" Height="295" CanResize="False"
|
||||||
|
WindowStartupLocation="CenterOwner">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<main:AboutWindowModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Window.Styles>
|
||||||
|
<Style Selector="StackPanel">
|
||||||
|
<Setter Property="Orientation" Value="Horizontal" />
|
||||||
|
<Setter Property="Spacing" Value="5" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TextBlock">
|
||||||
|
<Setter Property="TextWrapping" Value="Wrap" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid > Button">
|
||||||
|
<Setter Property="Margin" Value="0 4" />
|
||||||
|
<Setter Property="Padding" Value="8 4" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
</Style>
|
||||||
|
</Window.Styles>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" Margin="20" Spacing="12">
|
||||||
|
|
||||||
|
<TextBlock VerticalAlignment="Center">
|
||||||
|
Discord History Tracker was created by chylex and released under the MIT license.
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<Button Command="{Binding ShowOfficialWebsite}">Official Website</Button>
|
||||||
|
<Button Command="{Binding ShowSourceCode}">Source Code</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Grid RowDefinitions="Auto,5,Auto,Auto,Auto,Auto,Auto,Auto" ColumnDefinitions="175,125,*" Margin="0 10 0 0">
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="0" FontWeight="Bold">Third-Party Software</TextBlock>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="1" FontWeight="Bold">License</TextBlock>
|
||||||
|
<TextBlock Grid.Row="0" Grid.Column="2" FontWeight="Bold">Link</TextBlock>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="0">.NET 5</TextBlock>
|
||||||
|
<TextBlock Grid.Row="2" Grid.Column="1">MIT</TextBlock>
|
||||||
|
<Button Grid.Row="2" Grid.Column="2" Command="{Binding ShowLibraryNetCore}">GitHub</Button>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="0">Avalonia</TextBlock>
|
||||||
|
<TextBlock Grid.Row="3" Grid.Column="1">MIT</TextBlock>
|
||||||
|
<Button Grid.Row="3" Grid.Column="2" Command="{Binding ShowLibraryAvalonia}">NuGet</Button>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="0">SQLite</TextBlock>
|
||||||
|
<TextBlock Grid.Row="4" Grid.Column="1">Public Domain</TextBlock>
|
||||||
|
<Button Grid.Row="4" Grid.Column="2" Command="{Binding ShowLibrarySqlite}">Official Website</Button>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="5" Grid.Column="0">Microsoft.Data.Sqlite</TextBlock>
|
||||||
|
<TextBlock Grid.Row="5" Grid.Column="1">Apache-2.0</TextBlock>
|
||||||
|
<Button Grid.Row="5" Grid.Column="2" Command="{Binding ShowLibrarySqliteAdoNet}">NuGet</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
</Window>
|
19
app/Desktop/Main/AboutWindow.axaml.cs
Normal file
19
app/Desktop/Main/AboutWindow.axaml.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class AboutWindow : Window {
|
||||||
|
public AboutWindow() {
|
||||||
|
InitializeComponent();
|
||||||
|
#if DEBUG
|
||||||
|
this.AttachDevTools();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
33
app/Desktop/Main/AboutWindowModel.cs
Normal file
33
app/Desktop/Main/AboutWindowModel.cs
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class AboutWindowModel {
|
||||||
|
public void ShowOfficialWebsite() {
|
||||||
|
OpenUrl("https://dht.chylex.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowSourceCode() {
|
||||||
|
OpenUrl("https://github.com/chylex/Discord-History-Tracker");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowLibraryAvalonia() {
|
||||||
|
OpenUrl("https://www.nuget.org/packages/Avalonia");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowLibrarySqlite() {
|
||||||
|
OpenUrl("https://www.sqlite.org");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowLibrarySqliteAdoNet() {
|
||||||
|
OpenUrl("https://www.nuget.org/packages/Microsoft.Data.Sqlite");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ShowLibraryNetCore() {
|
||||||
|
OpenUrl("https://github.com/dotnet/core");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OpenUrl(string url) {
|
||||||
|
Process.Start(new ProcessStartInfo { FileName = url, UseShellExecute = true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
app/Desktop/Main/Controls/StatusBar.axaml
Normal file
58
app/Desktop/Main/Controls/StatusBar.axaml
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
x:Class="DHT.Desktop.Main.Controls.StatusBar">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<controls:StatusBarModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Background>
|
||||||
|
<SolidColorBrush>#546A9F</SolidColorBrush>
|
||||||
|
</UserControl.Background>
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="StackPanel > TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="#E0E0E0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="StackPanel > TextBlock.label">
|
||||||
|
<Setter Property="FontSize" Value="15" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="StackPanel > TextBlock.value">
|
||||||
|
<Setter Property="FontSize" Value="16" />
|
||||||
|
<Setter Property="TextAlignment" Value="Right" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="StackPanel > Rectangle">
|
||||||
|
<Setter Property="Margin" Value="14 0" />
|
||||||
|
<Setter Property="Stroke" Value="#3B5287" />
|
||||||
|
<Setter Property="StrokeThickness" Value="2" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="4 3">
|
||||||
|
<StackPanel Orientation="Vertical" Width="65">
|
||||||
|
<TextBlock Classes="label">Status</TextBlock>
|
||||||
|
<TextBlock FontSize="12" Margin="0 2 0 0" Text="{Binding StatusText}" />
|
||||||
|
</StackPanel>
|
||||||
|
<Rectangle />
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock Classes="label">Servers</TextBlock>
|
||||||
|
<TextBlock Classes="value" Text="{Binding DatabaseStatistics.TotalServers, StringFormat={}{0:n0}}" />
|
||||||
|
</StackPanel>
|
||||||
|
<Rectangle />
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock Classes="label">Channels</TextBlock>
|
||||||
|
<TextBlock Classes="value" Text="{Binding DatabaseStatistics.TotalChannels, StringFormat={}{0:n0}}" />
|
||||||
|
</StackPanel>
|
||||||
|
<Rectangle />
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock Classes="label">Messages</TextBlock>
|
||||||
|
<TextBlock Classes="value" Text="{Binding DatabaseStatistics.TotalMessages, StringFormat={}{0:n0}}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
14
app/Desktop/Main/Controls/StatusBar.axaml.cs
Normal file
14
app/Desktop/Main/Controls/StatusBar.axaml.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Controls {
|
||||||
|
public class StatusBar : UserControl {
|
||||||
|
public StatusBar() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
app/Desktop/Main/Controls/StatusBarModel.cs
Normal file
45
app/Desktop/Main/Controls/StatusBarModel.cs
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
using System;
|
||||||
|
using DHT.Desktop.Models;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Controls {
|
||||||
|
public class StatusBarModel : BaseModel {
|
||||||
|
public DatabaseStatistics DatabaseStatistics { get; }
|
||||||
|
|
||||||
|
private Status status = Status.Stopped;
|
||||||
|
|
||||||
|
public Status CurrentStatus {
|
||||||
|
get => status;
|
||||||
|
set {
|
||||||
|
status = value;
|
||||||
|
OnPropertyChanged(nameof(StatusText));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StatusText {
|
||||||
|
get {
|
||||||
|
return CurrentStatus switch {
|
||||||
|
Status.Starting => "STARTING",
|
||||||
|
Status.Ready => "READY",
|
||||||
|
Status.Stopping => "STOPPING",
|
||||||
|
Status.Stopped => "STOPPED",
|
||||||
|
_ => ""
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public StatusBarModel() : this(new DatabaseStatistics()) {}
|
||||||
|
|
||||||
|
public StatusBarModel(DatabaseStatistics databaseStatistics) {
|
||||||
|
this.DatabaseStatistics = databaseStatistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Status {
|
||||||
|
Starting,
|
||||||
|
Ready,
|
||||||
|
Stopping,
|
||||||
|
Stopped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
app/Desktop/Main/MainContentScreen.axaml
Normal file
91
app/Desktop/Main/MainContentScreen.axaml
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||||
|
xmlns:controls="clr-namespace:DHT.Desktop.Main.Controls"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="DHT.Desktop.Main.MainContentScreen">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<main:MainContentScreenModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="TabControl WrapPanel">
|
||||||
|
<Setter Property="Background" Value="#546A9F" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem">
|
||||||
|
<Setter Property="Foreground" Value="#E9E9E9" />
|
||||||
|
<Setter Property="FontSize" Value="20" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem[TabStripPlacement=Left] /template/ ContentPresenter#PART_ContentPresenter">
|
||||||
|
<Setter Property="Margin" Value="5 0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:pointerover">
|
||||||
|
<Setter Property="Background" Value="#1F2E45" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:pointerover /template/ Border">
|
||||||
|
<Setter Property="Background" Value="#1F2E45" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:pointerover > TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="#E9E9E9" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:selected:pointerover /template/ Border">
|
||||||
|
<Setter Property="Background" Value="#FFFFFF" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:selected:pointerover > TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="#1A2234" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:selected">
|
||||||
|
<Setter Property="Foreground" Value="#1A2234" />
|
||||||
|
<Setter Property="Background" Value="#FFFFFF" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:selected /template/ Border#PART_SelectedPipe">
|
||||||
|
<Setter Property="IsVisible" Value="False" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem:disabled > TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="#B2B2B2" />
|
||||||
|
<Setter Property="TextDecorations" Value="Strikethrough" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabItem.first">
|
||||||
|
<Setter Property="Margin" Value="0 13 0 0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="TabControl">
|
||||||
|
<Setter Property="Padding" Value="0" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="ContentPresenter.page">
|
||||||
|
<Setter Property="Margin" Value="15 21" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<DockPanel>
|
||||||
|
<TabControl x:Name="TabControl" TabStripPlacement="Left">
|
||||||
|
<TabItem x:Name="TabDatabase" Header="Database" Classes="first">
|
||||||
|
<DockPanel>
|
||||||
|
<controls:StatusBar DataContext="{Binding StatusBarModel}" DockPanel.Dock="Bottom" />
|
||||||
|
<ScrollViewer>
|
||||||
|
<ContentPresenter Content="{Binding DatabasePage}" Classes="page" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem x:Name="TabTracking" Header="Tracking">
|
||||||
|
<DockPanel>
|
||||||
|
<controls:StatusBar DataContext="{Binding StatusBarModel}" DockPanel.Dock="Bottom" />
|
||||||
|
<ScrollViewer>
|
||||||
|
<ContentPresenter Content="{Binding TrackingPage}" Classes="page" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
<TabItem x:Name="TabViewer" Header="Viewer">
|
||||||
|
<DockPanel>
|
||||||
|
<controls:StatusBar DataContext="{Binding StatusBarModel}" DockPanel.Dock="Bottom" />
|
||||||
|
<ScrollViewer>
|
||||||
|
<ContentPresenter Content="{Binding ViewerPage}" Classes="page" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</TabItem>
|
||||||
|
</TabControl>
|
||||||
|
</DockPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
14
app/Desktop/Main/MainContentScreen.axaml.cs
Normal file
14
app/Desktop/Main/MainContentScreen.axaml.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class MainContentScreen : UserControl {
|
||||||
|
public MainContentScreen() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
app/Desktop/Main/MainContentScreenModel.cs
Normal file
57
app/Desktop/Main/MainContentScreenModel.cs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
using System;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using DHT.Desktop.Main.Controls;
|
||||||
|
using DHT.Desktop.Main.Pages;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
using DHT.Server.Service;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class MainContentScreenModel : IDisposable {
|
||||||
|
public DatabasePage DatabasePage { get; }
|
||||||
|
private DatabasePageModel DatabasePageModel { get; }
|
||||||
|
|
||||||
|
public TrackingPage TrackingPage { get; }
|
||||||
|
private TrackingPageModel TrackingPageModel { get; }
|
||||||
|
|
||||||
|
public ViewerPage ViewerPage { get; }
|
||||||
|
private ViewerPageModel ViewerPageModel { get; }
|
||||||
|
|
||||||
|
public StatusBarModel StatusBarModel { get; }
|
||||||
|
|
||||||
|
public event EventHandler? DatabaseClosed {
|
||||||
|
add { DatabasePageModel.DatabaseClosed += value; }
|
||||||
|
remove { DatabasePageModel.DatabaseClosed -= value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public MainContentScreenModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||||
|
|
||||||
|
public MainContentScreenModel(Window window, IDatabaseFile db) {
|
||||||
|
DatabasePageModel = new DatabasePageModel(window, db);
|
||||||
|
DatabasePage = new DatabasePage { DataContext = DatabasePageModel };
|
||||||
|
|
||||||
|
TrackingPageModel = new TrackingPageModel(window, db);
|
||||||
|
TrackingPage = new TrackingPage { DataContext = TrackingPageModel };
|
||||||
|
|
||||||
|
ViewerPageModel = new ViewerPageModel(window, db);
|
||||||
|
ViewerPage = new ViewerPage { DataContext = ViewerPageModel };
|
||||||
|
|
||||||
|
StatusBarModel = new StatusBarModel(db.Statistics);
|
||||||
|
TrackingPageModel.ServerStatusChanged += TrackingPageModelOnServerStatusChanged;
|
||||||
|
StatusBarModel.CurrentStatus = ServerLauncher.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize() {
|
||||||
|
TrackingPageModel.Initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrackingPageModelOnServerStatusChanged(object? sender, StatusBarModel.Status e) {
|
||||||
|
StatusBarModel.CurrentStatus = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
TrackingPageModel.Dispose();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
app/Desktop/Main/MainWindow.axaml
Normal file
22
app/Desktop/Main/MainWindow.axaml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<Window xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="DHT.Desktop.Main.MainWindow"
|
||||||
|
Title="Discord History Tracker"
|
||||||
|
Icon="avares://DiscordHistoryTracker/Resources/icon.ico"
|
||||||
|
Width="800" Height="450"
|
||||||
|
MinWidth="480" MinHeight="240"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<main:MainWindowModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<Panel>
|
||||||
|
<ContentPresenter Content="{Binding WelcomeScreen}" IsVisible="{Binding ShowWelcomeScreen}" />
|
||||||
|
<ContentPresenter Content="{Binding MainContentScreen}" IsVisible="{Binding ShowMainContentScreen}" />
|
||||||
|
</Panel>
|
||||||
|
</Window>
|
26
app/Desktop/Main/MainWindow.axaml.cs
Normal file
26
app/Desktop/Main/MainWindow.axaml.cs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class MainWindow : Window {
|
||||||
|
[UsedImplicitly]
|
||||||
|
public MainWindow() {
|
||||||
|
InitializeComponent(Arguments.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MainWindow(Arguments args) {
|
||||||
|
InitializeComponent(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent(Arguments args) {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
DataContext = new MainWindowModel(this, args);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
this.AttachDevTools();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
101
app/Desktop/Main/MainWindowModel.cs
Normal file
101
app/Desktop/Main/MainWindowModel.cs
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using DHT.Desktop.Dialogs;
|
||||||
|
using DHT.Desktop.Main.Pages;
|
||||||
|
using DHT.Desktop.Models;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class MainWindowModel : BaseModel {
|
||||||
|
public WelcomeScreen WelcomeScreen { get; }
|
||||||
|
private WelcomeScreenModel WelcomeScreenModel { get; }
|
||||||
|
|
||||||
|
public MainContentScreen? MainContentScreen { get; private set; }
|
||||||
|
private MainContentScreenModel? MainContentScreenModel { get; set; }
|
||||||
|
|
||||||
|
public bool ShowWelcomeScreen => db == null;
|
||||||
|
public bool ShowMainContentScreen => db != null;
|
||||||
|
|
||||||
|
private readonly Window window;
|
||||||
|
|
||||||
|
private IDatabaseFile? db;
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public MainWindowModel() : this(null!, Arguments.Empty) {}
|
||||||
|
|
||||||
|
public MainWindowModel(Window window, Arguments args) {
|
||||||
|
this.window = window;
|
||||||
|
|
||||||
|
WelcomeScreenModel = new WelcomeScreenModel(window);
|
||||||
|
WelcomeScreen = new WelcomeScreen { DataContext = WelcomeScreenModel };
|
||||||
|
|
||||||
|
WelcomeScreenModel.PropertyChanged += WelcomeScreenModelOnPropertyChanged;
|
||||||
|
|
||||||
|
var dbFile = args.DatabaseFile;
|
||||||
|
if (!string.IsNullOrWhiteSpace(dbFile)) {
|
||||||
|
async void OnWindowOpened(object? o, EventArgs eventArgs) {
|
||||||
|
window.Opened -= OnWindowOpened;
|
||||||
|
|
||||||
|
// https://github.com/AvaloniaUI/Avalonia/issues/3071
|
||||||
|
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
|
||||||
|
await Task.Delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(dbFile)) {
|
||||||
|
await WelcomeScreenModel.OpenOrCreateDatabaseFromPath(dbFile);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await Dialog.ShowOk(window, "Database Error", "Database file not found:\n" + dbFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Opened += OnWindowOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.ServerPort != null) {
|
||||||
|
TrackingPageModel.ServerPort = args.ServerPort.ToString()!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.ServerToken != null) {
|
||||||
|
TrackingPageModel.ServerToken = args.ServerToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WelcomeScreenModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName == nameof(WelcomeScreenModel.Db)) {
|
||||||
|
if (MainContentScreenModel != null) {
|
||||||
|
MainContentScreenModel.DatabaseClosed -= MainContentScreenModelOnDatabaseClosed;
|
||||||
|
MainContentScreenModel.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
db?.Dispose();
|
||||||
|
db = WelcomeScreenModel.Db;
|
||||||
|
|
||||||
|
if (db == null) {
|
||||||
|
MainContentScreenModel = null;
|
||||||
|
MainContentScreen = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
MainContentScreenModel = new MainContentScreenModel(window, db);
|
||||||
|
MainContentScreenModel.Initialize();
|
||||||
|
MainContentScreenModel.DatabaseClosed += MainContentScreenModelOnDatabaseClosed;
|
||||||
|
MainContentScreen = new MainContentScreen { DataContext = MainContentScreenModel };
|
||||||
|
OnPropertyChanged(nameof(MainContentScreen));
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(ShowWelcomeScreen));
|
||||||
|
OnPropertyChanged(nameof(ShowMainContentScreen));
|
||||||
|
|
||||||
|
window.Focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void MainContentScreenModelOnDatabaseClosed(object? sender, EventArgs e) {
|
||||||
|
WelcomeScreenModel.CloseDatabase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
21
app/Desktop/Main/Pages/DatabasePage.axaml
Normal file
21
app/Desktop/Main/Pages/DatabasePage.axaml
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="DHT.Desktop.Main.Pages.DatabasePage">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<pages:DatabasePageModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<DockPanel>
|
||||||
|
<Button Command="{Binding CloseDatabase}" DockPanel.Dock="Right">Close Database</Button>
|
||||||
|
<TextBox Text="{Binding Db.Path}" Width="NaN" Margin="0 0 10 0" IsEnabled="True" />
|
||||||
|
</DockPanel>
|
||||||
|
<Button Command="{Binding OpenDatabaseFolder}">Open Database Folder</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
14
app/Desktop/Main/Pages/DatabasePage.axaml.cs
Normal file
14
app/Desktop/Main/Pages/DatabasePage.axaml.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Pages {
|
||||||
|
public class DatabasePage : UserControl {
|
||||||
|
public DatabasePage() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
58
app/Desktop/Main/Pages/DatabasePageModel.cs
Normal file
58
app/Desktop/Main/Pages/DatabasePageModel.cs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using DHT.Desktop.Dialogs;
|
||||||
|
using DHT.Desktop.Models;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
using DHT.Server.Service;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Pages {
|
||||||
|
public class DatabasePageModel : BaseModel {
|
||||||
|
public IDatabaseFile Db { get; }
|
||||||
|
|
||||||
|
public event EventHandler? DatabaseClosed;
|
||||||
|
|
||||||
|
private readonly Window window;
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public DatabasePageModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||||
|
|
||||||
|
public DatabasePageModel(Window window, IDatabaseFile db) {
|
||||||
|
this.window = window;
|
||||||
|
this.Db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OpenDatabaseFolder() {
|
||||||
|
string file = Db.Path;
|
||||||
|
string? folder = Path.GetDirectoryName(file);
|
||||||
|
|
||||||
|
if (folder == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (Environment.OSVersion.Platform) {
|
||||||
|
case PlatformID.Win32NT:
|
||||||
|
Process.Start("explorer.exe", "/select,\"" + file + "\"");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlatformID.Unix:
|
||||||
|
Process.Start("xdg-open", new string[] { folder });
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PlatformID.MacOSX:
|
||||||
|
Process.Start("open", new string[] { folder });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
await Dialog.ShowOk(window, "Feature Not Supported", "This feature is not supported for your operating system.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseDatabase() {
|
||||||
|
ServerLauncher.Stop();
|
||||||
|
DatabaseClosed?.Invoke(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
54
app/Desktop/Main/Pages/TrackingPage.axaml
Normal file
54
app/Desktop/Main/Pages/TrackingPage.axaml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="DHT.Desktop.Main.Pages.TrackingPage">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<pages:TrackingPageModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="TextBox">
|
||||||
|
<Setter Property="FontFamily" Value="Consolas,Courier" />
|
||||||
|
<Setter Property="FontSize" Value="15" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<StackPanel Spacing="10">
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
To start tracking messages, copy the tracking script and paste it into the console of either the Discord app (Ctrl+Shift+I), or your browser with Discord open.
|
||||||
|
</TextBlock>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button x:Name="CopyTrackingScript" Click="CopyTrackingScriptButton_OnClick">Copy Tracking Script</Button>
|
||||||
|
<Button Command="{Binding OnClickToggleButton}" Content="{Binding ToggleButtonText}" IsEnabled="{Binding IsToggleButtonEnabled}" />
|
||||||
|
</StackPanel>
|
||||||
|
<Expander Header="Advanced Settings">
|
||||||
|
<StackPanel Spacing="10" Margin="0 10 0 0">
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
The following settings determine how the tracking script communicates with this application. If you change them, you will have to copy and apply the tracking script again.
|
||||||
|
</TextBlock>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<Label Target="Port">Port</Label>
|
||||||
|
<TextBox x:Name="Port" Width="70" Text="{Binding InputPort}" />
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<Label Target="Token">Token</Label>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBox x:Name="Token" Width="200" Text="{Binding InputToken}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<Button Command="{Binding OnClickRandomizeToken}" VerticalAlignment="Bottom">Randomize Token</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="10">
|
||||||
|
<Button IsEnabled="{Binding HasMadeChanges}" Command="{Binding OnClickApplyChanges}">Apply & Restart</Button>
|
||||||
|
<Button IsEnabled="{Binding HasMadeChanges}" Command="{Binding OnClickCancelChanges}">Cancel</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Expander>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
38
app/Desktop/Main/Pages/TrackingPage.axaml.cs
Normal file
38
app/Desktop/Main/Pages/TrackingPage.axaml.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Interactivity;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Pages {
|
||||||
|
public class TrackingPage : UserControl {
|
||||||
|
private bool isCopyingScript;
|
||||||
|
|
||||||
|
public TrackingPage() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void CopyTrackingScriptButton_OnClick(object? sender, RoutedEventArgs e) {
|
||||||
|
if (DataContext is TrackingPageModel model) {
|
||||||
|
var button = this.FindControl<Button>("CopyTrackingScript");
|
||||||
|
var originalText = button.Content;
|
||||||
|
button.MinWidth = button.Bounds.Width;
|
||||||
|
|
||||||
|
await model.OnClickCopyTrackingScript();
|
||||||
|
|
||||||
|
if (!isCopyingScript) {
|
||||||
|
isCopyingScript = true;
|
||||||
|
button.Content = "Script Copied!";
|
||||||
|
|
||||||
|
await Task.Delay(TimeSpan.FromSeconds(2));
|
||||||
|
button.Content = originalText;
|
||||||
|
isCopyingScript = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
150
app/Desktop/Main/Pages/TrackingPageModel.cs
Normal file
150
app/Desktop/Main/Pages/TrackingPageModel.cs
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using DHT.Desktop.Dialogs;
|
||||||
|
using DHT.Desktop.Main.Controls;
|
||||||
|
using DHT.Desktop.Models;
|
||||||
|
using DHT.Desktop.Resources;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
using DHT.Server.Logging;
|
||||||
|
using DHT.Server.Service;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Pages {
|
||||||
|
public class TrackingPageModel : BaseModel, IDisposable {
|
||||||
|
internal static string ServerPort { get; set; } = ServerUtils.FindAvailablePort(50000, 60000).ToString();
|
||||||
|
internal static string ServerToken { get; set; } = ServerUtils.GenerateRandomToken(20);
|
||||||
|
|
||||||
|
private string inputPort = ServerPort;
|
||||||
|
|
||||||
|
public string InputPort {
|
||||||
|
get => inputPort;
|
||||||
|
set {
|
||||||
|
Change(ref inputPort, value);
|
||||||
|
OnPropertyChanged(nameof(HasMadeChanges));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string inputToken = ServerToken;
|
||||||
|
|
||||||
|
public string InputToken {
|
||||||
|
get => inputToken;
|
||||||
|
set {
|
||||||
|
Change(ref inputToken, value);
|
||||||
|
OnPropertyChanged(nameof(HasMadeChanges));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasMadeChanges => ServerPort != InputPort || ServerToken != InputToken;
|
||||||
|
|
||||||
|
private bool isToggleButtonEnabled = true;
|
||||||
|
|
||||||
|
public bool IsToggleButtonEnabled {
|
||||||
|
get => isToggleButtonEnabled;
|
||||||
|
set => Change(ref isToggleButtonEnabled, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToggleButtonText => ServerLauncher.IsRunning ? "Pause Tracking" : "Resume Tracking";
|
||||||
|
|
||||||
|
public event EventHandler<StatusBarModel.Status>? ServerStatusChanged;
|
||||||
|
|
||||||
|
private readonly Window window;
|
||||||
|
private readonly IDatabaseFile db;
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public TrackingPageModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||||
|
|
||||||
|
public TrackingPageModel(Window window, IDatabaseFile db) {
|
||||||
|
this.window = window;
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize() {
|
||||||
|
ServerLauncher.ServerStatusChanged += ServerLauncherOnServerStatusChanged;
|
||||||
|
ServerLauncher.ServerManagementExceptionCaught += ServerLauncherOnServerManagementExceptionCaught;
|
||||||
|
|
||||||
|
if (int.TryParse(ServerPort, out int port)) {
|
||||||
|
string token = ServerToken;
|
||||||
|
ServerLauncher.Relaunch(port, token, db);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose() {
|
||||||
|
ServerLauncher.ServerManagementExceptionCaught -= ServerLauncherOnServerManagementExceptionCaught;
|
||||||
|
ServerLauncher.ServerStatusChanged -= ServerLauncherOnServerStatusChanged;
|
||||||
|
ServerLauncher.Stop();
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> StartServer() {
|
||||||
|
if (!int.TryParse(InputPort, out int port) || port is < 0 or > 65535) {
|
||||||
|
await Dialog.ShowOk(window, "Invalid Port", "Port must be a number between 0 and 65535.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsToggleButtonEnabled = false;
|
||||||
|
ServerStatusChanged?.Invoke(this, StatusBarModel.Status.Starting);
|
||||||
|
ServerLauncher.Relaunch(port, InputToken, db);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopServer() {
|
||||||
|
IsToggleButtonEnabled = false;
|
||||||
|
ServerStatusChanged?.Invoke(this, StatusBarModel.Status.Stopping);
|
||||||
|
ServerLauncher.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<bool> OnClickToggleButton() {
|
||||||
|
if (ServerLauncher.IsRunning) {
|
||||||
|
StopServer();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return await StartServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnClickCopyTrackingScript() {
|
||||||
|
string bootstrap = await ResourceLoader.ReadTextAsync("Tracker/bootstrap.js");
|
||||||
|
string script = bootstrap.Replace("= 0; /*[PORT]*/", "= " + ServerPort + ";")
|
||||||
|
.Replace("/*[TOKEN]*/", HttpUtility.JavaScriptStringEncode(ServerToken))
|
||||||
|
.Replace("/*[IMPORTS]*/", await ResourceLoader.ReadJoinedAsync("Tracker/scripts/", '\n'))
|
||||||
|
.Replace("/*[CSS-CONTROLLER]*/", await ResourceLoader.ReadTextAsync("Tracker/styles/controller.css"))
|
||||||
|
.Replace("/*[CSS-SETTINGS]*/", await ResourceLoader.ReadTextAsync("Tracker/styles/settings.css"));
|
||||||
|
|
||||||
|
await Application.Current.Clipboard.SetTextAsync(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnClickRandomizeToken() {
|
||||||
|
InputToken = ServerUtils.GenerateRandomToken(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnClickApplyChanges() {
|
||||||
|
if (await StartServer()) {
|
||||||
|
ServerPort = InputPort;
|
||||||
|
ServerToken = InputToken;
|
||||||
|
OnPropertyChanged(nameof(HasMadeChanges));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnClickCancelChanges() {
|
||||||
|
InputPort = ServerPort;
|
||||||
|
InputToken = ServerToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ServerLauncherOnServerStatusChanged(object? sender, EventArgs e) {
|
||||||
|
ServerStatusChanged?.Invoke(this, ServerLauncher.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped);
|
||||||
|
OnPropertyChanged(nameof(ToggleButtonText));
|
||||||
|
IsToggleButtonEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ServerLauncherOnServerManagementExceptionCaught(object? sender, Exception ex) {
|
||||||
|
Log.Error(ex);
|
||||||
|
|
||||||
|
string message = ex.Message;
|
||||||
|
Dispatcher.UIThread.Post(async () => { await Dialog.ShowOk(window, "Server Error", message); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
app/Desktop/Main/Pages/ViewerPage.axaml
Normal file
44
app/Desktop/Main/Pages/ViewerPage.axaml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:pages="clr-namespace:DHT.Desktop.Main.Pages"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="DHT.Desktop.Main.Pages.ViewerPage">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<pages:ViewerPageModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="Grid > Label">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Grid > CalendarDatePicker">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="IsTodayHighlighted" Value="True" />
|
||||||
|
<Setter Property="SelectedDateFormat" Value="Short" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" Spacing="20">
|
||||||
|
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
|
||||||
|
<Button Command="{Binding OnClickOpenViewer}" Margin="0 0 5 0">Open Viewer</Button>
|
||||||
|
<Button Command="{Binding OnClickSaveViewer}" Margin="5 0 0 0">Save Viewer</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock Text="{Binding ExportedMessageText}" />
|
||||||
|
<StackPanel>
|
||||||
|
<CheckBox IsChecked="{Binding FilterByDate}">Filter by Date</CheckBox>
|
||||||
|
<Grid ColumnDefinitions="Auto, 4, 140" RowDefinitions="Auto, 4, Auto" Margin="4">
|
||||||
|
<Label Grid.Row="0" Grid.Column="0">From:</Label>
|
||||||
|
<CalendarDatePicker Grid.Row="0" Grid.Column="2" x:Name="StartDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||||
|
<Label Grid.Row="2" Grid.Column="0">To:</Label>
|
||||||
|
<CalendarDatePicker Grid.Row="2" Grid.Column="2" x:Name="EndDatePicker" IsEnabled="{Binding FilterByDate}" SelectedDateChanged="CalendarDatePicker_OnSelectedDateChanged" />
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
21
app/Desktop/Main/Pages/ViewerPage.axaml.cs
Normal file
21
app/Desktop/Main/Pages/ViewerPage.axaml.cs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Pages {
|
||||||
|
public class ViewerPage : UserControl {
|
||||||
|
public ViewerPage() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CalendarDatePicker_OnSelectedDateChanged(object? sender, SelectionChangedEventArgs e) {
|
||||||
|
if (DataContext is ViewerPageModel model) {
|
||||||
|
model.StartDate = this.FindControl<CalendarDatePicker>("StartDatePicker").SelectedDate;
|
||||||
|
model.EndDate = this.FindControl<CalendarDatePicker>("EndDatePicker").SelectedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
128
app/Desktop/Main/Pages/ViewerPageModel.cs
Normal file
128
app/Desktop/Main/Pages/ViewerPageModel.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Web;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using DHT.Desktop.Models;
|
||||||
|
using DHT.Desktop.Resources;
|
||||||
|
using DHT.Server.Data.Filters;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
using DHT.Server.Database.Export;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main.Pages {
|
||||||
|
public class ViewerPageModel : BaseModel {
|
||||||
|
public string ExportedMessageText { get; private set; } = "";
|
||||||
|
|
||||||
|
private bool filterByDate = false;
|
||||||
|
|
||||||
|
public bool FilterByDate {
|
||||||
|
get => filterByDate;
|
||||||
|
set => Change(ref filterByDate, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? startDate = null;
|
||||||
|
|
||||||
|
public DateTime? StartDate {
|
||||||
|
get => startDate;
|
||||||
|
set => Change(ref startDate, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DateTime? endDate = null;
|
||||||
|
|
||||||
|
public DateTime? EndDate {
|
||||||
|
get => endDate;
|
||||||
|
set => Change(ref endDate, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Window window;
|
||||||
|
private readonly IDatabaseFile db;
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public ViewerPageModel() : this(null!, DummyDatabaseFile.Instance) {}
|
||||||
|
|
||||||
|
public ViewerPageModel(Window window, IDatabaseFile db) {
|
||||||
|
this.window = window;
|
||||||
|
this.db = db;
|
||||||
|
|
||||||
|
this.PropertyChanged += OnPropertyChanged;
|
||||||
|
this.db.Statistics.PropertyChanged += OnDbStatisticsChanged;
|
||||||
|
UpdateStatistics();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName is nameof(FilterByDate) or nameof(StartDate) or nameof(EndDate)) {
|
||||||
|
UpdateStatistics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDbStatisticsChanged(object? sender, PropertyChangedEventArgs e) {
|
||||||
|
if (e.PropertyName == nameof(DatabaseStatistics.TotalMessages)) {
|
||||||
|
UpdateStatistics();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private MessageFilter CreateFilter() {
|
||||||
|
MessageFilter filter = new();
|
||||||
|
|
||||||
|
if (FilterByDate) {
|
||||||
|
filter.StartDate = StartDate;
|
||||||
|
filter.EndDate = EndDate;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateStatistics() {
|
||||||
|
ExportedMessageText = "Will export " + db.CountMessages(CreateFilter()) + " out of " + db.Statistics.TotalMessages + " message(s).";
|
||||||
|
OnPropertyChanged(nameof(ExportedMessageText));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<string> GenerateViewerContents() {
|
||||||
|
string json = ViewerJsonExport.Generate(db, CreateFilter());
|
||||||
|
|
||||||
|
string index = await ResourceLoader.ReadTextAsync("Viewer/index.html");
|
||||||
|
string viewer = index.Replace("/*[JS]*/", await ResourceLoader.ReadJoinedAsync("Viewer/scripts/", '\n'))
|
||||||
|
.Replace("/*[CSS]*/", await ResourceLoader.ReadJoinedAsync("Viewer/styles/", '\n'))
|
||||||
|
.Replace("/*[ARCHIVE]*/", HttpUtility.JavaScriptStringEncode(json));
|
||||||
|
return viewer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnClickOpenViewer() {
|
||||||
|
string rootPath = Path.Combine(Path.GetTempPath(), "DiscordHistoryTracker");
|
||||||
|
string filenameBase = Path.GetFileNameWithoutExtension(db.Path) + "-" + DateTime.Now.ToString("yyyy-MM-dd");
|
||||||
|
string fullPath = Path.Combine(rootPath, filenameBase + ".html");
|
||||||
|
int counter = 0;
|
||||||
|
|
||||||
|
while (File.Exists(fullPath)) {
|
||||||
|
fullPath = Path.Combine(rootPath, filenameBase + "-" + (++counter) + ".html");
|
||||||
|
}
|
||||||
|
|
||||||
|
Directory.CreateDirectory(rootPath);
|
||||||
|
await File.WriteAllTextAsync(fullPath, await GenerateViewerContents());
|
||||||
|
|
||||||
|
Process.Start(new ProcessStartInfo(fullPath) { UseShellExecute = true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OnClickSaveViewer() {
|
||||||
|
var dialog = new SaveFileDialog {
|
||||||
|
Title = "Save Viewer",
|
||||||
|
InitialFileName = "archive.html",
|
||||||
|
Directory = Path.GetDirectoryName(db.Path),
|
||||||
|
Filters = new List<FileDialogFilter> {
|
||||||
|
new() {
|
||||||
|
Name = "Discord History Viewer",
|
||||||
|
Extensions = { "html" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.ShowAsync(window);
|
||||||
|
|
||||||
|
string path = await dialog;
|
||||||
|
if (!string.IsNullOrEmpty(path)) {
|
||||||
|
await File.WriteAllTextAsync(path, await GenerateViewerContents());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
40
app/Desktop/Main/WelcomeScreen.axaml
Normal file
40
app/Desktop/Main/WelcomeScreen.axaml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
<UserControl xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:main="clr-namespace:DHT.Desktop.Main"
|
||||||
|
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||||
|
x:Class="DHT.Desktop.Main.WelcomeScreen">
|
||||||
|
|
||||||
|
<Design.DataContext>
|
||||||
|
<main:WelcomeScreenModel />
|
||||||
|
</Design.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Background>
|
||||||
|
<SolidColorBrush>#546A9F</SolidColorBrush>
|
||||||
|
</UserControl.Background>
|
||||||
|
|
||||||
|
<UserControl.Styles>
|
||||||
|
<Style Selector="Panel#RootPanel">
|
||||||
|
<Setter Property="Background" Value="#FFFFFF" />
|
||||||
|
<Setter Property="Margin" Value="20" />
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Center" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="Button">
|
||||||
|
<Setter Property="Margin" Value="5 0" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Styles>
|
||||||
|
|
||||||
|
<Panel Name="RootPanel">
|
||||||
|
<StackPanel Margin="42">
|
||||||
|
<TextBlock Text="{Binding Version, StringFormat=Discord History Tracker v.{0}}" FontSize="25" Margin="0 0 0 30" HorizontalAlignment="Center" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||||
|
<Button Command="{Binding OpenOrCreateDatabase}">Open or Create Database</Button>
|
||||||
|
<Button Command="{Binding ShowAboutDialog}">About</Button>
|
||||||
|
<Button Command="{Binding Exit}">Exit</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
</Panel>
|
||||||
|
</UserControl>
|
14
app/Desktop/Main/WelcomeScreen.axaml.cs
Normal file
14
app/Desktop/Main/WelcomeScreen.axaml.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class WelcomeScreen : UserControl {
|
||||||
|
public WelcomeScreen() {
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent() {
|
||||||
|
AvaloniaXamlLoader.Load(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
91
app/Desktop/Main/WelcomeScreenModel.cs
Normal file
91
app/Desktop/Main/WelcomeScreenModel.cs
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using DHT.Desktop.Dialogs;
|
||||||
|
using DHT.Desktop.Models;
|
||||||
|
using DHT.Server.Database;
|
||||||
|
using DHT.Server.Database.Exceptions;
|
||||||
|
using DHT.Server.Database.Sqlite;
|
||||||
|
using DHT.Server.Logging;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Main {
|
||||||
|
public class WelcomeScreenModel : BaseModel {
|
||||||
|
public string Version => Program.Version;
|
||||||
|
|
||||||
|
public IDatabaseFile? Db { get; private set; }
|
||||||
|
public bool HasDatabase => Db != null;
|
||||||
|
|
||||||
|
private readonly Window window;
|
||||||
|
|
||||||
|
private string? dbFilePath;
|
||||||
|
|
||||||
|
[Obsolete("Designer")]
|
||||||
|
public WelcomeScreenModel() : this(null!) {}
|
||||||
|
|
||||||
|
public WelcomeScreenModel(Window window) {
|
||||||
|
this.window = window;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void OpenOrCreateDatabase() {
|
||||||
|
var dialog = new SaveFileDialog {
|
||||||
|
Title = "Open or Create Database File",
|
||||||
|
InitialFileName = "archive.dht",
|
||||||
|
Directory = Path.GetDirectoryName(dbFilePath),
|
||||||
|
Filters = new List<FileDialogFilter> {
|
||||||
|
new() {
|
||||||
|
Name = "Discord History Tracker Database",
|
||||||
|
Extensions = { "dht" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.ShowAsync(window);
|
||||||
|
|
||||||
|
string path = await dialog;
|
||||||
|
if (!string.IsNullOrWhiteSpace(path)) {
|
||||||
|
await OpenOrCreateDatabaseFromPath(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OpenOrCreateDatabaseFromPath(string path) {
|
||||||
|
if (Db != null) {
|
||||||
|
Db = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
dbFilePath = path;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Db = await SqliteDatabaseFile.OpenOrCreate(path, CheckCanUpgradeDatabase);
|
||||||
|
} catch (InvalidDatabaseVersionException ex) {
|
||||||
|
await Dialog.ShowOk(window, "Database Error", "This database appears to be corrupted (invalid version: " + ex.Version + ").");
|
||||||
|
} catch (DatabaseTooNewException ex) {
|
||||||
|
await Dialog.ShowOk(window, "Database Error", "This database was opened in a newer version of DHT (database version " + ex.DatabaseVersion + ", app version " + ex.CurrentVersion + ").");
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Log.Error(ex);
|
||||||
|
await Dialog.ShowOk(window, "Database Error", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(Db));
|
||||||
|
OnPropertyChanged(nameof(HasDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> CheckCanUpgradeDatabase() {
|
||||||
|
return DialogResult.YesNo.Yes == await Dialog.ShowYesNo(window, "Database Upgrade", "This database was created with an older version of DHT. If you proceed, the database will be upgraded and will no longer open in previous versions of DHT. Do you want to proceed?");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseDatabase() {
|
||||||
|
Db = null;
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(Db));
|
||||||
|
OnPropertyChanged(nameof(HasDatabase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void ShowAboutDialog() {
|
||||||
|
await new AboutWindow() { DataContext = new AboutWindowModel() }.ShowDialog(this.window);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Exit() {
|
||||||
|
window.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
app/Desktop/Models/BaseModel.cs
Normal file
22
app/Desktop/Models/BaseModel.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Models {
|
||||||
|
public abstract class BaseModel : INotifyPropertyChanged {
|
||||||
|
public event PropertyChangedEventHandler? PropertyChanged;
|
||||||
|
|
||||||
|
[NotifyPropertyChangedInvocator]
|
||||||
|
protected void OnPropertyChanged([CallerMemberName] string? propertyName = null) {
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Change<T>(ref T field, T newValue, [CallerMemberName] string? propertyName = null) {
|
||||||
|
if (!EqualityComparer<T>.Default.Equals(field, newValue)) {
|
||||||
|
field = newValue;
|
||||||
|
OnPropertyChanged(propertyName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
app/Desktop/Program.cs
Normal file
31
app/Desktop/Program.cs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using System.Reflection;
|
||||||
|
using Avalonia;
|
||||||
|
|
||||||
|
namespace DHT.Desktop {
|
||||||
|
internal static class Program {
|
||||||
|
public static string Version { get; }
|
||||||
|
|
||||||
|
static Program() {
|
||||||
|
Version = Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "";
|
||||||
|
while (Version.EndsWith(".0")) {
|
||||||
|
Version = Version[..^2];
|
||||||
|
}
|
||||||
|
|
||||||
|
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
CultureInfo.CurrentUICulture = CultureInfo.InvariantCulture;
|
||||||
|
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.InvariantCulture;
|
||||||
|
CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.InvariantCulture;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Main(string[] args) {
|
||||||
|
BuildAvaloniaApp().StartWithClassicDesktopLifetime(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static AppBuilder BuildAvaloniaApp() {
|
||||||
|
return AppBuilder.Configure<App>()
|
||||||
|
.UsePlatformDetect()
|
||||||
|
.LogToTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
app/Desktop/Resources/ResourceLoader.cs
Normal file
44
app/Desktop/Resources/ResourceLoader.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DHT.Desktop.Resources {
|
||||||
|
public static class ResourceLoader {
|
||||||
|
private static Stream GetEmbeddedStream(string filename) {
|
||||||
|
Stream? stream = null;
|
||||||
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||||
|
foreach (var embeddedName in assembly.GetManifestResourceNames()) {
|
||||||
|
if (embeddedName.Replace('\\', '/') == filename) {
|
||||||
|
stream = assembly.GetManifestResourceStream(embeddedName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return stream ?? throw new ArgumentException("Missing embedded resource: " + filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> ReadTextAsync(Stream stream) {
|
||||||
|
using var reader = new StreamReader(stream, Encoding.UTF8);
|
||||||
|
return await reader.ReadToEndAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> ReadTextAsync(string filename) {
|
||||||
|
return await ReadTextAsync(GetEmbeddedStream(filename));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<string> ReadJoinedAsync(string path, char separator) {
|
||||||
|
StringBuilder joined = new();
|
||||||
|
|
||||||
|
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||||
|
foreach (var embeddedName in assembly.GetManifestResourceNames()) {
|
||||||
|
if (embeddedName.Replace('\\', '/').StartsWith(path)) {
|
||||||
|
joined.Append(await ReadTextAsync(assembly.GetManifestResourceStream(embeddedName)!)).Append(separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return joined.ToString(0, Math.Max(0, joined.Length - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
BIN
app/Desktop/Resources/icon.ico
Normal file
BIN
app/Desktop/Resources/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
22
app/DiscordHistoryTracker.sln
Normal file
22
app/DiscordHistoryTracker.sln
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Desktop\Desktop.csproj", "{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Server\Server.csproj", "{7F94B470-B06F-43C0-9655-6592A9AE2D92}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6E7E573B-B13E-4F45-B3E3-1BE722DCAACD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{7F94B470-B06F-43C0-9655-6592A9AE2D92}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
BIN
app/Resources/Icons/16.png
Normal file
BIN
app/Resources/Icons/16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
app/Resources/Icons/24.png
Normal file
BIN
app/Resources/Icons/24.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
app/Resources/Icons/256.png
Normal file
BIN
app/Resources/Icons/256.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
BIN
app/Resources/Icons/32.png
Normal file
BIN
app/Resources/Icons/32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
BIN
app/Resources/Icons/48.png
Normal file
BIN
app/Resources/Icons/48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
BIN
app/Resources/Icons/icon.afdesign
Normal file
BIN
app/Resources/Icons/icon.afdesign
Normal file
Binary file not shown.
42
app/Resources/Schemas/track-channel.yml
Normal file
42
app/Resources/Schemas/track-channel.yml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
$schema: http://json-schema.org/draft-07/schema
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
server:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- SERVER
|
||||||
|
- GROUP
|
||||||
|
- DM
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- type
|
||||||
|
channel:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
position:
|
||||||
|
type: number
|
||||||
|
minimum: 0
|
||||||
|
topic:
|
||||||
|
type: string
|
||||||
|
nsfw:
|
||||||
|
type: boolean
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
required:
|
||||||
|
- server
|
||||||
|
- channel
|
81
app/Resources/Schemas/track-messages.yml
Normal file
81
app/Resources/Schemas/track-messages.yml
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
$schema: http://json-schema.org/draft-07/schema
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
sender:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
channel:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
text:
|
||||||
|
type: string
|
||||||
|
timestamp:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
editTimestamp:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
repliedToId:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
attachments:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
size:
|
||||||
|
type: number
|
||||||
|
minimum: 0
|
||||||
|
url:
|
||||||
|
type: string
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
- size
|
||||||
|
- url
|
||||||
|
embeds:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
pattern: "^\{.*\}$"
|
||||||
|
reactions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
isAnimated:
|
||||||
|
type: boolean
|
||||||
|
count:
|
||||||
|
type: number
|
||||||
|
minimum: 1
|
||||||
|
anyOf:
|
||||||
|
- required:
|
||||||
|
- id
|
||||||
|
- count
|
||||||
|
- required:
|
||||||
|
- name
|
||||||
|
- count
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- sender
|
||||||
|
- channel
|
||||||
|
- text
|
||||||
|
- timestamp
|
19
app/Resources/Schemas/track-users.yml
Normal file
19
app/Resources/Schemas/track-users.yml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
$schema: http://json-schema.org/draft-07/schema
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
anyOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]+$"
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
avatar:
|
||||||
|
type: string
|
||||||
|
discriminator:
|
||||||
|
type: string
|
||||||
|
pattern: "^[0-9]{4}$"
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
160
app/Resources/Tracker/bootstrap.js
vendored
Normal file
160
app/Resources/Tracker/bootstrap.js
vendored
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
(function() {
|
||||||
|
const url = window.location.href;
|
||||||
|
|
||||||
|
if (!url.includes("discord.com/") && !url.includes("discordapp.com/") && !confirm("Could not detect Discord in the URL, do you want to run the script anyway?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.DHT_LOADED) {
|
||||||
|
alert("Discord History Tracker is already loaded.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.DHT_LOADED = true;
|
||||||
|
window.DHT_ON_UNLOAD = [];
|
||||||
|
|
||||||
|
/*[IMPORTS]*/
|
||||||
|
|
||||||
|
const port = 0; /*[PORT]*/
|
||||||
|
const token = "/*[TOKEN]*/";
|
||||||
|
STATE.setup(port, token);
|
||||||
|
|
||||||
|
let delayedStopRequests = 0;
|
||||||
|
const stopTrackingDelayed = function(callback) {
|
||||||
|
delayedStopRequests++;
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
STATE.setIsTracking(false);
|
||||||
|
delayedStopRequests--;
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}, 200); // give the user visual feedback after clicking the button before switching off
|
||||||
|
};
|
||||||
|
|
||||||
|
let hasJustStarted = false;
|
||||||
|
let isSending = false;
|
||||||
|
|
||||||
|
const onError = function(e) {
|
||||||
|
console.log(e);
|
||||||
|
GUI.setStatus(e.status === "DISCONNECTED" ? "Disconnected" : "Error");
|
||||||
|
stopTrackingDelayed(() => isSending = false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTrackingContinued = function(anyNewMessages) {
|
||||||
|
if (!STATE.isTracking()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.setStatus("Tracking");
|
||||||
|
|
||||||
|
if (hasJustStarted) {
|
||||||
|
anyNewMessages = true;
|
||||||
|
hasJustStarted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSending = false;
|
||||||
|
|
||||||
|
if (SETTINGS.autoscroll) {
|
||||||
|
let action = null;
|
||||||
|
|
||||||
|
if (!anyNewMessages) {
|
||||||
|
action = SETTINGS.afterSavedMsg;
|
||||||
|
}
|
||||||
|
else if (!DISCORD.hasMoreMessages()) {
|
||||||
|
action = SETTINGS.afterFirstMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action === null || action === CONSTANTS.AUTOSCROLL_ACTION_NOTHING) {
|
||||||
|
DISCORD.loadOlderMessages();
|
||||||
|
}
|
||||||
|
else if (action === CONSTANTS.AUTOSCROLL_ACTION_PAUSE || (action === CONSTANTS.AUTOSCROLL_ACTION_SWITCH && !DISCORD.selectNextTextChannel())) {
|
||||||
|
GUI.setStatus("Reached End");
|
||||||
|
STATE.setIsTracking(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let waitUntilSendingFinishedTimer = null;
|
||||||
|
|
||||||
|
const onMessagesUpdated = async messages => {
|
||||||
|
if (!STATE.isTracking() || delayedStopRequests > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSending) {
|
||||||
|
window.clearTimeout(waitUntilSendingFinishedTimer);
|
||||||
|
|
||||||
|
waitUntilSendingFinishedTimer = window.setTimeout(() => {
|
||||||
|
waitUntilSendingFinishedTimer = null;
|
||||||
|
onMessagesUpdated(messages);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = DISCORD.getSelectedChannel();
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
GUI.setStatus("Stopped");
|
||||||
|
stopTrackingDelayed();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSending = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await STATE.addDiscordChannel(info.server, info.channel);
|
||||||
|
} catch (e) {
|
||||||
|
onError(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!messages.length) {
|
||||||
|
DISCORD.loadOlderMessages();
|
||||||
|
isSending = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const anyNewMessages = await STATE.addDiscordMessages(info.id, messages);
|
||||||
|
onTrackingContinued(anyNewMessages);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
onError(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const callbackTimer = DISCORD.setupMessageCallback(onMessagesUpdated);
|
||||||
|
window.DHT_ON_UNLOAD.push(() => window.clearTimeout(callbackTimer));
|
||||||
|
|
||||||
|
STATE.onTrackingStateChanged(enabled => {
|
||||||
|
if (enabled) {
|
||||||
|
if (DISCORD.getSelectedChannel() == null) {
|
||||||
|
stopTrackingDelayed(() => alert("The selected channel is not visible in the channel list."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = DISCORD.getMessages();
|
||||||
|
|
||||||
|
if (messages == null) {
|
||||||
|
stopTrackingDelayed(() => alert("Cannot see any messages."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GUI.setStatus("Starting");
|
||||||
|
hasJustStarted = true;
|
||||||
|
// noinspection JSIgnoredPromiseFromCall
|
||||||
|
onMessagesUpdated(messages);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isSending = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
GUI.showController();
|
||||||
|
|
||||||
|
if (IS_FIRST_RUN) {
|
||||||
|
GUI.showSettings();
|
||||||
|
}
|
||||||
|
})();
|
1
app/Resources/Tracker/scripts.min/discord.js
Normal file
1
app/Resources/Tracker/scripts.min/discord.js
Normal file
File diff suppressed because one or more lines are too long
1
app/Resources/Tracker/scripts.min/dom.js
Normal file
1
app/Resources/Tracker/scripts.min/dom.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
class DOM{static id(id,parent){return(parent||document).getElementById(id)}static queryReactClass(cls,parent){return(parent||document).querySelector(`[class*="${cls}-"]`)}static createElement(tag,parent,id,html){const ele=document.createElement(tag);ele.id=id||"";ele.innerHTML=html||"";parent.appendChild(ele);return ele}static removeElement(ele){return ele.parentNode.removeChild(ele)}static createStyle(styles){return this.createElement("style",document.head,"",styles)}static saveToCookie(name,obj,expiresInSeconds){const expires=new Date(Date.now()+1e3*expiresInSeconds).toUTCString();document.cookie=name+"="+encodeURIComponent(JSON.stringify(obj))+";path=/;expires="+expires}static loadFromCookie(name){const value=document.cookie.replace(new RegExp("(?:(?:^|.*;\\s*)"+name+"\\s*\\=\\s*([^;]*).*$)|^.*$"),"$1");return value.length?JSON.parse(decodeURIComponent(value)):null}static getReactProps(ele){const keys=Object.keys(ele||{});let key=keys.find(key=>key.startsWith("__reactInternalInstance"));if(key){return ele[key].memoizedProps}key=keys.find(key=>key.startsWith("__reactProps$"));return key?ele[key]:null}}
|
17
app/Resources/Tracker/scripts.min/gui.js
Normal file
17
app/Resources/Tracker/scripts.min/gui.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const GUI=function(){let controller=null;let settings=null;const stateChangedEvent=()=>{if(settings){settings.ui.cbAutoscroll.checked=SETTINGS.autoscroll;settings.ui.optsAfterFirstMsg[SETTINGS.afterFirstMsg].checked=true;settings.ui.optsAfterSavedMsg[SETTINGS.afterSavedMsg].checked=true;const autoscrollDisabled=!SETTINGS.autoscroll;Object.values(settings.ui.optsAfterFirstMsg).forEach(ele=>ele.disabled=autoscrollDisabled);Object.values(settings.ui.optsAfterSavedMsg).forEach(ele=>ele.disabled=autoscrollDisabled)}};return{showController(){if(controller){return}const html=`
|
||||||
|
<button id='dht-ctrl-close'>X</button>
|
||||||
|
<button id='dht-ctrl-settings'>Settings</button>
|
||||||
|
<button id='dht-ctrl-track'></button>
|
||||||
|
<p id='dht-ctrl-status'>Waiting</p>`;controller={styles:DOM.createStyle(`/*[CSS-CONTROLLER]*/`),ele:DOM.createElement("div",document.body,"dht-ctrl",html)};controller.ui={btnSettings:DOM.id("dht-ctrl-settings"),btnTrack:DOM.id("dht-ctrl-track"),btnClose:DOM.id("dht-ctrl-close"),textStatus:DOM.id("dht-ctrl-status")};controller.ui.btnSettings.addEventListener("click",()=>{this.showSettings()});controller.ui.btnTrack.addEventListener("click",()=>{const isTracking=!STATE.isTracking();STATE.setIsTracking(isTracking);if(!isTracking){controller.ui.textStatus.innerText="Stopped"}});controller.ui.btnClose.addEventListener("click",()=>{this.hideController();window.DHT_ON_UNLOAD.forEach(f=>f());window.DHT_LOADED=false});STATE.onTrackingStateChanged(isTracking=>{controller.ui.btnTrack.innerText=isTracking?"Pause Tracking":"Start Tracking";controller.ui.btnSettings.disabled=isTracking});SETTINGS.onSettingsChanged(stateChangedEvent);stateChangedEvent()},hideController(){if(controller){DOM.removeElement(controller.ele);DOM.removeElement(controller.styles);controller=null}},showSettings(){if(settings){return}const radio=(type,id,label)=>"<label><input id='dht-cfg-"+type+"-"+id+"' name='dht-"+type+"' type='radio'> "+label+"</label><br>";const html=`
|
||||||
|
<label><input id='dht-cfg-autoscroll' type='checkbox'> Autoscroll</label><br>
|
||||||
|
<br>
|
||||||
|
<label>After reaching the first message in channel...</label><br>
|
||||||
|
${radio("afm","nothing","Do Nothing")}
|
||||||
|
${radio("afm","pause","Pause Tracking")}
|
||||||
|
${radio("afm","switch","Switch to Next Channel")}
|
||||||
|
<br>
|
||||||
|
<label>After reaching a previously saved message...</label><br>
|
||||||
|
${radio("asm","nothing","Do Nothing")}
|
||||||
|
${radio("asm","pause","Pause Tracking")}
|
||||||
|
${radio("asm","switch","Switch to Next Channel")}
|
||||||
|
<p id='dht-cfg-note'>It is recommended to disable link and image previews to avoid putting unnecessary strain on your browser.</p>`;settings={styles:DOM.createStyle(`/*[CSS-SETTINGS]*/`),overlay:DOM.createElement("div",document.body,"dht-cfg-overlay"),ele:DOM.createElement("div",document.body,"dht-cfg",html)};settings.overlay.addEventListener("click",()=>{this.hideSettings()});settings.ui={cbAutoscroll:DOM.id("dht-cfg-autoscroll"),optsAfterFirstMsg:{},optsAfterSavedMsg:{}};settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING]=DOM.id("dht-cfg-afm-nothing");settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE]=DOM.id("dht-cfg-afm-pause");settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH]=DOM.id("dht-cfg-afm-switch");settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING]=DOM.id("dht-cfg-asm-nothing");settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE]=DOM.id("dht-cfg-asm-pause");settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH]=DOM.id("dht-cfg-asm-switch");settings.ui.cbAutoscroll.addEventListener("change",()=>{SETTINGS.autoscroll=settings.ui.cbAutoscroll.checked});Object.keys(settings.ui.optsAfterFirstMsg).forEach(key=>{settings.ui.optsAfterFirstMsg[key].addEventListener("click",()=>{SETTINGS.afterFirstMsg=key})});Object.keys(settings.ui.optsAfterSavedMsg).forEach(key=>{settings.ui.optsAfterSavedMsg[key].addEventListener("click",()=>{SETTINGS.afterSavedMsg=key})});stateChangedEvent()},hideSettings(){if(settings){DOM.removeElement(settings.overlay);DOM.removeElement(settings.ele);DOM.removeElement(settings.styles);settings=null}},setStatus(state){if(controller){controller.ui.textStatus.innerText=state}}}}();
|
1
app/Resources/Tracker/scripts.min/settings.js
Normal file
1
app/Resources/Tracker/scripts.min/settings.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
const CONSTANTS={AUTOSCROLL_ACTION_NOTHING:"optNothing",AUTOSCROLL_ACTION_PAUSE:"optPause",AUTOSCROLL_ACTION_SWITCH:"optSwitch"};let IS_FIRST_RUN=false;const SETTINGS=function(){const settingsChangedEvents=[];const saveSettings=function(){DOM.saveToCookie("DHT_SETTINGS",root,60*60*24*365*5)};const triggerSettingsChanged=function(property){for(const callback of settingsChangedEvents){callback(property)}saveSettings()};const defineTriggeringProperty=function(obj,property,value){const name="_"+property;Object.defineProperty(obj,property,{get:()=>obj[name],set:value=>{obj[name]=value;triggerSettingsChanged(property)}});obj[name]=value};let loaded=DOM.loadFromCookie("DHT_SETTINGS");if(!loaded){loaded={_autoscroll:true,_afterFirstMsg:CONSTANTS.AUTOSCROLL_ACTION_PAUSE,_afterSavedMsg:CONSTANTS.AUTOSCROLL_ACTION_PAUSE};IS_FIRST_RUN=true}const root={onSettingsChanged(callback){settingsChangedEvents.push(callback)}};defineTriggeringProperty(root,"autoscroll",loaded._autoscroll);defineTriggeringProperty(root,"afterFirstMsg",loaded._afterFirstMsg);defineTriggeringProperty(root,"afterSavedMsg",loaded._afterSavedMsg);if(IS_FIRST_RUN){saveSettings()}return root}();
|
1
app/Resources/Tracker/scripts.min/state.js
Normal file
1
app/Resources/Tracker/scripts.min/state.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
const STATE=function(){let serverPort=-1;let serverToken="";const post=function(endpoint,json){const aborter=new AbortController;const timeout=window.setTimeout(()=>aborter.abort(),5e3);return new Promise(async(resolve,reject)=>{let r;try{r=await fetch("http://127.0.0.1:"+serverPort+endpoint,{method:"POST",headers:{"Content-Type":"application/json","X-DHT-Token":serverToken},credentials:"omit",redirect:"error",body:JSON.stringify(json),signal:aborter.signal})}catch(e){if(e.name==="AbortError"){reject({status:"DISCONNECTED"});return}else{reject({status:"ERROR",message:e.message});return}}finally{window.clearTimeout(timeout)}if(r.status===200){resolve(r);return}try{const message=await r.text();reject({status:"ERROR",message:message})}catch(e){reject({status:"ERROR",message:e.message})}})};const trackingStateChangedListeners=[];let isTracking=false;const addedChannels=new Set;const addedUsers=new Set;return{setup(port,token){serverPort=port;serverToken=token},onTrackingStateChanged(callback){trackingStateChangedListeners.push(callback);callback(isTracking)},isTracking(){return isTracking},setIsTracking(state){if(isTracking!==state){isTracking=state;if(isTracking){addedChannels.clear();addedUsers.clear()}for(const callback of trackingStateChangedListeners){callback(isTracking)}}},async addDiscordChannel(serverInfo,channelInfo){if(addedChannels.has(channelInfo.id)){return}const server={id:serverInfo.id,name:serverInfo.name,type:serverInfo.type};const channel={id:channelInfo.id,name:channelInfo.name};if("extra"in channelInfo){channel.position=channelInfo.extra.position;channel.topic=channelInfo.extra.topic;channel.nsfw=channelInfo.extra.nsfw}await post("/track-channel",{server:server,channel:channel});addedChannels.add(channelInfo.id)},async addDiscordMessages(channelId,discordMessageArray){const userInfo={};let hasNewUsers=false;for(const msg of discordMessageArray){const user=msg.author;if(!addedUsers.has(user.id)){const obj={id:user.id,name:user.username};if(user.avatar){obj.avatar=user.avatar}if(!user.bot){obj.discriminator=user.discriminator}userInfo[user.id]=obj;hasNewUsers=true}}if(hasNewUsers){await post("/track-users",Object.values(userInfo));for(const id of Object.keys(userInfo)){addedUsers.add(id)}}const response=await post("/track-messages",discordMessageArray.map(msg=>{const obj={id:msg.id,sender:msg.author.id,channel:msg.channel_id,text:msg.content,timestamp:msg.timestamp.toDate().getTime()};if(msg.editedTimestamp!==null){obj.editTimestamp=msg.editedTimestamp.toDate().getTime()}if(msg.messageReference!==null){obj.repliedToId=msg.messageReference.message_id}if(msg.attachments.length>0){obj.attachments=msg.attachments.map(attachment=>{const mapped={id:attachment.id,name:attachment.filename,size:attachment.size,url:attachment.url};if(attachment.content_type){mapped.type=attachment.content_type}return mapped})}if(msg.embeds.length>0){obj.embeds=msg.embeds.map(embed=>{const mapped={};for(const key of Object.keys(embed)){if(key==="id"){continue}if(key==="rawTitle"){mapped["title"]=embed[key]}else if(key==="rawDescription"){mapped["description"]=embed[key]}else{mapped[key]=embed[key]}}return JSON.stringify(mapped)})}if(msg.reactions.length>0){obj.reactions=msg.reactions.map(reaction=>{const emoji=reaction.emoji;const mapped={count:reaction.count};if(emoji.id){mapped.id=emoji.id}if(emoji.name){mapped.name=emoji.name}if(emoji.animated){mapped.isAnimated=emoji.animated}return mapped})}return obj}));const anyNewMessages=await response.text();return anyNewMessages==="1"}}}();
|
271
app/Resources/Tracker/scripts/discord.js
Normal file
271
app/Resources/Tracker/scripts/discord.js
Normal file
@ -0,0 +1,271 @@
|
|||||||
|
class DISCORD {
|
||||||
|
static getMessageOuterElement() {
|
||||||
|
return DOM.queryReactClass("messagesWrapper");
|
||||||
|
}
|
||||||
|
|
||||||
|
static getMessageScrollerElement() {
|
||||||
|
return this.getMessageOuterElement().querySelector("[class*='scroller-']");
|
||||||
|
}
|
||||||
|
|
||||||
|
static hasMoreMessages() {
|
||||||
|
return document.querySelector("#messagesNavigationDescription + [class^=container]") === null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadOlderMessages() {
|
||||||
|
const view = this.getMessageScrollerElement();
|
||||||
|
|
||||||
|
if (view.scrollTop > 0) {
|
||||||
|
view.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls the provided function with a list of messages whenever the currently loaded messages change.
|
||||||
|
* Returns a setInterval handle.
|
||||||
|
*/
|
||||||
|
static setupMessageCallback(callback) {
|
||||||
|
let skipsLeft = 0;
|
||||||
|
let waitForCleanup = false;
|
||||||
|
const previousMessages = new Set();
|
||||||
|
|
||||||
|
return window.setInterval(() => {
|
||||||
|
if (skipsLeft > 0) {
|
||||||
|
--skipsLeft;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const view = this.getMessageOuterElement();
|
||||||
|
|
||||||
|
if (!view) {
|
||||||
|
skipsLeft = 2;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anyMessage = this.getMessageOuterElement().querySelector("[class*='message-']");
|
||||||
|
const messageCount = anyMessage ? anyMessage.parentElement.children.length : 0;
|
||||||
|
|
||||||
|
if (messageCount > 300) {
|
||||||
|
if (waitForCleanup) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
skipsLeft = 3;
|
||||||
|
waitForCleanup = true;
|
||||||
|
|
||||||
|
window.setTimeout(() => {
|
||||||
|
const view = this.getMessageScrollerElement();
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
view.scrollTop = view.scrollHeight / 2;
|
||||||
|
}, 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
waitForCleanup = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = this.getMessages();
|
||||||
|
let hasChanged = false;
|
||||||
|
|
||||||
|
for (const message of messages) {
|
||||||
|
if (!previousMessages.has(message.id)) {
|
||||||
|
hasChanged = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasChanged) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previousMessages.clear();
|
||||||
|
for (const message of messages) {
|
||||||
|
previousMessages.add(message.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(messages);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object containing the selected server and channel information.
|
||||||
|
* For types DM and GROUP, the server and channel ids and names are identical.
|
||||||
|
* @returns {{}|null}
|
||||||
|
*/
|
||||||
|
static getSelectedChannel() {
|
||||||
|
try {
|
||||||
|
let obj;
|
||||||
|
let channelListEle = DOM.queryReactClass("privateChannels");
|
||||||
|
|
||||||
|
if (channelListEle) {
|
||||||
|
const channel = DOM.queryReactClass("selected", channelListEle);
|
||||||
|
|
||||||
|
if (!channel || !("href" in channel) || !channel.href.includes("/@me/")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkSplit = channel.href.split("/");
|
||||||
|
const id = linkSplit[linkSplit.length - 1];
|
||||||
|
|
||||||
|
if (!(/^\d+$/.test(id))) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let name;
|
||||||
|
|
||||||
|
for (const ele of channel.querySelectorAll("[class^='name-'] *")) {
|
||||||
|
const node = Array.prototype.find.call(ele.childNodes, node => node.nodeType === Node.TEXT_NODE);
|
||||||
|
|
||||||
|
if (node) {
|
||||||
|
name = node.nodeValue;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const icon = channel.querySelector("img[class*='avatar']");
|
||||||
|
const iconParent = icon && icon.closest("foreignObject");
|
||||||
|
const iconMask = iconParent && iconParent.getAttribute("mask");
|
||||||
|
|
||||||
|
obj = {
|
||||||
|
"server": { id, name, type: (iconMask && iconMask.includes("#svg-mask-avatar-default")) ? "GROUP" : "DM" },
|
||||||
|
"channel": { id, name }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
channelListEle = document.getElementById("channels");
|
||||||
|
|
||||||
|
const channel = channelListEle.querySelector("[class*='modeSelected']").parentElement;
|
||||||
|
// noinspection JSUnresolvedVariable
|
||||||
|
const props = DOM.getReactProps(channel).children.props;
|
||||||
|
|
||||||
|
if (!props) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnresolvedVariable
|
||||||
|
const channelObj = props.channel || props.children().props.channel;
|
||||||
|
|
||||||
|
if (!channelObj) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection JSUnresolvedVariable
|
||||||
|
obj = {
|
||||||
|
"server": {
|
||||||
|
"id": channelObj.guild_id,
|
||||||
|
"name": document.querySelector("nav header > h1").innerText,
|
||||||
|
"type": "SERVER"
|
||||||
|
},
|
||||||
|
"channel": {
|
||||||
|
"id": channelObj.id,
|
||||||
|
"name": channelObj.name,
|
||||||
|
"extra": {
|
||||||
|
"position": channelObj.position,
|
||||||
|
"topic": channelObj.topic,
|
||||||
|
"nsfw": channelObj.nsfw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj.channel.length === 0 ? null : obj;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array containing currently loaded messages, or null if the messages cannot be retrieved.
|
||||||
|
*/
|
||||||
|
static getMessages() {
|
||||||
|
try {
|
||||||
|
const scroller = this.getMessageScrollerElement();
|
||||||
|
const props = DOM.getReactProps(scroller);
|
||||||
|
let wrappers;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// noinspection JSUnresolvedVariable
|
||||||
|
wrappers = props.children.props.children.props.children.props.children.find(ele => Array.isArray(ele));
|
||||||
|
} catch (e) { // old version compatibility
|
||||||
|
wrappers = props.children.find(ele => Array.isArray(ele));
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = [];
|
||||||
|
|
||||||
|
for (const obj of wrappers) {
|
||||||
|
// noinspection JSUnresolvedVariable
|
||||||
|
const nested = obj.props;
|
||||||
|
|
||||||
|
if (nested && nested.message) {
|
||||||
|
messages.push(nested.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the next text channel and returns true, otherwise returns false if there are no more channels.
|
||||||
|
*/
|
||||||
|
static selectNextTextChannel() {
|
||||||
|
const dms = DOM.queryReactClass("privateChannels");
|
||||||
|
|
||||||
|
if (dms) {
|
||||||
|
const currentChannel = DOM.queryReactClass("selected", dms);
|
||||||
|
const nextChannel = currentChannel && currentChannel.nextElementSibling;
|
||||||
|
|
||||||
|
if (!nextChannel || !nextChannel.getAttribute("class").includes("channel-") || !("href" in nextChannel) || !nextChannel.href.includes("/@me/")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nextChannel.click();
|
||||||
|
nextChannel.scrollIntoView(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const channelIconNormal = "M5.88657 21C5.57547 21 5.3399 20.7189 5.39427 20.4126L6.00001 17H2.59511C2.28449 17 2.04905 16.7198 2.10259 16.4138L2.27759 15.4138C2.31946 15.1746 2.52722 15 2.77011 15H6.35001L7.41001 9H4.00511C3.69449 9 3.45905 8.71977 3.51259 8.41381L3.68759 7.41381C3.72946 7.17456 3.93722 7 4.18011 7H7.76001L8.39677 3.41262C8.43914 3.17391 8.64664 3 8.88907 3H9.87344C10.1845 3 10.4201 3.28107 10.3657 3.58738L9.76001 7H15.76L16.3968 3.41262C16.4391 3.17391 16.6466 3 16.8891 3H17.8734C18.1845 3 18.4201 3.28107 18.3657 3.58738L17.76 7H21.1649C21.4755 7 21.711 7.28023 21.6574 7.58619L21.4824 8.58619C21.4406 8.82544 21.2328 9 20.9899 9H17.41L16.35 15H19.7549C20.0655 15 20.301 15.2802 20.2474 15.5862L20.0724 16.5862C20.0306 16.8254 19.8228 17 19.5799 17H16L15.3632 20.5874C15.3209 20.8261 15.1134 21 14.8709 21H13.8866C13.5755 21 13.3399 20.7189 13.3943 20.4126L14 17H8.00001L7.36325 20.5874C7.32088 20.8261 7.11337 21 6.87094 21H5.88657ZM9.41045 9L8.35045 15H14.3504L15.4104 9H9.41045Z";
|
||||||
|
const channelIconSpecial = "M14 8C14 7.44772 13.5523 7 13 7H9.76001L10.3657 3.58738C10.4201 3.28107 10.1845 3 9.87344 3H8.88907C8.64664 3 8.43914 3.17391 8.39677 3.41262L7.76001 7H4.18011C3.93722 7 3.72946 7.17456 3.68759 7.41381L3.51259 8.41381C3.45905 8.71977 3.69449 9 4.00511 9H7.41001L6.35001 15H2.77011C2.52722 15 2.31946 15.1746 2.27759 15.4138L2.10259 16.4138C2.04905 16.7198 2.28449 17 2.59511 17H6.00001L5.39427 20.4126C5.3399 20.7189 5.57547 21 5.88657 21H6.87094C7.11337 21 7.32088 20.8261 7.36325 20.5874L8.00001 17H14L13.3943 20.4126C13.3399 20.7189 13.5755 21 13.8866 21H14.8709C15.1134 21 15.3209 20.8261 15.3632 20.5874L16 17H19.5799C19.8228 17 20.0306 16.8254 20.0724 16.5862L20.2474 15.5862C20.301 15.2802 20.0655 15 19.7549 15H16.35L16.6758 13.1558C16.7823 12.5529 16.3186 12 15.7063 12C15.2286 12 14.8199 12.3429 14.7368 12.8133L14.3504 15H8.35045L9.41045 9H13C13.5523 9 14 8.55228 14 8Z";
|
||||||
|
|
||||||
|
const isValidChannelClass = cls => cls.includes("wrapper-") && !cls.includes("clickable-");
|
||||||
|
const isValidChannelType = ele => !!ele.querySelector("path[d=\"" + channelIconNormal + "\"]") || !!ele.querySelector("path[d=\"" + channelIconSpecial + "\"]");
|
||||||
|
const isValidChannel = ele => ele.childElementCount > 0 && isValidChannelClass(ele.children[0].className) && isValidChannelType(ele);
|
||||||
|
|
||||||
|
const channelListEle = document.querySelector("div[class*='sidebar'] > nav[class*='container'] > div[class*='scroller']");
|
||||||
|
|
||||||
|
if (!channelListEle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const allChannels = Array.prototype.filter.call(channelListEle.querySelectorAll("[class*='containerDefault']"), isValidChannel);
|
||||||
|
let nextChannel = null;
|
||||||
|
|
||||||
|
for (let index = 0; index < allChannels.length - 1; index++) {
|
||||||
|
if (allChannels[index].children[0].className.includes("modeSelected")) {
|
||||||
|
nextChannel = allChannels[index + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextChannel === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextChannelLink = nextChannel.querySelector("a[href^='/channels/']");
|
||||||
|
if (!nextChannelLink) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
nextChannelLink.click();
|
||||||
|
nextChannel.scrollIntoView(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
74
app/Resources/Tracker/scripts/dom.js
Normal file
74
app/Resources/Tracker/scripts/dom.js
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
class DOM {
|
||||||
|
/**
|
||||||
|
* Returns a child element by its ID. Parent defaults to the entire document.
|
||||||
|
* @returns {HTMLElement}
|
||||||
|
*/
|
||||||
|
static id(id, parent) {
|
||||||
|
return (parent || document).getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first child element containing the specified obfuscated class. Parent defaults to the entire document.
|
||||||
|
*/
|
||||||
|
static queryReactClass(cls, parent) {
|
||||||
|
return (parent || document).querySelector(`[class*="${cls}-"]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an element, adds it to the DOM, and returns it.
|
||||||
|
*/
|
||||||
|
static createElement(tag, parent, id, html) {
|
||||||
|
/** @type HTMLElement */
|
||||||
|
const ele = document.createElement(tag);
|
||||||
|
ele.id = id || "";
|
||||||
|
ele.innerHTML = html || "";
|
||||||
|
parent.appendChild(ele);
|
||||||
|
return ele;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an element from the DOM.
|
||||||
|
*/
|
||||||
|
static removeElement(ele) {
|
||||||
|
return ele.parentNode.removeChild(ele);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new style element with the specified CSS and returns it.
|
||||||
|
*/
|
||||||
|
static createStyle(styles) {
|
||||||
|
return this.createElement("style", document.head, "", styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to save an object into a cookie.
|
||||||
|
*/
|
||||||
|
static saveToCookie(name, obj, expiresInSeconds) {
|
||||||
|
const expires = new Date(Date.now() + 1000 * expiresInSeconds).toUTCString();
|
||||||
|
document.cookie = name + "=" + encodeURIComponent(JSON.stringify(obj)) + ";path=/;expires=" + expires;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to load an object from a cookie.
|
||||||
|
*/
|
||||||
|
static loadFromCookie(name) {
|
||||||
|
const value = document.cookie.replace(new RegExp("(?:(?:^|.*;\\s*)" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1");
|
||||||
|
return value.length ? JSON.parse(decodeURIComponent(value)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns internal React state object of an element.
|
||||||
|
*/
|
||||||
|
static getReactProps(ele) {
|
||||||
|
const keys = Object.keys(ele || {});
|
||||||
|
let key = keys.find(key => key.startsWith("__reactInternalInstance"));
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
// noinspection JSUnresolvedVariable
|
||||||
|
return ele[key].memoizedProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
key = keys.find(key => key.startsWith("__reactProps$"));
|
||||||
|
return key ? ele[key] : null;
|
||||||
|
}
|
||||||
|
}
|
156
app/Resources/Tracker/scripts/gui.js
Normal file
156
app/Resources/Tracker/scripts/gui.js
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// noinspection FunctionWithInconsistentReturnsJS
|
||||||
|
const GUI = (function() {
|
||||||
|
let controller = null;
|
||||||
|
let settings = null;
|
||||||
|
|
||||||
|
const stateChangedEvent = () => {
|
||||||
|
if (settings) {
|
||||||
|
settings.ui.cbAutoscroll.checked = SETTINGS.autoscroll;
|
||||||
|
settings.ui.optsAfterFirstMsg[SETTINGS.afterFirstMsg].checked = true;
|
||||||
|
settings.ui.optsAfterSavedMsg[SETTINGS.afterSavedMsg].checked = true;
|
||||||
|
|
||||||
|
const autoscrollDisabled = !SETTINGS.autoscroll;
|
||||||
|
Object.values(settings.ui.optsAfterFirstMsg).forEach(ele => ele.disabled = autoscrollDisabled);
|
||||||
|
Object.values(settings.ui.optsAfterSavedMsg).forEach(ele => ele.disabled = autoscrollDisabled);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
showController() {
|
||||||
|
if (controller) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const html = `
|
||||||
|
<button id='dht-ctrl-close'>X</button>
|
||||||
|
<button id='dht-ctrl-settings'>Settings</button>
|
||||||
|
<button id='dht-ctrl-track'></button>
|
||||||
|
<p id='dht-ctrl-status'>Waiting</p>`;
|
||||||
|
|
||||||
|
controller = {
|
||||||
|
styles: DOM.createStyle(`/*[CSS-CONTROLLER]*/`),
|
||||||
|
ele: DOM.createElement("div", document.body, "dht-ctrl", html)
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.ui = {
|
||||||
|
btnSettings: DOM.id("dht-ctrl-settings"),
|
||||||
|
btnTrack: DOM.id("dht-ctrl-track"),
|
||||||
|
btnClose: DOM.id("dht-ctrl-close"),
|
||||||
|
textStatus: DOM.id("dht-ctrl-status")
|
||||||
|
};
|
||||||
|
|
||||||
|
controller.ui.btnSettings.addEventListener("click", () => {
|
||||||
|
this.showSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.ui.btnTrack.addEventListener("click", () => {
|
||||||
|
const isTracking = !STATE.isTracking();
|
||||||
|
STATE.setIsTracking(isTracking);
|
||||||
|
|
||||||
|
if (!isTracking) {
|
||||||
|
controller.ui.textStatus.innerText = "Stopped";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
controller.ui.btnClose.addEventListener("click", () => {
|
||||||
|
this.hideController();
|
||||||
|
window.DHT_ON_UNLOAD.forEach(f => f());
|
||||||
|
window.DHT_LOADED = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
STATE.onTrackingStateChanged(isTracking => {
|
||||||
|
controller.ui.btnTrack.innerText = isTracking ? "Pause Tracking" : "Start Tracking";
|
||||||
|
controller.ui.btnSettings.disabled = isTracking;
|
||||||
|
});
|
||||||
|
|
||||||
|
SETTINGS.onSettingsChanged(stateChangedEvent);
|
||||||
|
stateChangedEvent();
|
||||||
|
},
|
||||||
|
|
||||||
|
hideController() {
|
||||||
|
if (controller) {
|
||||||
|
DOM.removeElement(controller.ele);
|
||||||
|
DOM.removeElement(controller.styles);
|
||||||
|
controller = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showSettings() {
|
||||||
|
if (settings) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const radio = (type, id, label) => "<label><input id='dht-cfg-" + type + "-" + id + "' name='dht-" + type + "' type='radio'> " + label + "</label><br>";
|
||||||
|
const html = `
|
||||||
|
<label><input id='dht-cfg-autoscroll' type='checkbox'> Autoscroll</label><br>
|
||||||
|
<br>
|
||||||
|
<label>After reaching the first message in channel...</label><br>
|
||||||
|
${radio("afm", "nothing", "Do Nothing")}
|
||||||
|
${radio("afm", "pause", "Pause Tracking")}
|
||||||
|
${radio("afm", "switch", "Switch to Next Channel")}
|
||||||
|
<br>
|
||||||
|
<label>After reaching a previously saved message...</label><br>
|
||||||
|
${radio("asm", "nothing", "Do Nothing")}
|
||||||
|
${radio("asm", "pause", "Pause Tracking")}
|
||||||
|
${radio("asm", "switch", "Switch to Next Channel")}
|
||||||
|
<p id='dht-cfg-note'>It is recommended to disable link and image previews to avoid putting unnecessary strain on your browser.</p>`;
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
styles: DOM.createStyle(`/*[CSS-SETTINGS]*/`),
|
||||||
|
overlay: DOM.createElement("div", document.body, "dht-cfg-overlay"),
|
||||||
|
ele: DOM.createElement("div", document.body, "dht-cfg", html)
|
||||||
|
};
|
||||||
|
|
||||||
|
settings.overlay.addEventListener("click", () => {
|
||||||
|
this.hideSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
settings.ui = {
|
||||||
|
cbAutoscroll: DOM.id("dht-cfg-autoscroll"),
|
||||||
|
optsAfterFirstMsg: {},
|
||||||
|
optsAfterSavedMsg: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING] = DOM.id("dht-cfg-afm-nothing");
|
||||||
|
settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE] = DOM.id("dht-cfg-afm-pause");
|
||||||
|
settings.ui.optsAfterFirstMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH] = DOM.id("dht-cfg-afm-switch");
|
||||||
|
|
||||||
|
settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_NOTHING] = DOM.id("dht-cfg-asm-nothing");
|
||||||
|
settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_PAUSE] = DOM.id("dht-cfg-asm-pause");
|
||||||
|
settings.ui.optsAfterSavedMsg[CONSTANTS.AUTOSCROLL_ACTION_SWITCH] = DOM.id("dht-cfg-asm-switch");
|
||||||
|
|
||||||
|
settings.ui.cbAutoscroll.addEventListener("change", () => {
|
||||||
|
SETTINGS.autoscroll = settings.ui.cbAutoscroll.checked;
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(settings.ui.optsAfterFirstMsg).forEach(key => {
|
||||||
|
settings.ui.optsAfterFirstMsg[key].addEventListener("click", () => {
|
||||||
|
SETTINGS.afterFirstMsg = key;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(settings.ui.optsAfterSavedMsg).forEach(key => {
|
||||||
|
settings.ui.optsAfterSavedMsg[key].addEventListener("click", () => {
|
||||||
|
SETTINGS.afterSavedMsg = key;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
stateChangedEvent();
|
||||||
|
},
|
||||||
|
|
||||||
|
hideSettings() {
|
||||||
|
if (settings) {
|
||||||
|
DOM.removeElement(settings.overlay);
|
||||||
|
DOM.removeElement(settings.ele);
|
||||||
|
DOM.removeElement(settings.styles);
|
||||||
|
settings = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setStatus(state) {
|
||||||
|
if (controller) {
|
||||||
|
controller.ui.textStatus.innerText = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
65
app/Resources/Tracker/scripts/settings.js
Normal file
65
app/Resources/Tracker/scripts/settings.js
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
const CONSTANTS = {
|
||||||
|
AUTOSCROLL_ACTION_NOTHING: "optNothing",
|
||||||
|
AUTOSCROLL_ACTION_PAUSE: "optPause",
|
||||||
|
AUTOSCROLL_ACTION_SWITCH: "optSwitch"
|
||||||
|
};
|
||||||
|
|
||||||
|
let IS_FIRST_RUN = false;
|
||||||
|
|
||||||
|
const SETTINGS = (function() {
|
||||||
|
const settingsChangedEvents = [];
|
||||||
|
|
||||||
|
const saveSettings = function() {
|
||||||
|
DOM.saveToCookie("DHT_SETTINGS", root, 60 * 60 * 24 * 365 * 5);
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerSettingsChanged = function(property) {
|
||||||
|
for (const callback of settingsChangedEvents) {
|
||||||
|
callback(property);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveSettings();
|
||||||
|
};
|
||||||
|
|
||||||
|
const defineTriggeringProperty = function(obj, property, value) {
|
||||||
|
const name = "_" + property;
|
||||||
|
|
||||||
|
Object.defineProperty(obj, property, {
|
||||||
|
get: (() => obj[name]),
|
||||||
|
set: (value => {
|
||||||
|
obj[name] = value;
|
||||||
|
triggerSettingsChanged(property);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
obj[name] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
let loaded = DOM.loadFromCookie("DHT_SETTINGS");
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
loaded = {
|
||||||
|
"_autoscroll": true,
|
||||||
|
"_afterFirstMsg": CONSTANTS.AUTOSCROLL_ACTION_PAUSE,
|
||||||
|
"_afterSavedMsg": CONSTANTS.AUTOSCROLL_ACTION_PAUSE
|
||||||
|
};
|
||||||
|
|
||||||
|
IS_FIRST_RUN = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = {
|
||||||
|
onSettingsChanged(callback) {
|
||||||
|
settingsChangedEvents.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineTriggeringProperty(root, "autoscroll", loaded._autoscroll);
|
||||||
|
defineTriggeringProperty(root, "afterFirstMsg", loaded._afterFirstMsg);
|
||||||
|
defineTriggeringProperty(root, "afterSavedMsg", loaded._afterSavedMsg);
|
||||||
|
|
||||||
|
if (IS_FIRST_RUN) {
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
})();
|
299
app/Resources/Tracker/scripts/state.js
Normal file
299
app/Resources/Tracker/scripts/state.js
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
// noinspection FunctionWithInconsistentReturnsJS
|
||||||
|
const STATE = (function() {
|
||||||
|
let serverPort = -1;
|
||||||
|
let serverToken = "";
|
||||||
|
|
||||||
|
const post = function(endpoint, json) {
|
||||||
|
const aborter = new AbortController();
|
||||||
|
const timeout = window.setTimeout(() => aborter.abort(), 5000);
|
||||||
|
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let r;
|
||||||
|
try {
|
||||||
|
r = await fetch("http://127.0.0.1:" + serverPort + endpoint, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"X-DHT-Token": serverToken
|
||||||
|
},
|
||||||
|
credentials: "omit",
|
||||||
|
redirect: "error",
|
||||||
|
body: JSON.stringify(json),
|
||||||
|
signal: aborter.signal
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e.name === "AbortError") {
|
||||||
|
reject({ status: "DISCONNECTED" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
reject({ status: "ERROR", message: e.message });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
window.clearTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r.status === 200) {
|
||||||
|
resolve(r);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const message = await r.text();
|
||||||
|
reject({ status: "ERROR", message });
|
||||||
|
} catch (e) {
|
||||||
|
reject({ status: "ERROR", message: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const trackingStateChangedListeners = [];
|
||||||
|
let isTracking = false;
|
||||||
|
|
||||||
|
const addedChannels = new Set();
|
||||||
|
const addedUsers = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name DiscordUser
|
||||||
|
* @property {String} id
|
||||||
|
* @property {String} username
|
||||||
|
* @property {String} discriminator
|
||||||
|
* @property {String} [avatar]
|
||||||
|
* @property {Boolean} [bot]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name DiscordMessage
|
||||||
|
* @property {String} id
|
||||||
|
* @property {String} channel_id
|
||||||
|
* @property {DiscordUser} author
|
||||||
|
* @property {String} content
|
||||||
|
* @property {Timestamp} timestamp
|
||||||
|
* @property {Timestamp|null} editedTimestamp
|
||||||
|
* @property {DiscordAttachment[]} attachments
|
||||||
|
* @property {Object[]} embeds
|
||||||
|
* @property {DiscordMessageReaction[]} [reactions]
|
||||||
|
* @property {DiscordMessageReference} [messageReference]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name DiscordAttachment
|
||||||
|
* @property {String} id
|
||||||
|
* @property {String} filename
|
||||||
|
* @property {String} [content_type]
|
||||||
|
* @property {String} size
|
||||||
|
* @property {String} url
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name DiscordMessageReaction
|
||||||
|
* @property {DiscordEmoji} emoji
|
||||||
|
* @property {Number} count
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name DiscordMessageReference
|
||||||
|
* @property {String} [message_id]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name DiscordEmoji
|
||||||
|
* @property {String|null} id
|
||||||
|
* @property {String|null} name
|
||||||
|
* @property {Boolean} animated
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name Timestamp
|
||||||
|
* @property {Function} toDate
|
||||||
|
*/
|
||||||
|
|
||||||
|
return {
|
||||||
|
setup(port, token) {
|
||||||
|
serverPort = port;
|
||||||
|
serverToken = token;
|
||||||
|
},
|
||||||
|
|
||||||
|
onTrackingStateChanged(callback) {
|
||||||
|
trackingStateChangedListeners.push(callback);
|
||||||
|
callback(isTracking);
|
||||||
|
},
|
||||||
|
|
||||||
|
isTracking() {
|
||||||
|
return isTracking;
|
||||||
|
},
|
||||||
|
|
||||||
|
setIsTracking(state) {
|
||||||
|
if (isTracking !== state) {
|
||||||
|
isTracking = state;
|
||||||
|
|
||||||
|
if (isTracking) {
|
||||||
|
addedChannels.clear();
|
||||||
|
addedUsers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const callback of trackingStateChangedListeners) {
|
||||||
|
callback(isTracking);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async addDiscordChannel(serverInfo, channelInfo) {
|
||||||
|
if (addedChannels.has(channelInfo.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = {
|
||||||
|
id: serverInfo.id,
|
||||||
|
name: serverInfo.name,
|
||||||
|
type: serverInfo.type
|
||||||
|
};
|
||||||
|
|
||||||
|
const channel = {
|
||||||
|
id: channelInfo.id,
|
||||||
|
name: channelInfo.name
|
||||||
|
};
|
||||||
|
|
||||||
|
if ("extra" in channelInfo) {
|
||||||
|
channel.position = channelInfo.extra.position;
|
||||||
|
channel.topic = channelInfo.extra.topic;
|
||||||
|
channel.nsfw = channelInfo.extra.nsfw;
|
||||||
|
}
|
||||||
|
|
||||||
|
await post("/track-channel", { server, channel });
|
||||||
|
addedChannels.add(channelInfo.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {String} channelId
|
||||||
|
* @param {DiscordMessage[]} discordMessageArray
|
||||||
|
*/
|
||||||
|
async addDiscordMessages(channelId, discordMessageArray) {
|
||||||
|
const userInfo = {};
|
||||||
|
let hasNewUsers = false;
|
||||||
|
|
||||||
|
for (const msg of discordMessageArray) {
|
||||||
|
const user = msg.author;
|
||||||
|
|
||||||
|
if (!addedUsers.has(user.id)) {
|
||||||
|
const obj = {
|
||||||
|
id: user.id,
|
||||||
|
name: user.username
|
||||||
|
};
|
||||||
|
|
||||||
|
if (user.avatar) {
|
||||||
|
obj.avatar = user.avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.bot) {
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
obj.discriminator = user.discriminator;
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo[user.id] = obj;
|
||||||
|
hasNewUsers = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasNewUsers) {
|
||||||
|
await post("/track-users", Object.values(userInfo));
|
||||||
|
|
||||||
|
for (const id of Object.keys(userInfo)) {
|
||||||
|
addedUsers.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await post("/track-messages", discordMessageArray.map(msg => {
|
||||||
|
const obj = {
|
||||||
|
id: msg.id,
|
||||||
|
sender: msg.author.id,
|
||||||
|
channel: msg.channel_id,
|
||||||
|
text: msg.content,
|
||||||
|
timestamp: msg.timestamp.toDate().getTime()
|
||||||
|
};
|
||||||
|
|
||||||
|
if (msg.editedTimestamp !== null) {
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
obj.editTimestamp = msg.editedTimestamp.toDate().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.messageReference !== null) {
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
obj.repliedToId = msg.messageReference.message_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.attachments.length > 0) {
|
||||||
|
obj.attachments = msg.attachments.map(attachment => {
|
||||||
|
const mapped = {
|
||||||
|
id: attachment.id,
|
||||||
|
name: attachment.filename,
|
||||||
|
size: attachment.size,
|
||||||
|
url: attachment.url
|
||||||
|
};
|
||||||
|
|
||||||
|
if (attachment.content_type) {
|
||||||
|
mapped.type = attachment.content_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapped;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.embeds.length > 0) {
|
||||||
|
obj.embeds = msg.embeds.map(embed => {
|
||||||
|
const mapped = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(embed)) {
|
||||||
|
if (key === "id") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === "rawTitle") {
|
||||||
|
mapped["title"] = embed[key];
|
||||||
|
}
|
||||||
|
else if (key === "rawDescription") {
|
||||||
|
mapped["description"] = embed[key];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
mapped[key] = embed[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSON.stringify(mapped);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.reactions.length > 0) {
|
||||||
|
obj.reactions = msg.reactions.map(reaction => {
|
||||||
|
const emoji = reaction.emoji;
|
||||||
|
|
||||||
|
const mapped = {
|
||||||
|
count: reaction.count
|
||||||
|
};
|
||||||
|
|
||||||
|
if (emoji.id) {
|
||||||
|
mapped.id = emoji.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emoji.name) {
|
||||||
|
mapped.name = emoji.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emoji.animated) {
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
mapped.isAnimated = emoji.animated;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapped;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}));
|
||||||
|
|
||||||
|
const anyNewMessages = await response.text();
|
||||||
|
return anyNewMessages === "1";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
31
app/Resources/Tracker/styles/controller.css
Normal file
31
app/Resources/Tracker/styles/controller.css
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#app-mount > div[class*="app-"] {
|
||||||
|
margin-bottom: 48px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dht-ctrl {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dht-ctrl button {
|
||||||
|
height: 32px;
|
||||||
|
margin: 8px 0 8px 8px;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 0 12px;
|
||||||
|
background-color: #7289da;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
#dht-ctrl button:disabled {
|
||||||
|
background-color: #7a7a7a;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dht-ctrl p {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 14px 12px;
|
||||||
|
}
|
28
app/Resources/Tracker/styles/settings.css
Normal file
28
app/Resources/Tracker/styles/settings.css
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#dht-cfg-overlay {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #000;
|
||||||
|
opacity: 0.5;
|
||||||
|
display: block;
|
||||||
|
z-index: 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dht-cfg {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 800px;
|
||||||
|
height: 262px;
|
||||||
|
margin-left: -400px;
|
||||||
|
margin-top: -131px;
|
||||||
|
padding: 8px;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 1001;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dht-cfg-note {
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
76
app/Resources/Viewer/index.html
Normal file
76
app/Resources/Viewer/index.html
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Discord Offline History</title>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
window.DHT_EMBEDDED = "/*[ARCHIVE]*/";
|
||||||
|
/*[JS]*/
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
/*[CSS]*/
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="menu">
|
||||||
|
<button id="btn-settings">Settings</button>
|
||||||
|
|
||||||
|
<div> <!-- needed to stop the select from messing up -->
|
||||||
|
<select id="opt-messages-per-page">
|
||||||
|
<option value="50">50 messages per page </option>
|
||||||
|
<option value="100">100 messages per page </option>
|
||||||
|
<option value="250">250 messages per page </option>
|
||||||
|
<option value="500">500 messages per page </option>
|
||||||
|
<option value="1000">1000 messages per page </option>
|
||||||
|
<option value="0">All messages </option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="nav">
|
||||||
|
<button id="nav-first" data-nav="first" class="icon">«</button>
|
||||||
|
<button id="nav-prev" data-nav="prev" class="icon">‹</button>
|
||||||
|
<button id="nav-pick" data-nav="pick">Page <span id="nav-page-current">1</span>/<span id="nav-page-total">?</span></button>
|
||||||
|
<button id="nav-next" data-nav="next" class="icon">›</button>
|
||||||
|
<button id="nav-last" data-nav="last" class="icon">»</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="splitter"></div>
|
||||||
|
|
||||||
|
<div> <!-- needed to stop the select from messing up -->
|
||||||
|
<select id="opt-messages-filter">
|
||||||
|
<option value="">No filter </option>
|
||||||
|
<option value="user">Filter messages by user </option>
|
||||||
|
<option value="contents">Filter messages by contents </option>
|
||||||
|
<option value="withimages">Only messages with images </option>
|
||||||
|
<option value="withdownloads">Only messages with downloads </option>
|
||||||
|
<option value="edited">Only edited messages </option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="opt-filter-list">
|
||||||
|
<select id="opt-filter-user" data-filter-type="user">
|
||||||
|
<option value="">Select user...</option>
|
||||||
|
</select>
|
||||||
|
<input id="opt-filter-contents" type="text" data-filter-type="contents" placeholder="Messages containing...">
|
||||||
|
<input type="hidden" data-filter-type="withimages" value="1">
|
||||||
|
<input type="hidden" data-filter-type="withdownloads" value="1">
|
||||||
|
<input type="hidden" data-filter-type="edited" value="1">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="separator"></div>
|
||||||
|
|
||||||
|
<button id="btn-about">About</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<div id="channels"></div>
|
||||||
|
<div id="messages"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="modal">
|
||||||
|
<div id="overlay"></div>
|
||||||
|
<div id="dialog"></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
39
app/Resources/Viewer/scripts/bootstrap.js
vendored
Normal file
39
app/Resources/Viewer/scripts/bootstrap.js
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
DISCORD.setup();
|
||||||
|
GUI.setup();
|
||||||
|
|
||||||
|
GUI.onOptionMessagesPerPageChanged(() => {
|
||||||
|
STATE.setMessagesPerPage(GUI.getOptionMessagesPerPage());
|
||||||
|
});
|
||||||
|
|
||||||
|
STATE.setMessagesPerPage(GUI.getOptionMessagesPerPage());
|
||||||
|
|
||||||
|
GUI.onOptMessageFilterChanged(filter => {
|
||||||
|
STATE.setActiveFilter(filter);
|
||||||
|
});
|
||||||
|
|
||||||
|
GUI.onNavigationButtonClicked(action => {
|
||||||
|
STATE.updateCurrentPage(action);
|
||||||
|
});
|
||||||
|
|
||||||
|
STATE.onUsersRefreshed(users => {
|
||||||
|
GUI.updateUserList(users);
|
||||||
|
});
|
||||||
|
|
||||||
|
STATE.onChannelsRefreshed((channels, selected) => {
|
||||||
|
GUI.updateChannelList(channels, selected, STATE.selectChannel);
|
||||||
|
});
|
||||||
|
|
||||||
|
STATE.onMessagesRefreshed(messages => {
|
||||||
|
GUI.updateNavigation(STATE.getCurrentPage(), STATE.getPageCount());
|
||||||
|
GUI.updateMessageList(messages);
|
||||||
|
GUI.scrollMessagesToTop();
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
STATE.uploadFile(JSON.parse(window.DHT_EMBEDDED));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
alert("Could not parse embedded file, see console for details.");
|
||||||
|
}
|
||||||
|
});
|
257
app/Resources/Viewer/scripts/discord.js
Normal file
257
app/Resources/Viewer/scripts/discord.js
Normal file
@ -0,0 +1,257 @@
|
|||||||
|
const DISCORD = (function() {
|
||||||
|
const regex = {
|
||||||
|
formatBold: /\*\*([\s\S]+?)\*\*(?!\*)/g,
|
||||||
|
formatItalic: /(.)?\*([\s\S]+?)\*(?!\*)/g,
|
||||||
|
formatUnderline: /__([\s\S]+?)__(?!_)/g,
|
||||||
|
formatStrike: /~~([\s\S]+?)~~(?!~)/g,
|
||||||
|
formatCodeInline: /(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/g,
|
||||||
|
formatCodeBlock: /```(?:([A-z0-9\-]+?)\n+)?\n*([^]+?)\n*```/g,
|
||||||
|
formatUrl: /(\b(?:https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig,
|
||||||
|
formatUrlNoEmbed: /<(\b(?:https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])>/ig,
|
||||||
|
specialEscapedBacktick: /\\`/g,
|
||||||
|
specialEscapedSingle: /\\([*\\])/g,
|
||||||
|
specialEscapedDouble: /\\__|_\\_|\\_\\_|\\~~|~\\~|\\~\\~/g,
|
||||||
|
specialUnescaped: /([*_~\\])/g,
|
||||||
|
mentionRole: /<@&(\d+?)>/g,
|
||||||
|
mentionUser: /<@!?(\d+?)>/g,
|
||||||
|
mentionChannel: /<#(\d+?)>/g,
|
||||||
|
customEmojiStatic: /<:([^:]+):(\d+?)>/g,
|
||||||
|
customEmojiAnimated: /<a:([^:]+):(\d+?)>/g
|
||||||
|
};
|
||||||
|
|
||||||
|
let templateChannelServer;
|
||||||
|
let templateChannelPrivate;
|
||||||
|
let templateMessageNoAvatar;
|
||||||
|
let templateMessageWithAvatar;
|
||||||
|
let templateUserAvatar;
|
||||||
|
let templateAttachmentDownload;
|
||||||
|
let templateEmbedImage;
|
||||||
|
let templateEmbedRich;
|
||||||
|
let templateEmbedRichNoDescription;
|
||||||
|
let templateEmbedUrl;
|
||||||
|
let templateEmbedUnsupported;
|
||||||
|
let templateReaction;
|
||||||
|
let templateReactionCustom;
|
||||||
|
|
||||||
|
const processMessageContents = function(contents) {
|
||||||
|
let processed = DOM.escapeHTML(contents.replace(regex.formatUrlNoEmbed, "$1"));
|
||||||
|
|
||||||
|
if (SETTINGS.enableFormatting) {
|
||||||
|
const escapeHtmlMatch = (full, match) => "&#" + match.charCodeAt(0) + ";";
|
||||||
|
|
||||||
|
processed = processed
|
||||||
|
.replace(regex.specialEscapedBacktick, "`")
|
||||||
|
.replace(regex.formatCodeBlock, (full, ignore, match) => "<code class='block'>" + match.replace(regex.specialUnescaped, escapeHtmlMatch) + "</code>")
|
||||||
|
.replace(regex.formatCodeInline, (full, ignore, match) => "<code class='inline'>" + match.replace(regex.specialUnescaped, escapeHtmlMatch) + "</code>")
|
||||||
|
.replace(regex.specialEscapedSingle, escapeHtmlMatch)
|
||||||
|
.replace(regex.specialEscapedDouble, full => full.replace(/\\/g, "").replace(/(.)/g, escapeHtmlMatch))
|
||||||
|
.replace(regex.formatBold, "<b>$1</b>")
|
||||||
|
.replace(regex.formatItalic, (full, pre, match) => pre === "\\" ? full : (pre || "") + "<i>" + match + "</i>")
|
||||||
|
.replace(regex.formatUnderline, "<u>$1</u>")
|
||||||
|
.replace(regex.formatStrike, "<s>$1</s>");
|
||||||
|
}
|
||||||
|
|
||||||
|
const animatedEmojiExtension = SETTINGS.enableAnimatedEmoji ? "gif" : "png";
|
||||||
|
|
||||||
|
// noinspection HtmlUnknownTarget
|
||||||
|
processed = processed
|
||||||
|
.replace(regex.formatUrl, "<a href='$1' target='_blank' rel='noreferrer'>$1</a>")
|
||||||
|
.replace(regex.mentionChannel, (full, match) => "<span class='link mention-chat'>#" + STATE.getChannelName(match) + "</span>")
|
||||||
|
.replace(regex.mentionUser, (full, match) => "<span class='link mention-user' title='#" + (STATE.getUserTag(match) || "????") + "'>@" + STATE.getUserName(match) + "</span>")
|
||||||
|
.replace(regex.customEmojiStatic, "<img src='https://cdn.discordapp.com/emojis/$2.png' alt=':$1:' title=':$1:' class='emoji'>")
|
||||||
|
.replace(regex.customEmojiAnimated, "<img src='https://cdn.discordapp.com/emojis/$2." + animatedEmojiExtension + "' alt=':$1:' title=':$1:' class='emoji'>");
|
||||||
|
|
||||||
|
return "<p>" + processed + "</p>";
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
setup() {
|
||||||
|
templateChannelServer = new TEMPLATE([
|
||||||
|
"<div data-channel='{id}'>",
|
||||||
|
"<div class='info' title='{topic}'><strong class='name'>#{name}</strong>{nsfw}<span class='tag'>{msgcount}</span></div>",
|
||||||
|
"<span class='server'>{server.name} ({server.type})</span>",
|
||||||
|
"</div>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
templateChannelPrivate = new TEMPLATE([
|
||||||
|
"<div data-channel='{id}'>",
|
||||||
|
"<div class='info'><strong class='name'>{name}</strong><span class='tag'>{msgcount}</span></div>",
|
||||||
|
"<span class='server'>({server.type})</span>",
|
||||||
|
"</div>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
templateMessageNoAvatar = new TEMPLATE([
|
||||||
|
"<div>",
|
||||||
|
"<div class='reply-message'>{reply}</div>",
|
||||||
|
"<h2><strong class='username' title='#{user.tag}'>{user.name}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>",
|
||||||
|
"<div class='message'>{contents}{embeds}{attachments}</div>",
|
||||||
|
"{reactions}",
|
||||||
|
"</div>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
templateMessageWithAvatar = new TEMPLATE([
|
||||||
|
"<div>",
|
||||||
|
"<div class='reply-message reply-message-with-avatar'>{reply}</div>",
|
||||||
|
"<div class='avatar-wrapper'>",
|
||||||
|
"<div class='avatar'>{avatar}</div>",
|
||||||
|
"<div>",
|
||||||
|
"<h2><strong class='username' title='#{user.tag}'>{user.name}</strong><span class='info time'>{timestamp}</span>{edit}{jump}</h2>",
|
||||||
|
"<div class='message'>{contents}{embeds}{attachments}</div>",
|
||||||
|
"{reactions}",
|
||||||
|
"</div>",
|
||||||
|
"</div>",
|
||||||
|
"</div>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
templateUserAvatar = new TEMPLATE([
|
||||||
|
"<img src='https://cdn.discordapp.com/avatars/{id}/{path}.webp?size=128' alt=''>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
// noinspection HtmlUnknownTarget
|
||||||
|
templateAttachmentDownload = new TEMPLATE([
|
||||||
|
"<a href='{url}' class='embed download'>Download {filename}</a>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
// noinspection HtmlUnknownTarget
|
||||||
|
templateEmbedImage = new TEMPLATE([
|
||||||
|
"<a href='{url}' class='embed thumbnail'><img src='{src}' alt='(image attachment not found)'></a><br>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
// noinspection HtmlUnknownTarget
|
||||||
|
templateEmbedRich = new TEMPLATE([
|
||||||
|
"<div class='embed download'><a href='{url}' class='title'>{title}</a><p class='desc'>{description}</p></div>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
// noinspection HtmlUnknownTarget
|
||||||
|
templateEmbedRichNoDescription = new TEMPLATE([
|
||||||
|
"<div class='embed download'><a href='{url}' class='title'>{title}</a></div>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
// noinspection HtmlUnknownTarget
|
||||||
|
templateEmbedUrl = new TEMPLATE([
|
||||||
|
"<a href='{url}' class='embed download'>{url}</a>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
templateEmbedUnsupported = new TEMPLATE([
|
||||||
|
"<div class='embed download'><p>(Unsupported embed)</p></div>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
templateReaction = new TEMPLATE([
|
||||||
|
"<span class='reaction-wrapper'><span class='reaction-emoji'>{n}</span><span class='count'>{c}</span></span>"
|
||||||
|
].join(""));
|
||||||
|
|
||||||
|
templateReactionCustom = new TEMPLATE([
|
||||||
|
"<span class='reaction-wrapper'><img src='https://cdn.discordapp.com/emojis/{id}.{ext}' alt=':{n}:' title=':{n}:' class='reaction-emoji-custom'><span class='count'>{c}</span></span>"
|
||||||
|
].join(""));
|
||||||
|
},
|
||||||
|
|
||||||
|
isImageAttachment(attachment) {
|
||||||
|
const dot = attachment.url.lastIndexOf(".");
|
||||||
|
const ext = dot === -1 ? "" : attachment.url.substring(dot).toLowerCase();
|
||||||
|
return ext === ".png" || ext === ".gif" || ext === ".jpg" || ext === ".jpeg";
|
||||||
|
},
|
||||||
|
|
||||||
|
getChannelHTML(channel) { // noinspection FunctionWithInconsistentReturnsJS
|
||||||
|
return (channel.server.type === "server" ? templateChannelServer : templateChannelPrivate).apply(channel, (property, value) => {
|
||||||
|
if (property === "nsfw") {
|
||||||
|
return value ? "<span class='tag'>NSFW</span>" : "";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getMessageHTML(message) { // noinspection FunctionWithInconsistentReturnsJS
|
||||||
|
return (SETTINGS.enableUserAvatars ? templateMessageWithAvatar : templateMessageNoAvatar).apply(message, (property, value) => {
|
||||||
|
if (property === "avatar") {
|
||||||
|
return value ? templateUserAvatar.apply(value) : "";
|
||||||
|
}
|
||||||
|
else if (property === "user.tag") {
|
||||||
|
return value ? value : "????";
|
||||||
|
}
|
||||||
|
else if (property === "timestamp") {
|
||||||
|
return DOM.getHumanReadableTime(value);
|
||||||
|
}
|
||||||
|
else if (property === "contents") {
|
||||||
|
return value == null || value.length === 0 ? "" : processMessageContents(value);
|
||||||
|
}
|
||||||
|
else if (property === "embeds") {
|
||||||
|
if (!value) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map(embed => {
|
||||||
|
if (!embed.url) {
|
||||||
|
return templateEmbedUnsupported.apply(embed);
|
||||||
|
}
|
||||||
|
else if ("image" in embed && embed.image.url) {
|
||||||
|
return SETTINGS.enableImagePreviews ? templateEmbedImage.apply({ url: embed.url, src: embed.image.url }) : "";
|
||||||
|
}
|
||||||
|
else if ("thumbnail" in embed && embed.thumbnail.url) {
|
||||||
|
return SETTINGS.enableImagePreviews ? templateEmbedImage.apply({ url: embed.url, src: embed.thumbnail.url }) : "";
|
||||||
|
}
|
||||||
|
else if ("title" in embed && "description" in embed) {
|
||||||
|
return templateEmbedRich.apply(embed);
|
||||||
|
}
|
||||||
|
else if ("title" in embed) {
|
||||||
|
return templateEmbedRichNoDescription.apply(embed);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return templateEmbedUrl.apply(embed);
|
||||||
|
}
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
else if (property === "attachments") {
|
||||||
|
if (!value) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.map(attachment => {
|
||||||
|
if (this.isImageAttachment(attachment) && SETTINGS.enableImagePreviews) {
|
||||||
|
return templateEmbedImage.apply({ url: attachment.url, src: attachment.url });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const sliced = attachment.url.split("/");
|
||||||
|
|
||||||
|
return templateAttachmentDownload.apply({
|
||||||
|
"url": attachment.url,
|
||||||
|
"filename": sliced[sliced.length - 1]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).join("");
|
||||||
|
}
|
||||||
|
else if (property === "edit") {
|
||||||
|
return value ? "<span class='info edited'>Edited " + DOM.getHumanReadableTime(value) + "</span>" : "";
|
||||||
|
}
|
||||||
|
else if (property === "jump") {
|
||||||
|
return STATE.hasActiveFilter ? "<span class='info jump' data-jump='" + value + "'>Jump to message</span>" : "";
|
||||||
|
}
|
||||||
|
else if (property === "reply") {
|
||||||
|
if (value === null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = "<span class='reply-username' title='#" + (value.user.tag ? value.user.tag : "????") + "'>" + value.user.name + "</span>";
|
||||||
|
const avatar = SETTINGS.enableUserAvatars && value.avatar ? "<span class='reply-avatar'>" + templateUserAvatar.apply(value.avatar) + "</span>" : "";
|
||||||
|
const contents = value.contents ? "<span class='reply-contents'>" + processMessageContents(value.contents) + "</span>" : "";
|
||||||
|
|
||||||
|
return "<span class='jump' data-jump='" + value.id + "'>Jump to reply</span><span class='user'>" + avatar + user + "</span>" + contents;
|
||||||
|
}
|
||||||
|
else if (property === "reactions"){
|
||||||
|
if (value === null){
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
return "<div class='reactions'>" + value.map(reaction => {
|
||||||
|
if ("id" in reaction){
|
||||||
|
// noinspection JSUnusedGlobalSymbols, JSUnresolvedVariable
|
||||||
|
reaction.ext = reaction.a && SETTINGS.enableAnimatedEmoji ? "gif" : "png";
|
||||||
|
return templateReactionCustom.apply(reaction);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return templateReaction.apply(reaction);
|
||||||
|
}
|
||||||
|
}).join("") + "</div>";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
54
app/Resources/Viewer/scripts/dom.js
Normal file
54
app/Resources/Viewer/scripts/dom.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const HTML_ENTITY_MAP = {
|
||||||
|
"&": "&",
|
||||||
|
"<": "<",
|
||||||
|
">": ">",
|
||||||
|
"\"": """,
|
||||||
|
"'": "'"
|
||||||
|
};
|
||||||
|
|
||||||
|
const HTML_ENTITY_REGEX = /[&<>"']/g;
|
||||||
|
|
||||||
|
class DOM {
|
||||||
|
/**
|
||||||
|
* Returns a child element by its ID. Parent defaults to the entire document.
|
||||||
|
*/
|
||||||
|
static id(id, parent) {
|
||||||
|
return (parent || document).getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all child elements containing the specified class. Parent defaults to the entire document.
|
||||||
|
*/
|
||||||
|
static cls(cls, parent) {
|
||||||
|
return Array.prototype.slice.call((parent || document).getElementsByClassName(cls));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an array of all child elements that have the specified tag. Parent defaults to the entire document.
|
||||||
|
*/
|
||||||
|
static tag(tag, parent) {
|
||||||
|
return Array.prototype.slice.call((parent || document).getElementsByTagName(tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the first child element containing the specified class. Parent defaults to the entire document.
|
||||||
|
*/
|
||||||
|
static fcls(cls, parent) {
|
||||||
|
return (parent || document).getElementsByClassName(cls)[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts characters to their HTML entity form.
|
||||||
|
*/
|
||||||
|
static escapeHTML(html) {
|
||||||
|
return String(html).replace(HTML_ENTITY_REGEX, s => HTML_ENTITY_MAP[s]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a timestamp into a human readable time, using the browser locale.
|
||||||
|
*/
|
||||||
|
static getHumanReadableTime(timestamp) {
|
||||||
|
const date = new Date(timestamp);
|
||||||
|
return date.toLocaleDateString() + ", " + date.toLocaleTimeString();
|
||||||
|
};
|
||||||
|
}
|
249
app/Resources/Viewer/scripts/gui.js
Normal file
249
app/Resources/Viewer/scripts/gui.js
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
const GUI = (function() {
|
||||||
|
let eventOnOptMessagesPerPageChanged;
|
||||||
|
let eventOnOptMessageFilterChanged;
|
||||||
|
let eventOnNavButtonClicked;
|
||||||
|
|
||||||
|
const getActiveFilter = function() {
|
||||||
|
const active = DOM.fcls("active", DOM.id("opt-filter-list"));
|
||||||
|
|
||||||
|
return active && active.value !== "" ? {
|
||||||
|
"type": active.getAttribute("data-filter-type"),
|
||||||
|
"value": active.value
|
||||||
|
} : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerFilterChanged = function() {
|
||||||
|
eventOnOptMessageFilterChanged && eventOnOptMessageFilterChanged(getActiveFilter());
|
||||||
|
};
|
||||||
|
|
||||||
|
const showModal = function(width, html) {
|
||||||
|
const dialog = DOM.id("dialog");
|
||||||
|
dialog.innerHTML = html;
|
||||||
|
dialog.style.width = width + "px";
|
||||||
|
dialog.style.marginLeft = (-width / 2) + "px";
|
||||||
|
|
||||||
|
DOM.id("modal").classList.add("visible");
|
||||||
|
return dialog;
|
||||||
|
};
|
||||||
|
|
||||||
|
// -------------
|
||||||
|
// Modal dialogs
|
||||||
|
// -------------
|
||||||
|
|
||||||
|
const showSettingsModal = function() {
|
||||||
|
showModal(560, `
|
||||||
|
<label><input id='dht-cfg-imgpreviews' type='checkbox'> Image Previews</label><br>
|
||||||
|
<label><input id='dht-cfg-formatting' type='checkbox'> Message Formatting</label><br>
|
||||||
|
<label><input id='dht-cfg-useravatars' type='checkbox'> User Avatars</label><br>
|
||||||
|
<label><input id='dht-cfg-animemoji' type='checkbox'> Animated Emoji</label><br>`);
|
||||||
|
|
||||||
|
const setupCheckBox = function(id, settingName) {
|
||||||
|
const ele = DOM.id(id);
|
||||||
|
ele.checked = SETTINGS[settingName];
|
||||||
|
ele.addEventListener("change", () => SETTINGS[settingName] = ele.checked);
|
||||||
|
};
|
||||||
|
|
||||||
|
setupCheckBox("dht-cfg-imgpreviews", "enableImagePreviews");
|
||||||
|
setupCheckBox("dht-cfg-formatting", "enableFormatting");
|
||||||
|
setupCheckBox("dht-cfg-useravatars", "enableUserAvatars");
|
||||||
|
setupCheckBox("dht-cfg-animemoji", "enableAnimatedEmoji");
|
||||||
|
};
|
||||||
|
|
||||||
|
const showInfoModal = function() {
|
||||||
|
const linkGH = "https://github.com/chylex/Discord-History-Tracker";
|
||||||
|
|
||||||
|
showModal(560, `
|
||||||
|
<p>Discord History Tracker is developed by <a href='https://chylex.com'>chylex</a> as an <a href='${linkGH}/blob/master/LICENSE.md'>open source</a> project.</p>
|
||||||
|
<p>Please, report any issues and suggestions to the <a href='${linkGH}/issues'>tracker</a>. If you want to support the development, please spread the word and consider <a href='https://www.patreon.com/chylex'>becoming a patron</a> or <a href='https://ko-fi.com/chylex'>buying me a coffee</a>. Any support is appreciated!</p>
|
||||||
|
<p><a href='${linkGH}/issues'>Issue Tracker</a> — <a href='${linkGH}'>GitHub Repository</a> — <a href='https://twitter.com/chylexmc'>Developer's Twitter</a></p>`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
// ---------
|
||||||
|
// GUI setup
|
||||||
|
// ---------
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
const inputMessageFilter = DOM.id("opt-messages-filter");
|
||||||
|
const containerFilterList = DOM.id("opt-filter-list");
|
||||||
|
|
||||||
|
const resetActiveFilter = function() {
|
||||||
|
inputMessageFilter.value = "";
|
||||||
|
inputMessageFilter.dispatchEvent(new Event("change"));
|
||||||
|
|
||||||
|
DOM.id("opt-filter-contents").value = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
inputMessageFilter.value = ""; // required to prevent browsers from remembering old value
|
||||||
|
|
||||||
|
inputMessageFilter.addEventListener("change", () => {
|
||||||
|
DOM.cls("active", containerFilterList).forEach(ele => ele.classList.remove("active"));
|
||||||
|
|
||||||
|
if (inputMessageFilter.value) {
|
||||||
|
containerFilterList.querySelector("[data-filter-type='" + inputMessageFilter.value + "']").classList.add("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerFilterChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
Array.prototype.forEach.call(containerFilterList.children, ele => {
|
||||||
|
ele.addEventListener(ele.tagName === "SELECT" ? "change" : "input", () => triggerFilterChanged());
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.id("opt-messages-per-page").addEventListener("change", () => {
|
||||||
|
eventOnOptMessagesPerPageChanged && eventOnOptMessagesPerPageChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.tag("button", DOM.fcls("nav")).forEach(button => {
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
eventOnNavButtonClicked && eventOnNavButtonClicked(button.getAttribute("data-nav"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.id("btn-settings").addEventListener("click", () => {
|
||||||
|
showSettingsModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.id("btn-about").addEventListener("click", () => {
|
||||||
|
showInfoModal();
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.id("messages").addEventListener("click", e => {
|
||||||
|
const jump = e.target.getAttribute("data-jump");
|
||||||
|
|
||||||
|
if (jump) {
|
||||||
|
resetActiveFilter();
|
||||||
|
|
||||||
|
const index = STATE.navigateToMessage(jump);
|
||||||
|
DOM.id("messages").children[index].scrollIntoView();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.id("overlay").addEventListener("click", () => {
|
||||||
|
DOM.id("modal").classList.remove("visible");
|
||||||
|
DOM.id("dialog").innerHTML = "";
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
// -----------------
|
||||||
|
// Event registering
|
||||||
|
// -----------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a callback for when the user changes the messages per page option. The callback is not passed any arguments.
|
||||||
|
*/
|
||||||
|
onOptionMessagesPerPageChanged(callback) {
|
||||||
|
eventOnOptMessagesPerPageChanged = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a callback for when the user changes the active filter. The callback is passed either null or an object such as { type: <filter type>, value: <filter value> }.
|
||||||
|
*/
|
||||||
|
onOptMessageFilterChanged(callback) {
|
||||||
|
eventOnOptMessageFilterChanged = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a callback for when the user clicks a navigation button. The callback is passed one of the following strings: first, prev, next, last.
|
||||||
|
*/
|
||||||
|
onNavigationButtonClicked(callback) {
|
||||||
|
eventOnNavButtonClicked = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------
|
||||||
|
// Options and navigation
|
||||||
|
// ----------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the selected amount of messages per page.
|
||||||
|
*/
|
||||||
|
getOptionMessagesPerPage() {
|
||||||
|
/** @type HTMLInputElement */
|
||||||
|
const messagesPerPage = DOM.id("opt-messages-per-page");
|
||||||
|
return parseInt(messagesPerPage.value, 10);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateNavigation(currentPage, totalPages) {
|
||||||
|
DOM.id("nav-page-current").innerHTML = currentPage;
|
||||||
|
DOM.id("nav-page-total").innerHTML = totalPages || "?";
|
||||||
|
|
||||||
|
DOM.id("nav-first").disabled = currentPage === 1;
|
||||||
|
DOM.id("nav-prev").disabled = currentPage === 1;
|
||||||
|
DOM.id("nav-pick").disabled = (totalPages || 0) <= 1;
|
||||||
|
DOM.id("nav-next").disabled = currentPage === (totalPages || 1);
|
||||||
|
DOM.id("nav-last").disabled = currentPage === (totalPages || 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
// Updating lists
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the channel list and sets up their click events. The callback is triggered whenever a channel is selected, and takes the channel ID as its argument.
|
||||||
|
*/
|
||||||
|
updateChannelList(channels, selected, callback) {
|
||||||
|
const eleChannels = DOM.id("channels");
|
||||||
|
|
||||||
|
if (!channels) {
|
||||||
|
eleChannels.innerHTML = "";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (getActiveFilter() != null) {
|
||||||
|
channels = channels.filter(channel => channel.msgcount > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
eleChannels.innerHTML = channels.map(channel => DISCORD.getChannelHTML(channel)).join("");
|
||||||
|
|
||||||
|
Array.prototype.forEach.call(eleChannels.children, ele => {
|
||||||
|
ele.addEventListener("click", () => {
|
||||||
|
const currentChannel = DOM.fcls("active", eleChannels);
|
||||||
|
|
||||||
|
if (currentChannel) {
|
||||||
|
currentChannel.classList.remove("active");
|
||||||
|
}
|
||||||
|
|
||||||
|
ele.classList.add("active");
|
||||||
|
callback(ele.getAttribute("data-channel"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
const activeChannel = eleChannels.querySelector("[data-channel='" + selected + "']");
|
||||||
|
activeChannel && activeChannel.classList.add("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMessageList(messages) {
|
||||||
|
DOM.id("messages").innerHTML = messages ? messages.map(message => DISCORD.getMessageHTML(message)).join("") : "";
|
||||||
|
},
|
||||||
|
|
||||||
|
updateUserList(users) {
|
||||||
|
/** @type HTMLSelectElement */
|
||||||
|
const eleSelect = DOM.id("opt-filter-user");
|
||||||
|
|
||||||
|
while (eleSelect.length > 1) {
|
||||||
|
eleSelect.remove(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const options = [];
|
||||||
|
|
||||||
|
for (const key of Object.keys(users)) {
|
||||||
|
const option = document.createElement("option");
|
||||||
|
option.value = key;
|
||||||
|
option.text = users[key].name;
|
||||||
|
options.push(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
options.sort((a, b) => a.text.toLocaleLowerCase().localeCompare(b.text.toLocaleLowerCase()));
|
||||||
|
options.forEach(option => eleSelect.add(option));
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollMessagesToTop() {
|
||||||
|
DOM.id("messages").scrollTop = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
41
app/Resources/Viewer/scripts/processor.js
Normal file
41
app/Resources/Viewer/scripts/processor.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
const PROCESSOR = {};
|
||||||
|
|
||||||
|
// ------------------------
|
||||||
|
// Global filter generators
|
||||||
|
// ------------------------
|
||||||
|
|
||||||
|
PROCESSOR.FILTER = {
|
||||||
|
byUser: ((userindex) => message => message.u === userindex),
|
||||||
|
byTime: ((timeStart, timeEnd) => message => message.t >= timeStart && message.t <= timeEnd),
|
||||||
|
byContents: ((substr) => message => ("m" in message ? message.m : "").indexOf(substr) !== -1),
|
||||||
|
byRegex: ((regex) => message => regex.test("m" in message ? message.m : "")),
|
||||||
|
withImages: (() => message => (message.e && message.e.some(embed => embed.type === "image")) || (message.a && message.a.some(DISCORD.isImageAttachment))),
|
||||||
|
withDownloads: (() => message => message.a && message.a.some(attachment => !DISCORD.isImageAttachment(attachment))),
|
||||||
|
withEmbeds: (() => message => message.e && message.e.length > 0),
|
||||||
|
withAttachments: (() => message => message.a && message.a.length > 0),
|
||||||
|
isEdited: (() => message => ("te" in message) ? message.te : (message.f & 1) === 1)
|
||||||
|
};
|
||||||
|
|
||||||
|
// --------------
|
||||||
|
// Global sorters
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
PROCESSOR.SORTER = {
|
||||||
|
oldestToNewest: (key1, key2) => {
|
||||||
|
if (key1.length === key2.length) {
|
||||||
|
return key1 > key2 ? 1 : key1 < key2 ? -1 : 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return key1.length > key2.length ? 1 : -1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
newestToOldest: (key1, key2) => {
|
||||||
|
if (key1.length === key2.length) {
|
||||||
|
return key1 > key2 ? -1 : key1 < key2 ? 1 : 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return key1.length > key2.length ? -1 : 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
80
app/Resources/Viewer/scripts/settings.js
Normal file
80
app/Resources/Viewer/scripts/settings.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
const SETTINGS = (function() {
|
||||||
|
/**
|
||||||
|
* @type {{}}
|
||||||
|
* @property {Function} onSettingsChanged
|
||||||
|
* @property {Boolean} enableImagePreviews
|
||||||
|
* @property {Boolean} enableFormatting
|
||||||
|
* @property {Boolean} enableUserAvatars
|
||||||
|
* @property {Boolean} enableAnimatedEmoji
|
||||||
|
*/
|
||||||
|
const root = {
|
||||||
|
onSettingsChanged(callback) {
|
||||||
|
settingsChangedEvents.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const settingsChangedEvents = [];
|
||||||
|
|
||||||
|
const triggerSettingsChanged = function(property) {
|
||||||
|
for (const callback of settingsChangedEvents) {
|
||||||
|
callback(property);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getStorageItem = (property) => {
|
||||||
|
try {
|
||||||
|
return localStorage.getItem(property);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const setStorageItem = (property, value) => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(property, value);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const defineSettingProperty = (property, defaultValue, storageToValue) => {
|
||||||
|
const name = "_" + property;
|
||||||
|
|
||||||
|
Object.defineProperty(root, property, {
|
||||||
|
get: (() => root[name]),
|
||||||
|
set: (value => {
|
||||||
|
root[name] = value;
|
||||||
|
triggerSettingsChanged(property);
|
||||||
|
setStorageItem(property, value);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let stored = getStorageItem(property);
|
||||||
|
|
||||||
|
if (stored !== null) {
|
||||||
|
stored = storageToValue(stored);
|
||||||
|
}
|
||||||
|
|
||||||
|
root[name] = stored === null ? defaultValue : stored;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fromBooleanString = (value) => {
|
||||||
|
if (value === "true") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (value === "false") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
defineSettingProperty("enableImagePreviews", true, fromBooleanString);
|
||||||
|
defineSettingProperty("enableFormatting", true, fromBooleanString);
|
||||||
|
defineSettingProperty("enableUserAvatars", true, fromBooleanString);
|
||||||
|
defineSettingProperty("enableAnimatedEmoji", true, fromBooleanString);
|
||||||
|
|
||||||
|
return root;
|
||||||
|
})();
|
303
app/Resources/Viewer/scripts/state.js
Normal file
303
app/Resources/Viewer/scripts/state.js
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
// noinspection FunctionWithInconsistentReturnsJS
|
||||||
|
const STATE = (function() {
|
||||||
|
/**
|
||||||
|
* @type {{}}
|
||||||
|
* @property {{}} users
|
||||||
|
* @property {String[]} userindex
|
||||||
|
* @property {{}[]} servers
|
||||||
|
* @property {{}} channels
|
||||||
|
*/
|
||||||
|
let loadedFileMeta;
|
||||||
|
let loadedFileData;
|
||||||
|
|
||||||
|
let loadedMessages;
|
||||||
|
|
||||||
|
let filterFunction;
|
||||||
|
let selectedChannel;
|
||||||
|
let currentPage;
|
||||||
|
let messagesPerPage;
|
||||||
|
|
||||||
|
const getUser = function(index) {
|
||||||
|
return loadedFileMeta.users[loadedFileMeta.userindex[index]] || { "name": "<unknown>" };
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserId = function(index) {
|
||||||
|
return loadedFileMeta.userindex[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getUserList = function() {
|
||||||
|
return loadedFileMeta ? loadedFileMeta.users : [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChannelList = function() {
|
||||||
|
if (!loadedFileMeta) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const channels = loadedFileMeta.channels;
|
||||||
|
|
||||||
|
return Object.keys(channels).map(key => ({
|
||||||
|
"id": key,
|
||||||
|
"name": channels[key].name,
|
||||||
|
"server": loadedFileMeta.servers[channels[key].server] || { "name": "<unknown>", "type": "unknown" },
|
||||||
|
"msgcount": getFilteredMessageKeys(key).length,
|
||||||
|
"topic": channels[key].topic || "",
|
||||||
|
"nsfw": channels[key].nsfw || false,
|
||||||
|
"position": channels[key].position || -1
|
||||||
|
})).sort((ac, bc) => {
|
||||||
|
const as = ac.server;
|
||||||
|
const bs = bc.server;
|
||||||
|
|
||||||
|
return as.type.localeCompare(bs.type, "en") ||
|
||||||
|
as.name.toLocaleLowerCase().localeCompare(bs.name.toLocaleLowerCase(), undefined, { numeric: true }) ||
|
||||||
|
ac.position - bc.position ||
|
||||||
|
ac.name.toLocaleLowerCase().localeCompare(bc.name.toLocaleLowerCase(), undefined, { numeric: true });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMessages = function(channel) {
|
||||||
|
return loadedFileData[channel] || {};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getMessageList = function() {
|
||||||
|
if (!loadedMessages) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = getMessages(selectedChannel);
|
||||||
|
const startIndex = messagesPerPage * (root.getCurrentPage() - 1);
|
||||||
|
|
||||||
|
return loadedMessages.slice(startIndex, !messagesPerPage ? undefined : startIndex + messagesPerPage).map(key => {
|
||||||
|
/**
|
||||||
|
* @type {{}}
|
||||||
|
* @property {Number} u
|
||||||
|
* @property {Number} t
|
||||||
|
* @property {String} m
|
||||||
|
* @property {Number} [te]
|
||||||
|
* @property {String} [r]
|
||||||
|
* @property {{}[]} [a]
|
||||||
|
* @property {String[]} [e]
|
||||||
|
* @property {{}[]} [re]
|
||||||
|
*/
|
||||||
|
const message = messages[key];
|
||||||
|
const user = getUser(message.u);
|
||||||
|
const avatar = user.avatar ? { id: getUserId(message.u), path: user.avatar } : null;
|
||||||
|
|
||||||
|
const reply = ("r" in message && message.r in messages) ? messages[message.r] : null;
|
||||||
|
const replyUser = reply ? getUser(reply.u) : null;
|
||||||
|
const replyAvatar = replyUser && replyUser.avatar ? { id: getUserId(reply.u), path: replyUser.avatar } : null;
|
||||||
|
const replyObj = reply ? {
|
||||||
|
"id": message.r,
|
||||||
|
"user": replyUser,
|
||||||
|
"avatar": replyAvatar,
|
||||||
|
"contents": reply.m
|
||||||
|
} : null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
user,
|
||||||
|
avatar,
|
||||||
|
"timestamp": message.t,
|
||||||
|
"contents": ("m" in message) ? message.m : null,
|
||||||
|
"embeds": ("e" in message) ? message.e.map(embed => JSON.parse(embed)) : [],
|
||||||
|
"attachments": ("a" in message) ? message.a : [],
|
||||||
|
"edit": ("te" in message) ? message.te : null,
|
||||||
|
"jump": key,
|
||||||
|
"reply": replyObj,
|
||||||
|
"reactions": ("re" in message) ? message.re : null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let eventOnUsersRefreshed;
|
||||||
|
let eventOnChannelsRefreshed;
|
||||||
|
let eventOnMessagesRefreshed;
|
||||||
|
|
||||||
|
const triggerUsersRefreshed = function() {
|
||||||
|
eventOnUsersRefreshed && eventOnUsersRefreshed(getUserList());
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerChannelsRefreshed = function(selectedChannel) {
|
||||||
|
eventOnChannelsRefreshed && eventOnChannelsRefreshed(getChannelList(), selectedChannel);
|
||||||
|
};
|
||||||
|
|
||||||
|
const triggerMessagesRefreshed = function() {
|
||||||
|
eventOnMessagesRefreshed && eventOnMessagesRefreshed(getMessageList());
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFilteredMessageKeys = function(channel) {
|
||||||
|
const messages = getMessages(channel);
|
||||||
|
let keys = Object.keys(messages);
|
||||||
|
|
||||||
|
if (filterFunction) {
|
||||||
|
keys = keys.filter(key => filterFunction(messages[key]));
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
const root = {
|
||||||
|
onChannelsRefreshed(callback) {
|
||||||
|
eventOnChannelsRefreshed = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
onMessagesRefreshed(callback) {
|
||||||
|
eventOnMessagesRefreshed = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
onUsersRefreshed(callback) {
|
||||||
|
eventOnUsersRefreshed = callback;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ meta, data }} file
|
||||||
|
*/
|
||||||
|
uploadFile(file) {
|
||||||
|
if (loadedFileMeta != null) {
|
||||||
|
throw "A file is already loaded!";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file || typeof file.meta !== "object" || typeof file.data !== "object") {
|
||||||
|
throw "Invalid file format!";
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedFileMeta = file.meta;
|
||||||
|
loadedFileData = file.data;
|
||||||
|
loadedMessages = null;
|
||||||
|
|
||||||
|
selectedChannel = null;
|
||||||
|
currentPage = 1;
|
||||||
|
|
||||||
|
triggerUsersRefreshed();
|
||||||
|
triggerChannelsRefreshed();
|
||||||
|
triggerMessagesRefreshed();
|
||||||
|
|
||||||
|
SETTINGS.onSettingsChanged(() => triggerMessagesRefreshed());
|
||||||
|
},
|
||||||
|
|
||||||
|
getChannelName(channel) {
|
||||||
|
return loadedFileMeta.channels[channel].name || channel;
|
||||||
|
},
|
||||||
|
|
||||||
|
getUserTag(user) {
|
||||||
|
return loadedFileMeta.users[user].tag;
|
||||||
|
},
|
||||||
|
|
||||||
|
getUserName(user) {
|
||||||
|
return loadedFileMeta.users[user].name || user;
|
||||||
|
},
|
||||||
|
|
||||||
|
selectChannel(channel) {
|
||||||
|
currentPage = 1;
|
||||||
|
selectedChannel = channel;
|
||||||
|
|
||||||
|
loadedMessages = getFilteredMessageKeys(channel).sort(PROCESSOR.SORTER.oldestToNewest);
|
||||||
|
triggerMessagesRefreshed();
|
||||||
|
},
|
||||||
|
|
||||||
|
setMessagesPerPage(amount) {
|
||||||
|
messagesPerPage = amount;
|
||||||
|
triggerMessagesRefreshed();
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCurrentPage(action) {
|
||||||
|
switch (action) {
|
||||||
|
case "first":
|
||||||
|
currentPage = 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "prev":
|
||||||
|
currentPage = Math.max(1, currentPage - 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "next":
|
||||||
|
currentPage = Math.min(this.getPageCount(), currentPage + 1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "last":
|
||||||
|
currentPage = this.getPageCount();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "pick":
|
||||||
|
const page = parseInt(prompt("Select page:", currentPage), 10);
|
||||||
|
|
||||||
|
if (!page && page !== 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage = Math.max(1, Math.min(this.getPageCount(), page));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerMessagesRefreshed();
|
||||||
|
},
|
||||||
|
|
||||||
|
getCurrentPage() {
|
||||||
|
const total = this.getPageCount();
|
||||||
|
|
||||||
|
if (currentPage > total && total > 0) {
|
||||||
|
currentPage = total;
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentPage || 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
getPageCount() {
|
||||||
|
return !loadedMessages ? 0 : (!messagesPerPage ? 1 : Math.ceil(loadedMessages.length / messagesPerPage));
|
||||||
|
},
|
||||||
|
|
||||||
|
navigateToMessage(id) {
|
||||||
|
if (!loadedMessages) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = loadedMessages.indexOf(id);
|
||||||
|
|
||||||
|
if (index === -1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPage = Math.max(1, Math.min(this.getPageCount(), 1 + Math.floor(index / messagesPerPage)));
|
||||||
|
triggerMessagesRefreshed();
|
||||||
|
return index % messagesPerPage;
|
||||||
|
},
|
||||||
|
|
||||||
|
setActiveFilter(filter) {
|
||||||
|
switch (filter ? filter.type : "") {
|
||||||
|
case "user":
|
||||||
|
filterFunction = PROCESSOR.FILTER.byUser(loadedFileMeta.userindex.indexOf(filter.value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "contents":
|
||||||
|
filterFunction = PROCESSOR.FILTER.byContents(filter.value);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "withimages":
|
||||||
|
filterFunction = PROCESSOR.FILTER.withImages();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "withdownloads":
|
||||||
|
filterFunction = PROCESSOR.FILTER.withDownloads();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "edited":
|
||||||
|
filterFunction = PROCESSOR.FILTER.isEdited();
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
filterFunction = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hasActiveFilter = filterFunction != null;
|
||||||
|
|
||||||
|
triggerChannelsRefreshed(selectedChannel);
|
||||||
|
|
||||||
|
if (selectedChannel) {
|
||||||
|
this.selectChannel(selectedChannel); // resets current page and updates messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
root.hasActiveFilter = false;
|
||||||
|
return root;
|
||||||
|
})();
|
20
app/Resources/Viewer/scripts/template.js
Normal file
20
app/Resources/Viewer/scripts/template.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
const TEMPLATE_REGEX = /{([^{}]+?)}/g;
|
||||||
|
|
||||||
|
class TEMPLATE {
|
||||||
|
constructor(contents) {
|
||||||
|
this.contents = contents;
|
||||||
|
};
|
||||||
|
|
||||||
|
apply(obj, processor) {
|
||||||
|
return this.contents.replace(TEMPLATE_REGEX, (full, match) => {
|
||||||
|
const value = match.split(".").reduce((o, property) => o[property], obj);
|
||||||
|
|
||||||
|
if (processor) {
|
||||||
|
const updated = processor(match, value);
|
||||||
|
return typeof updated === "undefined" ? DOM.escapeHTML(value) : updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DOM.escapeHTML(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
42
app/Resources/Viewer/styles/channels.css
Normal file
42
app/Resources/Viewer/styles/channels.css
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#channels {
|
||||||
|
width: 15vw;
|
||||||
|
min-width: 215px;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #1c1e22;
|
||||||
|
}
|
||||||
|
|
||||||
|
#channels > div {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 10px 12px;
|
||||||
|
color: #eee;
|
||||||
|
font-size: 15px;
|
||||||
|
border-bottom: 1px solid #333333;
|
||||||
|
}
|
||||||
|
|
||||||
|
#channels > div:hover, #channels > div.active {
|
||||||
|
background-color: #282b30;
|
||||||
|
}
|
||||||
|
|
||||||
|
#channels .info {
|
||||||
|
display: flex;
|
||||||
|
height: 16px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#channels .name {
|
||||||
|
flex-grow: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#channels .tag {
|
||||||
|
flex-shrink: 1;
|
||||||
|
background-color: rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-left: 4px;
|
||||||
|
margin-top: 1px;
|
||||||
|
padding: 2px 5px;
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
13
app/Resources/Viewer/styles/main.css
Normal file
13
app/Resources/Viewer/styles/main.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
font-family: Whitney, "Helvetica Neue", Helvetica, Verdana, "Lucida Grande", sans-serif;
|
||||||
|
line-height: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#app {
|
||||||
|
height: calc(100vh - 48px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
78
app/Resources/Viewer/styles/menu.css
Normal file
78
app/Resources/Viewer/styles/menu.css
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
#menu {
|
||||||
|
width: 100%;
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
background-color: #17181c;
|
||||||
|
border-bottom: 1px dotted #5d626b;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu .splitter {
|
||||||
|
width: 1px;
|
||||||
|
margin: 9px 4px;
|
||||||
|
background-color: #5d626b;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu .separator {
|
||||||
|
flex: 1 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu :disabled {
|
||||||
|
background-color: #555;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu button, #menu select, #menu input[type="text"] {
|
||||||
|
margin: 8px;
|
||||||
|
background-color: #7289da;
|
||||||
|
color: #fff;
|
||||||
|
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu button {
|
||||||
|
font-size: 17px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu select {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 6px;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu input[type="text"] {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu .nav {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu .nav > button {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu .nav > button.icon {
|
||||||
|
font-family: Lucida Console, monospace;
|
||||||
|
font-size: 17px;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#menu .nav > button, #menu .nav > p {
|
||||||
|
margin: 8px 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#opt-filter-list > select, #opt-filter-list > input {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#opt-filter-list > .active {
|
||||||
|
display: block;
|
||||||
|
}
|
254
app/Resources/Viewer/styles/messages.css
Normal file
254
app/Resources/Viewer/styles/messages.css
Normal file
@ -0,0 +1,254 @@
|
|||||||
|
#messages {
|
||||||
|
flex: 1 1 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
background-color: #36393E;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages > div {
|
||||||
|
margin: 0 24px;
|
||||||
|
padding: 4px 0 12px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages h2 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .avatar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
align-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .avatar-wrapper > div {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .avatar {
|
||||||
|
flex: 0 0 38px !important;
|
||||||
|
margin: 8px 14px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .avatar img {
|
||||||
|
width: 38px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .username {
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 3px;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .info {
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .info::before {
|
||||||
|
content: "\2022";
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#messages .jump {
|
||||||
|
cursor: pointer;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-underline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
margin-top: 6px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.1em;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .link, .reply-message .link {
|
||||||
|
color: #7289DA;
|
||||||
|
background-color: rgba(115, 139, 215, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.message a, .reply-message a {
|
||||||
|
color: #0096CF;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .embed {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .embed .title {
|
||||||
|
font-weight: bold;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .embed .desc {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .thumbnail {
|
||||||
|
max-width: calc(100% - 20px);
|
||||||
|
max-height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .thumbnail img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 320px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .download {
|
||||||
|
margin-right: 8px;
|
||||||
|
padding: 8px 9px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .embed:first-child, .message .download + .download {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message code {
|
||||||
|
background-color: #2E3136;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: Menlo, Consolas, Monaco, monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message code.inline {
|
||||||
|
display: inline;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message code.block {
|
||||||
|
display: block;
|
||||||
|
border: 2px solid #282B30;
|
||||||
|
margin-top: 6px;
|
||||||
|
padding: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message .emoji {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
margin: 0 1px;
|
||||||
|
vertical-align: -30%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-message {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
line-height: 120%;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-message-with-avatar {
|
||||||
|
margin: 0 0 -2px 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-message .jump {
|
||||||
|
color: rgba(255, 255, 255, 0.4);
|
||||||
|
font-size: 12px;
|
||||||
|
text-underline-offset: 1px;
|
||||||
|
margin-right: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-message .emoji {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
vertical-align: -20%;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-message .user {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-avatar img {
|
||||||
|
width: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-username {
|
||||||
|
color: #FFF;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-contents {
|
||||||
|
display: inline-block;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-size: 12px;
|
||||||
|
max-width: calc(80%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-contents p {
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-contents code {
|
||||||
|
background-color: #2E3136;
|
||||||
|
font-family: Menlo, Consolas, Monaco, monospace;
|
||||||
|
padding: 1px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reactions {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reactions .reaction-wrapper {
|
||||||
|
display: inline-block;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 3px 2px 0 0;
|
||||||
|
padding: 3px 6px;
|
||||||
|
background: #42454a;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reactions .reaction-emoji {
|
||||||
|
margin-right: 5px;
|
||||||
|
font-size: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: -5%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reactions .reaction-emoji-custom {
|
||||||
|
height: 15px;
|
||||||
|
margin-right: 5px;
|
||||||
|
vertical-align: -10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reactions .count {
|
||||||
|
color: rgba(255, 255, 255, 0.45);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
46
app/Resources/Viewer/styles/modal.css
Normal file
46
app/Resources/Viewer/styles/modal.css
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
#modal div {
|
||||||
|
position: absolute;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal.visible div {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal #overlay {
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
#modal.visible #overlay {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog {
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: #fff;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog p {
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog p:first-child, #dialog p:last-child {
|
||||||
|
margin-top: 1px;
|
||||||
|
margin-bottom: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog a {
|
||||||
|
color: #0096cf;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
19
app/Server/Collections/MultiDictionary.cs
Normal file
19
app/Server/Collections/MultiDictionary.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DHT.Server.Collections {
|
||||||
|
public class MultiDictionary<TKey, TValue> where TKey : notnull {
|
||||||
|
private readonly Dictionary<TKey, List<TValue>> dict = new();
|
||||||
|
|
||||||
|
public void Add(TKey key, TValue value) {
|
||||||
|
if (!dict.TryGetValue(key, out var list)) {
|
||||||
|
dict[key] = list = new List<TValue>();
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<TValue>? GetListOrNull(TKey key) {
|
||||||
|
return dict.TryGetValue(key, out var list) ? list : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
9
app/Server/Data/Attachment.cs
Normal file
9
app/Server/Data/Attachment.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace DHT.Server.Data {
|
||||||
|
public readonly struct Attachment {
|
||||||
|
public ulong Id { get; init; }
|
||||||
|
public string Name { get; init; }
|
||||||
|
public string? Type { get; init; }
|
||||||
|
public string Url { get; init; }
|
||||||
|
public ulong Size { get; init; }
|
||||||
|
}
|
||||||
|
}
|
10
app/Server/Data/Channel.cs
Normal file
10
app/Server/Data/Channel.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
namespace DHT.Server.Data {
|
||||||
|
public readonly struct Channel {
|
||||||
|
public ulong Id { get; init; }
|
||||||
|
public ulong Server { get; init; }
|
||||||
|
public string Name { get; init; }
|
||||||
|
public int? Position { get; init; }
|
||||||
|
public string? Topic { get; init; }
|
||||||
|
public bool? Nsfw { get; init; }
|
||||||
|
}
|
||||||
|
}
|
5
app/Server/Data/Embed.cs
Normal file
5
app/Server/Data/Embed.cs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
namespace DHT.Server.Data {
|
||||||
|
public readonly struct Embed {
|
||||||
|
public string Json { get; init; }
|
||||||
|
}
|
||||||
|
}
|
9
app/Server/Data/EmojiFlags.cs
Normal file
9
app/Server/Data/EmojiFlags.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace DHT.Server.Data {
|
||||||
|
[Flags]
|
||||||
|
public enum EmojiFlags : ushort {
|
||||||
|
None = 0,
|
||||||
|
Animated = 0b1
|
||||||
|
}
|
||||||
|
}
|
11
app/Server/Data/Filters/MessageFilter.cs
Normal file
11
app/Server/Data/Filters/MessageFilter.cs
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace DHT.Server.Data.Filters {
|
||||||
|
public class MessageFilter {
|
||||||
|
public DateTime? StartDate { get; set; }
|
||||||
|
public DateTime? EndDate { get; set; }
|
||||||
|
|
||||||
|
public HashSet<ulong> MessageIds { get; } = new();
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user