Rubka 7.1.4__py3-none-any.whl → 7.1.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of Rubka might be problematic. Click here for more details.
- rubka/api.py +0 -6
- rubka/asynco.py +73 -32
- rubka/context.py +105 -14
- {rubka-7.1.4.dist-info → rubka-7.1.5.dist-info}/METADATA +3 -3
- {rubka-7.1.4.dist-info → rubka-7.1.5.dist-info}/RECORD +8 -8
- {rubka-7.1.4.dist-info → rubka-7.1.5.dist-info}/WHEEL +0 -0
- {rubka-7.1.4.dist-info → rubka-7.1.5.dist-info}/entry_points.txt +0 -0
- {rubka-7.1.4.dist-info → rubka-7.1.5.dist-info}/top_level.txt +0 -0
rubka/api.py
CHANGED
|
@@ -61,13 +61,7 @@ def check_rubka_version():
|
|
|
61
61
|
latest_version = get_latest_version(package_name)
|
|
62
62
|
if latest_version is None:return
|
|
63
63
|
if installed_version != latest_version:
|
|
64
|
-
print(f"\n\nWARNING: Your installed version of '{package_name}' is OUTDATED and may cause errors or security risks!")
|
|
65
|
-
print(f"Installed version : {installed_version}")
|
|
66
|
-
print(f"Latest available version : {latest_version}")
|
|
67
|
-
print(f"Please update IMMEDIATELY by running:")
|
|
68
64
|
print(f"\npip install {package_name}=={latest_version}\n")
|
|
69
|
-
print(f"Not updating may lead to malfunctions or incompatibility.")
|
|
70
|
-
print(f"To see new methods : @rubka_library\n\n")
|
|
71
65
|
|
|
72
66
|
check_rubka_version()
|
|
73
67
|
def show_last_six_words(text):
|
rubka/asynco.py
CHANGED
|
@@ -72,14 +72,16 @@ async def check_rubka_version():
|
|
|
72
72
|
return
|
|
73
73
|
|
|
74
74
|
if installed_version != latest_version:
|
|
75
|
-
print(f"
|
|
76
|
-
print(
|
|
77
|
-
print(f"
|
|
78
|
-
print(f"
|
|
79
|
-
print(
|
|
80
|
-
print("
|
|
81
|
-
print("
|
|
82
|
-
|
|
75
|
+
print(f"CRITICAL WARNING: Your installed version of '{package_name}' is outdated.")
|
|
76
|
+
print("This poses a serious risk to stability, security, and compatibility with current features.")
|
|
77
|
+
print(f"- Installed version : {installed_version}")
|
|
78
|
+
print(f"- Latest version : {latest_version}")
|
|
79
|
+
print("\nImmediate action is required.")
|
|
80
|
+
print(f"Run the following command to update safely:")
|
|
81
|
+
print(f"\n pip install {package_name}=={latest_version}\n")
|
|
82
|
+
print("Delaying this update may result in unexpected crashes, data loss, or broken functionality.")
|
|
83
|
+
print("Stay up-to-date to ensure full support and access to the latest improvements.")
|
|
84
|
+
print("For new methods and updates, visit: @rubka_library\n")
|
|
83
85
|
|
|
84
86
|
|
|
85
87
|
|
|
@@ -141,11 +143,12 @@ safeSendMode ensures reliable message sending even if replying by message_id fai
|
|
|
141
143
|
max_cache_size and max_msg_age help manage duplicate message processing efficiently.
|
|
142
144
|
"""
|
|
143
145
|
|
|
144
|
-
def __init__(self, token: str, session_name: str = None, auth: str = None, Key: str = None, platform: str = "web", web_hook: str = None, timeout: int = 10, show_progress: bool = False, raise_errors: bool = True,proxy: str = None,retries: int = 2,retry_delay: float = 0.5,user_agent: str = None,safeSendMode = False,max_cache_size: int = 2000,max_msg_age : int = 60):
|
|
146
|
+
def __init__(self, token: str, session_name: str = None, auth: str = None, Key: str = None, platform: str = "web", web_hook: str = None, timeout: int = 10, show_progress: bool = False, raise_errors: bool = True,proxy: str = None,retries: int = 2,retry_delay: float = 0.5,user_agent: str = None,safeSendMode = False,max_cache_size: int = 2000,max_msg_age : int = 60,chunk_size : int = 64 * 1024):
|
|
145
147
|
self.token = token
|
|
146
148
|
self._inline_query_handlers: List[dict] = []
|
|
147
149
|
self.timeout = timeout
|
|
148
150
|
self.auth = auth
|
|
151
|
+
self.chunk_size = chunk_size
|
|
149
152
|
self.safeSendMode = safeSendMode
|
|
150
153
|
self.user_agent = user_agent
|
|
151
154
|
self.proxy = proxy
|
|
@@ -1864,41 +1867,45 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
|
|
|
1864
1867
|
if chat_keypad_type: payload["chat_keypad_type"] = chat_keypad_type
|
|
1865
1868
|
return await self._post("sendLocation", {k: v for k, v in payload.items() if v is not None})
|
|
1866
1869
|
async def upload_media_file(self, upload_url: str, name: str, path: Union[str, Path]) -> str:
|
|
1867
|
-
is_temp_file = False
|
|
1868
1870
|
session = await self._get_session()
|
|
1871
|
+
is_temp_file = False
|
|
1869
1872
|
if isinstance(path, str) and path.startswith("http"):
|
|
1870
1873
|
async with session.get(path) as response:
|
|
1871
1874
|
if response.status != 200:
|
|
1872
1875
|
raise Exception(f"Failed to download file from URL ({response.status})")
|
|
1873
1876
|
content = await response.read()
|
|
1874
|
-
with tempfile.NamedTemporaryFile(delete=False) as
|
|
1875
|
-
|
|
1876
|
-
path =
|
|
1877
|
+
with tempfile.NamedTemporaryFile(delete=False) as tmp:
|
|
1878
|
+
tmp.write(content)
|
|
1879
|
+
path = tmp.name
|
|
1877
1880
|
is_temp_file = True
|
|
1881
|
+
|
|
1878
1882
|
file_size = os.path.getsize(path)
|
|
1879
|
-
|
|
1880
|
-
|
|
1883
|
+
chunk_size = self.chunk_size
|
|
1884
|
+
|
|
1885
|
+
progress_bar = tqdm(total=file_size, unit='B', unit_scale=True, unit_divisor=1024,
|
|
1886
|
+
desc=f'Uploading: {name}', colour='cyan', disable=not getattr(self, 'show_progress', True))
|
|
1887
|
+
|
|
1888
|
+
async def file_generator(file_path):
|
|
1881
1889
|
async with aiofiles.open(file_path, 'rb') as f:
|
|
1882
|
-
while
|
|
1883
|
-
chunk = await f.read(chunk_size)
|
|
1884
|
-
if not chunk:
|
|
1885
|
-
break
|
|
1890
|
+
while chunk := await f.read(chunk_size):
|
|
1886
1891
|
progress_bar.update(len(chunk))
|
|
1887
1892
|
yield chunk
|
|
1888
|
-
|
|
1889
|
-
|
|
1893
|
+
|
|
1894
|
+
form = aiohttp.FormData()
|
|
1895
|
+
form.add_field('file', file_generator(path), filename=name, content_type='application/octet-stream')
|
|
1896
|
+
|
|
1890
1897
|
try:
|
|
1891
|
-
async with session.post(upload_url, data=
|
|
1898
|
+
async with session.post(upload_url, data=form, timeout=aiohttp.ClientTimeout(total=None)) as response:
|
|
1892
1899
|
progress_bar.close()
|
|
1893
1900
|
if response.status != 200:
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1901
|
+
text = await response.text()
|
|
1902
|
+
raise Exception(f"Upload failed ({response.status}): {text}")
|
|
1903
|
+
return (await response.json()).get('data', {}).get('file_id')
|
|
1904
|
+
except Exception as e:
|
|
1905
|
+
raise FeatureNotAvailableError(f"File upload not supported: {e}")
|
|
1906
|
+
finally:
|
|
1907
|
+
if is_temp_file:
|
|
1908
|
+
os.remove(path)
|
|
1902
1909
|
def get_extension(content_type: str) -> str:
|
|
1903
1910
|
ext = mimetypes.guess_extension(content_type)
|
|
1904
1911
|
return ext if ext else ''
|
|
@@ -2237,8 +2244,42 @@ max_cache_size and max_msg_age help manage duplicate message processing efficien
|
|
|
2237
2244
|
def get_all_member(self, channel_guid: str, search_text: str = None, start_id: str = None, just_get_guids: bool = False):
|
|
2238
2245
|
client = self._get_client()
|
|
2239
2246
|
return client.get_all_members(channel_guid, search_text, start_id, just_get_guids)
|
|
2240
|
-
async def send_poll(
|
|
2241
|
-
|
|
2247
|
+
async def send_poll(
|
|
2248
|
+
self,
|
|
2249
|
+
chat_id: str,
|
|
2250
|
+
question: str,
|
|
2251
|
+
options: List[str],
|
|
2252
|
+
type: Literal["Regular", "Quiz"] = "Regular",
|
|
2253
|
+
allows_multiple_answers: bool = False,
|
|
2254
|
+
is_anonymous: bool = True,
|
|
2255
|
+
correct_option_index: Optional[int] = None,
|
|
2256
|
+
hint: Optional[str] = None,
|
|
2257
|
+
reply_to_message_id: Optional[str] = None,
|
|
2258
|
+
disable_notification: bool = False,
|
|
2259
|
+
show_results : bool = False,
|
|
2260
|
+
inline_keypad : Optional[Dict[str, Any]] = None,
|
|
2261
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
2262
|
+
chat_keypad_type: Optional[Literal["New", "Remove", "None"]] = None,
|
|
2263
|
+
|
|
2264
|
+
) -> AttrDict:
|
|
2265
|
+
return await self._post(
|
|
2266
|
+
"sendPoll", {
|
|
2267
|
+
"chat_id": chat_id,
|
|
2268
|
+
"question": question,
|
|
2269
|
+
"options": options,
|
|
2270
|
+
"type": type,
|
|
2271
|
+
"allows_multiple_answers": allows_multiple_answers,
|
|
2272
|
+
"is_anonymous": is_anonymous,
|
|
2273
|
+
"correct_option_index": correct_option_index,
|
|
2274
|
+
"explanation": hint,
|
|
2275
|
+
"show_results":show_results,
|
|
2276
|
+
"reply_to_message_id": reply_to_message_id,
|
|
2277
|
+
"disable_notification": disable_notification,
|
|
2278
|
+
"chat_keypad": chat_keypad,
|
|
2279
|
+
"inline_keypad":inline_keypad,
|
|
2280
|
+
"chat_keypad_type": chat_keypad_type,
|
|
2281
|
+
}
|
|
2282
|
+
)
|
|
2242
2283
|
async def check_join(self, channel_guid: str, chat_id: str = None) -> Union[bool, list[str]]:
|
|
2243
2284
|
client = self._get_client()
|
|
2244
2285
|
if chat_id:
|
rubka/context.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from typing import Any, Dict, List,Optional
|
|
2
1
|
from typing import Union
|
|
3
2
|
from pathlib import Path
|
|
3
|
+
from typing import List, Optional, Dict, Any, Literal, Callable, Union,Set
|
|
4
4
|
import re,inspect
|
|
5
5
|
import asyncio
|
|
6
6
|
class File:
|
|
@@ -336,14 +336,40 @@ class Message:
|
|
|
336
336
|
)
|
|
337
337
|
|
|
338
338
|
|
|
339
|
-
def reply_poll(
|
|
340
|
-
|
|
339
|
+
def reply_poll(
|
|
340
|
+
self,
|
|
341
|
+
question: str,
|
|
342
|
+
options: List[str],
|
|
343
|
+
type: Literal['Regular', 'Quiz'] = "Regular",
|
|
344
|
+
allows_multiple_answers: bool = False,
|
|
345
|
+
is_anonymous: bool = True,
|
|
346
|
+
correct_option_index: Optional[int] = None,
|
|
347
|
+
hint: Optional[str] = None,
|
|
348
|
+
reply_to_message_id: Optional[str] = None,
|
|
349
|
+
disable_notification: bool = False,
|
|
350
|
+
show_results: bool = False,
|
|
351
|
+
inline_keypad: Optional[Dict[str, Any]] = None,
|
|
352
|
+
chat_keypad: Optional[Dict[str, Any]] = None,
|
|
353
|
+
chat_keypad_type: Optional[Literal['New', 'Remove', 'None']] = None,
|
|
354
|
+
**kwargs
|
|
355
|
+
) -> dict:
|
|
356
|
+
payload = {
|
|
341
357
|
"chat_id": self.chat_id,
|
|
342
358
|
"question": question,
|
|
343
359
|
"options": options,
|
|
344
|
-
"
|
|
345
|
-
|
|
346
|
-
|
|
360
|
+
"type": type,
|
|
361
|
+
"allows_multiple_answers": allows_multiple_answers,
|
|
362
|
+
"is_anonymous": is_anonymous,
|
|
363
|
+
"correct_option_index": correct_option_index,
|
|
364
|
+
"hint": hint,
|
|
365
|
+
"reply_to_message_id": self.message_id if not reply_to_message_id else reply_to_message_id,
|
|
366
|
+
"disable_notification": disable_notification,
|
|
367
|
+
"show_results": show_results,
|
|
368
|
+
"inline_keypad": inline_keypad,
|
|
369
|
+
"chat_keypad": chat_keypad,
|
|
370
|
+
"chat_keypad_type": chat_keypad_type,
|
|
371
|
+
}
|
|
372
|
+
return self.bot._post("sendPoll", {key: value for key, value in payload.items() if value is not None}, **kwargs)
|
|
347
373
|
|
|
348
374
|
|
|
349
375
|
def reply_document(
|
|
@@ -617,13 +643,69 @@ class InlineMessage:
|
|
|
617
643
|
if self.chat_id not in self.bot.sessions:
|
|
618
644
|
self.bot.sessions[self.chat_id] = {}
|
|
619
645
|
return self.bot.sessions[self.chat_id]
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
646
|
+
|
|
647
|
+
def reply(self, text: str, delete_after: int = None, **kwargs):
|
|
648
|
+
async def _reply_async():
|
|
649
|
+
send_func = self.bot.send_message
|
|
650
|
+
if inspect.iscoroutinefunction(send_func):
|
|
651
|
+
msg = await send_func(
|
|
652
|
+
self.chat_id,
|
|
653
|
+
text,
|
|
654
|
+
reply_to_message_id=self.message_id,
|
|
655
|
+
delete_after=delete_after,
|
|
656
|
+
**kwargs
|
|
657
|
+
)
|
|
658
|
+
else:
|
|
659
|
+
msg = send_func(
|
|
660
|
+
self.chat_id,
|
|
661
|
+
text,
|
|
662
|
+
reply_to_message_id=self.message_id,
|
|
663
|
+
delete_after=delete_after,
|
|
664
|
+
**kwargs
|
|
665
|
+
)
|
|
666
|
+
class Pick:
|
|
667
|
+
def __init__(self, bot, chat_id, message_id):
|
|
668
|
+
self.bot = bot
|
|
669
|
+
self.chat_id = chat_id
|
|
670
|
+
self.message_id = message_id
|
|
671
|
+
|
|
672
|
+
def edit(self, new_text):
|
|
673
|
+
async def _edit():
|
|
674
|
+
func = self.bot.edit_message_text
|
|
675
|
+
if inspect.iscoroutinefunction(func):
|
|
676
|
+
await func(self.chat_id, self.message_id, new_text)
|
|
677
|
+
else:
|
|
678
|
+
func(self.chat_id, self.message_id, new_text)
|
|
679
|
+
|
|
680
|
+
try:
|
|
681
|
+
loop = asyncio.get_running_loop()
|
|
682
|
+
if loop.is_running():
|
|
683
|
+
return asyncio.create_task(_edit())
|
|
684
|
+
except RuntimeError:
|
|
685
|
+
return asyncio.run(_edit())
|
|
686
|
+
|
|
687
|
+
def delete(self):
|
|
688
|
+
async def _delete():
|
|
689
|
+
func = self.bot.delete_message
|
|
690
|
+
if inspect.iscoroutinefunction(func):
|
|
691
|
+
await func(self.chat_id, self.message_id)
|
|
692
|
+
else:
|
|
693
|
+
func(self.chat_id, self.message_id)
|
|
694
|
+
try:
|
|
695
|
+
loop = asyncio.get_running_loop()
|
|
696
|
+
if loop.is_running():
|
|
697
|
+
return asyncio.create_task(_delete())
|
|
698
|
+
except RuntimeError:
|
|
699
|
+
return asyncio.run(_delete())
|
|
700
|
+
chat_id = msg.get("chat_id") if isinstance(msg, dict) else getattr(msg, "chat_id", self.chat_id)
|
|
701
|
+
message_id = msg.get("message_id") if isinstance(msg, dict) else getattr(msg, "message_id", self.message_id)
|
|
702
|
+
return Pick(self.bot, chat_id, message_id)
|
|
703
|
+
try:
|
|
704
|
+
loop = asyncio.get_running_loop()
|
|
705
|
+
if loop.is_running():
|
|
706
|
+
return asyncio.create_task(_reply_async())
|
|
707
|
+
except RuntimeError:
|
|
708
|
+
return asyncio.run(_reply_async())
|
|
627
709
|
def answer(self, text: str, **kwargs):
|
|
628
710
|
return self.bot.send_message(
|
|
629
711
|
self.chat_id,
|
|
@@ -631,6 +713,7 @@ class InlineMessage:
|
|
|
631
713
|
reply_to_message_id=self.message_id,
|
|
632
714
|
**kwargs
|
|
633
715
|
)
|
|
716
|
+
|
|
634
717
|
|
|
635
718
|
def reply_poll(self, question: str, options: List[str], **kwargs) -> Dict[str, Any]:
|
|
636
719
|
return self.bot._post("sendPoll", {
|
|
@@ -841,4 +924,12 @@ class InlineMessage:
|
|
|
841
924
|
return self.bot.delete_message(
|
|
842
925
|
chat_id=self.chat_id,
|
|
843
926
|
message_id=self.message_id
|
|
844
|
-
)
|
|
927
|
+
)
|
|
928
|
+
@hybrid_property
|
|
929
|
+
async def author_name(self):return await self.bot.get_name(self.chat_id)
|
|
930
|
+
@hybrid_property
|
|
931
|
+
async def name(self):return await self.bot.get_name(self.chat_id)
|
|
932
|
+
@hybrid_property
|
|
933
|
+
async def username(self):return await self.bot.get_username(self.chat_id)
|
|
934
|
+
@hybrid_property
|
|
935
|
+
async def author_info(self):return await self.bot.get_chat(self.chat_id)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: Rubka
|
|
3
|
-
Version: 7.1.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 7.1.5
|
|
4
|
+
Summary: Rubika: A Python library for interacting with the Rubika Bot API. This library provides an easy-to-use interface to send messages, polls, stickers, media files, manage groups and channels, handle inline keyboards, and implement advanced bot features like subscription management, user authentication, and message handling. Ideal for developers looking to automate and extend their Rubika bots with Python.
|
|
5
5
|
Home-page: https://github.com/Mahdy-Ahmadi/Rubka
|
|
6
6
|
Download-URL: https://github.com/Mahdy-Ahmadi/rubka/archive/refs/tags/v6.6.4.zip
|
|
7
7
|
Author: Mahdi Ahmadi
|
|
@@ -12,7 +12,7 @@ License: MIT
|
|
|
12
12
|
Project-URL: Bug Tracker, https://t.me/Bprogrammer
|
|
13
13
|
Project-URL: Documentation, https://github.com/Mahdy-Ahmadi/rubka/blob/main/README.md
|
|
14
14
|
Project-URL: Source Code, https://github.com/Mahdy-Ahmadi/Rubka
|
|
15
|
-
Keywords: rubika bot api library chat messaging rubpy pyrubi rubigram
|
|
15
|
+
Keywords: rubika bot api library chat messaging rubpy pyrubi rubigram rubika_bot rubika_api fast_rub
|
|
16
16
|
Classifier: Development Status :: 5 - Production/Stable
|
|
17
17
|
Classifier: Intended Audience :: Developers
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
rubka/__init__.py,sha256=P6IBiORfp-GqKHe5LZ-5lldWyG7tnrUYUcAQDUgwXmY,1973
|
|
2
|
-
rubka/api.py,sha256=
|
|
3
|
-
rubka/asynco.py,sha256=
|
|
2
|
+
rubka/api.py,sha256=wa1gQj7NDc7QEbmNNRz-TIOdVqfMWFC362tndRKdqig,68449
|
|
3
|
+
rubka/asynco.py,sha256=Zh5_oyiRH7_8xyLABREyI-Qcoy6GkybLgJqlDcR_Mm0,109869
|
|
4
4
|
rubka/button.py,sha256=woSzZVd5MtTqOrP-YgkH5b0GS9y4DuKBsFSc9-KuLnk,13320
|
|
5
5
|
rubka/config.py,sha256=Bck59xkOiqioLv0GkQ1qPGnBXVctz1hKk6LT4h2EPx0,78
|
|
6
|
-
rubka/context.py,sha256
|
|
6
|
+
rubka/context.py,sha256=OowmsvqkThzB1NDhK5629Jm4ExdaWcjUo4rsBjpTtjA,38536
|
|
7
7
|
rubka/decorators.py,sha256=hGwUoE4q2ImrunJIGJ_kzGYYxQf1ueE0isadqraKEts,1157
|
|
8
8
|
rubka/exceptions.py,sha256=DDOGIHEMoliHNW5E7C_s38WZgqqMBv9812fcJGvj7TY,1173
|
|
9
9
|
rubka/filters.py,sha256=DY1bdkpRKIiLtVcy6X3hOnlGPcVOK4HFb3QgmaPx6Oo,12116
|
|
@@ -37,8 +37,8 @@ rubka/adaptorrubka/types/socket/message.py,sha256=0WgLMZh4eow8Zn7AiSX4C3GZjQTkIg
|
|
|
37
37
|
rubka/adaptorrubka/utils/__init__.py,sha256=OgCFkXdNFh379quNwIVOAWY2NP5cIOxU5gDRRALTk4o,54
|
|
38
38
|
rubka/adaptorrubka/utils/configs.py,sha256=nMUEOJh1NqDJsf9W9PurkN_DLYjO6kKPMm923i4Jj_A,492
|
|
39
39
|
rubka/adaptorrubka/utils/utils.py,sha256=5-LioLNYX_TIbQGDeT50j7Sg9nAWH2LJUUs-iEXpsUY,8816
|
|
40
|
-
rubka-7.1.
|
|
41
|
-
rubka-7.1.
|
|
42
|
-
rubka-7.1.
|
|
43
|
-
rubka-7.1.
|
|
44
|
-
rubka-7.1.
|
|
40
|
+
rubka-7.1.5.dist-info/METADATA,sha256=a2vt5zLh2ZmkEtDUxihvZbZKAj66TaFN3PrUiiF1RjA,34667
|
|
41
|
+
rubka-7.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
42
|
+
rubka-7.1.5.dist-info/entry_points.txt,sha256=4aESuUmuUOALMUy7Kucv_Gb5YlqhsJmTmdXLlZU9sJ0,46
|
|
43
|
+
rubka-7.1.5.dist-info/top_level.txt,sha256=vy2A4lot11cRMdQS-F4HDCIXL3JK8RKfu7HMDkezJW4,6
|
|
44
|
+
rubka-7.1.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|