mirror of
https://github.com/microsoft/PowerToys.git
synced 2024-11-27 14:59:16 +08:00
[GPO][AdvPaste]Online AI models GPO (#33045)
* [Advanced Paste] AI gpo * address PR comments * XAML format * Fix showing Enable Paste with AI with module disabled * Rename variable in ViewModel for clarity * Update adml revision * Move policy registry key around * Update src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com> --------- Co-authored-by: Jaime Bernardo <jaime@janeasystems.com> Co-authored-by: Heiko <61519853+htcfreek@users.noreply.github.com>
This commit is contained in:
parent
8bb5a33572
commit
d0d2f3cd9c
@ -172,4 +172,8 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetAllowedAdvancedPasteOnlineAIModelsValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue());
|
||||
}
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
||||
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredEnvironmentVariablesEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredQoiPreviewEnabledValue();
|
||||
static GpoRuleConfigured GetConfiguredQoiThumbnailsEnabledValue();
|
||||
static GpoRuleConfigured GetAllowedAdvancedPasteOnlineAIModelsValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ namespace powertoys_gpo {
|
||||
// The registry value names for other PowerToys policies.
|
||||
const std::wstring POLICY_ALLOW_EXPERIMENTATION = L"AllowExperimentation";
|
||||
const std::wstring POLICY_CONFIGURE_ENABLED_POWER_LAUNCHER_ALL_PLUGINS = L"PowerLauncherAllPluginsEnabledState";
|
||||
|
||||
const std::wstring POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS = L"AllowPowerToysAdvancedPasteOnlineAIModels";
|
||||
|
||||
inline std::optional<std::wstring> readRegistryStringValue(HKEY hRootKey, const std::wstring& subKey, const std::wstring& value_name)
|
||||
{
|
||||
@ -470,4 +470,9 @@ namespace powertoys_gpo {
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_CONFIGURE_ENABLED_QOI_THUMBNAILS);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getAllowedAdvancedPasteOnlineAIModelsValue()
|
||||
{
|
||||
return getUtilityEnabledValue(POLICY_ALLOW_ADVANCED_PASTE_ONLINE_AI_MODELS);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License. -->
|
||||
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.9" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.10" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<policyNamespaces>
|
||||
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
|
||||
</policyNamespaces>
|
||||
<resources minRequiredRevision="1.9"/><!-- Last changed with PowerToys v0.81.0 -->
|
||||
<resources minRequiredRevision="1.10"/><!-- Last changed with PowerToys v0.81.1 -->
|
||||
<supportedOn>
|
||||
<definitions>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
|
||||
@ -18,6 +18,7 @@
|
||||
<definition name="SUPPORTED_POWERTOYS_0_77_0" displayName="$(string.SUPPORTED_POWERTOYS_0_77_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_78_0" displayName="$(string.SUPPORTED_POWERTOYS_0_78_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_81_0" displayName="$(string.SUPPORTED_POWERTOYS_0_81_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_81_1" displayName="$(string.SUPPORTED_POWERTOYS_0_81_1)"/>
|
||||
</definitions>
|
||||
</supportedOn>
|
||||
<categories>
|
||||
@ -28,6 +29,9 @@
|
||||
<category name="PowerToysRun" displayName="$(string.PowerToysRun)">
|
||||
<parentCategory ref="PowerToys" />
|
||||
</category>
|
||||
<category name="AdvancedPaste" displayName="$(string.AdvancedPaste)">
|
||||
<parentCategory ref="PowerToys" />
|
||||
</category>
|
||||
</categories>
|
||||
<policies>
|
||||
|
||||
@ -489,5 +493,15 @@
|
||||
<list id="PowerToysRunIndividualPluginEnabledList" explicitValue="true" />
|
||||
</elements>
|
||||
</policy>
|
||||
<policy name="AllowPowerToysAdvancedPasteOnlineAIModels" class="Both" displayName="$(string.AllowPowerToysAdvancedPasteOnlineAIModels)" explainText="$(string.AllowPowerToysAdvancedPasteOnlineAIModelsDescription)" key="Software\Policies\PowerToys" valueName="AllowPowerToysAdvancedPasteOnlineAIModels">
|
||||
<parentCategory ref="AdvancedPaste" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_81_1" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
</policies>
|
||||
</policyDefinitions>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License. -->
|
||||
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.9" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.10" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<displayName>PowerToys</displayName>
|
||||
<description>PowerToys</description>
|
||||
<resources>
|
||||
@ -9,6 +9,7 @@
|
||||
<string id="PowerToys">Microsoft PowerToys</string>
|
||||
<string id="InstallerUpdates">Installer and Updates</string>
|
||||
<string id="PowerToysRun">PowerToys Run</string>
|
||||
<string id="AdvancedPaste">Advanced Paste</string>
|
||||
|
||||
<string id="SUPPORTED_POWERTOYS_0_64_0">PowerToys version 0.64.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_68_0">PowerToys version 0.68.0 or later</string>
|
||||
@ -20,6 +21,7 @@
|
||||
<string id="SUPPORTED_POWERTOYS_0_77_0">PowerToys version 0.77.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_78_0">PowerToys version 0.78.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_81_0">PowerToys version 0.81.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_81_1">PowerToys version 0.81.1 or later</string>
|
||||
|
||||
<string id="ConfigureGlobalUtilityEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
|
||||
|
||||
@ -118,6 +120,12 @@ If you disable or don't configure this policy, either the user or the policy "Co
|
||||
You can set the enabled state for all plugins not configured by this policy using the policy "Configure enabled state for all plugins".
|
||||
|
||||
Note: Changes require a restart of PowerToys Run.
|
||||
</string>
|
||||
<string id="AllowPowerToysAdvancedPasteOnlineAIModelsDescription">This policy configures the enabled disable state for using Advanced Paste online AI models.
|
||||
|
||||
If you enable or don't configure this policy, the user takes control over the enabled state of the Enable paste with AI Advanced Paste setting.
|
||||
|
||||
If you disable this policy, the user won't be able to enable Enable paste with AI Advanced Paste setting and use Advanced Paste AI prompt nor set up the Open AI key in PowerToys Settings.
|
||||
</string>
|
||||
<string id="ConfigureGlobalUtilityEnabledState">Configure global utility enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityAdvancedPaste">Advanced Paste: Configure enabled state</string>
|
||||
@ -165,6 +173,7 @@ Note: Changes require a restart of PowerToys Run.
|
||||
<string id="PowerToysRunIndividualPluginEnabledState">Configure enabled state for individual plugins</string>
|
||||
<string id="ConfigureEnabledUtilityFileExplorerQOIPreview">QOI file preview: Configure enabled state</string>
|
||||
<string id="ConfigureEnabledUtilityFileExplorerQOIThumbnails">QOI file thumbnail: Configure enabled state</string>
|
||||
<string id="AllowPowerToysAdvancedPasteOnlineAIModels">Advanced Paste: Allow using online AI models</string>
|
||||
</stringTable>
|
||||
|
||||
<presentationTable>
|
||||
|
@ -225,4 +225,7 @@
|
||||
<data name="TermsLink.Text" xml:space="preserve">
|
||||
<value>OpenAI Terms</value>
|
||||
</data>
|
||||
<data name="OpenAIGpoDisabled" xml:space="preserve">
|
||||
<value>To custom with AI is disabled by your organization</value>
|
||||
</data>
|
||||
</root>
|
@ -16,7 +16,6 @@ using CommunityToolkit.Mvvm.Input;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Win32;
|
||||
using Windows.ApplicationModel.DataTransfer;
|
||||
using WinUIEx;
|
||||
@ -81,32 +80,40 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
GetClipboardData();
|
||||
|
||||
var openAIKey = AICompletionsHelper.LoadOpenAIKey();
|
||||
var currentKey = aiHelper.GetKey();
|
||||
bool keyChanged = openAIKey != currentKey;
|
||||
|
||||
if (keyChanged)
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
app.GetMainWindow().StartLoading();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
aiHelper.SetOpenAIKey(openAIKey);
|
||||
}).ContinueWith(
|
||||
(t) =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled);
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
});
|
||||
},
|
||||
TaskScheduler.Default);
|
||||
IsCustomAIEnabled = false;
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
var openAIKey = AICompletionsHelper.LoadOpenAIKey();
|
||||
var currentKey = aiHelper.GetKey();
|
||||
bool keyChanged = openAIKey != currentKey;
|
||||
|
||||
if (keyChanged)
|
||||
{
|
||||
app.GetMainWindow().StartLoading();
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
aiHelper.SetOpenAIKey(openAIKey);
|
||||
}).ContinueWith(
|
||||
(t) =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
app.GetMainWindow().FinishLoading(aiHelper.IsAIEnabled);
|
||||
OnPropertyChanged(nameof(InputTxtBoxPlaceholderText));
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
});
|
||||
},
|
||||
TaskScheduler.Default);
|
||||
}
|
||||
else
|
||||
{
|
||||
IsCustomAIEnabled = IsClipboardDataText && aiHelper.IsAIEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
ClipboardHistoryEnabled = IsClipboardHistoryEnabled();
|
||||
@ -146,7 +153,11 @@ namespace AdvancedPaste.ViewModels
|
||||
{
|
||||
app.GetMainWindow().ClearInputText();
|
||||
|
||||
if (!aiHelper.IsAIEnabled)
|
||||
if (PowerToys.GPOWrapper.GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue() == PowerToys.GPOWrapper.GpoRuleConfigured.Disabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAIGpoDisabled");
|
||||
}
|
||||
else if (!aiHelper.IsAIEnabled)
|
||||
{
|
||||
return ResourceLoaderInstance.ResourceLoader.GetString("OpenAINotConfigured");
|
||||
}
|
||||
|
@ -45,7 +45,7 @@
|
||||
Severity="Informational" />
|
||||
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_EnableAISettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPaste_EnableAISettingsCard">
|
||||
<tkcontrols:SettingsCard x:Uid="AdvancedPaste_EnableAISettingsCard" IsEnabled="{x:Bind ViewModel.IsOnlineAIModelsDisallowedByGPO, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}">
|
||||
<tkcontrols:SettingsCard.HeaderIcon>
|
||||
<PathIcon Data="M128 766q0-42 24-77t65-48l178-57q32-11 61-30t52-42q50-50 71-114l58-179q13-40 48-65t78-26q42 0 77 24t50 65l58 177q21 66 72 117 49 50 117 72l176 58q43 14 69 48t26 80q0 41-25 76t-64 49l-178 58q-66 21-117 72-32 32-51 73t-33 84-26 83-30 73-45 51-71 20q-42 0-77-24t-49-65l-58-178q-8-25-19-47t-28-43q-34-43-77-68t-89-41-89-27-78-29-55-45-21-75zm1149 7q-76-29-145-53t-129-60-104-88-73-138l-57-176-67 176q-18 48-42 89t-60 78q-34 34-76 61t-89 43l-177 57q75 29 144 53t127 60 103 89 73 137l57 176 67-176q37-97 103-168t168-103l177-57zm-125 759q0-31 20-57t49-36l99-32q34-11 53-34t30-51 20-59 20-54 33-41 58-16q32 0 59 19t38 50q6 20 11 40t13 40 17 38 25 34q16 17 39 26t48 18 49 16 44 20 31 32 12 50q0 33-18 60t-51 38q-19 6-39 11t-41 13-39 17-34 25q-24 25-35 62t-24 73-35 61-68 25q-32 0-59-19t-38-50q-6-18-11-39t-13-41-17-40-24-33q-18-17-41-27t-47-17-49-15-43-20-30-33-12-54zm583 4q-43-13-74-30t-55-41-40-55-32-74q-12 41-29 72t-42 55-55 42-71 31q81 23 128 71t71 129q15-43 31-74t40-54 53-40 75-32z" />
|
||||
</tkcontrols:SettingsCard.HeaderIcon>
|
||||
@ -73,6 +73,12 @@
|
||||
IsEnabled="{x:Bind ViewModel.IsOpenAIEnabled, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.ShowCustomPreview, Mode=TwoWay}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<InfoBar
|
||||
x:Uid="GPO_SettingIsManaged"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.ShowOnlineAIModelsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
IsTabStop="{x:Bind ViewModel.ShowOnlineAIModelsGpoConfiguredInfoBar, Mode=OneWay}"
|
||||
Severity="Informational" />
|
||||
</controls:SettingsGroup>
|
||||
|
||||
<controls:SettingsGroup x:Uid="AdvancedPaste_ClipboardHistorySettingsGroup" IsEnabled="{x:Bind ViewModel.IsEnabled, Mode=OneWay}">
|
||||
|
@ -32,6 +32,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private GpoRuleConfigured _enabledGpoRuleConfiguration;
|
||||
private bool _enabledStateIsGPOConfigured;
|
||||
private GpoRuleConfigured _onlineAIModelsGpoRuleConfiguration;
|
||||
private bool _onlineAIModelsDisallowedByGPO;
|
||||
private bool _isEnabled;
|
||||
|
||||
private Func<string, int> SendConfigMSG { get; }
|
||||
@ -80,6 +82,15 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
_isEnabled = GeneralSettingsConfig.Enabled.AdvancedPaste;
|
||||
}
|
||||
|
||||
_onlineAIModelsGpoRuleConfiguration = GPOWrapper.GetAllowedAdvancedPasteOnlineAIModelsValue();
|
||||
if (_onlineAIModelsGpoRuleConfiguration == GpoRuleConfigured.Disabled)
|
||||
{
|
||||
_onlineAIModelsDisallowedByGPO = true;
|
||||
|
||||
// disable AI if it was enabled
|
||||
DisableAI();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsEnabled
|
||||
@ -124,13 +135,23 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return cred is not null;
|
||||
}
|
||||
|
||||
public bool IsOpenAIEnabled => OpenAIKeyExists();
|
||||
public bool IsOpenAIEnabled => OpenAIKeyExists() && !IsOnlineAIModelsDisallowedByGPO;
|
||||
|
||||
public bool IsEnabledGpoConfigured
|
||||
{
|
||||
get => _enabledStateIsGPOConfigured;
|
||||
}
|
||||
|
||||
public bool IsOnlineAIModelsDisallowedByGPO
|
||||
{
|
||||
get => _onlineAIModelsDisallowedByGPO || _enabledGpoRuleConfiguration == GpoRuleConfigured.Disabled;
|
||||
}
|
||||
|
||||
public bool ShowOnlineAIModelsGpoConfiguredInfoBar
|
||||
{
|
||||
get => _onlineAIModelsDisallowedByGPO && _enabledGpoRuleConfiguration != GpoRuleConfigured.Disabled;
|
||||
}
|
||||
|
||||
private bool IsClipboardHistoryEnabled()
|
||||
{
|
||||
string registryKey = @"HKEY_CURRENT_USER\Software\Microsoft\Clipboard\";
|
||||
@ -334,18 +355,30 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
internal void DisableAI()
|
||||
{
|
||||
PasswordVault vault = new PasswordVault();
|
||||
PasswordCredential cred = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
vault.Remove(cred);
|
||||
OnPropertyChanged(nameof(IsOpenAIEnabled));
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new PasswordVault();
|
||||
PasswordCredential cred = vault.Retrieve("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey");
|
||||
vault.Remove(cred);
|
||||
OnPropertyChanged(nameof(IsOpenAIEnabled));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal void EnableAI(string password)
|
||||
{
|
||||
PasswordVault vault = new PasswordVault();
|
||||
PasswordCredential cred = new PasswordCredential("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey", password);
|
||||
vault.Add(cred);
|
||||
OnPropertyChanged(nameof(IsOpenAIEnabled));
|
||||
try
|
||||
{
|
||||
PasswordVault vault = new PasswordVault();
|
||||
PasswordCredential cred = new PasswordCredential("https://platform.openai.com/api-keys", "PowerToys_AdvancedPaste_OpenAIKey", password);
|
||||
vault.Add(cred);
|
||||
OnPropertyChanged(nameof(IsOpenAIEnabled));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,4 +67,5 @@ void ReportGPOValues(const std::filesystem::path& tmpDir)
|
||||
report << "getAllowExperimentationValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowExperimentationValue()) << std::endl;
|
||||
report << "getConfiguredQoiPreviewEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredQoiPreviewEnabledValue()) << std::endl;
|
||||
report << "getConfiguredQoiThumbnailsEnabledValue: " << gpo_rule_configured_to_string(powertoys_gpo::getConfiguredQoiThumbnailsEnabledValue()) << std::endl;
|
||||
report << "getAllowedAdvancedPasteOnlineAIModelsValue: " << gpo_rule_configured_to_string(powertoys_gpo::getAllowedAdvancedPasteOnlineAIModelsValue()) << std::endl;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user