diff --git a/post_parser.py b/post_parser.py index d50caff..3a71d5e 100644 --- a/post_parser.py +++ b/post_parser.py @@ -18,8 +18,7 @@ from datetime import datetime from typing import Union, Dict, Any, List, Optional from pyrogram.types import Message from pyrogram.enums import MessageMediaType -from bleach.css_sanitizer import CSSSanitizer -from bleach import clean as HTMLSanitizer +from sanitizer import sanitize_html from config import get_settings from file_io import upsert_media_file_ids_bulk_sync, DB_PATH from url_signer import generate_media_digest @@ -700,41 +699,10 @@ class PostParser: def _sanitize_html(self, html_raw: str) -> str: - import time as _time - sanitize_start = _time.monotonic() - allowed_tags = ['p', 'a', 'b', 'i', 'strong', - 'em', 's', 'del', 'ul', 'ol', 'li', 'br', - 'div', 'span', 'img', 'video', 'audio', - 'source'] - allowed_attributes = { - 'a': ['href', 'title', 'target'], - 'img': ['src', 'alt', 'style'], - 'video': ['controls', 'src', 'style'], - 'audio': ['controls', 'style'], - 'source': ['src', 'type'], - 'div': ['class', 'style'], - 'span': ['class'] - } - - try: - css_sanitizer = CSSSanitizer( - allowed_css_properties=["max-width", "max-height", "object-fit", "width", "height"] - ) - sanitized_html = HTMLSanitizer( - html_raw, - tags=allowed_tags, - attributes=allowed_attributes, - protocols=['http', 'https', 'tg'], - css_sanitizer=css_sanitizer, - strip=True, - ) - elapsed = _time.monotonic() - sanitize_start - if elapsed > 0.05: - logger.warning(f"diag_sanitize_slow: bleach.clean() took {elapsed:.3f}s, input_len={len(html_raw)}") - return sanitized_html - except Exception as e: - logger.error(f"html_sanitization_error: error {str(e)}") - return html_raw + # Delegate to the single project-wide bleach config (sanitizer.sanitize_html). + # This drops the former fail-open branch: sanitize_html is fail-closed and + # html.escape()s on any bleach error (registry §3.2). + return sanitize_html(html_raw) def _format_forward_info(self, message: Message) -> Union[str, None]: if forward_origin := getattr(message, "forward_origin", None): diff --git a/rss_generator.py b/rss_generator.py index 0c6f7ea..b5a6287 100644 --- a/rss_generator.py +++ b/rss_generator.py @@ -14,7 +14,6 @@ import logging import asyncio import re import time -from html import escape as html_escape from datetime import datetime, timezone from types import SimpleNamespace from typing import Optional @@ -24,8 +23,7 @@ from pyrogram.types import Message from post_parser import PostParser from config import get_settings from tg_throttle import tg_rpc_bounded -from bleach.css_sanitizer import CSSSanitizer -from bleach import clean as HTMLSanitizer +from sanitizer import sanitize_html Config = get_settings() @@ -327,21 +325,37 @@ def _render_pipeline(messages: list[Message], exclude_flags: str | None, exclude_text: str | None, merge_seconds: int, - time_based_merge: bool): + time_based_merge: bool, + channel: str | int): """ - Full synchronous feed render pipeline (grouping + trimming + rendering). + Full synchronous feed render pipeline (grouping + trimming + rendering + sanitize). Runs entirely in a worker thread via a single asyncio.to_thread call. It contains NO await, NO asyncio primitives, NO create_task/get_running_loop — all the CPU-heavy - work (deepcopy, grouping, bleach-free rendering) happens here off the event loop. + work (deepcopy, grouping, rendering, bleach) happens here off the event loop. Media file-id records are accumulated on post_parser._pending_media_ids and flushed by the caller after this returns. + + `channel` is used only for the sanitize log_context (grep-ability). """ if time_based_merge: messages = _create_time_based_media_groups(messages, merge_seconds) message_groups = _create_messages_groups(messages) message_groups = _trim_messages_groups(message_groups, limit) - return _render_messages_groups(message_groups, post_parser, exclude_flags, exclude_text) + posts = _render_messages_groups(message_groups, post_parser, exclude_flags, exclude_text) + # Sanitize each surviving (post-filter) post exactly once, here in the worker + # thread — no per-post thread hop / per-post CSSSanitizer anymore. Per-post + # granularity means a failed post is html.escape()d in isolation instead of the + # whole feed (registry §3.5), and an unbalanced fragment is normalized within its + # OWN post rather than swallowing the following ones (registry §3.4). sanitize_html + # is fail-closed (registry §3.2). For the HTML path the
divider is joined in + # the formatter AFTER this, so it survives sanitize (registry §3.3). + for post in posts: + post['html'] = sanitize_html( + post['html'], + log_context=f"channel {channel}, message_id {post['message_id']}", + ) + return posts async def generate_channel_rss(channel: str | int, @@ -433,6 +447,7 @@ async def generate_channel_rss(channel: str | int, final_posts = await asyncio.to_thread( _render_pipeline, messages, post_parser, limit, exclude_flags, exclude_text, merge_seconds, Config['time_based_merge'], + channel, ) finally: # Persist media file-ids collected during rendering with a single bulk @@ -463,38 +478,10 @@ async def generate_channel_rss(channel: str | int, fe.link(href=post_link) fe.description(post['text'].replace('\n', ' ')) - # Sanitize heavy HTML in thread to avoid blocking the loop - try: - def _sanitize_sync(html_raw: str) -> str: - css_sanitizer = CSSSanitizer( - allowed_css_properties=["max-width", "max-height", "object-fit", "width", "height"] - ) - return HTMLSanitizer( - html_raw, - tags=['p', 'a', 'b', 'i', 'strong', 'em', 'ul', 'ol', 'li', 'br', 'div', 'span', 'img', 'video', 'audio', 'source'], - attributes={ - 'a': ['href', 'title', 'target'], - 'img': ['src', 'alt', 'style'], - 'video': ['controls', 'src', 'style'], - 'audio': ['controls', 'style'], - 'source': ['src', 'type'], - 'div': ['class', 'style'], - 'span': ['class'] - }, - protocols=['http', 'https', 'tg'], - css_sanitizer=css_sanitizer, - strip=True, - ) - sanitized_html = await asyncio.to_thread(_sanitize_sync, post['html']) - except Exception as e: - # FAIL CLOSED: since 4.4 removed the per-fragment passes, post['html'] is - # raw channel-controlled HTML, and this is its ONLY sanitize. If bleach - # itself throws (e.g. RecursionError on pathological nested HTML), do NOT - # emit the raw payload (that would be stored XSS) — html.escape it so the - # content survives as inert text. - logger.error(f"rss_html_sanitization_error: channel {channel}, message_id {post['message_id']}, error {str(e)}") - sanitized_html = html_escape(post['html']) - fe.content(content=sanitized_html, type='CDATA') + # post['html'] is already sanitized per-post inside _render_pipeline + # (single project-wide bleach config, fail-closed). No per-post thread + # hop / CSSSanitizer here anymore. + fe.content(content=post['html'], type='CDATA') if post['date'] is not None: pub_date = datetime.fromtimestamp(post['date'], tz=timezone.utc) @@ -657,6 +644,7 @@ async def generate_channel_html(channel: str | int, final_posts = await asyncio.to_thread( _render_pipeline, messages, post_parser, limit, exclude_flags, exclude_text, merge_seconds, Config['time_based_merge'], + channel, ) finally: # Persist media file-ids collected during rendering with a single bulk @@ -666,44 +654,14 @@ async def generate_channel_html(channel: str | int, processing_elapsed = time.time() - processing_start_time logger.debug(f"html_messages_processing_timing: channel {channel}, {len(final_posts)} posts processed in {processing_elapsed:.3f} seconds") - # Generate HTML content + # Generate HTML content. html_gen_start_time = time.time() - # Concatenate HTML in thread to avoid blocking the loop on large payloads - html_posts = [post['html'] for post in final_posts] - def _concat_html(parts: list[str]) -> str: - return '\n
\n'.join(parts) - html = await asyncio.to_thread(_concat_html, html_posts) - - # Optionally re-sanitize the final big HTML to ensure safety without blocking the loop - try: - def _sanitize_sync(html_raw: str) -> str: - css_sanitizer = CSSSanitizer( - allowed_css_properties=["max-width", "max-height", "object-fit", "width", "height"] - ) - return HTMLSanitizer( - html_raw, - tags=['p', 'a', 'b', 'i', 'strong', 'em', 'ul', 'ol', 'li', 'br', 'div', 'span', 'img', 'video', 'audio', 'source'], - attributes={ - 'a': ['href', 'title', 'target'], - 'img': ['src', 'alt', 'style'], - 'video': ['controls', 'src', 'style'], - 'audio': ['controls', 'style'], - 'source': ['src', 'type'], - 'div': ['class', 'style'], - 'span': ['class'] - }, - protocols=['http', 'https', 'tg'], - css_sanitizer=css_sanitizer, - strip=True, - ) - html = await asyncio.to_thread(_sanitize_sync, html) - except Exception as e: - # FAIL CLOSED (see the RSS path): `html` is now the raw concatenated feed - # (4.4 removed the per-fragment passes). If bleach throws, html.escape the - # whole feed rather than returning raw channel HTML/JS to the client. - logger.error(f"html_final_sanitization_error: channel {channel}, error {str(e)}") - html = html_escape(html) - + # Each post is already sanitized per-post inside _render_pipeline. Join with the + #
divider AFTER sanitize so the divider survives (registry §3.3), and each + # post's DOM was normalized within its own fragment (registry §3.4). The join is + # a trivial string op — no worker thread needed. + html = '\n
\n'.join(post['html'] for post in final_posts) + html_gen_elapsed = time.time() - html_gen_start_time logger.debug(f"html_generation_timing: channel {channel}, HTML generated in {html_gen_elapsed:.3f} seconds") diff --git a/sanitizer.py b/sanitizer.py new file mode 100644 index 0000000..35f3276 --- /dev/null +++ b/sanitizer.py @@ -0,0 +1,88 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# flake8: noqa +# pylint: disable=broad-exception-caught, logging-fstring-interpolation, line-too-long + +"""The ONLY bleach configuration in the project. + +Before this module the sanitize config was copy-pasted three times (the single-post +path in post_parser plus the RSS and HTML feed paths in rss_generator) and had +drifted: `s`/`del` survived only in the single-post copy, and the three error-log +names diverged. Here the config lives exactly once; every render path routes +through sanitize_html(). +""" + +import logging +import time as _time +from html import escape as _html_escape + +# Imported under this name so tests can monkeypatch `sanitizer.HTMLSanitizer` +# (API relocation from rss_generator — no behaviour change). +from bleach import clean as HTMLSanitizer +from bleach.css_sanitizer import CSSSanitizer + +logger = logging.getLogger(__name__) + +# Union of the three former copies. `s`/`del` (strikethrough) were previously +# allowed only in the single-post path; registry §3.1 makes them survive in feeds too. +ALLOWED_TAGS = ['p', 'a', 'b', 'i', 'strong', 'em', 's', 'del', + 'ul', 'ol', 'li', 'br', 'div', 'span', + 'img', 'video', 'audio', 'source'] + +# Identical in all three former copies — moved here as-is. +ALLOWED_ATTRIBUTES = { + 'a': ['href', 'title', 'target'], + 'img': ['src', 'alt', 'style'], + 'video': ['controls', 'src', 'style'], + 'audio': ['controls', 'style'], + 'source': ['src', 'type'], + 'div': ['class', 'style'], + 'span': ['class'], +} + +ALLOWED_CSS_PROPERTIES = ["max-width", "max-height", "object-fit", "width", "height"] + +# Non-default! bleach's default protocol list would strip tg:// links (channel +# footers / service links use them). Load-bearing. +ALLOWED_PROTOCOLS = ['http', 'https', 'tg'] + +# Shared instance is safe: CSSSanitizer only holds the allowed-properties list +# (stateless config); bleach builds a fresh Cleaner per clean() call anyway. +_CSS_SANITIZER = CSSSanitizer(allowed_css_properties=ALLOWED_CSS_PROPERTIES) + + +def sanitize_html(html_raw: str, log_context: str = "") -> str: + """Sanitize one HTML fragment. + + FAIL-CLOSED: on any bleach error the fragment is html.escape()d, never returned + raw (stored-XSS guard — registry §3.2). log_context (e.g. "channel X, message_id + Y") is included in the error/slow logs to keep operational grep-ability across + call sites. + """ + sanitize_start = _time.monotonic() + try: + # Both non-default params are load-bearing: + # protocols=ALLOWED_PROTOCOLS keeps tg:// links alive; + # strip=True REMOVES disallowed tags (bleach's default False would escape + # them into visible text). strip_comments stays default (True), matching + # all former call sites. + sanitized_html = HTMLSanitizer( + html_raw, + tags=ALLOWED_TAGS, + attributes=ALLOWED_ATTRIBUTES, + protocols=ALLOWED_PROTOCOLS, + css_sanitizer=_CSS_SANITIZER, + strip=True, + ) + except Exception as e: + # Single error-log name across all call sites (registry §3.16); log_context + # distinguishes them. Fail-closed: escape rather than emit the raw payload. + _ctx = f"{log_context}, " if log_context else "" + logger.error(f"html_sanitization_error: {_ctx}error {str(e)}") + return _html_escape(html_raw) + elapsed = _time.monotonic() - sanitize_start + if elapsed > 0.05: + _ctx = f", {log_context}" if log_context else "" + logger.warning(f"diag_sanitize_slow: bleach.clean() took {elapsed:.3f}s, input_len={len(html_raw)}{_ctx}") + return sanitized_html diff --git a/tests/test_data/golden/bladerunnerblues.feed.html b/tests/test_data/golden/bladerunnerblues.feed.html index 620ac8b..bc51217 100644 --- a/tests/test_data/golden/bladerunnerblues.feed.html +++ b/tests/test_data/golden/bladerunnerblues.feed.html @@ -9,8 +9,7 @@ - - +
@@ -22,8 +21,7 @@ - - +
@@ -35,8 +33,7 @@ - - +
@@ -48,8 +45,7 @@ - - +
@@ -61,8 +57,7 @@ - - +
@@ -74,8 +69,7 @@ - - +
@@ -87,8 +81,7 @@ - - +
@@ -100,8 +93,7 @@ - - +
@@ -112,8 +104,7 @@ - - +
@@ -125,16 +116,14 @@ - - +
📊 Poll: США выигрывает войну в Иране?
1. Да, однозначно
2. Нет, это очевидно
3. Выигрывает Нетаньяху, но об этом никто не говорит
4. Никто не выигрывает, тупик

→ Vote in Telegram 🔗

- - +
@@ -146,8 +135,7 @@ - - +
Да, я понимаю, что всех сейчас интересует Иран, Израиль, что будет дальше.... Этот пост я тоже вот-вот допишу, ибо, наконец-то выздоровел и вернулся в строй (всем спасибо за теплые пожелания и лучи добра, это было очень приятно, когда было тяжко). Но сейчас пост будет о другом - не одними войнами живет человечество - есть масса изменений в технологиях, пищевых цепочках, - да вообще, а социуме, которые заслуживают внимания - это ИИ (точнее дыры в нем), кибербезопасность, космические дата-центры, бионика И так далее. Спасибо каналу Телекомуналка за интересный разговор и не унылые вопросы, а реально интересные, иногда болезненные. Например - то, что я говорю про космос и про инновации в РФ - не самые приятные ответы. Но я искренне верею, что все будет хорошо. Дорогу осилит идущий:) главное - не сдаваться.

Всем добра и бобра, экипаж:)
@@ -167,8 +155,7 @@ - - +
@@ -180,8 +167,7 @@ - - +
@@ -193,8 +179,7 @@ - - +
@@ -206,16 +191,14 @@ - - +
📊 Poll: [СССР - 2]
Экипаж, представьте, что война закончится в следующем году. В России, Беларуси и на Украине (вне НАТО), проводится референдум о воссоединении в СССР. Но не с плановой, а рыночной экономикой по китайской модели. Без повторения ошибок. Мнение?
1. Да! Если так случится, это будет величайший праздник и победа. Вместе мы - сила. И одна семья.
2. Я против. Развалился или развалили не важно - СССР больше нет и не надо ворошить историю.
3. Честно не знаю. Слишком все запутано.
4. Мне нет 20 лет, не понимаю, в чем ценность воссоединения. У меня нет позиции по этому вопросу.

→ Vote in Telegram 🔗

- - +
@@ -227,8 +210,7 @@ - - +
@@ -240,8 +222,7 @@ - - +
@@ -253,8 +234,7 @@ - - +
@@ -266,8 +246,7 @@ - - +
@@ -279,8 +258,7 @@ - - +
@@ -299,8 +277,7 @@ - - +
@@ -312,8 +289,7 @@ - - +
@@ -325,8 +301,7 @@ - - +
@@ -338,8 +313,7 @@ - - +
@@ -351,8 +325,7 @@ - - +
@@ -364,8 +337,7 @@

- - +
@@ -377,8 +349,7 @@ - - +
@@ -390,8 +361,7 @@ - - +
@@ -403,8 +373,7 @@ - - +
@@ -451,8 +420,7 @@ - - +
@@ -464,8 +432,7 @@ - - +
@@ -477,8 +444,7 @@ - - +
@@ -490,8 +456,7 @@ - - +
@@ -503,8 +468,7 @@ - - +
[... ЕЩЕ Я ПОКИНУЛ ПОСТ ВИЦЕ-ПРЕЗИДЕНТА МТС]

Экипаж, есть еще одна новость.

Многие из вас знают, что последние годы я работал в МТС, где занимался двумя вещами. Во-первых, созданием инновационного прорывного R&D. Во-вторых, весь 2024й год формировал новую стратегию, которая вылилась в большую и очень нужную трансформацию группы, анонсированную в декабре 2024 Президентом МТС на прямом эфире с инвесторами.

Помимо обновленной стратегии группы, на мой взгляд, дружными усилиями удалось создать потрясающий актив - R&D центр Futurecrew, он же «Экипаж будущего». Именно из этого центра вышла в свет платформа кибербезопасности «Цикада-8», которая стабильно растет год к году, как на дрожжах. Теперь почти одновременно стартовали «Membrana Kids» (чистый «лягушатник» для детей в Сети), «Прогрессоры» (SaaS для молодежи) и Membrana (приватная связь, вырезающая рекламу, трекеры и скрипты профилирования, защищающая от большинства вирусных атак и других угроз) – есть основания полагать, что они станут важными элементами продуктового портфеля группы.

Все эти решения были разработаны с нуля b совокупно только в 2024 было подано 17 патентов на регистрацию, что о многом говорит. Это верхушка айсберга – в портфеле команды еще несколько прорывных продуктов в состоянии разной степени зрелости, о которых вы, надеюсь, скоро узнаете.

Я горжусь этой командой. Очень. Они реально крутаны и «маги вне категорий».

Однако, в текущем сетапе работать дальше невозможно - это слишком большая нагрузка и очень разношерстная деятельность, каждый аспект которой требует 200% сосредоточения. Поэтому было принято решение об изменениях. Я уступаю место вице-президента по стратегии новому человеку. Он суперпрофессионал, который, в команде с Инессой Галактионовой и Ровшаном Алиеевым точно выведет МТС на новый уровень. Я в этом не сомневаюсь.

Что касается Futurecrew, то я остаюсь его СЕО, остаюсь в группе. Но в ближайшее время сделаю все, чтобы дать раскрыться ключевым лидерам «Экипажа», которые объективно должны расти и заслужили этого. Я, скорее всего, останусь в совете директоров или какой-то роли, которая позволяет влиять на стратегию развития R&D, но не является операционной.

Чем буду заниматься я? Пока я предпочел бы не рассказывать об этом публично. Если коротко – есть фронт совершенно иного масштаба, в котором хочется применить себя и свой опыт.

В МТС было много радости. И смеха. И сарказма. И трудностей. И работы на выносливость. И спринтов по 24 часа и караоке под утро:) много чего. Но в сухом остатке – это было очень крутое и точно полезное время для меня. Я встретил немало профессионалов уровня «бог» в своих областях. И по многим из них я буду скучать.

Искренне благодарю команду МТС за все.

Да пребудет с вами Сила.
Но у меня - другой путь.
@@ -512,8 +476,7 @@ - - +
@@ -525,8 +488,7 @@ - - +
@@ -538,8 +500,7 @@ - - +
@@ -551,8 +512,7 @@ - - +
@@ -564,8 +524,7 @@ - - +
@@ -577,8 +536,7 @@ - - +
@@ -590,16 +548,14 @@ - - +
🏆 Giveaway winners: 5 of 5
+42 в карму

- - +
Чтобы все, у кого стандартный эккаунт Телеграмм поняли, как выглядит оригинал подарка премиум на год, я решил раздать 5 премиум годовых учеток.;)
@@ -607,8 +563,7 @@ - - +



@@ -618,8 +573,7 @@ - - +
@@ -631,8 +585,7 @@ - - +
@@ -644,8 +597,7 @@ - - +
@@ -657,8 +609,7 @@ - - +
@@ -670,8 +621,7 @@ - - +
@@ -683,8 +633,7 @@ - - +
@@ -696,8 +645,7 @@ - - +
@@ -709,8 +657,7 @@ - - +
@@ -722,8 +669,7 @@ - - +
@@ -735,8 +681,7 @@ - - +
@@ -748,8 +693,7 @@ - - +
@@ -761,8 +705,7 @@ - - +
@@ -774,8 +717,7 @@ - - +
@@ -787,8 +729,7 @@ - - +
@@ -800,8 +741,7 @@ - - +
@@ -813,8 +753,7 @@ - - +
@@ -826,8 +765,7 @@ - - +
@@ -839,8 +777,7 @@ - - +
@@ -852,8 +789,7 @@ - - +
@@ -865,8 +801,7 @@ - - +
@@ -878,8 +813,7 @@ - - +
@@ -891,8 +825,7 @@ - - +
@@ -904,8 +837,7 @@ - - +
@@ -917,8 +849,7 @@ - - +
@@ -930,8 +861,7 @@ - - +
@@ -942,8 +872,7 @@ - - +
@@ -955,8 +884,7 @@ - - +
@@ -968,8 +896,7 @@ - - +
@@ -981,8 +908,7 @@ - - +
@@ -994,8 +920,7 @@ - - +
@@ -1007,8 +932,7 @@ - - +
@@ -1020,8 +944,7 @@ - - +
@@ -1032,8 +955,7 @@ - - +
@@ -1045,8 +967,7 @@ - - +
@@ -1058,8 +979,7 @@ - - +

@@ -1068,9 +988,8 @@

- - +
🏷 link
+

@@ -1079,9 +998,8 @@

- - +
🏷 link
+

@@ -1090,9 +1008,8 @@

- - +
🏷 link
+
@@ -1104,8 +1021,7 @@ - - +
@@ -1134,8 +1050,7 @@ - - +
@@ -1147,8 +1062,7 @@ - - +
@@ -1160,8 +1074,7 @@ - - +
@@ -1171,8 +1084,7 @@

- - +

@@ -1181,9 +1093,8 @@

- - +
🏷 link
+
@@ -1194,4 +1105,4 @@

\ No newline at end of file +
🏷 link
\ No newline at end of file diff --git a/tests/test_data/golden/embedoka.feed.html b/tests/test_data/golden/embedoka.feed.html index 1943267..82ab385 100644 --- a/tests/test_data/golden/embedoka.feed.html +++ b/tests/test_data/golden/embedoka.feed.html @@ -9,8 +9,7 @@ - - +
@@ -22,8 +21,7 @@ - - +
@@ -35,8 +33,7 @@ - - +
@@ -48,8 +45,7 @@ - - +
@@ -68,8 +64,7 @@ - - +
@@ -82,8 +77,7 @@ - - +
@@ -95,8 +89,7 @@ - - +
@@ -122,8 +115,7 @@ - - +
@@ -135,8 +127,7 @@ - - +
@@ -148,8 +139,7 @@ - - +
@@ -161,21 +151,19 @@ - - +

-Невероятно, но rust 🤯

Topola PCB - роутер и автороутер печатных плат, написанный на расте. Проект в активной фазе развития, судя по локализации пилят его наши восточные соседи разрабатывается на европейские деньги (грант известной NLnet). Что интересно: название перекликается с отечественным продуктом из 2000х с аналогичным функционалом 😅

🔗 https://topola.dev/
🐱 https://codeberg.org/topola/topola

PS: Есть даже веб-версия, а вот поддерживаемых форматов я не нашёл нигде 🤓

@embedoka +Невероятно, но rust 🤯

Topola PCB - роутер и автороутер печатных плат, написанный на расте. Проект в активной фазе развития, судя по локализации пилят его наши восточные соседи разрабатывается на европейские деньги (грант известной NLnet). Что интересно: название перекликается с отечественным продуктом из 2000х с аналогичным функционалом 😅

🔗 https://topola.dev/
🐱 https://codeberg.org/topola/topola

PS: Есть даже веб-версия, а вот поддерживаемых форматов я не нашёл нигде 🤓

@embedoka

- - +
@@ -187,8 +175,7 @@ - - +
@@ -200,8 +187,7 @@ - - +
@@ -214,8 +200,7 @@ - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -230,8 +215,7 @@ - - +
@@ -243,8 +227,7 @@ - - +
@@ -256,8 +239,7 @@ - - +
@@ -269,8 +251,7 @@ - - +
@@ -282,8 +263,7 @@ - - +
@@ -296,8 +276,7 @@ - - +
@@ -309,8 +288,7 @@ - - +
@@ -329,8 +307,7 @@ - - +
@@ -342,8 +319,7 @@ - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -358,8 +334,7 @@ - - +
@@ -371,8 +346,7 @@ - - +
@@ -384,8 +358,7 @@ - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -400,8 +373,7 @@ - - +
@@ -448,8 +420,7 @@ - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -464,8 +435,7 @@ - - +
@@ -478,17 +448,15 @@ - - +
-640 КБ хватит всем +640 КБ хватит всем

- - +
@@ -500,8 +468,7 @@ - - +
@@ -513,8 +480,7 @@ - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -525,8 +491,7 @@ - - +
@@ -538,8 +503,7 @@ - - +
@@ -551,8 +515,7 @@ - - +
--- Forwarded from VLSI HUB (@vlsihub) ---

@@ -567,8 +530,7 @@ - - +
@@ -580,21 +542,19 @@ - - +

-Наконец-то что-то дельное про нишевый микро-бизнес в эмбеддед за последние годы прочёл. А не про эти ваши диктофоны с телесисек, которым 100 лет в обед и которые уже всем в смартфон не впились (остались нишевые применения, но они сильно завязаны на гос.регуляторику).

TLDR: Чел из Омска разработал и теперь производит платы-"обманки" для уменьшения костов дорогостоящего ремонта холодильника. Маркетплейсы душат комиссией, но на хлеб с маслом хватает (в масштабах выпро-живания в миллионнике).

PS: если кому-то попадалось схожее чтиво (да еще и с юнит-экономикой) - кидайте в коменты 🙏

@embedoka +Наконец-то что-то дельное про нишевый микро-бизнес в эмбеддед за последние годы прочёл. А не про эти ваши диктофоны с телесисек, которым 100 лет в обед и которые уже всем в смартфон не впились (остались нишевые применения, но они сильно завязаны на гос.регуляторику).

TLDR: Чел из Омска разработал и теперь производит платы-"обманки" для уменьшения костов дорогостоящего ремонта холодильника. Маркетплейсы душат комиссией, но на хлеб с маслом хватает (в масштабах выпро-живания в миллионнике).

PS: если кому-то попадалось схожее чтиво (да еще и с юнит-экономикой) - кидайте в коменты 🙏

@embedoka

- - +
@@ -606,8 +566,7 @@ - - +
@@ -620,8 +579,7 @@ - - +
@@ -633,18 +591,16 @@ - - +
В электронике тоже есть своё правило№34:
-На любую вашу хотелку существует готовая реализация в интегральном исполнении (с) Doka 😅

Правило#35: если нет микросхемы, перепроверь еще раз у китайцев

Правило#36: всегда существует еще более тупая дешевая дискретная реализация нужного функционала из говна и палок на рассыпухе

Правило#37: рассыпуха под рукой всегда будет не той мощности, которая нужна в данный момент, поэтому TO-3 балласт будет соседствовать с 0201

Правило#38: Всегда существует реализация которая займет больше времени на разработку и отладку и вы выберете именно ее

Правило#39
: [Если вы не китаец, то] что бы вы не делали у китайцев этот же функционал реализован проще

@embedoka +На любую вашу хотелку существует готовая реализация в интегральном исполнении (с) Doka 😅

Правило#35: если нет микросхемы, перепроверь еще раз у китайцев

Правило#36: всегда существует еще более тупая дешевая дискретная реализация нужного функционала из говна и палок на рассыпухе

Правило#37: рассыпуха под рукой всегда будет не той мощности, которая нужна в данный момент, поэтому TO-3 балласт будет соседствовать с 0201

Правило#38: Всегда существует реализация которая займет больше времени на разработку и отладку и вы выберете именно ее

Правило#39
: [Если вы не китаец, то] что бы вы не делали у китайцев этот же функционал реализован проще

@embedoka

- - +
@@ -656,35 +612,32 @@ - - +

-The SM9 module boasts hardware compatibility with NVIDIA Jetson series modules. Customers using NVIDIA Jetson-based products can seamlessly replace the original module with SM9 without hardware development efforts— simply unplug the NVIDIA module, plug in SM9, and complete hardware integration.

Ай-да китайцы, ай-да молодцы. Правильной дорогой идут, нефиг Nvidia булки расслаблять 😅

Понятно, что вопрос поддержки компиляторов и вот это всё тоже немаловажен, но DeepSeek R1 distilled 7B and 1.5B на ней уже запустили и отбенчмарками (скрин в коментах).

Кстати-кстати, это не просто рандомный китайский стартап, а перелицованный старый добрый Bitmain - мировой лидер в разработке и производстве чипов для майнинга крипты (и AI темой они начали заниматься еще в эпоху криптолихорадки - в общем, всегда держат руку на пульсе).

@embedoka +The SM9 module boasts hardware compatibility with NVIDIA Jetson series modules. Customers using NVIDIA Jetson-based products can seamlessly replace the original module with SM9 without hardware development efforts— simply unplug the NVIDIA module, plug in SM9, and complete hardware integration.

Ай-да китайцы, ай-да молодцы. Правильной дорогой идут, нефиг Nvidia булки расслаблять 😅

Понятно, что вопрос поддержки компиляторов и вот это всё тоже немаловажен, но DeepSeek R1 distilled 7B and 1.5B на ней уже запустили и отбенчмарками (скрин в коментах).

Кстати-кстати, это не просто рандомный китайский стартап, а перелицованный старый добрый Bitmain - мировой лидер в разработке и производстве чипов для майнинга крипты (и AI темой они начали заниматься еще в эпоху криптолихорадки - в общем, всегда держат руку на пульсе).

@embedoka

- - +

-Если предыдущий адаптер был прям "находкой", то вот идея не столь однозначного адаптера: можно назвать это 1000 и 1 способ выломать RJ-45 к чертям 😈

А внутри у неё неонка RTL8153

@embedoka +Если предыдущий адаптер был прям "находкой", то вот идея не столь однозначного адаптера: можно назвать это 1000 и 1 способ выломать RJ-45 к чертям 😈

А внутри у неё неонка RTL8153

@embedoka

- - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -699,8 +652,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -712,8 +664,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -725,8 +676,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -741,8 +691,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -756,8 +705,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -772,8 +720,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -788,8 +735,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -804,8 +750,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -824,8 +769,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -837,8 +781,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
1️⃣ и 2️⃣: прям ностальгия по одному из предыдущих мест работ 🥰

PS: коллегам пламенный привет 🖤
@@ -846,8 +789,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
два
@@ -855,8 +797,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
раз
@@ -864,8 +805,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -877,8 +817,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -893,16 +832,14 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
📊 Poll: Стоит ли выкладывать копии релевантных видео в канал (для участников, у которых ограничен доступ к ютьюб)? И вообще дублировать какой-либо контент в тг, доступ к которому может быть ограничен?
1. да
2. нет
3. всё равно
4. напишу в комментариях

→ Vote in Telegram 🔗

- - +
@@ -914,8 +851,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -927,8 +863,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Aleksei ---

@@ -944,8 +879,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +

@@ -961,8 +895,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -974,8 +907,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -987,8 +919,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1000,8 +931,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -1016,8 +946,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1029,8 +958,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
А вы знали что если к названию вакансии "Senior С/C++ Developer" добавить "embedded" то вилка по з/п сразу уменьшается в 2 раза (c) hold3r
@@ -1038,8 +966,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1058,8 +985,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1071,8 +997,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1084,8 +1009,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from асоциальный пикотранзистор ᶘಠᴥಠᶅ ---

@@ -1097,9 +1021,8 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo

- - +
🏷 fwd 🏷 link
+
--- Forwarded from kitnerlive (@kitnerlive) ---

@@ -1114,8 +1037,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -1130,8 +1052,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1164,8 +1085,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
DIY rule #43:

На каждую хотелку самодельщика-энтузиаста найдётся готовое решение в интегральном исполнении из Поднебесной.

По мотивам мема про могущественный китайский чип и блестящих примеров исполнения за "1 рубль":

▫️BLE-UART
▫️BLE-CAN

А теперь вопрос знатокам:

На рынке есть такие решения, которые позволяют управлять режимами работы девайса последовательностями включения, например, реальный кейс: освещение, в зависимости от порядкового номера включения выбирается тот или иной канал/режим:
1️⃣ включение: канал/режим 1
2️⃣ включение: канал/режим 2
3️⃣ включение: канал/режим 3

(порядковый номер включения - это сколько щёлканий тумблером было в течении 15секунд, например, если прошло больше времени, то FSM автоматически сбрабывается в начальное состояние и следующее включение будет считаться 1️⃣ым)

Т.е. по факту некое многоканальное "реле" с управлением через последовательность вкл/выкл подачи питания.

❓Кому-нибудь попадались подобные чипы/модули, которые такое умеют "из коробки"?

@embedoka @@ -1174,8 +1094,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
--- Forwarded from Embedded Doka (@embedoka) ---

@@ -1189,8 +1108,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1202,8 +1120,7 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo - - +
@@ -1214,4 +1131,4 @@ The SM9 module boasts hardware compatibility with NVIDIA Jetson series mo

\ No newline at end of file +
🏷 link 🏷 mention
\ No newline at end of file diff --git a/tests/test_data/golden/embedoka.rss.xml b/tests/test_data/golden/embedoka.rss.xml index 80cbe6c..89d6efe 100644 --- a/tests/test_data/golden/embedoka.rss.xml +++ b/tests/test_data/golden/embedoka.rss.xml @@ -732,7 +732,7 @@

-Если предыдущий адаптер был прям "находкой", то вот идея не столь однозначного адаптера: можно назвать это 1000 и 1 способ выломать RJ-45 к чертям 😈

А внутри у неё неонка RTL8153

@embedoka +Если предыдущий адаптер был прям "находкой", то вот идея не столь однозначного адаптера: можно назвать это 1000 и 1 способ выломать RJ-45 к чертям 😈

А внутри у неё неонка RTL8153

@embedoka


-The SM9 module boasts hardware compatibility with NVIDIA Jetson series modules. Customers using NVIDIA Jetson-based products can seamlessly replace the original module with SM9 without hardware development efforts— simply unplug the NVIDIA module, plug in SM9, and complete hardware integration.

Ай-да китайцы, ай-да молодцы. Правильной дорогой идут, нефиг Nvidia булки расслаблять 😅

Понятно, что вопрос поддержки компиляторов и вот это всё тоже немаловажен, но DeepSeek R1 distilled 7B and 1.5B на ней уже запустили и отбенчмарками (скрин в коментах).

Кстати-кстати, это не просто рандомный китайский стартап, а перелицованный старый добрый Bitmain - мировой лидер в разработке и производстве чипов для майнинга крипты (и AI темой они начали заниматься еще в эпоху криптолихорадки - в общем, всегда держат руку на пульсе).

@embedoka +The SM9 module boasts hardware compatibility with NVIDIA Jetson series modules. Customers using NVIDIA Jetson-based products can seamlessly replace the original module with SM9 without hardware development efforts— simply unplug the NVIDIA module, plug in SM9, and complete hardware integration.

Ай-да китайцы, ай-да молодцы. Правильной дорогой идут, нефиг Nvidia булки расслаблять 😅

Понятно, что вопрос поддержки компиляторов и вот это всё тоже немаловажен, но DeepSeek R1 distilled 7B and 1.5B на ней уже запустили и отбенчмарками (скрин в коментах).

Кстати-кстати, это не просто рандомный китайский стартап, а перелицованный старый добрый Bitmain - мировой лидер в разработке и производстве чипов для майнинга крипты (и AI темой они начали заниматься еще в эпоху криптолихорадки - в общем, всегда держат руку на пульсе).

@embedoka


-Наконец-то что-то дельное про нишевый микро-бизнес в эмбеддед за последние годы прочёл. А не про эти ваши диктофоны с телесисек, которым 100 лет в обед и которые уже всем в смартфон не впились (остались нишевые применения, но они сильно завязаны на гос.регуляторику).

TLDR: Чел из Омска разработал и теперь производит платы-"обманки" для уменьшения костов дорогостоящего ремонта холодильника. Маркетплейсы душат комиссией, но на хлеб с маслом хватает (в масштабах выпро-живания в миллионнике).

PS: если кому-то попадалось схожее чтиво (да еще и с юнит-экономикой) - кидайте в коменты 🙏

@embedoka +Наконец-то что-то дельное про нишевый микро-бизнес в эмбеддед за последние годы прочёл. А не про эти ваши диктофоны с телесисек, которым 100 лет в обед и которые уже всем в смартфон не впились (остались нишевые применения, но они сильно завязаны на гос.регуляторику).

TLDR: Чел из Омска разработал и теперь производит платы-"обманки" для уменьшения костов дорогостоящего ремонта холодильника. Маркетплейсы душат комиссией, но на хлеб с маслом хватает (в масштабах выпро-живания в миллионнике).

PS: если кому-то попадалось схожее чтиво (да еще и с юнит-экономикой) - кидайте в коменты 🙏

@embedoka


-Невероятно, но rust 🤯

Topola PCB - роутер и автороутер печатных плат, написанный на расте. Проект в активной фазе развития, судя по локализации пилят его наши восточные соседи разрабатывается на европейские деньги (грант известной NLnet). Что интересно: название перекликается с отечественным продуктом из 2000х с аналогичным функционалом 😅

🔗 https://topola.dev/
🐱 https://codeberg.org/topola/topola

PS: Есть даже веб-версия, а вот поддерживаемых форматов я не нашёл нигде 🤓

@embedoka +Невероятно, но rust 🤯

Topola PCB - роутер и автороутер печатных плат, написанный на расте. Проект в активной фазе развития, судя по локализации пилят его наши восточные соседи разрабатывается на европейские деньги (грант известной NLnet). Что интересно: название перекликается с отечественным продуктом из 2000х с аналогичным функционалом 😅

🔗 https://topola.dev/
🐱 https://codeberg.org/topola/topola

PS: Есть даже веб-версия, а вот поддерживаемых форматов я не нашёл нигде 🤓

@embedoka