├── .gitignore
├── .vscode
    ├── launch.json
    └── settings.json
├── LICENSE
├── README.MD
├── docs
    ├── data
    │   └── channels.json
    ├── img
    │   ├── logo
    │   │   ├── one-s.ico
    │   │   ├── plus.png
    │   │   └── telegram.svg
    │   └── one-s
    │   │   ├── favorite.svg
    │   │   ├── functions.svg
    │   │   ├── history.svg
    │   │   ├── menu.svg
    │   │   ├── notify.svg
    │   │   ├── search.svg
    │   │   ├── window-list-left.png
    │   │   └── window-list-right.png
    ├── index.html
    ├── scripts
    │   ├── chanels.js
    │   └── links.js
    └── styles
    │   └── main.css
└── tools
    └── updater
        ├── ConsoleUpdater
            ├── ConsoleUpdater.csproj
            └── Program.cs
        ├── Updater.sln
        ├── UpdaterLibrary.Tests
            ├── FakeUpdater.cs
            ├── IrrelevantTagsUpdateHandlerTest.cs
            └── UpdaterLibrary.Tests.csproj
        └── UpdaterLibrary
            ├── Models
                ├── ChannelModel.cs
                ├── MetadataColumnModel.cs
                ├── MetadataModel.cs
                ├── TagModel.cs
                └── UpdateDataModel.cs
            ├── Settings
                ├── SettingsManager.cs
                └── SettingsModel.cs
            ├── SiteUpdater
                ├── IUpdater.cs
                ├── LogEventModel.cs
                ├── LogEventType.cs
                └── UpdateParametrs.cs
            ├── TelegramBot
                └── TelegramApi.cs
            ├── Tools
                ├── ArrayExtension.cs
                └── ReflectionHelper.cs
            ├── UpdateHandlers
                ├── ChannelInfoUpdateHandler.cs
                ├── IUpdateHandler.cs
                ├── IrrelevantTagsUpdateHandler.cs
                ├── MemberCountUpdateHandler.cs
                └── TempUpdateHandler.cs
            ├── Updater.cs
            ├── UpdaterLibrary.csproj
            └── appsettings.json
/.gitignore:
--------------------------------------------------------------------------------
  1 | # Файл с настройками и токеном
  2 | tools/updater/UpdaterLibrary/appsettings.json
  3 | 
  4 | ## Ignore Visual Studio temporary files, build results, and
  5 | ## files generated by popular Visual Studio add-ons.
  6 | ##
  7 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
  8 | 
  9 | # User-specific files
 10 | *.rsuser
 11 | *.suo
 12 | *.user
 13 | *.userosscache
 14 | *.sln.docstates
 15 | 
 16 | # User-specific files (MonoDevelop/Xamarin Studio)
 17 | *.userprefs
 18 | 
 19 | # Mono auto generated files
 20 | mono_crash.*
 21 | 
 22 | # Build results
 23 | [Dd]ebug/
 24 | [Dd]ebugPublic/
 25 | [Rr]elease/
 26 | [Rr]eleases/
 27 | x64/
 28 | x86/
 29 | [Ww][Ii][Nn]32/
 30 | [Aa][Rr][Mm]/
 31 | [Aa][Rr][Mm]64/
 32 | bld/
 33 | [Bb]in/
 34 | [Oo]bj/
 35 | [Ll]og/
 36 | [Ll]ogs/
 37 | 
 38 | # Visual Studio 2015/2017 cache/options directory
 39 | .vs/
 40 | # Uncomment if you have tasks that create the project's static files in wwwroot
 41 | #wwwroot/
 42 | 
 43 | # Visual Studio 2017 auto generated files
 44 | Generated\ Files/
 45 | 
 46 | # MSTest test Results
 47 | [Tt]est[Rr]esult*/
 48 | [Bb]uild[Ll]og.*
 49 | 
 50 | # NUnit
 51 | *.VisualState.xml
 52 | TestResult.xml
 53 | nunit-*.xml
 54 | 
 55 | # Build Results of an ATL Project
 56 | [Dd]ebugPS/
 57 | [Rr]eleasePS/
 58 | dlldata.c
 59 | 
 60 | # Benchmark Results
 61 | BenchmarkDotNet.Artifacts/
 62 | 
 63 | # .NET Core
 64 | project.lock.json
 65 | project.fragment.lock.json
 66 | artifacts/
 67 | 
 68 | # ASP.NET Scaffolding
 69 | ScaffoldingReadMe.txt
 70 | 
 71 | # StyleCop
 72 | StyleCopReport.xml
 73 | 
 74 | # Files built by Visual Studio
 75 | *_i.c
 76 | *_p.c
 77 | *_h.h
 78 | *.ilk
 79 | *.meta
 80 | *.obj
 81 | *.iobj
 82 | *.pch
 83 | *.pdb
 84 | *.ipdb
 85 | *.pgc
 86 | *.pgd
 87 | *.rsp
 88 | *.sbr
 89 | *.tlb
 90 | *.tli
 91 | *.tlh
 92 | *.tmp
 93 | *.tmp_proj
 94 | *_wpftmp.csproj
 95 | *.log
 96 | *.tlog
 97 | *.vspscc
 98 | *.vssscc
 99 | .builds
100 | *.pidb
101 | *.svclog
102 | *.scc
103 | 
104 | # Chutzpah Test files
105 | _Chutzpah*
106 | 
107 | # Visual C++ cache files
108 | ipch/
109 | *.aps
110 | *.ncb
111 | *.opendb
112 | *.opensdf
113 | *.sdf
114 | *.cachefile
115 | *.VC.db
116 | *.VC.VC.opendb
117 | 
118 | # Visual Studio profiler
119 | *.psess
120 | *.vsp
121 | *.vspx
122 | *.sap
123 | 
124 | # Visual Studio Trace Files
125 | *.e2e
126 | 
127 | # TFS 2012 Local Workspace
128 | $tf/
129 | 
130 | # Guidance Automation Toolkit
131 | *.gpState
132 | 
133 | # ReSharper is a .NET coding add-in
134 | _ReSharper*/
135 | *.[Rr]e[Ss]harper
136 | *.DotSettings.user
137 | 
138 | # TeamCity is a build add-in
139 | _TeamCity*
140 | 
141 | # DotCover is a Code Coverage Tool
142 | *.dotCover
143 | 
144 | # AxoCover is a Code Coverage Tool
145 | .axoCover/*
146 | !.axoCover/settings.json
147 | 
148 | # Coverlet is a free, cross platform Code Coverage Tool
149 | coverage*.json
150 | coverage*.xml
151 | coverage*.info
152 | 
153 | # Visual Studio code coverage results
154 | *.coverage
155 | *.coveragexml
156 | 
157 | # NCrunch
158 | _NCrunch_*
159 | .*crunch*.local.xml
160 | nCrunchTemp_*
161 | 
162 | # MightyMoose
163 | *.mm.*
164 | AutoTest.Net/
165 | 
166 | # Web workbench (sass)
167 | .sass-cache/
168 | 
169 | # Installshield output folder
170 | [Ee]xpress/
171 | 
172 | # DocProject is a documentation generator add-in
173 | DocProject/buildhelp/
174 | DocProject/Help/*.HxT
175 | DocProject/Help/*.HxC
176 | DocProject/Help/*.hhc
177 | DocProject/Help/*.hhk
178 | DocProject/Help/*.hhp
179 | DocProject/Help/Html2
180 | DocProject/Help/html
181 | 
182 | # Click-Once directory
183 | publish/
184 | 
185 | # Publish Web Output
186 | *.[Pp]ublish.xml
187 | *.azurePubxml
188 | # Note: Comment the next line if you want to checkin your web deploy settings,
189 | # but database connection strings (with potential passwords) will be unencrypted
190 | *.pubxml
191 | *.publishproj
192 | 
193 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
194 | # checkin your Azure Web App publish settings, but sensitive information contained
195 | # in these scripts will be unencrypted
196 | PublishScripts/
197 | 
198 | # NuGet Packages
199 | *.nupkg
200 | # NuGet Symbol Packages
201 | *.snupkg
202 | # The packages folder can be ignored because of Package Restore
203 | **/[Pp]ackages/*
204 | # except build/, which is used as an MSBuild target.
205 | !**/[Pp]ackages/build/
206 | # Uncomment if necessary however generally it will be regenerated when needed
207 | #!**/[Pp]ackages/repositories.config
208 | # NuGet v3's project.json files produces more ignorable files
209 | *.nuget.props
210 | *.nuget.targets
211 | 
212 | # Microsoft Azure Build Output
213 | csx/
214 | *.build.csdef
215 | 
216 | # Microsoft Azure Emulator
217 | ecf/
218 | rcf/
219 | 
220 | # Windows Store app package directories and files
221 | AppPackages/
222 | BundleArtifacts/
223 | Package.StoreAssociation.xml
224 | _pkginfo.txt
225 | *.appx
226 | *.appxbundle
227 | *.appxupload
228 | 
229 | # Visual Studio cache files
230 | # files ending in .cache can be ignored
231 | *.[Cc]ache
232 | # but keep track of directories ending in .cache
233 | !?*.[Cc]ache/
234 | 
235 | # Others
236 | ClientBin/
237 | ~$*
238 | *~
239 | *.dbmdl
240 | *.dbproj.schemaview
241 | *.jfm
242 | *.pfx
243 | *.publishsettings
244 | orleans.codegen.cs
245 | 
246 | # Including strong name files can present a security risk
247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
248 | #*.snk
249 | 
250 | # Since there are multiple workflows, uncomment next line to ignore bower_components
251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
252 | #bower_components/
253 | 
254 | # RIA/Silverlight projects
255 | Generated_Code/
256 | 
257 | # Backup & report files from converting an old project file
258 | # to a newer Visual Studio version. Backup files are not needed,
259 | # because we have git ;-)
260 | _UpgradeReport_Files/
261 | Backup*/
262 | UpgradeLog*.XML
263 | UpgradeLog*.htm
264 | ServiceFabricBackup/
265 | *.rptproj.bak
266 | 
267 | # SQL Server files
268 | *.mdf
269 | *.ldf
270 | *.ndf
271 | 
272 | # Business Intelligence projects
273 | *.rdl.data
274 | *.bim.layout
275 | *.bim_*.settings
276 | *.rptproj.rsuser
277 | *- [Bb]ackup.rdl
278 | *- [Bb]ackup ([0-9]).rdl
279 | *- [Bb]ackup ([0-9][0-9]).rdl
280 | 
281 | # Microsoft Fakes
282 | FakesAssemblies/
283 | 
284 | # GhostDoc plugin setting file
285 | *.GhostDoc.xml
286 | 
287 | # Node.js Tools for Visual Studio
288 | .ntvs_analysis.dat
289 | node_modules/
290 | 
291 | # Visual Studio 6 build log
292 | *.plg
293 | 
294 | # Visual Studio 6 workspace options file
295 | *.opt
296 | 
297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
298 | *.vbw
299 | 
300 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
301 | *.vbp
302 | 
303 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
304 | *.dsw
305 | *.dsp
306 | 
307 | # Visual Studio 6 technical files
308 | *.ncb
309 | *.aps
310 | 
311 | # Visual Studio LightSwitch build output
312 | **/*.HTMLClient/GeneratedArtifacts
313 | **/*.DesktopClient/GeneratedArtifacts
314 | **/*.DesktopClient/ModelManifest.xml
315 | **/*.Server/GeneratedArtifacts
316 | **/*.Server/ModelManifest.xml
317 | _Pvt_Extensions
318 | 
319 | # Paket dependency manager
320 | .paket/paket.exe
321 | paket-files/
322 | 
323 | # FAKE - F# Make
324 | .fake/
325 | 
326 | # CodeRush personal settings
327 | .cr/personal
328 | 
329 | # Python Tools for Visual Studio (PTVS)
330 | __pycache__/
331 | *.pyc
332 | 
333 | # Cake - Uncomment if you are using it
334 | # tools/**
335 | # !tools/packages.config
336 | 
337 | # Tabs Studio
338 | *.tss
339 | 
340 | # Telerik's JustMock configuration file
341 | *.jmconfig
342 | 
343 | # BizTalk build output
344 | *.btp.cs
345 | *.btm.cs
346 | *.odx.cs
347 | *.xsd.cs
348 | 
349 | # OpenCover UI analysis results
350 | OpenCover/
351 | 
352 | # Azure Stream Analytics local run output
353 | ASALocalRun/
354 | 
355 | # MSBuild Binary and Structured Log
356 | *.binlog
357 | 
358 | # NVidia Nsight GPU debugger configuration file
359 | *.nvuser
360 | 
361 | # MFractors (Xamarin productivity tool) working folder
362 | .mfractor/
363 | 
364 | # Local History for Visual Studio
365 | .localhistory/
366 | 
367 | # Visual Studio History (VSHistory) files
368 | .vshistory/
369 | 
370 | # BeatPulse healthcheck temp database
371 | healthchecksdb
372 | 
373 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
374 | MigrationBackup/
375 | 
376 | # Ionide (cross platform F# VS Code tools) working folder
377 | .ionide/
378 | 
379 | # Fody - auto-generated XML schema
380 | FodyWeavers.xsd
381 | 
382 | # VS Code files for those working on multiple tools
383 | .vscode/*
384 | !.vscode/settings.json
385 | !.vscode/tasks.json
386 | !.vscode/launch.json
387 | !.vscode/extensions.json
388 | *.code-workspace
389 | 
390 | # Local History for Visual Studio Code
391 | .history/
392 | 
393 | # Windows Installer files from build outputs
394 | *.cab
395 | *.msi
396 | *.msix
397 | *.msm
398 | *.msp
399 | 
400 | # JetBrains Rider
401 | *.sln.iml
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     // Use IntelliSense to learn about possible attributes.
 3 |     // Hover to view descriptions of existing attributes.
 4 |     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
 5 |     "version": "0.2.0",
 6 |     "configurations": [
 7 |         {
 8 |             "type": "chrome",
 9 |             "request": "launch",
10 |             "name": "Launch Chrome against localhost",
11 |             "url": "http://localhost:5500",
12 |             "webRoot": "${workspaceFolder}",
13 |             "file": "${workspaceRoot}/site/index.html"
14 |         }
15 |     ]
16 | }
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 |     "cSpell.language": "en,ru",
3 |     "cSpell.ignorePaths": [
4 |         "*.json",
5 |     ],
6 |     "cSpell.words": [
7 |         "SeiOkami"
8 |     ],
9 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2024 Vitaliy Chernenko
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
 1 | [](https://openyellow.org/grid?data=top&repo=800923299)
 2 | 
 3 | # Реестр 1С в Telegram
 4 | 
 5 | Репозиторий реестра всех каналов, чатов и т.д. по теме 1С в Telegram
 6 | 
 7 | Список ссылок:
 8 | - [Опубликованный реестр](https://SeiOkami.github.io/links-one-s/)
 9 | - [Предложения по улучшению](https://github.com/SeiOkami/links-one-s/issues)
10 | - [Автор](https://github.com/SeiOkami)
11 | - [Личка в Telegram](https://t.me/SeiOkami)
12 | 
13 | ## Как добавить канал
14 | Для добавления нового канала можно сделать одним из следующих способов:
15 | - Написать мне в личку Telegram
16 | - Зарегистрировать issue на странице предложений
17 | - Самому создать пулл реквест. Для этого необходимо добавить свой канал в конце списка `channels` файла [channels.json](/docs/data/channels.json). Указать нужно URL и ID. Остальная информация будет обработана автоматически
18 | 
19 | ## Структура проекта
20 | Проект содержит:
21 | - [docs](/docs/) - Статический сайт, опубликованный на GitHub Pages
22 | - [channels.json](/docs/data/channels.json) - список всех данных сайта (теги, выводимые колонки, каналы, чаты и т.д.)
23 | - [updater](/tools/updater/) - C# проект для автоматического обновления данных реестра из Telegram
24 | 
--------------------------------------------------------------------------------
/docs/img/logo/one-s.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeiOkami/links-one-s/7509e914e936f38e73413b7cda7b7cd91da0f0de/docs/img/logo/one-s.ico
--------------------------------------------------------------------------------
/docs/img/logo/plus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeiOkami/links-one-s/7509e914e936f38e73413b7cda7b7cd91da0f0de/docs/img/logo/plus.png
--------------------------------------------------------------------------------
/docs/img/logo/telegram.svg:
--------------------------------------------------------------------------------
1 | 
--------------------------------------------------------------------------------
/docs/img/one-s/favorite.svg:
--------------------------------------------------------------------------------
1 | 
9 | 
--------------------------------------------------------------------------------
/docs/img/one-s/functions.svg:
--------------------------------------------------------------------------------
 1 | 
10 | 
--------------------------------------------------------------------------------
/docs/img/one-s/history.svg:
--------------------------------------------------------------------------------
 1 | 
11 | 
--------------------------------------------------------------------------------
/docs/img/one-s/menu.svg:
--------------------------------------------------------------------------------
1 | 
--------------------------------------------------------------------------------
/docs/img/one-s/notify.svg:
--------------------------------------------------------------------------------
 1 | 
--------------------------------------------------------------------------------
/docs/img/one-s/search.svg:
--------------------------------------------------------------------------------
 1 | 
10 | 
--------------------------------------------------------------------------------
/docs/img/one-s/window-list-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeiOkami/links-one-s/7509e914e936f38e73413b7cda7b7cd91da0f0de/docs/img/one-s/window-list-left.png
--------------------------------------------------------------------------------
/docs/img/one-s/window-list-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeiOkami/links-one-s/7509e914e936f38e73413b7cda7b7cd91da0f0de/docs/img/one-s/window-list-right.png
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 |     
 6 |     
 7 |     
 8 |     
 9 |     
10 |     1С в Telegram
11 |     
12 |     
13 |     
14 |     
15 | 
16 | 
17 | 
18 | 
19 |     
32 | 
33 |     
34 | 
35 |         
Репозиторий
36 |         
Как добавить канал
37 |         
Идеи и предложения
38 |         
Желтый Чайник 1С
39 |         
Автор (GitHub)
40 |         
Автор (Telegram)
41 |         
42 |     
 
43 | 
44 |     
45 |         
46 | 
47 |             
52 | 
53 |             
54 |                 
55 |                 
56 |             
57 | 
58 |             
59 |             
60 |             
61 | 
62 |         
 
63 |     
64 | 
65 | 
66 | 
--------------------------------------------------------------------------------
/docs/scripts/chanels.js:
--------------------------------------------------------------------------------
  1 | {
  2 |     const classColumn = "column-"; // Префикс стиля колонки
  3 |     const searchInput = document.getElementById('searchInput'); // Поле строки поиска
  4 |     const table = document.getElementById('channels'); // Таблица каналов на странице
  5 |     let channels = undefined; // Коллекция каналов из файла
  6 |     let tags = undefined; // Коллекция тегов из файла
  7 |     let metadata = undefined; // Метаданные таблицы каналов из файла
  8 |     let filterTags = []; // Теги, выбранные пользователем для просмотра
  9 |     let searchText = ""; // Текущий текст в поле поиска
 10 | 
 11 |     // Создание таблицы каналов на основе прочитанных данных из файла
 12 |     function initialTable() {
 13 | 
 14 |         initialTable_columns();
 15 |         initialTable_rows();
 16 | 
 17 |     }
 18 | 
 19 |     // Создание заголовков колонок таблицы каналов
 20 |     function initialTable_columns(){
 21 |         
 22 |         const thead = document.createElement('thead');
 23 |         const headerRow = document.createElement('tr');
 24 | 
 25 |         let index = 0;
 26 |         metadata.Columns.forEach(column => {
 27 |             const th = document.createElement('th');
 28 |             th.classList.add(classColumn + column.Name);
 29 |             const thisIndex = index;
 30 |             th.onclick = () => sortTable(thisIndex);
 31 |             th.textContent = column.Presentation;
 32 |             headerRow.appendChild(th);
 33 |             index++;
 34 |         });
 35 | 
 36 |         thead.appendChild(headerRow);
 37 |         table.appendChild(thead);
 38 | 
 39 |     }
 40 | 
 41 |     // Создание строк таблицы каналов
 42 |     function initialTable_rows(){
 43 | 
 44 |         const tbody = document.createElement('tbody');
 45 | 
 46 |         channels.forEach(item => {
 47 |             const row = document.createElement('tr');
 48 |             
 49 |             metadata.Columns.forEach(column => {
 50 |         
 51 |                 const columnName = column.Name;
 52 |                 const cell = document.createElement('td');
 53 |                 cell.classList.add(classColumn + columnName);
 54 | 
 55 |                 const value = item[columnName];
 56 | 
 57 |                 if (columnName === 'Tags') {
 58 |                     value.forEach(tagName => {
 59 |                         addTagElement(tagName, cell, false);
 60 |                     });
 61 |                 } else {
 62 |                     cell.innerHTML = makeLinksClickable(value);
 63 |                 }
 64 | 
 65 |                 row.appendChild(cell);
 66 |             })
 67 | 
 68 |             tbody.appendChild(row);
 69 | 
 70 |         });
 71 | 
 72 |         table.appendChild(tbody);
 73 | 
 74 |     }
 75 | 
 76 |     // Функция делает ссылки в ячейке кликабельными
 77 |     // Метод сделан при помощи claude, можно улучшить
 78 |     function makeLinksClickable(text) {
 79 |         
 80 |         // Разделяем текст на массив строк
 81 |         const lines = text.toString().split('\n');
 82 | 
 83 |         // Обрабатываем каждую строку
 84 |         const formattedLines = lines.map(line => {
 85 |             // Регулярное выражение для URL-ссылок
 86 |             const urlPattern = /(https?:\/\/[^\s]+)/g;
 87 | 
 88 |             // Регулярное выражение для имен пользователей
 89 |             const userPattern = /(? `${url}`;
 93 | 
 94 |             // Функция для замены имен пользователей
 95 |             const replaceUser = (match, username) => `@${username}`;
 96 | 
 97 |             // Замена URL-ссылок
 98 |             line = line.replace(urlPattern, replaceUrl);
 99 | 
100 |             // Замена имен пользователей
101 |             line = line.replace(userPattern, replaceUser);
102 | 
103 |             return line;
104 |         });
105 | 
106 |         // Объединяем строки обратно в один текст
107 |         const formattedText = formattedLines.join('
');
108 | 
109 |         return formattedText;
110 |     }
111 | 
112 |     // Добавляет элемент тега в указанный объект
113 |     function addTagElement(tagName, parent, deleteExisting){
114 |         const tag = tags.find(el => el.Name === tagName);
115 |         if (tag) {
116 |             const tagElement = document.createElement('span');
117 |             tagElement.classList.add('tag');
118 |             tagElement.textContent = tagName;
119 |             tagElement.style.backgroundColor = `${tag.Color}`;
120 |             tagElement.title = tag?.Description || '';
121 |             tagElement.onclick = () => addFilterTag(tagName, deleteExisting);
122 |             parent.appendChild(tagElement);
123 |             return tagElement;
124 |         } else {
125 |             console.error(`Not found tag: ${tagName}`);
126 |         }
127 |     }
128 | 
129 |     // Корректирует строку под правила именования классов
130 |     function convertToClassName(str) {
131 |         return str.trim().replace(/\s+/g, '_').toLowerCase();
132 |     }
133 | 
134 |     // Добавляет указанный тег в фильтры или удаляет из них
135 |     function addFilterTag(tagName, deleteExisting){
136 |         const panel = document.getElementById("filters-panel");
137 |         const id = "tag-filter-" + convertToClassName(tagName);
138 |         let elem = document.getElementById(id);
139 |         if (elem == undefined){
140 |             elem = addTagElement(tagName, panel, true);
141 |             elem.id = id;
142 |             elem.classList.add('tag-filter');
143 |             filterTags.push(tagName);
144 |         } else if (deleteExisting) {
145 |             elem.remove();
146 |             filterTags = filterTags.filter(item => item !== tagName);
147 |         }
148 | 
149 |         updateFilter();
150 |     }
151 | 
152 |     // Загрузка данных из файла channels.json
153 |     function loadFile(){
154 |         fetch('data/channels.json')
155 |             .then(response => response.json())
156 |             .then(data => {
157 |                 
158 |                 channels = data.Channels;
159 |                 tags = data.Tags;
160 |                 metadata = data.Metadata;
161 | 
162 |                 initialTable();
163 |         })
164 |         .catch(error => console.error('Error file load:', error));
165 |     }
166 | 
167 |     // Настройка поля ввода поиска по таблице
168 |     function initialSearchInput() {
169 |         searchInput.addEventListener('input', updateFilter);
170 |     }
171 | 
172 |     // Проверяет соответствие строки условиям отбора
173 |     function rowVisible(row){
174 | 
175 |         return rowVisible_tag(row) && rowVisible_input(row);
176 | 
177 |     }
178 | 
179 |     // Проверяет соответствие строки выбранным тегам
180 |     function rowVisible_tag(row){
181 |         
182 |         let result = true;
183 | 
184 |         if (filterTags.length) {
185 |             const tagElements = row.querySelectorAll('.column-Tags .tag');
186 |             result = Array.from(filterTags).every(filterTag => 
187 |                 Array.from(tagElements).some(element => element.textContent === filterTag));
188 |         }
189 | 
190 |         return result;
191 | 
192 |     }
193 | 
194 |     // Проверяет соответствие строки поиску
195 |     function rowVisible_input(row){
196 | 
197 |         if (searchText === '') {
198 |             return true;
199 |         } else {
200 |             return Array.from(row.children)
201 |                 .map(cell => cell.textContent.toLowerCase())
202 |                 .some(data => data.includes(searchText));
203 |         }
204 | 
205 |     }
206 | 
207 |     // Обновляет фильтр строк таблицы
208 |     function updateFilter(){
209 |         searchText = searchInput.value.toLowerCase();
210 |         Array.from(table.querySelectorAll('tbody tr')).forEach(row => {
211 |             row.style.display = rowVisible(row) ? '' : 'none';
212 |         });
213 |     }
214 | 
215 |     // Сортировка по колонке по индексу (при клике на заголовок колонки)
216 |     // Метод сделан при помощи claude, можно улучшить
217 |     function sortTable(columnIndex) {
218 | 
219 |         const rows = Array.from(table.getElementsByTagName("tr"));
220 | 
221 |         rows.shift(); // Убираем заголовок таблицы из массива
222 | 
223 |         // Определяем тип сортировки (по возрастанию или убыванию)
224 |         let sortOrder = 1;
225 |         if (table.rows[0].cells[columnIndex].classList.contains("sorted-asc")) {
226 |             sortOrder = -1;
227 |         }
228 | 
229 |         // Сортируем строки таблицы
230 |         rows.sort((a, b) => {
231 |             const aValue = a.cells[columnIndex].textContent.trim();
232 |             const bValue = b.cells[columnIndex].textContent.trim();
233 |             if (isNaN(aValue) || isNaN(bValue)) {
234 |                 return sortOrder * aValue.localeCompare(bValue);
235 |             } else {
236 |                 return sortOrder * (parseInt(aValue) - parseInt(bValue));
237 |             }
238 |         });
239 | 
240 |         // Удаляем текущие строки из таблицы
241 |         while (table.rows.length > 1) {
242 |             table.deleteRow(1);
243 |         }
244 | 
245 |         // Вставляем отсортированные строки обратно в таблицу
246 |         const tbody = table.getElementsByTagName("tbody")[0];
247 |         rows.forEach(row => {
248 |             tbody.appendChild(row);
249 |         });
250 | 
251 |         // Удаляем классы сортировки из всех заголовков столбцов
252 |         Array.from(table.getElementsByTagName("th")).forEach(th => {
253 |             th.classList.remove("sorted-asc");
254 |             th.classList.remove("sorted-desc");
255 |         });
256 | 
257 |         // Добавляем класс сортировки к выбранному заголовку столбца
258 |         const selectedColumnHeader = table.rows[0].cells[columnIndex];
259 |         if (sortOrder === 1) {
260 |             selectedColumnHeader.classList.add("sorted-asc");
261 |         } else {
262 |             selectedColumnHeader.classList.add("sorted-desc");
263 |         }
264 |     }
265 | 
266 |     { //Initial
267 | 
268 |         loadFile();
269 | 
270 |         initialSearchInput();
271 | 
272 |     }
273 | }
--------------------------------------------------------------------------------
/docs/scripts/links.js:
--------------------------------------------------------------------------------
 1 | 
 2 | {
 3 |     const urlAuthorGithub = "https://github.com/SeiOkami";
 4 |     const urlRepository = urlAuthorGithub + "/links-one-s";
 5 |     const urlHowAdd = urlRepository + "#Как-добавить-канал";
 6 |     const urlIssues = urlRepository + "/issues";
 7 |     const urlAuthorTelegram = "https://t.me/SeiOkami";
 8 |     const urlJuniorOneS = "https://t.me/JuniorOneS";
 9 |     
10 |     function openRepository(){
11 |         openUrl(urlRepository);
12 |     }
13 | 
14 |     function openHowAdd(){
15 |         openUrl(urlHowAdd);
16 |     }
17 | 
18 |     function openIssues(){
19 |         openUrl(urlIssues);
20 |     }
21 | 
22 |     function openAuthorGithub(){
23 |         openUrl(urlAuthorGithub);
24 |     }
25 | 
26 |     function openAuthorTelegram(){
27 |         openUrl(urlAuthorTelegram);
28 |     }
29 | 
30 |     function openJuniorOneS(){
31 |         openUrl(urlJuniorOneS);
32 |     }
33 | 
34 |     function openUrl(url){
35 |         window.open(url, '_blank');
36 |     }
37 | 
38 | }
39 | 
--------------------------------------------------------------------------------
/docs/styles/main.css:
--------------------------------------------------------------------------------
  1 | 
  2 | /* ПЕРЕМЕННЫЕ */
  3 | 
  4 | :root {
  5 |     --color-text: #4b4b4b;
  6 |     --border-func-color: #aea575;
  7 |     --border-color: #dbdada;
  8 |     --background-main: #fbed9e;
  9 |     --shadow-color: rgba(233, 233, 233, 1);
 10 |     --first-row-color: #ffffff;
 11 |     --second-row-color: #fafafa;
 12 |     --th-color: #f2f2f2;
 13 | }
 14 | 
 15 | 
 16 | /* ТЕЛО */
 17 | 
 18 | body {
 19 |     color: var(--color-text);
 20 |     font-family: '', Arial, sans-serif;
 21 |     font-size: 10pt;
 22 |     cursor: default;
 23 |     overflow: hidden;
 24 |     position: absolute;
 25 |     top: 0;
 26 |     right: 0;
 27 |     bottom: 0;
 28 |     left: 0;
 29 |     margin: 0;
 30 |     height: 100%;
 31 | }
 32 | 
 33 | .flex {
 34 |     display: flex;
 35 | }
 36 | 
 37 | /* ШАПКА СТРАНИЦЫ */
 38 | 
 39 | header, .functions {
 40 |     background: var(--background-main);
 41 |     border-bottom: 1px solid var(--border-func-color);
 42 |     align-items: center;
 43 |     display: flex;
 44 | }
 45 | 
 46 | header {
 47 |     height: 31px;
 48 | }
 49 | 
 50 | .functions {
 51 |     padding: 0px 10px;
 52 |     height: 52px;
 53 | }
 54 | 
 55 | .function {
 56 |     padding-left: 7px;
 57 |     padding-right: 10px;
 58 | }
 59 | 
 60 | .function:hover {
 61 |     cursor: pointer;
 62 |     text-decoration: underline;
 63 | }
 64 | 
 65 | .header_logo {
 66 |     align-items: center;
 67 |     display: flex;
 68 |     background-color: #f4e8a0;
 69 |     align-items: center; /* Вертикальное выравнивание */
 70 |     justify-content: center; /* Горизонтальное выравнивание */
 71 | }
 72 | 
 73 | .header-right-img{
 74 |     width: 16px;
 75 |     height: 16px;
 76 |     padding: 5px;
 77 | }
 78 | 
 79 | .header-left-img{
 80 |     width: 20px;
 81 |     height: 20px;
 82 | }
 83 | 
 84 | /* ТАБЛИЦА КАНАЛОВ */
 85 | 
 86 | #div_main{
 87 |     padding: 1px 16px;
 88 | }
 89 | 
 90 | #div_channels{
 91 |     border: solid 1px var(--border-color);
 92 | }
 93 | 
 94 | .channels_header {
 95 |     margin: 5px 0px;
 96 |     height: 28px;
 97 | }
 98 | 
 99 | table {
100 |     border-collapse: collapse;
101 |     width: 100%;
102 |     border-radius: 2px;
103 |     overflow: auto; /* Добавление полос прокрутки при необходимости */
104 |     display: block; /* Устанавливает таблицу как блочный элемент */
105 |     height: calc(100vh - 200px);
106 |     flex-grow: 1;
107 |     table-layout: auto; /* Устанавливает фиксированную ширину для ячеек */
108 |     border-collapse: collapse; /* Убирает двойные границы между ячейками */
109 |     box-sizing: border-box;
110 | }
111 | 
112 | th.sorted-asc,
113 | th.sorted-desc{
114 |     padding-right: 20px; /* Отступ справа для размещения стрелочки */
115 | }
116 | 
117 | th.sorted-asc::after,
118 | th.sorted-desc::after{
119 |     position: absolute; /* Абсолютное позиционирование для стрелочки */
120 |     right: 5px; /* Отступ стрелочки от правого края */
121 |     top: 50%; /* Выравнивание стрелочки по вертикали */
122 |     transform: translateY(-50%); /* Сдвиг стрелочки вверх на 50% ее высоты */
123 | }
124 | 
125 | th.sorted-asc::after{
126 |     content: "↓";
127 | }
128 | 
129 | th.sorted-desc::after{
130 |     content: "↑";
131 | }
132 | 
133 | #searchInput {
134 |     width: 100%;
135 |     border: 1px solid var(--border-func-color);
136 |     border-radius: 3px;
137 | }
138 | 
139 | table tr:nth-child(even) {
140 |     background-color: var(--second-row-color); /* Цвет фона для четных строк */
141 | }
142 |   
143 | table tr:nth-child(odd) {
144 |     background-color: var(--first-row-color); /* Цвет фона для нечетных строк */
145 | }
146 | 
147 | th, td {
148 |     border: 1px solid var(--border-color);
149 |     padding: 8px;
150 |     text-align: left;
151 |     word-wrap: break-word; /* Переносит слова на новую строку, если они не помещаются в ячейку */
152 |     padding: 5px; /* Добавляет небольшой отступ внутри ячеек */
153 |     max-width: 500px; /* Позволяет ячейке сжиматься при необходимости */
154 | }
155 | 
156 | th {
157 |     cursor: pointer;
158 |     background-color: var(--th-color);
159 |     font-weight: normal;
160 |     height: 28px;
161 |     padding-left: 8px;
162 |     position: sticky;
163 |     top: 0;
164 | }
165 | 
166 | .header_title{
167 |     margin-left: 4px;
168 |     flex: 1 1 auto;
169 | }
170 | 
171 | .btn-arrow {
172 |     margin: 0 -1px;
173 |     height: inherit;
174 |     padding: 2px 9px;
175 |     border: 1px solid rgba(0, 0, 0, 0.3);
176 |     box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.2);
177 | }
178 | 
179 | .channels_commands{
180 |     margin-bottom: 5px;
181 | }
182 | 
183 | .margin-right{
184 |     margin-right: 8px;
185 | }
186 | 
187 | button{
188 |     height: 24px;
189 |     padding: 12px 10px;
190 |     border: 1px solid rgba(0, 0, 0, 0.3);
191 |     border-radius: 2px;
192 |     color: var(--color-text);
193 |     background-color: #fff;
194 |     display: inline-flex;
195 |     align-items: center;
196 |     box-shadow: inset 0 -1px 3px rgb(230, 230, 230),
197 |             0 0 0 rgb(214, 214, 214);
198 | }
199 | 
200 | button:hover{
201 |     cursor: pointer;
202 |     border: 1px solid rgba(0, 0, 0, 0.6);
203 | }
204 | 
205 | .topLineBox {
206 |     font-size: 14pt;
207 |     position: relative;
208 |     width: 100%;
209 |     min-width: 2em;
210 |     height: 1.5em;
211 |     line-height: 1.5em;
212 |     padding: 2px 10px;
213 |     vertical-align: middle;
214 | }
215 | 
216 | .btn-arrow:before {
217 |     content: '';
218 |     position: absolute;
219 |     height: 100%;
220 |     bottom: -100%;
221 |     right: -50%;
222 |     left: -50%;
223 |     box-shadow: 0 0 1em 0 rgba(0, 0, 0, 0.1);
224 | }
225 | 
226 | .column-ID, .column-Actual {
227 |     display: none;
228 | }
229 | 
230 | .tag {
231 |     padding: 1px 5px;
232 |     margin: 10px 3px;
233 |     border-radius: 3px;
234 |     cursor: pointer;
235 | }
236 | 
237 | .tag-filter::after{
238 |     content: " ✖";
239 | }
240 | 
--------------------------------------------------------------------------------
/tools/updater/ConsoleUpdater/ConsoleUpdater.csproj:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     Exe
 5 |     net8.0
 6 |     enable
 7 |     enable
 8 |   
 9 | 
10 |   
11 |     
12 |   
13 | 
14 | 
15 | 
--------------------------------------------------------------------------------
/tools/updater/ConsoleUpdater/Program.cs:
--------------------------------------------------------------------------------
 1 | using UpdaterLibrary;
 2 | using UpdaterLibrary.SiteUpdater;
 3 | using UpdaterLibrary.Tools;
 4 | 
 5 | namespace ConsoleUpdater;
 6 | 
 7 | static class Program
 8 | {
 9 |     static async Task Main(string[] args)
10 |     {
11 | 
12 |         string filterChanel = "";
13 | 
14 |         Console.WriteLine("Обновление сайта с ресурсами по теме 1С".ToUpper());
15 |         Console.WriteLine("---------------------------------------");
16 |         Console.WriteLine("1. Обновить все данные");
17 |         Console.WriteLine("2. Обновить по каналу");
18 |         Console.WriteLine("3. Выйти");
19 | 
20 |         while (true)
21 |         {
22 |             var key = Console.ReadKey();
23 |             if (key.KeyChar == '1')
24 |             {
25 |                 Console.WriteLine("\n Обновление всех данных");
26 |                 break;
27 |             } else if (key.KeyChar == '2') {
28 |                 Console.Write("\n Имя канала: ");
29 |                 filterChanel = Console.ReadLine() ?? "";
30 |                 if (!string.IsNullOrEmpty(filterChanel))
31 |                     break;
32 |             }
33 |             else if (key.KeyChar == '3')
34 |             {
35 |                 return;
36 |             }
37 |         }
38 | 
39 |         var updater = new Updater();
40 |         updater.AddLogHandler(AddToLog);
41 |         updater.Parametrs.ChannelUserName = filterChanel;
42 |         await updater.UpdateAsync();
43 | 
44 |         Console.WriteLine($"Всего каналов: {updater.Data.Channels.Count}");
45 |         Console.WriteLine($"Всего тегов: {updater.Data.Tags.Count}");
46 | 
47 |         Console.ReadLine();
48 | 
49 |     }
50 |     public static void AddToLog(string message, object obj, LogEventType type)
51 |     {
52 |         if (type == LogEventType.Error)
53 |             Console.ForegroundColor = ConsoleColor.Red;
54 |         else
55 |             Console.ForegroundColor = ConsoleColor.Green;
56 |         
57 |         Console.WriteLine($"[{DateTime.Now}] {obj.ToStringRecurs()}: {message}");
58 |     }
59 | 
60 | }
61 | 
--------------------------------------------------------------------------------
/tools/updater/Updater.sln:
--------------------------------------------------------------------------------
 1 | 
 2 | Microsoft Visual Studio Solution File, Format Version 12.00
 3 | # Visual Studio Version 17
 4 | VisualStudioVersion = 17.9.34728.123
 5 | MinimumVisualStudioVersion = 10.0.40219.1
 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleUpdater", "ConsoleUpdater\ConsoleUpdater.csproj", "{328D96DF-27E7-49A9-846B-BA3FABDDB673}"
 7 | EndProject
 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UpdaterLibrary", "UpdaterLibrary\UpdaterLibrary.csproj", "{3D5B5F55-3AFE-4134-970F-9F9C25ACF9F1}"
 9 | EndProject
10 | Global
11 | 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | 		Debug|Any CPU = Debug|Any CPU
13 | 		Release|Any CPU = Release|Any CPU
14 | 	EndGlobalSection
15 | 	GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | 		{328D96DF-27E7-49A9-846B-BA3FABDDB673}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | 		{328D96DF-27E7-49A9-846B-BA3FABDDB673}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | 		{328D96DF-27E7-49A9-846B-BA3FABDDB673}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | 		{328D96DF-27E7-49A9-846B-BA3FABDDB673}.Release|Any CPU.Build.0 = Release|Any CPU
20 | 		{3D5B5F55-3AFE-4134-970F-9F9C25ACF9F1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | 		{3D5B5F55-3AFE-4134-970F-9F9C25ACF9F1}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | 		{3D5B5F55-3AFE-4134-970F-9F9C25ACF9F1}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | 		{3D5B5F55-3AFE-4134-970F-9F9C25ACF9F1}.Release|Any CPU.Build.0 = Release|Any CPU
24 | 	EndGlobalSection
25 | 	GlobalSection(SolutionProperties) = preSolution
26 | 		HideSolutionNode = FALSE
27 | 	EndGlobalSection
28 | 	GlobalSection(ExtensibilityGlobals) = postSolution
29 | 		SolutionGuid = {2B2F8A1F-18F6-4080-BBBD-27FB036C60BA}
30 | 	EndGlobalSection
31 | EndGlobal
32 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary.Tests/FakeUpdater.cs:
--------------------------------------------------------------------------------
 1 | using Telegram.Bot.Types;
 2 | using UpdaterLibrary.Models;
 3 | using UpdaterLibrary.SiteUpdater;
 4 | 
 5 | namespace UpdaterLibrary.Tests;
 6 | internal class FakeUpdater : IUpdater
 7 | {
 8 |     public UpdateDataModel Data { get; set; } = new();
 9 |     public Dictionary Log { get; set; } = new();
10 | 
11 |     public void AddToLog(string message, object obj, LogEventType type = LogEventType.Change)
12 |     {
13 |         throw new NotImplementedException();
14 |     }
15 | 
16 |     public Task GetChatAsync(ChannelModel channel)
17 |     {
18 |         throw new NotImplementedException();
19 |     }
20 | 
21 |     public Task GetMemberCountAsync(ChannelModel channel)
22 |     {
23 |         throw new NotImplementedException();
24 |     }
25 | 
26 |     public string LogText()
27 |     {
28 |         throw new NotImplementedException();
29 |     }
30 | 
31 |     public Task UpdateAsync()
32 |     {
33 |         throw new NotImplementedException();
34 |     }
35 | 
36 |     public void UpdateChannelTag(ChannelModel channel, string TagName, bool Add)
37 |     {
38 |         throw new NotImplementedException();
39 |     }
40 | }
41 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary.Tests/IrrelevantTagsUpdateHandlerTest.cs:
--------------------------------------------------------------------------------
 1 | using UpdaterLibrary.Models;
 2 | using UpdaterLibrary.UpdateHandlers;
 3 | 
 4 | namespace UpdaterLibrary.Tests;
 5 | 
 6 | [TestClass]
 7 | public class IrrelevantTagsUpdateHandlerTest
 8 | {
 9 |     [TestMethod]
10 |     public async Task TestUpdateAsync()
11 |     {
12 |         var updater = new FakeUpdater();
13 |         updater.Data.Tags.Add(new TagModel(){
14 |                 Name = "test",
15 |         });
16 | 
17 |         updater.Data.Channels.Add(new ChannelModel(){
18 |                 Tags = ["test"]
19 |         });
20 | 
21 |         var handler = new IrrelevantTagsUpdateHandler();
22 |         await handler.UpdateAsync(updater);
23 |         
24 |         
25 | 
26 |     }
27 | }
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary.Tests/UpdaterLibrary.Tests.csproj:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     net8.0
 5 |     enable
 6 |     enable
 7 | 
 8 |     false
 9 |     true
10 |   
11 | 
12 |   
13 |     
14 |     
15 |     
16 |     
17 |   
18 | 
19 |   
20 |     
21 |   
22 | 
23 |   
24 |     
25 |   
26 | 
27 | 
28 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/Models/ChannelModel.cs:
--------------------------------------------------------------------------------
 1 | using System.Text.Json.Serialization;
 2 | using UpdaterLibrary.Settings;
 3 | 
 4 | namespace UpdaterLibrary.Models;
 5 | 
 6 | public class ChannelModel
 7 | {
 8 | 
 9 |     [JsonIgnore]
10 |     public string UserName { 
11 |         get {
12 |             return Url.Replace(SettingsManager.TELEGRAM_URL, "").Split('?')[0]; 
13 |         }
14 |         set {
15 |             Url = SettingsManager.TELEGRAM_URL + value;
16 |         }
17 |     }
18 | 
19 |     public long ID { get; set; }
20 |     [JsonRequired]
21 |     public string Url { get; set; } = string.Empty;
22 |     public bool IsChannel { get; set; }
23 |     public string Name { get; set; } = string.Empty;
24 |     public string Description { get; set; } = string.Empty;
25 |     public string Contact { get; set; } = string.Empty;
26 |     public DateTime Actual { get; set; }
27 |     public int MemberCount { get; set; }
28 |     public string Comment { get; set; } = string.Empty;
29 |     public List Tags { get; set; } = new();
30 | 
31 |     public override string ToString() => UserName;
32 | 
33 | }
34 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/Models/MetadataColumnModel.cs:
--------------------------------------------------------------------------------
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace UpdaterLibrary.Models;
 4 | 
 5 | public class MetadataColumnModel
 6 | {
 7 |     [JsonRequired]
 8 |     public string Name { get; set; } = string.Empty;
 9 | 
10 |     [JsonRequired]
11 |     public string Presentation { get; set; } = string.Empty;
12 | }
13 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/Models/MetadataModel.cs:
--------------------------------------------------------------------------------
1 | namespace UpdaterLibrary.Models;
2 | 
3 | public class MetadataModel
4 | {
5 |     public List Columns { get; set; } = new();
6 | }
7 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/Models/TagModel.cs:
--------------------------------------------------------------------------------
 1 | using System.Text.Json.Serialization;
 2 | 
 3 | namespace UpdaterLibrary.Models;
 4 | 
 5 | public class TagModel
 6 | {
 7 |     [JsonRequired] 
 8 |     public string Name { get; set; } = string.Empty;
 9 |     public string Description { get; set; } = string.Empty;
10 |     public string Color { get; set; } = string.Empty;
11 |     public override string ToString() => Name;
12 | 
13 | }
14 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/Models/UpdateDataModel.cs:
--------------------------------------------------------------------------------
1 | namespace UpdaterLibrary.Models;
2 | 
3 | public class UpdateDataModel
4 | {
5 |     public MetadataModel Metadata { get; set; } = new();
6 |     public List Tags { get; set; } = new(); 
7 |     public List Channels { get; set; } = new();
8 | }
9 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/Settings/SettingsManager.cs:
--------------------------------------------------------------------------------
 1 | namespace UpdaterLibrary.Settings;
 2 | using Newtonsoft.Json;
 3 | 
 4 | public static class SettingsManager
 5 | {
 6 |     private const string SETTINGS_FILE_NAME = "appsettings.json";
 7 |     private const string CHANNELS_FILE_PATH = "data/channels.json";
 8 |     public const string TELEGRAM_URL = $"https://t.me/";
 9 |     public const string TAG_NAME_BEGIN = "Малыши (<500)";
10 |     public const string TAG_NAME_CLOSE = "Закрыт";
11 |     public const string TAG_NAME_CHANNEL = "Канал";
12 |     public const string TAG_NAME_CHAT = "Чат";
13 |     public const string TAG_NAME_BOT = "Бот";
14 |     public const string TAG_NAME_USER = "Пользователь";
15 |     public const int MAX_LENGTH_MESSAGE = 3000;
16 |     public const int MAX_NUMBER_ATTEMPTS = 3;
17 |     public const string ERROR_CHAT_NOT_FOUND = "Bad Request: chat not found";
18 |     public const string ERROR_CHAT_SUPERGROUP = "Forbidden: bot is not a member of the supergroup chat";
19 | 
20 |     private static SettingsModel Instance { get; set; }
21 | 
22 |     public static string PathToFileChannels { get; private set; }
23 |     public static string TelegramBotToken => Instance.TelegramBotToken;
24 |     public static int DelayRequestsApi => Instance.DelayRequestsApi;
25 |     public static List Subscribers => Instance.Subscribers;
26 | 
27 |     static SettingsManager()
28 |     {
29 |         Instance = ReadSettingsFile();
30 |         PathToFileChannels = FullPathToFile(
31 |             false,
32 |             Instance.PathToSite,
33 |             CHANNELS_FILE_PATH);
34 |     }
35 | 
36 |     private static SettingsModel ReadSettingsFile()
37 |     {
38 |         var filePath = FullPathToFile(true, SETTINGS_FILE_NAME);
39 | 
40 |         if (!File.Exists(filePath))
41 |         {
42 |             throw new FileNotFoundException($"Файл настроек не найден: {filePath}");
43 |         }
44 | 
45 |         using (var reader = new StreamReader(filePath))
46 |         {
47 |             var fileContents = reader.ReadToEnd();
48 |             var result = JsonConvert.DeserializeObject(fileContents);
49 |             if (result is null)
50 |                 throw new FileLoadException($"Файл настроек пустой: {filePath}");
51 |             else
52 |                 return result;
53 |         }
54 |     }
55 | 
56 |     private static string FullPathToFile(bool isRelative, params string[] paths)
57 |     {
58 |         var path = Path.Combine(paths);
59 |         if (isRelative)
60 |         {
61 |             path = Path.Combine(Directory.GetCurrentDirectory(), path);
62 |             path = Path.GetFullPath(path);
63 |         }
64 |         return path;
65 |     }
66 | 
67 | }
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/Settings/SettingsModel.cs:
--------------------------------------------------------------------------------
1 | namespace UpdaterLibrary.Settings;
2 | public class SettingsModel
3 | {
4 |     public string TelegramBotToken { get; set; } = string.Empty;
5 |     public string PathToSite { get; set; } = string.Empty;
6 |     public int DelayRequestsApi { get; set; }
7 |     public List Subscribers { get; set; } = new();
8 | }
9 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/SiteUpdater/IUpdater.cs:
--------------------------------------------------------------------------------
 1 | using TelegramTypes = Telegram.Bot.Types;
 2 | using UpdaterLibrary.Models;
 3 | 
 4 | namespace UpdaterLibrary.SiteUpdater;
 5 | 
 6 | public interface IUpdater
 7 | {
 8 |     public UpdateDataModel Data { get; set; }
 9 |     public Dictionary Log { get; set; }
10 |     public UpdateParametrs Parametrs { get; set; }
11 |     public Task UpdateAsync();
12 |     public bool ChannelUpdateRequired(ChannelModel channel);
13 |     public void UpdateChannelTag(ChannelModel channel, string TagName, bool Add);
14 |     public void AddLogHandler(Action handler);
15 |     public void AddToLog(string message, object obj, LogEventType type = LogEventType.Change);
16 |     public string LogText(LogEventType? filterType = null);
17 |     public Task GetMemberCountAsync(ChannelModel channel);
18 |     public Task GetChatAsync(ChannelModel channel);
19 | }
20 | 
--------------------------------------------------------------------------------
/tools/updater/UpdaterLibrary/SiteUpdater/LogEventModel.cs:
--------------------------------------------------------------------------------
1 | namespace UpdaterLibrary.SiteUpdater;
2 | 
3 | public class LogEventModel
4 | {
5 |     public List