ДР

24 августа, 2010 · Posted in Без рубрики · 4 комментария 

ДР моей страны. Всех поздравляю.

UPD: Обновился youtube-плагин для дешевых медиатанков.  Учтено изменение отдачи контента и немного упрощен код. Доступен там же:  Youtube Lite Reloaded.

Промо-код для GoDaddy, окончание 12.09.2010

gda835p -30% для .com доменов

Simple Machines Forum кросс-пост и авторизация.

18 августа, 2010 · Posted in Без рубрики · 2 комментария 

Захотелось мне добавить на сайт http://maps.ck.ua систему комментариев. Да не простую, а золотую необычную. Т.е. чтобы можно было постить и просматривать комментарии и с сайта и с более удобного для просмотра большого количества тем и текста — с форума.

В качестве форума был выбран любимый SMF, 2 версии. Почему любимый — потому что симпатичный дизайн, очень, очень удобная админка и просто хорошая скорость работы.

Официального API для SMF 2.х еще нету, поэтому пришлось использовать чужие наработки в этом плане, плюс немного фантазии и чтения кода форума.

Приведенные ниже строчки фактически являются хардкодингом. Поэтому если вас воротит от этого слова, этот пост можно пропускать.

Итак, авторизация:

function login($_user,$_password ) {
require_once ("...../forum/SSI.php");
require_once("...../forum/Sources/Subs-Auth.php");
 $db_prefix="ИМЯ_БД.ПРЕФИКС_ТАБЛИЦ;

 $request = mysql_query("
 SELECT ID_MEMBER
 FROM {$db_prefix}members
 WHERE member_name = '$_user'
 LIMIT 1");

 if ($request == false && mysql_num_rows($request) < 1)
 {
 mysql_free_result($request);
 setLoginCookie(-3600, 0);
 return "no user name";
 }

 $get_password_hash = mysql_query ("
 SELECT passwd, ID_MEMBER, ID_GROUP, lngfile, is_activated, email_address, additional_groups, member_name, password_salt
 FROM {$db_prefix}members
 WHERE member_name = '$_user'
 LIMIT 1");

 $user_settings = mysql_fetch_assoc($get_password_hash);
 $hash_input_password = sha1(strtolower($user_settings['member_name']) . un_htmlspecialchars(stripslashes($_password)));

 if ($user_settings['passwd']==$hash_input_password)
 {
 $cookielength = 3153600;
 setLoginCookie( $cookielength, $user_settings['ID_MEMBER'], sha1($user_settings['passwd'] . $user_settings['password_salt']));

 } else{
 setLoginCookie(-3600, 0);
 return "no user";
 }

 return "ok";
}

Аутентификация, загрузка из куки.

function smf_authenticateUser($mode="all"){
require_once ("...../forum/SSI.php");
require_once("...../forum//Sources/Subs-Auth.php");
 global $smf_connection, $smf_settings, $smf_user_info;
 $ID_MEMBER = 0;
 if (isset($_COOKIE[$cookiename]))
 {
 $_COOKIE[$cookiename] = stripslashes($_COOKIE[$cookiename]);
 // Fix a security hole in PHP 4.3.9 and below...
 if (preg_match('~^a:[34]:\{i:0;(i:\d{1,6}|s:[1-8]:"\d{1,8}");i:1;s:(0|40):"([a-fA-F0-9]{40})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~',$_COOKIE[$cookiename]) == 1)
 {
 list ($ID_MEMBER, $password) = @unserialize($_COOKIE[$cookiename]);
 $ID_MEMBER = !empty($ID_MEMBER) && strlen($password) > 0 ? (int) $ID_MEMBER : 0;
 } else return "no cookie";
 } else return "no cookie2";
 //echo $ID_MEMBER;
 // Don't even bother if they have no authentication data.
 if ($ID_MEMBER != 0){
 $request = mysql_query("
 SELECT *
 FROM {$db_prefix}members
 WHERE ID_MEMBER = $ID_MEMBER
 LIMIT 1");
 // Did we find 'im?  If not, junk it.
 if (mysql_num_rows($request) != 0)
 {
 // The base settings array.
 $smf_user_info = mysql_fetch_assoc($request);
 if (strlen($password) == 40)
 $check = sha1($smf_user_info['passwd'] . $smf_user_info['password_salt']) == $password;
 else
 $check = false;

 // Wrong password or not activated - either way, you're going nowhere.
 $ID_MEMBER = $check && ($smf_user_info['is_activated'] == 1 || $smf_user_info['is_activated'] == 11) ? $smf_user_info['id_member'] : 0;
 } else {
 $ID_MEMBER = 0;
 mysql_free_result($request);
 return "no user in mysql";
 }
 } else {
 return "no cookie";
 }
 if(!empty($ID_MEMBER)){
 $cookielength = 3153600;
 setLoginCookie( $cookielength, $smf_user_info['id_member'], sha1($smf_user_info['passwd'] . $smf_user_info['password_salt']));
 } else return "no member";

 // A few things to make life easier...
 $smf_user_info['id'] = &$smf_user_info['id_member'];
 $smf_user_info['username'] = &$smf_user_info['member_name'];
 $smf_user_info['name'] = &$smf_user_info['real_name'];
 $smf_user_info['email'] = &$smf_user_info['email_address'];
 $smf_user_info['messages'] = &$smf_user_info['instant_messages'];
 $smf_user_info['unread_messages'] = &$smf_user_info['unread_messages'];
 $smf_user_info['is_guest'] = $ID_MEMBER == 0;
 $smf_user_info['is_admin'] = in_array(1, $smf_user_info['id_group']);
 $smf_user_info['status']="ok";
 if (empty($smf_user_info['time_format']))
 $smf_user_info['time_format'] = $smf_settings['time_format'];

 if($mode=="all") return $smf_user_info;
 if($mode=="check") return "ok";
 if($mode=="json") return json_encode($smf_user_info);
 if($mode=="id")  return  $smf_user_info['id'];
}

Постинг в форум. Если topic пустой или равен 0, создается новая тема.

function post_to_smf($board,$topic,$subject,$txt,$oid,$otype,$maptype) {
 $ID_MEMBER = smf_authenticateUser("id");
 if($ID_MEMBER==0 or !is_numeric($ID_MEMBER)) return "wrong poster";
 require_once("......../forum/SSI.php");
 require_once("......../forum/Sources/Subs-Post.php");
 global $smcFunc;
 $TOPIC=$topic;
 $ID_BOARD=$board;

 $excerpt = stripcslashes(trim(strip_tags($txt)));
 $words = preg_split("/(?<=(\.|!|\?)+)\s/", $excerpt, -1, PREG_SPLIT_NO_EMPTY);
 foreach ( $words as $word )
 {
 $new_text = $text . substr($excerpt, 0, strpos($excerpt, $word) + strlen($word));
 $excerpt = substr($excerpt, strpos($excerpt, $word) + strlen($word), strlen($excerpt));

 if ( ( strlen($text) != 0 ) && ( strlen($new_text) > $max_length ) )
 {
 break;
 }

 $text = $new_text;
 }
 $post_text = $text;

 $msgOptions = array(
 'id' =>  0,
 'subject' => stripcslashes($subject),
 'body' => $post_text,
 'smileys_enabled' => true,
 );

 $topicOptions = array(
 'id' => $TOPIC,
 'board' => $ID_BOARD,
 'mark_as_read' => true,
 );

 $posterOptions = array(
 'id' => $ID_MEMBER,
 );

 $out=Array();

 $top=createPost2($msgOptions, $topicOptions, $posterOptions);
 if($top) {
 $out["status"]="ok";
 $out["topicid"]=$top;

 return json_encode($out);
 } else {
 $out["status"]="error create post";
 return json_encode($out);
 }
}

function createPost2(&$msgOptions, &$topicOptions, &$posterOptions)
{
 global $user_info, $txt, $modSettings, $smcFunc, $context;

 // Set optional parameters to the default value.
 $msgOptions['icon'] = empty($msgOptions['icon']) ? 'xx' : $msgOptions['icon'];
 $msgOptions['smileys_enabled'] = !empty($msgOptions['smileys_enabled']);
 $msgOptions['attachments'] = empty($msgOptions['attachments']) ? array() : $msgOptions['attachments'];
 $msgOptions['approved'] = isset($msgOptions['approved']) ? (int) $msgOptions['approved'] : 1;
 $topicOptions['id'] = empty($topicOptions['id']) ? 0 : (int) $topicOptions['id'];
 $topicOptions['poll'] = isset($topicOptions['poll']) ? (int) $topicOptions['poll'] : null;
 $topicOptions['lock_mode'] = isset($topicOptions['lock_mode']) ? $topicOptions['lock_mode'] : null;
 $topicOptions['sticky_mode'] = isset($topicOptions['sticky_mode']) ? $topicOptions['sticky_mode'] : null;
 $posterOptions['id'] = empty($posterOptions['id']) ? 0 : (int) $posterOptions['id'];
 $posterOptions['ip'] = empty($posterOptions['ip']) ? $user_info['ip'] : $posterOptions['ip'];

 $smcFunc['db_query']('', 'SET NAMES UTF8',null);
 // We need to know if the topic is approved. If we're told that's great - if not find out.
 if (!$modSettings['postmod_active'])
 $topicOptions['is_approved'] = true;
 elseif (!empty($topicOptions['id']) && !isset($topicOptions['is_approved']))
 {
 $request = $smcFunc['db_query']('', '
 SELECT approved
 FROM {db_prefix}topics
 WHERE id_topic = {int:id_topic}
 LIMIT 1',
 array(
 'id_topic' => $topicOptions['id'],
 )
 );
 list ($topicOptions['is_approved']) = $smcFunc['db_fetch_row']($request);
 $smcFunc['db_free_result']($request);
 }

 // If nothing was filled in as name/e-mail address, try the member table.
 if (!isset($posterOptions['name']) || $posterOptions['name'] == '' || (empty($posterOptions['email']) && !empty($posterOptions['id'])))
 {
 if (empty($posterOptions['id']))
 {
 $posterOptions['id'] = 0;
 $posterOptions['name'] = $txt['guest_title'];
 $posterOptions['email'] = '';
 }
 elseif ($posterOptions['id'] != $user_info['id'])
 {
 $request = $smcFunc['db_query']('', '
 SELECT member_name, email_address
 FROM {db_prefix}members
 WHERE id_member = {int:id_member}
 LIMIT 1',
 array(
 'id_member' => $posterOptions['id'],
 )
 );
 // Couldn't find the current poster?
 if ($smcFunc['db_num_rows']($request) == 0)
 {
 trigger_error('createPost(): Invalid member id ' . $posterOptions['id'], E_USER_NOTICE);
 $posterOptions['id'] = 0;
 $posterOptions['name'] = $txt['guest_title'];
 $posterOptions['email'] = '';
 }
 else
 list ($posterOptions['name'], $posterOptions['email']) = $smcFunc['db_fetch_row']($request);
 $smcFunc['db_free_result']($request);
 }
 else
 {
 $posterOptions['name'] = $user_info['name'];
 $posterOptions['email'] = $user_info['email'];
 }
 }

 // It's do or die time: forget any user aborts!
 $previous_ignore_user_abort = ignore_user_abort(true);

 $new_topic = empty($topicOptions['id']);

 // Insert the post.
 $smcFunc['db_insert']('',
 '{db_prefix}messages',
 array(
 'id_board' => 'int', 'id_topic' => 'int', 'id_member' => 'int', 'subject' => 'string-255', 'body' => (!empty($modSettings['max_messageLength']) && $modSettings['max_messageLength'] > 65534 ? 'string-' . $modSettings['max_messageLength'] : 'string-65534'),
 'poster_name' => 'string-255', 'poster_email' => 'string-255', 'poster_time' => 'int', 'poster_ip' => 'string-255',
 'smileys_enabled' => 'int', 'modified_name' => 'string', 'icon' => 'string-16', 'approved' => 'int',
 ),
 array(
 $topicOptions['board'], $topicOptions['id'], $posterOptions['id'], $msgOptions['subject'], $msgOptions['body'],
 $posterOptions['name'], $posterOptions['email'], time(), $posterOptions['ip'],
 $msgOptions['smileys_enabled'] ? 1 : 0, '', $msgOptions['icon'], $msgOptions['approved'],
 ),
 array('id_msg')
 );
 $msgOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}messages', 'id_msg');

 // Something went wrong creating the message...
 if (empty($msgOptions['id']))
 return false;

 // Fix the attachments.
 if (!empty($msgOptions['attachments']))
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}attachments
 SET id_msg = {int:id_msg}
 WHERE id_attach IN ({array_int:attachment_list})',
 array(
 'attachment_list' => $msgOptions['attachments'],
 'id_msg' => $msgOptions['id'],
 )
 );

 // Insert a new topic (if the topicID was left empty.
 if ($new_topic)
 {
 $smcFunc['db_insert']('',
 '{db_prefix}topics',
 array(
 'id_board' => 'int', 'id_member_started' => 'int', 'id_member_updated' => 'int', 'id_first_msg' => 'int',
 'id_last_msg' => 'int', 'locked' => 'int', 'is_sticky' => 'int', 'num_views' => 'int',
 'id_poll' => 'int', 'unapproved_posts' => 'int', 'approved' => 'int',
 ),
 array(
 $topicOptions['board'], $posterOptions['id'], $posterOptions['id'], $msgOptions['id'],
 $msgOptions['id'], $topicOptions['lock_mode'] === null ? 0 : $topicOptions['lock_mode'], $topicOptions['sticky_mode'] === null ? 0 : $topicOptions['sticky_mode'], 0,
 $topicOptions['poll'] === null ? 0 : $topicOptions['poll'], $msgOptions['approved'] ? 0 : 1, $msgOptions['approved'],
 ),
 array('id_topic')
 );
 $topicOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}topics', 'id_topic');

 // The topic couldn't be created for some reason.
 if (empty($topicOptions['id']))
 {
 // We should delete the post that did work, though...
 $smcFunc['db_query']('', '
 DELETE FROM {db_prefix}messages
 WHERE id_msg = {int:id_msg}',
 array(
 'id_msg' => $msgOptions['id'],
 )
 );

 return false;
 }

 // Fix the message with the topic.
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}messages
 SET id_topic = {int:id_topic}
 WHERE id_msg = {int:id_msg}',
 array(
 'id_topic' => $topicOptions['id'],
 'id_msg' => $msgOptions['id'],
 )
 );

 // There's been a new topic AND a new post today.
 trackStats(array('topics' => '+', 'posts' => '+'));

 updateStats('topic', true);
 updateStats('subject', $topicOptions['id'], $msgOptions['subject']);
 //What if we want to export new topics out to a CMS?
 if (isset($modSettings['integrate_create_topic']) && is_callable($modSettings['integrate_create_topic']))
 call_user_func(strpos($modSettings['integrate_create_topic'], '::') === false ? $modSettings['integrate_create_topic'] : explode('::', $modSettings['integrate_create_topic']), $msgOptions, $topicOptions, $posterOptions);
 }
 // The topic already exists, it only needs a little updating.
 else
 {
 $countChange = $msgOptions['approved'] ? 'num_replies = num_replies + 1' : 'unapproved_posts = unapproved_posts + 1';

 // Update the number of replies and the lock/sticky status.
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}topics
 SET
 ' . ($msgOptions['approved'] ? 'id_member_updated = {int:poster_id}, id_last_msg = {int:id_msg},' : '') . '
 ' . $countChange . ($topicOptions['lock_mode'] === null ? '' : ',
 locked = {int:locked}') . ($topicOptions['sticky_mode'] === null ? '' : ',
 is_sticky = {int:is_sticky}') . '
 WHERE id_topic = {int:id_topic}',
 array(
 'poster_id' => $posterOptions['id'],
 'id_msg' => $msgOptions['id'],
 'locked' => $topicOptions['lock_mode'],
 'is_sticky' => $topicOptions['sticky_mode'],
 'id_topic' => $topicOptions['id'],
 )
 );

 // One new post has been added today.
 trackStats(array('posts' => '+'));
 }

 // Creating is modifying...in a way.
 //!!! Why not set id_msg_modified on the insert?
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}messages
 SET id_msg_modified = {int:id_msg}
 WHERE id_msg = {int:id_msg}',
 array(
 'id_msg' => $msgOptions['id'],
 )
 );

 // Increase the number of posts and topics on the board.
 if ($msgOptions['approved'])
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}boards
 SET num_posts = num_posts + 1' . ($new_topic ? ', num_topics = num_topics + 1' : '') . '
 WHERE id_board = {int:id_board}',
 array(
 'id_board' => $topicOptions['board'],
 )
 );
 else
 {
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}boards
 SET unapproved_posts = unapproved_posts + 1' . ($new_topic ? ', unapproved_topics = unapproved_topics + 1' : '') . '
 WHERE id_board = {int:id_board}',
 array(
 'id_board' => $topicOptions['board'],
 )
 );

 // Add to the approval queue too.
 $smcFunc['db_insert']('',
 '{db_prefix}approval_queue',
 array(
 'id_msg' => 'int',
 ),
 array(
 $msgOptions['id'],
 ),
 array()
 );
 }

 // Mark inserted topic as read (only for the user calling this function).
 if (!empty($topicOptions['mark_as_read']) && !$user_info['is_guest'])
 {
 // Since it's likely they *read* it before replying, let's try an UPDATE first.
 if (!$new_topic)
 {
 $smcFunc['db_query']('', '
 UPDATE {db_prefix}log_topics
 SET id_msg = {int:id_msg}
 WHERE id_member = {int:current_member}
 AND id_topic = {int:id_topic}',
 array(
 'current_member' => $posterOptions['id'],
 'id_msg' => $msgOptions['id'],
 'id_topic' => $topicOptions['id'],
 )
 );

 $flag = $smcFunc['db_affected_rows']() != 0;
 }

 if (empty($flag))
 {
 $smcFunc['db_insert']('ignore',
 '{db_prefix}log_topics',
 array('id_topic' => 'int', 'id_member' => 'int', 'id_msg' => 'int'),
 array($topicOptions['id'], $posterOptions['id'], $msgOptions['id']),
 array('id_topic', 'id_member')
 );
 }
 }

 // If there's a custom search index, it needs updating...
 if (!empty($modSettings['search_custom_index_config']))
 {
 $customIndexSettings = unserialize($modSettings['search_custom_index_config']);

 $inserts = array();
 foreach (text2words($msgOptions['body'], $customIndexSettings['bytes_per_word'], true) as $word)
 $inserts[] = array($word, $msgOptions['id']);

 if (!empty($inserts))
 $smcFunc['db_insert']('ignore',
 '{db_prefix}log_search_words',
 array('id_word' => 'int', 'id_msg' => 'int'),
 $inserts,
 array('id_word', 'id_msg')
 );
 }

 // Increase the post counter for the user that created the post.
 if (!empty($posterOptions['update_post_count']) && !empty($posterOptions['id']) && $msgOptions['approved'])
 {
 // Are you the one that happened to create this post?
 if ($user_info['id'] == $posterOptions['id'])
 $user_info['posts']++;
 updateMemberData($posterOptions['id'], array('posts' => '+'));
 }

 // They've posted, so they can make the view count go up one if they really want. (this is to keep views >= replies...)
 $_SESSION['last_read_topic'] = 0;

 // Better safe than sorry.
 if (isset($_SESSION['topicseen_cache'][$topicOptions['board']]))
 $_SESSION['topicseen_cache'][$topicOptions['board']]--;

 // Update all the stats so everyone knows about this new topic and message.
 updateStats('message', true, $msgOptions['id']);

 // Update the last message on the board assuming it's approved AND the topic is.
 if ($msgOptions['approved'])
 updateLastMessages($topicOptions['board'], $new_topic || !empty($topicOptions['is_approved']) ? $msgOptions['id'] : 0);

 // Alright, done now... we can abort now, I guess... at least this much is done.
 ignore_user_abort($previous_ignore_user_abort);

 // Success.
 return $topicOptions['id'];
}

тут в строчке

$ID_MEMBER = smf_authenticateUser("id");

ждет сюрпиз. Если постит не администратор, функция возвращает ошибку.
Для исправления придется лезть в файл forum/SSI.php и выше строчки

loadPermissions();

добавлять

loadBoard();

Гнусный хардкодинг :\

В работе использованы куски кода моста Mambo-SMF, куски кода самого SMF, документация к форуму.

Тук-тук

10 августа, 2010 · Posted in Без рубрики · 2 комментария 

Сегодня позвонили из «Визикома«. Девушка с хорошо поставленным голосом поинтересовалась, каким образом я использую их сервер.

Врать я не стал, да и нет смысла. Тем более, что в ФАК сайта map.ck.ua указано,  что я использую сервис от Визикома. Она сказала, что запросы на их сервер идут, а где виден их результат, неясно.

Пришлось провести маленькую презентацию сайта и объяснять, что Визиком (посредством XML-апи) используется для прокладки маршрутов и для поиска адресов, а результат выводится на Яндекс.Карты. Заодно упрекнул визикомовские карты в однообразии и плохой документированности АПИ.

В целом возражений не было, она только выразила пожелание, чтобы логотип Визикома более четко отображался в сервисах, в которых он участвует. После мне пожелали успехов и попрощались. Приятно!

Что характерно: Есть подобная возможность и в использовании серверов Гугля. Однако в пользовательском соглашении Google API четко указано, что вывод результатов должен производиться только на картах Гугла.

Путешествие в Буки.

1 августа, 2010 · Posted in Без рубрики · 4 комментария 

Прошла почти неделя, как мы вернулись из небольшого отпуска: ездили в Буки (или, как говорят местные жители, Букы, ударение на ы).

Посетить это чудесное место я и жена планировали еще прошлым летом, но поехали на фестиваль «Свирж». В этом году звезды сошлись и мы запланировали поездку.

Мы рассчитывали выехать в пятницу утром на поезде, доехать до станции «Поташ» и оттуда, на велосипедах, доехать до Бук.

В пятницу, в 6:40 мы были на вокзале и пошли покупать билеты. Но…у нас часто бывает не так, как того хотим. Самое стабильное в этом плане — это практически каждый поход на пляж сопровождается дождем. Ну и других веселых вещей хватает.

В этот раз нам «повезло»: ремонтировали дорогу. Ближайший поезд был вечером, ждать нам не хотелось.
Поехали на автовокзал, на котором рассчитывали сесть в автобус Черкассы-Винница. У него большое багажное отделение и туда влезли бы наши велосипеды. Да, вполне ожидаемо, нас ждало известие о том, что автобус поломался :)

Решили поехать «автостопом». Проторчав час у дороги, поехали в Смелу, там более верно можно словить нужный транспорт. Через полтора часа уже махали рукой на Смелянском кольце.

Примерно час ожидания и нас подобрала Газелька, которая ехала до Шполы. Водитель, как это часто бывает, оказался умным и разговорчивым. Примерно 40км мы ехали не скучая, а велосипеды звонко побрякивали в кузове.
В Шполе мы немного поели и покрутили педали в сторону Звенигородки, периодически останавливаясь помахать рукой. Не везло, да и транспорта было мало.

Преодолев множество затяжных подъемов и основательно вспотев, остановились у Богачевки, перевести дух и искупаться в озере. После купания, только успев одеться, заметили грузовую Газель. После недолгих разговоров, водитель сжалился над нами и согласился довезти до Звенигородки.
Как оказалось, нам очень повезло. Эти недолгие 15 километров были насыщенны такими серьезными подъемами и спусками, что у нас этот путь занял бы часа 3, а то и больше. Высадили нас на окружной у поворота на Умань. Катимся на велосипедах дальше.

Едем через Водяники (те самые, в которых наш бывший губернатор забабахал горнолыжный курорт). Дорога совершенно пустынная, поэтому почти не останавливались. Делали только привалы попить воды. Отмахали километров 30, часам к восьми вечера решили остановиться и продолжить путь на следующий день.

Поставили палатку в заброшенном саду, как оказалось потом, примерно в паре километров от села Багва. Достаточно мирно поели, если не считать грома от лопнувшей покрышки проезжавшего мимо камаза с прицепом( все обошлось, покрышка лопнула на прицепе и скорость была очень маленькой, он как раз собирался поворачивать ), почитали книжки и легли спать.

Утром, часов в восемь выехали. По утренней прохладе дорога казалась ровнее и даже ветер был попутным. Спустя час-полтора увидели Буки.

Буки — знамениты своим каньоном. Конечно, это не Гранд-Каньон реки Колорадо, но для здешних мест очень прилично. Очень красивые берега, речка Горный Тикич, уточки и замечательные облака. Место можно было бы назвать живописным, если вычесть из него людей и их деятельность.

К сожалению, интернетом пользуются не только люди, у которых есть совесть. И эти самые люди, увидев в интернете красивые фотографии Букинского Каньона, едут сюда отдохнуть, оставляя после себя горы мусора среди живописной природы. Мы видим, что происходит в Крыму, где даже самые дикие на вид местности усеяны бутылками, пакетами прочей дрянью, привозимой туристами. Очень боюсь, что если не ввести жесткие меры, подобное будет и в Буках, где и так грязно и во всех других популярных местах.

Но не будем о грустном. Немного порыскав, нашли относительно не замусоренное место, и разбили там лагерь, куда вскоре подъехали наши друзья, которые добрались до Бук на утреннем автобусе.

Рассказывать купании речке и готовке пищи на костре нет смысла, это просто нужно делать :) Скажу лишь, что мы ходили с экскурсией к местым скалам, полежали под гидромассажными струями воды на порогах, попили воды из источника у переправы, исследовали недостроенную ГЭС, прошлись по обоим красивым берегам этой речки. Фото прилагаются ниже.

Друзья уехали на следующий день после обеда, мы же остались до вторника. Во вторник, без особых приключений, если не считать пару километровых подьемов, операции по спасению котенка, которого нашли в паре километров от ближайшего села (куда и доставили его), добрались до железнодорожной станции Поташ. Через 15 минут подошел поезд и спустя пять часов мы были в Черкассах.

Вот приблизительная схема нашего маршрута, дорога автомобилем не указана:


Подробнее: http://map.ck.ua/#umid=4SR5cb5

А вот немного фото (фотографировала Лена, моя жена):

Букинский каньон, начало

Букинский каньон, начало

Скала

Скала, начало экскурсии

Покорение скал

Мы с Жекой на скале

У источника

Почти кадр из сериала «Лост»

Пороги

Пороги и здание ГЭС

Немного меня

Гидромассаж на порогах

В заключении хочу сказать, что это первая долгая поездка желто-черного велосипеда, которую он выдержал с честью :)