Спортивная программа fitplotter (js)
Прошу высказаться физкультурников-программистов
https://github.com/karaul/fitplotter По ссылке выше текст на английском, а по ссылкам ниже по-русски в свободной форме Мотивация и начало работы http://maraforum-2.ru/viewtopic.php?f=6&t=3477 Развитие проекта http://maraforum-2.ru/viewtopic.php?...=3301&start=20 Спасибо. |
karaul,
:) по программе высказаться не могу, но дело хорошее. |
Похоже, что марафонцев среди js программистов столько же, сколько программистов среди бегунов. Мало.
Но вот Искандер Ядгаров - звезда Московского Марафона - он же программист. Если никто по существу не выскажется, можно тешить себя тем, что среди марафонцев я самый знающий программист, а среди программистов - лучший марафонец. |
Цитата:
|
Вложений: 1
Никто не пишет, а хочется поговорить
(1) При входе на форум глюки при автоподстановке пароля из гугл-хромовского менеджера паролей. Пришлось запрашивать воостановление пароля. И так уже второй раз. (2) Хочется получить совет по реализации программы. Делается для себя, вспомнить навык и выучить новое, чтобы другим было не стыдно показать и предложить для работы. Коммерческого интереса нет. Подробно. В моем спортивном архиве данные о 10 лет занятий, 37500км, несколько тысяч файлов. Каждый файл - это трек пробежки с записью GPS координат, пульса, темпа бега, частота шагов (каденс) и прочее из спортивных часов. На рисунке во вложении пример того что в файлах Я сделал форму для анализа данных записанных в файле, см. стартовое сообщение. Вот кстати, онлайн версия https://karaul.github.io/fitplotter/ если интересно поиграть с онлайн-версией, FIT файл для анализа можно взять из https://github.com/karaul/fitplotter/tree/main/examples файл проекта https://github.com/karaul/fitplotter Теперь мне хочется прикрутить к этому делу оболочку - таблицу из имен файлов и реперных данных из каждого файла (день, расстояние, средние и проч). Все файлы хранятся в одной директории на моей машине. Эта директория почти ежедневно пополняется. Мне хочется иметь таблицу вроде этой https://www.runningahead.com/logs/4e...1eca8/workouts Так чтобы щелкнув по строке таблицы я мог открыть из директории соответствующий файл и посмотреть график. Потом щелкнуть по другой строке и открыть на том же графике 2ой файл и сравнить. (Или на новом поле, но это неважно) Непонятно как сделать таблицу. Прикручивать базу данных? Я сделал подобное на питоне, там у меня просто за каждый год csv файл, который регулярно дописывается, все работает. Теперь хочется на JS, но чтобы было не кустарно, как на питоне, а согласно имеющимся практикам, и практики заодно выучить. Предполагается что программа работать будет локально, если удастся потом сделать выход в внешний мир, как получилось с fitplotter, то совсем хорошо. По моему разумению - это стандартная задача. Я нашел несколько описаний как это делается, например https://closebrace.com/tutorials/201...ss-and-mongodb Хочется знать - это правильное решение когда элементы хранения - файлы на диске? Мне кажется можно сделать проще. Спасибо за обсуждение. PS: В качестве благодарности могу надавать советов как правильно бегать. |
karaul,
на всякий случай, пример создания таблицы с возможностью сортировки https://javascript.ru/forum/misc/777...tml#post509074 |
Цитата:
Цитата:
Ставить для этого ноду с монго - стрельба из пушки по воробьям. Ну если только хочется поучиться работать с этим. Но с другой стороны без сервера обойтись трудно. Javascript не может на автомате читать файлы с локального компьютера без явного указания пользователем файла, через стандартное диалоговое окно Open File. Только если попробовать поизвращаться - подключать эти файлы через <script>, и делать их как JSON, или просто комментарий и потом парсить текстовое содержимое. |
Спасибо за обсуждение
Изменил нечитаемый пароль выданную гуглем на более человеческое - гугл-хромовский менеджер начал работалть рони спасибо за код, да именно это я и хочу. Надо просто добавить контроль для открытия карты и графиков. Как лучше: двойной щелчок по строке или отдельная колонка с кнопкой? voraa спасибо за идею. Интересно, комментарий на тысячи строк. А есть пример такой реализации? В качестве главной цели мне хочется чтобы программа прижилась в нашем спортивном клубе. Там не программисты, но про текстовый файл поймут. И возможность в т.ч. самому править файл заголовков (если что пошло не так), дописывая туда имя файла c свежей тренировкой выглядит просто и привлекательно. Человек понимает что делает и чувствует себя хозяином своей машины, программы на ней, и управляет своими данными. А необходимость шаманства с настройкой sqlite или mongodb и настройкой сервера людей оттолкнет. Можно сделать две версию, простую для людей, и правильную с БД (для себя - поучиться работать с БД). |
Цитата:
|
Цитата:
Это прокатывало, когда произвольные данные надо было помещать в <script> /* Тут произвольные данные */ </script> Но для внешних файлов набросал такой примерчик <!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8" lang="ru"> <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title> LOAD DATA </title> </head> <style> #out { position:relative; width: 300px; height:400px; overflow-y:auto; border: 1px red solid; } </style> <body > <pre id=out></pre> <button id=rd1>read1</button> <button id=rd2>read2</button> <button id=rd3>read3</button> <script> var data; const read = (fn) => { const uf = './data/'+ fn; const sf = document.createElement('script'); return new Promise (res => { sf.onload = () => { sf.remove(); res(data) } sf.src = uf; document.head.append(sf) }) } document.getElementById('rd1').addEventListener('click', async () => { const text = await read('file1.dat') // Тут какая то обработка. Пока так document.getElementById('out').textContent += text; }) document.getElementById('rd2').addEventListener('click', async () => { const text = await read('file2.dat') // Тут какая то обработка. Пока так document.getElementById('out').textContent += text; }) document.getElementById('rd3').addEventListener('click', async () => { const text = await read('file3.dat') // Тут какая то обработка. Пока так document.getElementById('out').textContent += text; }) </script> </body> </html> В директории с этим файлом создайте поддирескторию data И в нее три файла file1.dat data=` Это файл file1.dat 12345 67890 09876 54321 `; file2.dat data=` Это файл file2.dat qwert yuiop asdfg hjklz `; file3.dat data=` Это файл file3.dat йцуке нгшщзх фывап ролджэ `; В этих файлах между первой строкой data=` и последней `; Могут размещаться произвольные текстовые данные, которые можно получить и потом как то обрабатывать |
Здорово, спасибо большое voraa, рони
На 80% стало ясно что делать В качестве упражнения хочу взять кнопки отсюда и файл ajax https://datatables.net/examples/ajax...ta_source.html всё это прикрутить его к примеры рони и склеить с кодом voraa А то ломал головы все эти дни. Форум не позволяет выдавать кредиты подряд, так бы еще добавил плюсиков Общение - великая вещь |
Цитата:
Если бы можно было с локальными, то и не надо бы было со script возиться. |
Я имел в виду текстовое содержание файла Ajax на вкладке скопировать на диск для вашего примера сскриптом и способ вставить кнопки в ряд
|
Цитата:
А сам файл готовить отдельной утилитой из командной строки, сканируя директорию и собирая в file.dat имена файлов или просто в текстовом редакторе |
Сделал другой вариант. Чтобы читать локальные файлы надо скрипт класть в раздел <head>
<head> <script src=./main.js></script> </head> <body> <input id="read_table" type="file" value='' style="padding: 0px; size: 120px" /> </body> а потроха скрипта упаковать в функцию document.addEventListener('DOMContentLoaded', function () { var fReader = new FileReader(); var readtable = document.getElementById('read_table'); var filename, data, headers; readtable.onchange = function (e) { var file = this.files[0]; //console.log(file); filename = file.name; fReader.readAsText(file); } fReader.onload = function (e) { var text = e.target.result; ... } }) работающий пример с такой упаковкой https://github.com/gfmoore/BackFitCl...ileanalyser.js В этом примере разборка бинарного файла, но логика та же Другой пример здесь https://www.js-tutorials.com/javascr...ascript-html5/ http://js-tutorials.com/demos/read_csv_javascript_demo/ В этом примере много мусора, но главный принцип такой же - упаковать скрипт в функцию. (Интересно, в описании про мусор пишут а про упаковку скрипта в функцию нет. Когда весь Интернет просто криком исходит - как в простом js и html читать локальные файлы) Остались бантики - стили, форматирование таблицы, прикручивание кнопки для вызова поля с графиками и картой Интересно, что структурно решение выглядит так же как на питоне Логика везде одинаковая - здравый смысл - а синтаксис может быть какой угодно |
Цитата:
document.addEventListener('DOMContentLoaded', function () { то это совершенно не обязательно. Можно и без этого <head> </head> <body> <input id="read_table" type="file" value='' style="padding: 0px; size: 120px" /> <script> var fReader = new FileReader(); var readtable = document.getElementById('read_table'); var filename, data, headers; readtable.onchange = function (e) { var file = this.files[0]; //console.log(file); filename = file.name; fReader.readAsText(file); } fReader.onload = function (e) { var text = e.target.result; ... } </script> </body> Сработает точно так же Чтение файла через <input type=file> - это обычный способ. Только пользователь должен каждый раз указывать файл. И не получится прочитать файл по имени, указанному где то в программе или какой-нибудь таблице. |
voraa,
Вы правы, действительно в вашем коде файл читается, я проверил. Когда я писал fitplotter -- ссылка в певом сообщении темы -- у меня в начале постоянно вылезала ошибка нечтения. Возможно из-за того что в теле скрипта был вызов другого скрипта с диска, который занимается разборкой двоичного файла. Заработало только после упаковки в ф-цию. |
Просто всякие document.getElementById('read_table');
нормально срабатывают только тогда, когда браузер уже распарсил HTML текст и знает, что это за элемент с id='read_table'. Когда мы помещаем скрипт в конце body, все элементы уже известны и DOM дерево построено. Если поместить этот скрипт вначале, то это функция не сработает, т.к этого элемента еще нет. Но document.addEventListener('DOMContentLoaded', function () {}) говорит - выполни эту функцию, когда произойдет событие "Содержимое DOM готово", т.е все распарсено, все элементы известны. |
Цитата:
Будет ли способ описанный выше работать при однократном нажатии на простую кнопку в внешней таблице без диалога с пользователем? Или вместо простой кнопки в строку таблицы надо прописывать обязательно <input type=file> чтобы юзер обязательно сам нажимал на input ? И после нажатия input всегда появляется диалог? В моем случае диалог не нужен потому что имя файла и путь к нему уже известны, можно читать сразу. |
Цитата:
Иначе скрипты читали бы любые файлы на компьютере пользователя, и отправляли бы их куда-нибудь. |
Мда, незадача
Написал const queryString = window.location.search;//href; const urlParams = new URLSearchParams(queryString); const fitfilename = urlParams.get('file') console.log(fitfilename); document.getElementById('myfile').value = fitfilename; и получил (ниже 113 = 5 строка выше) <main.js:113 Uncaught DOMException: Failed to set the 'value' property on 'HTMLInputElement': This input element accepts a filename, which may only be programmatically set to the empty string. > Конечно, о таких вещах надо знать из систематического образования. А не хватая случайные знания из сети |
Первая версия fitplotter работала с http server (http-server-static.js) потому что я не понимал как читать файлы с диска и не мог коректно импортировать в скрипт парсер. Сервер нужен для разрешения импорта и работал через node.eхe. Потом я подсмотрел у других, как без сервера можно обойтись, и с радостью выбросил node.exe из инсталляции. Программа стала работать онлайн.
Получается что теперь надо вернуться к тому что было. Имя файла из таблицы посылать на сервер, т.к. только сервер может прочитать этот файл не включая диалога и не вовлекая юзера. А дальше возникает два варианта: (1) возвращать двоичное содержание файла чтобы его парсил клиент или (2) возвращать распарсенное содержание в виде объектов, по которым строится график Как лучше ? Просто я видел вопросы в сети - люди маялись с получением blob от сервера, он приходил испорченным |
Конечно лучше, пусть сервер этим занимается.
Может даже лучше, если заранее распарсить все и хранить на сервере уже текстовый вариант, что бы не приходилось при каждом запросе делать одно и тоже. |
Вы правы, если проект получит развитие, то надо так сделать.
Пока решил не есть слона целиком и попробовал простейшее решение. Посадил XMLHttpRequest на кнопку из таблицу, отправил на сервер путь к файлу, там считал в fs, послал назад, получил бинарные внутренности которые тут же распарсил на клиенте для проверки. Заработало. function plotdata(e) { var filename = e.target.id; console.log(filename); var xhr = new XMLHttpRequest(); xhr.onload = httpRequestfoo; xhr.open('GET', filename, true); xhr.responseType = 'arraybuffer' xhr.onerror = function (e) { console.log(error(xhr.statusText)); }; xhr.send(null); } function httpRequestfoo() { if (this.readyState === 4) { if (this.status === 200) { var blob = new Uint8Array(this.response); fitParser.parse(blob, function (error, data) { if (error) { console.error(error); } else { // data - распарсенное содержание console.log(data); } }); } } } |
Cделал проект публичным
https://github.com/karaul/fitalyser скриншоты https://github.com/karaul/fitalyser/...er/screenshots mainWindow.JPG - таблица занятий, fitplotterWindow.JPG- окно анализа. По клику в таблице окно анализа либо открывается, либо дополняется. Все выбранные в таблице дни дописываются в выпадающий список окна анализа, и потом легко выбирая эти дни можно переключаться между графиками. Визуально надо ещё потрудится над таблицей, но прототип работает как было задумано. Таблицу должен приготовить сам юзер. Это *.csv файл в котором прописаны пути к файлам тренировок. У меня такая таблицы есть, для в конце проекта может сделаю командную утититу которая бы по маске *.fit сама генерировала такую таблицу в заданной директории. |
прошу помощь, кажется опять я не знаю какого то секрета полишинеля
есть главное приложение main /main/index.html которое вызывает форму form изнутри main windowFitplotter = window.open('./form/index.html)/ /main/form/index.html Все работает, так и должно быть, main - главный, form - подчинененный и внутри main Теперь я хочу чтобы form и main были на одном уровне /main/index.html /form/index.html и делаю вызов windowFitplotter = window.open('./../form/index.html)/ (добавлено "/../" - чтобы уйти на уровень выше) Но по прежнему происходит вызов изнутри /main/form/index.html и "/../" в игноре Неправильно (bad practice) помещать form на том же уровне что и main? или что с настройками моего сервера, который игнорирует "/../" ? Дело в том что form является независимым от main и к нему должен быть доступ сверху , и я не хочу вставлять form в main. Но при изменении кода внутри form, я не хочу каждый раз копировать файлы из независимой form под крышу main cp form/* main/form/* Как правильно поступать в таком случае ? PS: включил подсказку форума changelog программы Table ordering is implemented. It turns by clicking on the head of the column, thanks to rony from Russian javascript.ru/forum; https://github.com/karaul/fitalyser/...r/CHANGELOG.md |
вроде здесь написанo, сервер игнорирует
https://en.wikipedia.org/wiki/Direct...aversal_attack Directory traversal in its simplest form uses the ../ pattern. |
Цитата:
Что бы обращаться к нему /form/intex.html Можно также иметь один каталог /form но настраивать к нему разные алиасы. Не знаю, каким сервером вы пользуетесь, но в аппаче это делается сравнительно легко Например у меня есть сейчас 2 проекта Они находятся (винда) d:\works\catalog d:\works\graph есть общая библиотека (js) d:\works\libjs В конфиге аппача пишу Alias /catalog/libjs "d:/works/libjs" Alias /catalog "d:/works/catalog" Alias /graph/libjs "d:/works/libjs" Alias /graph "d:/works/graph" Библиотека одна Из catalog я обращаюсь к ней ./libjs/ так же из /graph - ./libjs/ |
voraa,
Я сам слепил сервер из десятка команд по мануалу nodejs. Временным решением был костыль GOUP в имени файлы, котрый я менял его в сервере непосредственно перед чтением файла pathname = pathname.replace(/GOUP/g, '/../'); Cпасибо за волшебное слово аlias - теперь понятно как делать правильно |
Новый улучшайзинг. Чего-то фундаментального не знаю.
Добавлена кнопка загрузки данных с внешнего независимого сайта - это элементы URL и download по ссылке https://karaul.github.io/fitplotter/ Исполняется такой код (схематично) fileUrl = "https://connect.garmin.com/proxy/download-service/files/activity/"; // id = xxxxxxxx mist be provided bу user var downloadUrl = fileUrl + id; window.location.href = downloadUrl; // now there appears save as dialog Приходит двоичный файл и на его прием появляется системный диалог save as. Я файл сохраняю, а потом открываю его как локальный. Всё работает. Как перехватить бинарные данные посланные в этот диалог save as и сразу пустить эти данные в обработку, чтобы не сохранять файл и потом не открывать его заново ? Как сделать так тобы диалог save as не открывать, но данные получить Я попробовал заменить window.location.href = downloadUrl который приводит к диалогу на XMLHttpRequest типа (сроки 13-25 чтобы создать свой диалог) let id = document.getElementById('downloadURL').value.split("/").slice(-1); let downloadUrl = fileUrl + id; //console.log(downloadUrl); // to download the current id //window.location.href = downloadUrl; let xhr = new XMLHttpRequest(); xhr.onload = function (e) { if (this.readyState === 4) { if (this.status === 200) { const blob = new Uint8Array(this.response); //parseBLOB(blob); // [url]https://stackoverflow.com/questions/22724070/prompt-file-download-with-xmlhttprequest[/url] let fileName = 'test.zip'; window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem; window.requestFileSystem(window.TEMPORARY, 1024 * 1024, function (fs) { fs.root.getFile(fileName, { create: true }, function (fileEntry) { fileEntry.createWriter(function (fileWriter) { fileWriter.addEventListener("writeend", function () { window.location = fileEntry.toURL(); }, false); fileWriter.write(blob, "_blank"); }, function () { }); }, function () { }); }, function () { }); } if (this.status === 404) { const filename = document.getElementById("files").value; errorNoFile("status 404",filename, 1); } } }; и оказалось что так нельзя index.html:1 Access to XMLHttpRequest at 'https://connect.garmin.com/proxy/download-service/files/activity/6277409729' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Какие еще способы, как получить данные ? Эти данные сайт мне посылает сразу же, когда я просто ввожу в адрес сайта downloadUrl - появляется диалог - но как их перехватить в программе ? Спасибо |
Надо чтото вроде
xhr.setRequestHeader("X-CSRFToken", csrftoken);но где взять этот csrftoken ? С другой стороны, если внешний сайт мне посыоает данные, csrftoken уже есть где-то |
Я думаю не получится считать файл, если страница берется с локального компьютера используя XMLHttpRequest или fetch.
Там идет проверка протоколов по которым получена страница. А если она локальная (from origin 'null'), то у нее нет протокола и он никогда не будет действительным и подходящим . То, что она считывается браузером при указании в адресной строке - это совсем другое. Там таких проверок нет. Также как и при считывание с помощью <script> |
Спасибо за ответ. Он укрепил мою уверенность что я рою в верном направлении
В итоге нарыл пакет на питоне для соединения с гарминовским сайтом и закачки файлов - т.е. пакет делает то что мне надо. Для закачки он использует тот же URL https://github.com/petergardfjall/garminexport при запуске пакета сначала идет авторизация, потом там есть код типа def _get_csrf_token(self): """Retrieves a Cross-Site Request Forgery (CSRF) token from Garmin's login page. The token is passed along in the login form for increased security.""" log.info("fetching CSRF token ...") resp = self.session.get(SSO_LOGIN_URL, params=self._auth_params()) if resp.status_code != 200: raise ValueError("auth failure: could not load {}".format(SSO_LOGIN_URL)) # extract CSRF token csrf_token = re.search(r'<input type="hidden" name="_csrf" value="(\w+)"', resp.content.decode('utf-8')) if not csrf_token: raise ValueError("auth failure: no CSRF token in {}".format(SSO_LOGIN_URL)) return csrf_token.group(1) |
Питон делает из своей консоли для авторизации на внешнем сайте Гармине так
headers = {'origin': 'https://sso.garmin.com'}и там все работает Если я имитирую это поведение в JS с XMLHttpRequest xhr.setRequestHeader('Origin', 'https://sso.garmin.com');то получаю Refused to set unsafe header OriginПишут что подменить заголовоr мешает броузер (хром). Незадача Но есть еще один пакет для работы с Гармином на JS, в нем для авторизации используется axios вместо XMLHttpRequest и этот пакет работает из nodejs, и там также прописано в 'Origin', 'https://sso.garmin.com', иначе Гарминовский сайт возвращает пустую строку, а должен вернуть Cross-Site Request Forgery (CSRF) token Если переписать браузерный код с XMLHttpRequest на axios, то получится выставить headers нужные для авторизации или это безнадежно? |
Цитата:
На самом деле МОЖНО! Все дело в заголовках, которые посылает сервер Вот такой простецкий сервер на nodejs const http = require('http'); const hostname = '127.0.0.1'; const port = 3000; const server = http.createServer((req, res) => { res.statusCode = 200; res.setHeader('Content-Type', 'text/plain'); res.setHeader('Access-Control-Allow-Origin', '*'); // !important res.setHeader('Access-Control-Allow-Headers', 'origin, content-type, accept'); // !important res.end('Hello World with CORS!'); }); server.listen(port, hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); И вот такой html файл, расположенный локально <!DOCTYPE html> <html> <head> <meta http-equiv="Content-type" content="text/html; charset=utf-8" lang="ru"> <meta name="viewport" content="width=device-width, initial-scale=1.0" > <title>TESTE</title> </head> <body id="body"> <button type='button' id='bsend'>Send</button> <br> <div id='answ'> </div> </div> <script> document.getElementById('bsend').addEventListener('click', async () => { try { const resp = await fetch('http://127.0.0.1:3000', {mode:'cors'}) let str = await resp.text(); document.getElementById('answ').innerHTML = str console.log(str) }catch (err){ document.getElementById('answ').innerHTML=`Error ${err.name} ${err.code} ${err.message}`; } } ); </script> </body> </html> Совершенно спокойно взаимодействуют. |
Забанили
Garmin Express Error 1006 and 1015 After attempting multiple times to log in to Garmin Express, you may receive the following errors: "Error 1015 error RAY ID 58c1fd3vaa9dd314. You are being rate limited. The owner of this website has banned you temporarily from accessing this website". "Error 1006 Ray ID 5c7fda4d8a9a0d12. Access denied. The owner of this website (support.garmin.com) has banned your IP address (**.**.***.***). This is the expected behavior and is displayed when too many attempts have been made to log in. The account will be unblocked after 24 hours automatically. Please wait 24 hours before trying to sign in again. не так то просто отладить код при общении с чужим сайтом |
Меня разбанили и я кажется разобрался как правильно надо поступать.
Идея была в том чтобы прикрутить fitplotter к внешнему независимому сайту и скачивать оттда тренировки для анализа. Этот сайт требует аторизациии, считается что каждый пользователь эту автризацию проходит сам. Если просто скачивать по одному файлу с сайта, то все работает, и каждый раз юзер общается с диалогом save as - куча ненужных кликов. Если пытаться скачивать автоматически из браузера, то нельзя подменить заголовок и ничего не получается Решение такое. Ставить свой сервер, на нем предварительно делать авторизацию на чужом сервере. Клиентская программа должна сначала обращаться к своему серверу, на котором прошла авторизация, и уже с него идти за файлами к чужому серверу. Если чужой сервер такую практику приветствует, то он публикует свой API для доступа. Так сделала strava (и получается что если бы работал с ней, то я бы ломился в открытую дверь.) А Garmin такую практику осуждает и на гитхабе программисты матом исходят пытаясь разобраться что там Garmin придумал чтобы затруднить программную авторизацию. У меня есть отдельные куски для сервера и клиента, надо ли их сшивать - это теперь больше политический вопрос чем технический А если работать со strava, то она не выдает сырые файлы, из-за которых собственно весь проект начался. И ходят слухи что Garmin якобы откроет свой АПИ. Конечно, такие вещи надо знать до а не после. Неделя головоломки - это я дешево отлелался |
Цитата:
Есть некий адрес, который становится доступным после авторизации. Допустим, я авторизовался как человек через окно браузера. До авторизации мне этот адрес недоступен, после я могу открыть его и прочитать содержимое страницы глазами и вручную скопировать, сохранить как файл и проч. Но всё вручную, автоматизировать через браузер не получается, для этого нужны серверные команды. А если делать через сервер, то надо сначала делать авторизацию через сервер, и все команды для автоматического чтения файла посылать через этот сервер |
Часовой пояс GMT +3, время: 05:33. |