LoginSignup
532
46
お題は不問!Qiita Engineer Festa 2023で記事投稿!

【検証してみた】株式会社ゆめみは、Organization対抗戦で不正をしているのか?

Last updated at Posted at 2023-07-21

今年も盛り上がっている Qiita Engineer Festa!

Qiita Engineer Festa 2023、大いに盛り上がっていますね・・・!

特に、Organization対抗戦・・・!

いろんな企業さんが、必死になって沢山の記事を書いています。

現在のランキングはこちら

Organization対抗戦: 総合いいね賞ランキング

※7月21日 12:35時点のランキングです。

スクリーンショット 2023-07-21 12.34.42.png

なんと、弊社(株式会社ゆめみ)も2位につけています。

ただ、気になったことが・・・

ワイ「なんか、ワイが記事を書くと・・・」
ワイ「いつも、同僚たちが沢山いいねをくれる・・・」
ワイ「そのおかげで、いつもデイリートレンドに載ることができてる・・・」
ワイ「これって、ちょっとズルくないか?」
ワイ「組織票ってやつとちゃうか・・・?」

調べてみた

デイリートレンドの30記事を対象に「同僚からのいいね1が何%を占めているのか」を調べるブックマークレットを作成してみました!

コード

ブックマークレット用JavaScript(長いので折りたたんであります)
ブックマークレット用JavaScript
javascript: (async () => {
  const cache = {};
  const addQueryToUrl = (url, params) => {
    const urlObj = new URL(url);
    const paramsObj = new URLSearchParams(urlObj.search.slice(1));

    for (const key in params) {
      paramsObj.set(key, params[key]);
    }

    urlObj.search = paramsObj.toString();

    return urlObj.toString();
  };

  const getPageDoc = async (link, query = {}) => {
    const url = addQueryToUrl(link, query);
    if (url in cache) return cache[url];

    await sleep(500);
    const response = await fetch(url);
    const data = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(data, 'text/html');
    cache[url] = doc;
    return doc;
  };

  const sleep = (ms) => new Promise((res) => setTimeout(res, ms));

  const articles = Array.from(document.querySelectorAll("main article"));

  const getAllPageIds = async (pageUrl, querySelectorString) => {
    let page = 1;
    let hasPage = true;
    let allIds = [];

    while (hasPage) {
      const doc = await getPageDoc(pageUrl, { page });
      const docIds = Array.from(doc.querySelectorAll(querySelectorString)).map(({ href }) => href.replace("https://qiita.com/", ""));
      allIds = [...allIds, ...docIds];
      hasPage = docIds.length !== 0;
      page++;
    }

    return allIds;
  };

  const getOrgMemberIds = async (orgMembersPageHref) => {
    return await getAllPageIds(orgMembersPageHref, "main ul li > a")
  };

  const getLikerIds = async (likersPageHref) => {
    return await getAllPageIds(likersPageHref, "[id^=PersonalArticleLikersPage] li > a")
  };

  const getSelfLikeIds = (likerIds, orgMemberIds) => {
    return likerIds.filter(likerId => orgMemberIds.includes(likerId));
  };

  for (const article of articles) {
    const orgPageLink = article.querySelector("[href^='/organizations']");
    if (!orgPageLink) continue;

    const messageFrame = document.createElement("div");
    messageFrame.classList.add("messageFrame");
    messageFrame.style.position = "relative";
    messageFrame.style.border = "1px solid #aaa";
    messageFrame.style.backgroundColor = "#aaa";
    messageFrame.style.color = "white";
    messageFrame.style.marginTop = "10px";
    messageFrame.style.padding = "5px 10px";
    messageFrame.style.fontWeight = "bold";
    messageFrame.innerHTML = "組織票を集計中...(数十秒かかります)";
    article.appendChild(messageFrame);
  }

  for (const article of articles) {
    const orgPageLink = article.querySelector("[href^='/organizations']");
    if (!orgPageLink) continue;

    const orgPageHref = orgPageLink.href;
    const orgMembersPageHref = `${orgPageHref}/members`;
    const orgMemberIds = await getOrgMemberIds(orgMembersPageHref);
    const articleLink = article.querySelector("main article > a");
    if (!articleLink) continue;

    const articleLikersHref = `${articleLink.href}/likers`;
    const likerIds = await getLikerIds(articleLikersHref);
    const selfLikeIds = getSelfLikeIds(likerIds, orgMemberIds);
    const percent = Math.round(selfLikeIds.length / likerIds.length * 100);

    const message = document.createElement("div");
    message.style.position = "relative";
    message.style.zIndex = "1";
    message.innerHTML = `組織票: ${selfLikeIds.length} 票(${percent} %)`;
    const meter = document.createElement("div");
    meter.style.position = "absolute";
    meter.style.transition = "width 0.5s";
    meter.style.backgroundColor = "red";
    meter.style.opacity = "0.3";
    meter.style.height = "100%";
    meter.style.left = "0";
    meter.style.top = "0";
    meter.style.width = "0%";

    const messageFrame = article.querySelector(".messageFrame");
    if (!messageFrame) continue;

    messageFrame.style.border = "1px solid #ffbdbd";
    messageFrame.style.backgroundColor = "transparent";
    messageFrame.style.color = "black";
    messageFrame.innerHTML = "";
    messageFrame.appendChild(meter);
    messageFrame.appendChild(message);
    setTimeout(() => meter.style.width = `${percent}%`, 100);
  };
})();

ブックマークレットの登録方法

  1. ブラウザで適当なページをブックマークする
  2. そのブックマークの編集画面を開く
  3. URLの入力欄に、上記のJavaScriptコードを貼り付ける
  4. ブックマークを保存する

ブックマークレットの実行方法

  1. Qiitaのデイリートレンドページを開く
  2. 上で作ったブックマークをクリックする
  3. 数十秒待つ(サイトに負荷がかからないように、ゆっくりの挙動にしてあります)

検証してみる

早速ブックマークレットを実行してみました。

以下が実行結果のスクショ(抜粋)です。

スクリーンショット 2023-07-21 12.53.09.png
スクリーンショット 2023-07-21 12.53.25.png

検証結果

やっていた。2

今日のデイリートレンドには、ワイの記事が2つ載っていましたが、それぞれ───

  • 14票(23%)
  • 12票(34%)

↑このような組織投票がされていました。

デイリートレンドの他の記事たちも、かなり組織票が多かったです。

同僚からのいいねが 75% という記事もありました。
(日によっては、100%組織票でトレンド入りしている記事も…)

何が問題なのか

  • 組織票でいいねを稼いだ記事たちが、デイリートレンドを埋め尽くしてしまう
    • 本当に人気のある記事が、デイリートレンドに載らなくなってしまう
    • 普段から熱心に記事を書いている人が、トレンドから弾き出されてしまう
  • デイリートレンドが「同僚からいいねしてもらった人ランキング」になっている

現在はOrganization対抗戦が開催中ということもあり、特に組織票が多いようです。

Qiitaへの提言

同Organization同士でのいいねは、ノーカウントにしませんか?

Qiitaさんのミッションは「エンジニアを最高に幸せにする」のはずです。

こんな八百長ランキングを見せられても、誰も幸せになりません。

また、Qiitaのガイドラインには、こう書いてあります。

エンジニアにとって再利用性・汎用性の高い情報が集まる場をつくろう

「八百長いいねが集まる場をつくろう」ではないはずです。

最後に

仲間が頑張って書いた記事に「いいね」をすることは、何も悪いと思いません。
僕も、同僚の記事には積極的に「いいね」をします。

あと「絶対にOrganization対抗戦で優勝してやる!」という気持ちも、悪いと思いません。

そういった気持ち自体は、人生の中で大事なものだと思います。
ですが、その結果として取るべき行動は───

「みんながいいねを押したくなるような記事を書いてやる!」

↑こうあるべきだと思います。

「同僚同士でいいね押しまくってやる!」

↑こうあるべきではないと思います。

  1. ここでの「同僚からのいいね」とは「投稿者自身が所属しているOrganizationのメンバーからのいいね」を指しています

  2. 組織票がルールで禁止されている訳ではないので、不正と呼ぶかどうかは微妙ですが...

532
46
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
532
46