__version__ = (1, 0, 0)
# █▄▀ ▄▀█ █▀▄▀█ █▀▀ █▄▀ █ █ █▀█ █▀█
# █ █ █▀█ █ ▀ █ ██▄ █ █ ▀▄▄▀ █▀▄ █▄█ ▄
# © Copyright 2025
# ✈ https://t.me/kamekuro
# 🔒 Licensed under CC-BY-NC-ND 4.0 unless otherwise specified.
# 🌐 https://creativecommons.org/licenses/by-nc-nd/4.0
# + attribution
# + non-commercial
# + no-derivatives
# You CANNOT edit, distribute or redistribute this file without direct permission from the author.
# meta banner: https://raw.githubusercontent.com/kamekuro/hikka-mods/main/banners/privacy.png
# meta pic: https://raw.githubusercontent.com/kamekuro/hikka-mods/main/icons/privacy.png
# meta developer: @kamekuro_hmods
# scope: heroku_only
# scope: hikka_min 1.6.3
import logging
import re
import typing
import telethon
from telethon import types
from .. import loader, utils, inline
logger = logging.getLogger(__name__)
@loader.tds
class PrivacyMod(loader.Module):
"""Module for fastly changing privacy settings"""
_privacy_types = {
"phone": types.InputPrivacyKeyPhoneNumber,
"add_by_phone": types.InputPrivacyKeyAddedByPhone,
"online": types.InputPrivacyKeyStatusTimestamp,
"photos": types.InputPrivacyKeyProfilePhoto,
"forwards": types.InputPrivacyKeyForwards,
"calls": types.InputPrivacyKeyPhoneCall,
"p2p": types.InputPrivacyKeyPhoneP2P,
"voices": types.InputPrivacyKeyVoiceMessages,
# "messages": None,
"birthdate": getattr(types, "InputPrivacyKeyBirthday", None),
"gifts": getattr(types, "InputPrivacyKeyStarGiftsAutoSave", None),
"bio": getattr(types, "InputPrivacyKeyAbout", None),
"invites": types.InputPrivacyKeyChatInvite
}
_privacy_rules = {
types.PrivacyValueAllowAll: types.InputPrivacyValueAllowAll,
getattr(types, "PrivacyValueAllowBots", None): getattr(types, "InputPrivacyValueAllowBots", None),
types.PrivacyValueAllowChatParticipants: types.InputPrivacyValueAllowChatParticipants,
getattr(types, "PrivacyValueAllowCloseFriends", None): getattr(types, "InputPrivacyValueAllowCloseFriends", None),
types.PrivacyValueAllowContacts: types.InputPrivacyValueAllowContacts,
getattr(types, "PrivacyValueAllowPremium", None): getattr(types, "InputPrivacyValueAllowPremium", None),
types.PrivacyValueAllowUsers: types.InputPrivacyValueAllowUsers,
types.PrivacyValueDisallowAll: types.InputPrivacyValueDisallowAll,
getattr(types, "PrivacyValueDisallowBots", None): getattr(types, "InputPrivacyValueDisallowBots", None),
types.PrivacyValueDisallowChatParticipants: types.InputPrivacyValueDisallowChatParticipants,
types.PrivacyValueDisallowContacts: types.InputPrivacyValueDisallowContacts,
types.PrivacyValueDisallowUsers: types.InputPrivacyValueDisallowUsers
}
strings = {
"name": "Privacy",
"privacy_types": (
"🔗 Types of privacy settings:\n"
),
"no_user": "⚠️ You haven't specified user",
"u_silly": (
"🥺 You can't set privacy settings "
"exceptions for yourself, silly."
),
"choose_type": "🔑 Select the type of settings to set exceptions",
"not_supported_type": (
"⚠️ It is not possible to set exceptions "
"for the [{}] type of settings"
),
"allowed": (
"💕 {user} was added to the allowed "
"users for the [{pr}] type of settings"
),
"disallowed": (
"💔 {user} was added to the disallowed "
"users for the [{pr}] type of settings"
),
"privacy": {
"phone": "Phone Number",
"add_by_phone": "Who can find you by your number",
"p2p": "Using Peer-to-Peer in calls",
"online": "Last Seen & Online",
"photos": "Profile Photos",
"forwards": "Forwarded Messages",
"calls": "Calls",
"voices": "Voice Messages",
# "messages": "Messages" if _privacy_types.get("messages") else None,
"birthdate": "Date of Birth" if _privacy_types.get("birthdate") else None,
"gifts": "Gifts" if _privacy_types.get("gifts") else None,
"bio": "Bio" if _privacy_types.get("bio") else None,
"invites": "Invites"
}
}
strings_ru = {
"_cls_doc": "Модуль для быстрого изменения настроек конфиденциальности",
"privacy_types": (
"🔗 Типы настроек приватности:\n"
),
"no_user": "⚠️ Вы не указали пользователя",
"u_silly": (
"🥺 Ты не можешь выставить исключения "
"настроек приватности для самого себя, глупенький"
),
"choose_type": "🔑 Выберите тип настроек для выставления исключений",
"not_supported_type": (
"⚠️ Для типа настроек [{}] "
"невозможно выставить исключения"
),
"allowed": (
"💕 {user} добавлен в разрешённых "
"пользователей для настройки [{pr}]"
),
"disallowed": (
"💔 {user} добавлен в запрещённых "
"пользователей для настройки [{pr}]"
),
"privacy": {
"phone": "Номер телефона",
"add_by_phone": "Кто может найти Вас по номеру",
"p2p": "Использование peer-to-peer в звонках",
"online": "Время захода",
"photos": "Фотографии профиля",
"forwards": "Пересылка сообщений",
"calls": "Звонки",
"voices": "Голосовые сообщения",
# "messages": "Сообщения" if _privacy_types.get("messages") else None,
"birthdate": "Дата рождения" if _privacy_types.get("birthdate") else None,
"gifts": "Подарки" if _privacy_types.get("gifts") else None,
"bio": "О себе" if _privacy_types.get("bio") else None,
"invites": "Приглашения"
}
}
async def client_ready(self, client, db):
self._client: telethon.TelegramClient = client
self._db = db
@loader.command(
ru_doc="👉 Список типов настроек для указания их в командах",
alias="ptypes"
)
async def privacytypescmd(self, message: types.Message):
"""👉 List of setting types to pass it in commands"""
out = self.strings("privacy_types")
for key, item in self.strings("privacy").items():
if not item: continue
out += f" {key} — {item}\n"
await utils.answer(
message, out
)
@loader.command(
ru_doc="<пользователь> [настройка (необязательно)] 👉 Добавить пользователя в разрешённых для какой-либо настройки"
)
async def allowusercmd(self, message: types.Message):
""" [setting (optional)] 👉 Add user to includes for some setting"""
uid = await self.getID(message)
if (not uid) or (uid < 0):
return await utils.answer(message, self.strings("no_user"))
elif uid == (await self._client.get_me()).id:
return await utils.answer(message, self.strings("u_silly"))
args = utils.get_args(message)[(0 if message.is_reply else 1):]
new_allow_user: types.User = await self._client.get_entity(uid)
if (len(args) < 1) or (not self._privacy_types.get(args[0])):
return await self.inline.form(
message=message,
text=self.strings("choose_type"),
reply_markup=self.gen_kb_action(new_allow_user, "allow")
)
pr = self.strings('privacy').get(args[0])
if args[0] == "add_by_phone":
return await utils.answer(
message, self.strings("not_supported_type").format(pr)
)
key: types.TypeInputPrivacyKey = self._privacy_types.get(args[0])
rules = (await self._client(telethon.functions.account.GetPrivacyRequest(
key=key()
))).rules
await self.allow_user(new_allow_user, key, rules, "allow")
await utils.answer(
message, self.strings("allowed").format(
user=telethon.utils.get_display_name(new_allow_user), pr=pr
)
)
@loader.command(
ru_doc="<пользователь> [настройка (необязательно)] 👉 Добавить пользователя в запрещённых для какой-либо настройки"
)
async def disallowusercmd(self, message: types.Message):
""" [setting (optional)] 👉 Add user to excludes for some setting"""
uid = await self.getID(message)
if (not uid) or (uid < 0):
return await utils.answer(message, self.strings("no_user"))
elif uid == (await self._client.get_me()).id:
return await utils.answer(message, self.strings("u_silly"))
args = utils.get_args(message)[(0 if message.is_reply else 1):]
new_allow_user: types.User = await self._client.get_entity(uid)
if (len(args) < 1) or (not self._privacy_types.get(args[0])):
return await self.inline.form(
message=message,
text=self.strings("choose_type"),
reply_markup=self.gen_kb_action(new_allow_user, "disallow")
)
pr = self.strings('privacy').get(args[0])
if args[0] == "add_by_phone":
return await utils.answer(
message, self.strings("not_supported_type").format(pr)
)
key: types.TypeInputPrivacyKey = self._privacy_types.get(args[0])
rules = (await self._client(telethon.functions.account.GetPrivacyRequest(
key=key()
))).rules
await self.allow_user(new_allow_user, key, rules, "disallow")
await utils.answer(
message, self.strings("disallowed").format(
user=telethon.utils.get_display_name(new_allow_user), pr=pr
)
)
async def allow_by_kb(
self,
call: inline.types.InlineCall,
new_user: types.User,
key: types.TypeInputPrivacyKey
):
pr = "[Unknown]"
for i in self._privacy_types.keys():
if self._privacy_types[i] == key:
pr = self.strings('privacy').get(i); break
rules = (await self._client(telethon.functions.account.GetPrivacyRequest(
key=key()
))).rules
await self.allow_user(new_user, key, rules, "allow")
await call.edit(
text=self.strings("allowed").format(
user=telethon.utils.get_display_name(new_user), pr=pr
)
)
async def disallow_by_kb(
self,
call: inline.types.InlineCall,
new_user: types.User,
key: types.TypeInputPrivacyKey
):
pr = "[Unknown]"
for i in self._privacy_types.keys():
if self._privacy_types[i] == key:
pr = self.strings('privacy').get(i); break
rules = (await self._client(telethon.functions.account.GetPrivacyRequest(
key=key()
))).rules
await self.allow_user(new_user, key, rules, "disallow")
await call.edit(
text=self.strings("disallowed").format(
user=telethon.utils.get_display_name(new_user), pr=pr
)
)
async def allow_user(
self,
new_allow_user: types.User,
key: types.TypeInputPrivacyKey,
rules: list,
action: str = "allow" # allow/disallow
):
new_rules = []
f_all = [x for x in rules if type(x) in [types.PrivacyValueAllowAll, types.PrivacyValueDisallowAll]]
f_all = f_all[0] if f_all else None
allow_users, disallow_users = [], []
allow_chats, disallow_chats = [], []
if (action == "allow") and (type(f_all) != types.PrivacyValueAllowAll):
allow_users.append(types.InputUser(new_allow_user.id, new_allow_user.access_hash))
elif (action == "disallow") and (type(f_all) != types.PrivacyValueDisallowAll):
disallow_users.append(types.InputUser(new_allow_user.id, new_allow_user.access_hash))
need_to_set = True
for rule in rules:
rule_type = type(rule)
if rule_type == types.PrivacyValueAllowUsers:
for user in rule.users:
if (user == new_allow_user.id) and (action == "allow"):
need_to_set = False; break
if (user == new_allow_user.id) and (action == "disallow"):
continue
us: types.User = await self._client.get_entity(user)
allow_users.append(types.InputUser(us.id, us.access_hash))
elif rule_type == types.PrivacyValueDisallowUsers:
for user in rule.users:
if (user == new_allow_user.id) and (action == "disallow"):
need_to_set = False; break
if (user == new_allow_user.id) and (action == "allow"): continue
us: types.User = await self._client.get_entity(user)
disallow_users.append(types.InputUser(us.id, us.access_hash))
elif rule_type == types.PrivacyValueAllowChatParticipants:
for chat in rule.chats:
allow_chats.append(chat)
elif rule_type == types.PrivacyValueDisallowChatParticipants:
for chat in rule.chats:
disallow_chats.append(chat)
else:
new_rules.append(self._privacy_rules[rule_type]())
if allow_users:
new_rules.append(types.InputPrivacyValueAllowUsers(allow_users))
if disallow_users:
new_rules.append(types.InputPrivacyValueDisallowUsers(disallow_users))
if allow_chats:
new_rules.append(types.InputPrivacyValueAllowChatParticipants(allow_chats))
if disallow_chats:
new_rules.append(types.InputPrivacyValueDisallowChatParticipants(disallow_chats))
if need_to_set:
await self._client(telethon.functions.account.SetPrivacyRequest(
key=key(), rules=new_rules
))
def gen_kb_action(
self,
new_user: types.User,
action: str # allow/disallow
):
return self.split_list([
{
"text": self.strings("privacy").get(key),
"callback": self.allow_by_kb if action == "allow" else self.disallow_by_kb,
"args": (new_user, item)
} for key, item in self._privacy_types.items() if item
], 2)
async def getID(self, message: types.Message):
reply: types.Message = await message.get_reply_message()
if reply:
return reply.sender_id
args: list = utils.get_args(message)
if not args: return None
username = args[0] if args else ""
match = re.search(r"(?:t\.me/|@|^(\w+)\.t\.me$)([a-zA-Z0-9_\.]+)", username)
if match:
username = match.group(1) or match.group(2)
try:
response = await self._client(telethon.functions.contacts.ResolveUsernameRequest(username))
except telethon.errors.rpcbaseerrors.RPCError:
response = None
try:
user_entity = await self._client.get_entity(username)
except Exception:
user_entity = None
if response and response.users:
return response.users[0].id
if user_entity:
return getattr(user_entity, "user_id", None)
return None
def split_list(self, input_list: typing.List, chunk_size: int):
return [input_list[i:i+chunk_size] for i in range(0, len(input_list), chunk_size)]