bytebuster: (ITCrowd-Jen)
[personal profile] bytebuster
Ура, %SUBJ%, котани! Налітай!

Скрипт працює так:

  1. Додає от такий лінк на сторінку допису:
  2. По кліку на посилання відкриває http://www.dreamwidth.org/entry/new у новому вікні (target="_blank") та заповнює два поля: Subject та Body.
    Оновлено: з версії 0.8 вміє заповнювати також To-Journal, Icon, та Tags.
    Причому можна налаштовувати «шаблони» для перепосту: наприклад, «перепост до спільноти такої-то з юзерпикою такою-то і наперед прописаними тегами».

Known Bugs:

  • Не вставляється YouTube відео. Спробую побороти, але там багато возні. — Починив у v.0.3
  • Не розгортаються Cut Tags. Перед перепостом треба все руками розгортати
  • З тієї ж причини, Cut Tags у перепощеному дописі не завжди вставляються. Їх треба прописувати руками, якщо перепост робився зі сторінки конкретного допису. А якщо перепост зі сторінки журналу, то всі cut вставляються правильно.
  • Глючить, якщо пост містить <lj user="..."> — ім'я цього юзера помилково вставляється у шапку перепосту (замість імені того юзера, чий пост ви перепощуєте). — Починив у v.0.2

Зауваження:

  • Перше і найголовніше: Ніколи не встановлюйте скрипти від невідомих джерел!
    Особливо якщо ви не розумієте код.
  • Tags та To [Journal/Community] не прописує. Прописує з версії 0.8.
    Можливо, потім придумаю шаблони для перепостів, але поки що цього нема. Зроблено у версії 0.8.
  • Перевіряв на сторінках індивідуальних журналів (http://bytebuster.dreamwidth.org/), комун (http://bitter-onion.dreamwidth.org/) та індивідуальних дописів (http://bitter-onion.dreamwidth.org/1139072.html).
  • Перевіряв лише на Firefox. У кого Chrome, перевірте хто-небудь на Tampermonkey, га? Я не упевнений, що там працюють функції, специфічні для GM.
  • Багрепорти приймаються, але я не глибоко дружу з JavaScript, тому не гарантія, що я зможу вилікувати усі можливі проблеми.

Встановлення

  • Ставимо собі Greasemonkey;
  • Перезапускаємо Firefox;
  • Повертаємося до цього допису, виділяємо все тіло скрипта і копіюємо до Clipboard;
  • Тиць в кнопку Greasemonkey на Toolbar, тиць New User Script…;
  • У новому віконці внизу кнопка Use Script from Clipboard;
  • Налаштувати свої шаблони у секції:
    // **** SETTINGS ****************************************************
  • Зберегти;
  • Відкрити будь-яку сторінку на DreamWidth.
Сам скрипт залитий на BitBucket (завжди нова версія), а також (увага, стара версія!) під катом:

// ==UserScript==
// @name        DreamWidth Reposter
// @namespace   bytebuster.dreamwidth.org
// @description Adds LJ-like Repost functionality for DreamWidth
// @include     http://*.dreamwidth.org/*
// @exclude     http://*.dreamwidth.org/profile*
// @exclude     http://*.dreamwidth.org/calendar*
// @exclude     http://*.dreamwidth.org/tag/
// @version     0.3
// @grant       GM_log
// @grant       GM_getValue
// @grant       GM_setValue
// @grant       GM_deleteValue
// ==/UserScript==

/* **********************************************************************************

    This script has two main functions:
    * addRepostLinks - triggers on all DreamWidth pages; adds "Repost" links to entries;
    * populatePostForm - triggers on /entry/new page; fills up the "Post Entry" fields

    Цей скрипт містить дві основні функції:
    * addRepostLinks - запускається на усіх сторінках DreamWidth pages; додає кнопку «Перепостити»;
    * populatePostForm - запускається на сторінці /entry/new page; заповнює поля вводу

********************************************************************************** */

/*
 * 0.3 fixed applied domain and URL;
       fixed Youtube video embed
       minor fix if mistakenly decided that post in community,
         while it actually is in user's own journal
 * 0.2 added group/blog references
       fix bug on other users references
 * 0.1 Initial reease
*/

(function(){
  // **** SETTINGS ****************************************************
  // Main settings
  // Головні налаштування
  const REPOST_TEXT = "Перепостити"; // Так виглядатиме текст кнопки «Репост»
  const REPOST_TEMPLATE = 'Перепост допису <lj user="{poster}">: <a href="{link}">{subj}</a>\n{body}';
  const REPOST_TEMPLATE_COMMUNITY = 'Перепост допису <lj user="{poster}"> @ <lj user="{community}">: <a href="{link}">{subj}</a>\n{body}';
  const DEBUG = false;

  // **** CONSTANTS ***************************************************
  // You don't need it unless DreamWidth changes its classes (impossible, yeah)
  // Вам тут не треба нічого міняти, допоки Дрім не поміняє імена класів (майже нереально)

  const CLS_ENTRY_TITLE   = "entry-title";
  const CLS_ENTRY_WRAPPER = "entry-wrapper";
  const CLS_ENTRY_POSTER  = "entry-poster";
  const CLS_LJUSER        = "ljuser";
  const ATTR_LJUSER       = "lj:user";
  const CLS_ENTRY_CONTENT = "entry-content";
  const CLS_ENTRY_LINKS   = "entry-interaction-links";
  const ID_INPUT_SUBJECT  = "id-subject-0";
  const ID_INPUT_BODY     = "id-event-1";
  const CLS_EMBED         = "lj_embedcontent-wrapper";

  const POST_URL = "http://www.dreamwidth.org/update";
  const POST_URL2 = "/entry/new";
  const REPOST_PARAM="repost";
  const CONTENT_DEFAULT = "";
  const CONTENT_TAG = "DW_REPOST_CONTENT"; // how we save the reposted entry in GM_setValue

  const REGEX_YOUTUBE_LINK     =  /^.*(?:youtu\.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/gmi; // /www.youtube.com\/watch?v=(.+?)/gmi;
  const EMBED_YOUTUBE_TEMPLATE = '<iframe width="640" height="360" src="https://www.youtube.com/embed/{videoID}" frameborder="0" allowfullscreen></iframe>';

// applies a parser on strings and log error if failed
function parse(parser, where, description) {
  var ret;
  match = parser.exec(where);
  if( match === null ) {
    GM_log("Failed to parse " + description + " from text: " + where);
    ret = "(null)";
  } else {
    ret = match[1];
  }
  return ret;
}

function DebugMessage( what ) {
  if(DEBUG) { GM_log(what); }
}

// checks for URL parameter
function getQueryVariable(variable) {
  var query = window.location.search.substring(1);
  var vars = query.split("&");
  for (var i=0;i<vars.length;i++) {
    var pair = vars[i].split("=");
    if (pair[0] == variable) {
      return pair[1];
    }
  }
  DebugMessage('Query Variable ' + variable + ' not found');
}

// Adds Repost links to all entries on the page
// Додає посилання «Перепост» внизу усіх дописів на сторінці
function addRepostLinks() {
  DebugMessage("Adding Repost links begin...");
  var entries = document.getElementsByClassName(CLS_ENTRY_WRAPPER);

  for (var entry_index=0; entry_index<entries.length; entry_index++) {
    addRepostLink(entries[entry_index]);
  }
  DebugMessage("Adding Repost links end");
}

// Gets the saved content for repost
function getRepostData() {
  DebugMessage("getRepostData begin");

  return GM_getValue(CONTENT_TAG, CONTENT_DEFAULT);
}
// Sets the content for repost
function setRepostData(repostData) {
  DebugMessage("setRepostData begin " + repostData);
  // delete the state if it is the default
  if( repostData == CONTENT_DEFAULT ) {
    GM_deleteValue(CONTENT_TAG);
  } else {
    // set the state otherwise
    GM_setValue(CONTENT_TAG, repostData);
  }
  DebugMessage("setRepostData end");
}

// Saves JSON data from the reposted entry
// Зберігає допис і його шапку
function rememberPost(elEntry) {
  var elPoster, elLjuser, elTitle, elContent; // elements
  var entryPoster, entryCommunity, entryLink, entrySubject, entryBody, subdomain; // strings
  var repostData; // JSON object
  DebugMessage("rememberPost begin");

  subdomain = window.location.host.split(".")[0];
  // get Poster userId
  elPoster = elEntry.getElementsByClassName(CLS_ENTRY_POSTER)[0];

  if(elPoster && elPoster.hasChildNodes()) {
    // we seem to be in a community, parse out poster name from attribute
    elLjuser = elPoster.getElementsByClassName(CLS_LJUSER)[0];
    entryPoster = elLjuser.getAttribute(ATTR_LJUSER);
    entryCommunity = subdomain;
  } else {
    // we seem to be in a journal, parse out poster name from URL (FIXME)
    entryPoster = subdomain;
    entryCommunity = "";
  }
  // Quick fix: fix if we mistakenly set community
  if(entryPoster == entryCommunity) {
    entryCommunity = "";
  }

  // get Subj and Link
  elTitle = elEntry.getElementsByClassName(CLS_ENTRY_TITLE)[0];
  entryLink    = elTitle.getElementsByTagName("a")[0].href;
  entrySubject = elTitle.textContent;

  // get Body
  elContent = elEntry.getElementsByClassName(CLS_ENTRY_CONTENT)[0]
    .cloneNode(true);

  // Apply fixes
  elContent = FixYoutube(elContent);
  elContent = FixUsers(elContent);

  entryBody = elContent.innerHTML;

  repostData =
    JSON.stringify(
      {
        "link"      : entryLink,
        "poster"    : entryPoster,
        "community" : entryCommunity,
        "subj"      : entrySubject,
        "body"      : entryBody
      }
    );

  DebugMessage("Saving JSON: " + repostData);
  setRepostData(repostData);
  DebugMessage("rememberPost end");
}

// Fixes User references
// Takes a cloned node to fix
// Returns it as well
function FixUsers(element) {
  var embeds; // list of embed objects

  DebugMessage("FixUsers begin");

  embeds = element.getElementsByClassName(CLS_LJUSER);

  for (var embed_index=0; embed_index<embeds.length; embed_index++) {
    var elLjuser, elLjUser2; // elements
    var userName; // strings

    elLjuser = embeds[embed_index];
    if(elLjuser.nodeName != "SPAN") { DebugMessage("Element frame does not look like a user reference, exiting"); continue; }

    userName = elLjuser.getAttribute(ATTR_LJUSER);

    // creating an HTML fragment to replace the broken link
    elLjUser2 = document.createElement("lj");
    elLjUser2.setAttribute("user", userName);

    // replacing the element
    element.replaceChild(elLjUser2, elLjuser)
    embed_index--;
  }
  DebugMessage("FixUsers end");
  return element;
}

// Fixes YouTube links
// Takes a cloned node to fix
// Returns it as well
function FixYoutube(element) {
  var embeds; // list of embed objects

  DebugMessage("FixYoutube begin");

  embeds = element.getElementsByClassName(CLS_EMBED);

  for (var embed_index=0; embed_index<embeds.length; embed_index++) {
    var elFrame, elLinkDiv, elLink, elRange, elFragment, elFirstNode; // elements
    var linkAddress, linkBodyNew; // strings

    elFrame = embeds[embed_index];
    if(elFrame.nodeName != "DIV") { DebugMessage("Element frame does not look like a YouTube, exiting"); continue; }

    elLinkDiv = elFrame.nextSibling;
    if(elLinkDiv === null) { DebugMessage("Failed to find YouTube link DIV, exiting"); continue; }

    elLink = elLinkDiv.getElementsByTagName("a")[0];
    if(elLink === null) { DebugMessage("YouTube link seems to be broken, exiting"); continue; }

    linkAddress = parse(REGEX_YOUTUBE_LINK, elLink.href, "YouTube Link");
    linkBodyNew = EMBED_YOUTUBE_TEMPLATE
      .replace("{videoID}", linkAddress);

    // creating an HTML fragment to replace the broken link
    elRange = document.createRange();
    elRange.selectNode(document.body); // required in Safari
    elFragment = elRange.createContextualFragment(linkBodyNew);
    elFirstNode = elFragment.firstChild;

    // replacing the element
    element.removeChild(elFrame);
    element.replaceChild(elFirstNode, elLinkDiv)
    embed_index--;
  }
  DebugMessage("FixYoutube end");
  return element;
}

// Adds Repost links to one entry
// Додає посилання «Перепост» внизу допису
function addRepostLink(entry) {
  var elFooter, elRepostLink, elRepostLinkLI; // elements
  var entryTitle, entryLink, userName, entryId;

  // check if entry is a DOM element
  if( !entry.tagName ) { return; }

  elFooter = entry.getElementsByClassName(CLS_ENTRY_LINKS)[0];
  // must be < ul >

  elRepostLink = document.createElement("a");
  elRepostLink.href = POST_URL + "?" + REPOST_PARAM +"=1";
  elRepostLink.target = "_blank";
  elRepostLink.addEventListener('click', function(event) { rememberPost(entry); /*event.stopPropagation(); event.preventDefault();*/ }, true);
  elRepostLink.text = REPOST_TEXT;
  elRepostLinkLI = document.createElement("li");
  elRepostLinkLI.appendChild(elRepostLink);
  elFooter.appendChild(elRepostLinkLI);
}

// Fills up the "Post Entry" form
// Заповнює форму «Створити допис»
function populatePostForm() {
  DebugMessage("Populate Post begin");

  var repostedUrl = getQueryVariable(REPOST_PARAM);
  if(! repostedUrl) {
    DebugMessage("Does not look like a repost, exiting");
    return;
  }

  // continue

  var repostDataString, repostData; // JSON data
  var postBody; // strings
  var elSubject, elBody; // elements

  repostDataString = getRepostData();
  DebugMessage("Repost data=" + repostDataString);

  repostData = JSON.parse(repostDataString);
  DebugMessage("Repost data parsed=" + JSON.stringify(repostData));

  DebugMessage("Preparing postBody...");
  postBody =
    ((repostData.community == "") ? REPOST_TEMPLATE : REPOST_TEMPLATE_COMMUNITY)
    .replace('{poster}', repostData.poster)
    .replace('{community}', repostData.community)
    .replace('{link}', repostData.link)
    .replace('{subj}', repostData.subj)
    .replace('{body}', repostData.body)
  ;

  DebugMessage("postBody=" + postBody);

  elBody = document.getElementById(ID_INPUT_BODY);
  DebugMessage("elBody was: " + elBody.value);
  elBody.value = postBody;
  DebugMessage("elBody became: " + elBody.value);

  elSubject = document.getElementById(ID_INPUT_SUBJECT);
  DebugMessage("elSubject was: " + elSubject.value);
  elSubject.value = repostData.subj;
  DebugMessage("elSubject became: " + elSubject.value);
  setRepostData(CONTENT_DEFAULT);
  DebugMessage("Populate Post end");
}

  var thisUrl = window.location.href;
  if(thisUrl.indexOf(POST_URL2) != -1) {
    populatePostForm();
  } else {
    addRepostLinks();
  }
})();

...

Дата: Вівторок, 18 Квітень 2017 12:28 (UTC)
Від: [personal profile] ichthuss
Я розібрався, що там за проблема. Якщо нова (бета) сторінка створення повідомлення не увіменена в налаштуваннях, скрипт не спрацьовує, бо URL сторінки перепосту /update, а не /entry/new. Швидкий фікс:

--- a/DreamWidth_Reposter.user.js	2017-03-24 14:59:57.000000000 +0200
+++ b/DreamWidth_Reposter.user.js	2017-04-18 15:22:51.561341936 +0300
@@ -79,5 +79,5 @@
   const CLS_EMBED         = "lj_embedcontent-wrapper";
 
-  const POST_URL = "http://www.dreamwidth.org/update";
+  const POST_URL = "https://www.dreamwidth.org/entry/new";
   const POST_URL2 = "/entry/new";
   const REPOST_PARAM="repost";

Щоправда, цей варіант примусово видасть нову сторінку незалежно від налаштувань.
Змінено Дата: Вівторок, 18 Квітень 2017 12:29 (UTC)

...

Дата: Вівторок, 18 Квітень 2017 14:22 (UTC)
Від: [personal profile] ichthuss
Отуто
https://www.dreamwidth.org/beta
можна ввімкнути або вимкнути бета-фічі, серед яких - нова сторінка створення/редагування повідомлення. Наскільки я розумію, вмикання цієї фічі дає редирект з /update на /entry/new , якщо ж вона вимкнена, кожна з цих сторінок доступна окремо (і виглядають по-різному). Нова сторінка (/entry/new) доступна всім користувачам, але якщо ця опція не ввімкнена, то всі лінки ведуть на стару, тому, щоб потрапити на нову сторінку, потрібно вводити її адресу вручну.

...

Дата: Вівторок, 18 Квітень 2017 14:39 (UTC)
Від: [personal profile] ichthuss
Думаю, справді простіше детектувати не фічу, а відкриту сторінку.

...

Дата: Четвер, 31 Серпень 2017 22:06 (UTC)
lg_hater: (Default)
Від: [personal profile] lg_hater
Ну, тримайте ще багрепорт. Вірніше, одразу два.

1. Щось не працює фікс. Якщо я вірно зрозумів, то на /update JSON не підтягується взагалі, лише на /entry/new. Якщо ввімкнено в налаштуваннях відповідну бета-фічу, спрацьовує редирект з першого на друге, тоді все нормально. Але якщо її вимкнути, допомагає лише правка "const POST_URL = "https://www.dreamwidth.org/entry/new";" Ну та то таке, дрібниця.

2. А от у lj щось, схоже, знов змінили. От що відбувається на lg-hater.livejournal.com:

bytebuster.dreamwidth.org/DreamWidth Reposter:
rememberPost begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
FixYoutube begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
FixYoutube end
bytebuster.dreamwidth.org/DreamWidth Reposter:
FixUsers begin
NotFoundError: Node was not found DreamWidth_Reposter.user.js:218.
bytebuster.dreamwidth.org/DreamWidth Reposter:
Populate Post begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
getRepostData begin
bytebuster.dreamwidth.org/DreamWidth Reposter:
Repost data=
JSON.parse: unexpected end of data at line 1 column 1 of the JSON data DreamWidth_Reposter.user.js:387:16


Тобто помилка виникає в

element.replaceChild(elLjUser2, elLjuser)

І масив не формується взагалі, звідти й наступна помилка. Усе, до чого я дотумкав, це додати перед проблемною строкою:

DebugMessage("elLjUser"+elLjUser+" elLjuser2"+elLjuser2);

На що отримав ось таке повідомлення:

ReferenceError: elLjUser is not defined


Щось мені підказує, що проблема десь у CLS_LJUSER, і тому змінна embeds пуста. Але у джаваскрипті я повний невіглас, то знайти, де собаку зарито насправді, не спромігся.

А на ibigdan.livejournal.com взагалі посилання репосту не додається:

bytebuster.dreamwidth.org/DreamWidth Reposter:
Adding Repost links begin...
elFooter is undefined DreamWidth_Reposter.user.js:172:3

Здогадуюся, що значення elFooter має витягуватися звідкись з CLS_LJ_ENTRY_LINKS, але підробиць вже ніяк не второпаю.

Подивіться, будь ласка, як матимете час.
Змінено Дата: Четвер, 31 Серпень 2017 22:10 (UTC)
Сторінку створено Четвер, 10 Липень 2025 05:30

Травень 2025

П В С Ч П С Н
   1 234
567891011
12131415161718
192021222324 25
262728293031 
Створено з Dreamwidth Studios

За стиль дякувати