向こう岸

最近はもっぱら同人イベントの参加記録

コミックマーケット95サークル参加告知

コミックマーケット95のサークル参加告知です。

サークル名:バイトニック
参加日:12/30(2日目)
配置場所:東ト20a

新刊:
「Webカタログ用ツール作ってみた 周辺ツール編」
私が開発した下記3ツールを紹介する技術系同人誌です。

  • ブラウザ上で選択した単語をWebカタログで検索するChrome拡張機能
  • 同人誌委託書店通販履歴ページ記載のサークル名をWebカタログで検索するChrome拡張機能
  • コミケ以外の同人イベントのサークルリストページからWebカタログのお気に入りサークルCSVに入っているサークルを抽出するPythonスクリプト

頒布価格:200円


上記同人誌にソースを載せていますが、コピペできるようにここでもソースのみ載せます。


ブラウザ上で選択した単語をWebカタログで検索するChrome拡張機能

manifest.json
{
    "name": "Comike Catalog Search Context Menu",
    "version": "0.1.0",
    "permissions": ["contextMenus"],
    "background": {
        "scripts": ["background.js"],
        "persistent": false
    },
    "manifest_version": 2
}
background.js
chrome.runtime.onInstalled.addListener(function() {
    chrome.contextMenus.create({
        id: "searchCatalog",
        title: "コミケWebカタログで検索",
        contexts: ["selection"],
        type: "normal",
    });
});

chrome.contextMenus.onClicked.addListener(function (info) {
    if (info.menuItemId === "searchCatalog") {
        // サークル名、ヨミガナ、執筆者名を検索
        // 全日程及び落選が対象
        // 全ジャンル対象
        var keyword = encodeURIComponent(info.selectionText);
        
        // ジャンルコード追加変更時にはパラメータ変更要
        var url = `https://webcatalog.circle.ms/Search/Result?c.Keyword=${keyword}&c.op=0&c.d1=true&c.d1=false&c.d2=true&c.d2=false&c.d3=true&c.d3=false&c.dl=true&c.dl=false&c.cn=true&c.cn=false&c.ck=true&c.ck=false&c.ca=true&c.ca=false&c.cb=false&c.ct=false&c.ctw=false&c.cpi=false&c.cu=false&c.cm=false&c.cno=false&c.cfm=false&c.cmo=0&c.gls=111&c.gls=112&c.gls=113&c.gls=114&c.gls=115&c.gls=116&c.gls=211&c.gls=212&c.gls=213&c.gls=221&c.gls=232&c.gls=233&c.gls=234&c.gls=300&c.gls=311&c.gls=312&c.gls=313&c.gls=314&c.gls=315&c.gls=321&c.gls=331&c.gls=332&c.gls=333&c.gls=334&c.gls=400&c.gls=431&c.gls=432&c.gls=433&c.gls=500&c.gls=511&c.gls=531&c.gls=532&c.gls=533&c.gls=534&c.gls=535&c.gls=600&c.gls=611&c.gls=700&c.gls=711&c.gls=811&c.gls=812&c.gls=813&c.gls=831&c.gls=832&c.gls=833&c.gls=835&c.gls=911&c.gls=912&c.gls=998&page=1&c.SortOrderBy=0`;
        window.open(url);
    }
});

同人誌委託書店通販履歴ページ記載のサークル名をWebカタログで検索するChrome拡張機能

manifest.json
{
    "name": "Melon To Comike Catalog Search",
    "version": "0.1.0",
    "content_scripts": [
        {
            "matches": ["https://www.melonbooks.co.jp/mypage/history.php"],
            "js": ["content_script.js"]
        }
    ],
    "manifest_version": 2
}
content_script.js
var search = function(event) {
    var circleList = event.target.parentNode.parentNode.querySelectorAll(
        "table tbody tr td p.circle");
    
    circleList.forEach(function(elem) {
        // サークル名を検索
        // 全日程及び落選が対象
        // 全ジャンル対象
        var keyword = encodeURIComponent(elem.innerText);

        // ジャンルコード追加変更時にはパラメータ変更要
        var url = `https://webcatalog.circle.ms/Search/Result?c.Keyword=${keyword}&c.op=0&c.d1=true&c.d1=false&c.d2=true&c.d2=false&c.d3=true&c.d3=false&c.dl=true&c.dl=false&c.cn=true&c.cn=false&c.ck=false&c.ca=false&c.cb=false&c.ct=false&c.ctw=false&c.cpi=false&c.cu=false&c.cm=false&c.cno=false&c.cfm=false&c.cmo=0&c.gls=111&c.gls=112&c.gls=113&c.gls=114&c.gls=115&c.gls=116&c.gls=211&c.gls=212&c.gls=213&c.gls=221&c.gls=232&c.gls=233&c.gls=234&c.gls=300&c.gls=311&c.gls=312&c.gls=313&c.gls=314&c.gls=315&c.gls=321&c.gls=331&c.gls=332&c.gls=333&c.gls=334&c.gls=400&c.gls=431&c.gls=432&c.gls=433&c.gls=500&c.gls=511&c.gls=531&c.gls=532&c.gls=533&c.gls=534&c.gls=535&c.gls=600&c.gls=611&c.gls=700&c.gls=711&c.gls=811&c.gls=812&c.gls=813&c.gls=831&c.gls=832&c.gls=833&c.gls=835&c.gls=911&c.gls=912&c.gls=998&page=1&c.SortOrderBy=0`;
        window.open(url);
    });
};

// 各注文のステータス・配送業者部分のdiv要素
var orderStatusElems = document.querySelectorAll("div.status");

// 各div要素にボタン追加
orderStatusElems.forEach(function(elem) {
    var searchButton = document.createElement("button");
    searchButton.type = "button";
    searchButton.innerText = "Webカタログでサークルを検索";
    searchButton.style ="margin-top: 5px;";
    searchButton.addEventListener("click", search, false);

    elem.appendChild(searchButton);
});

コミケ以外の同人イベントのサークルリストページからWebカタログのお気に入りサークルCSVに入っているサークルを抽出するPythonスクリプト

circleListFavoriteMatchTool.py
import sys
import requests
from bs4 import BeautifulSoup
import csv
import unicodedata
import pprint
import re

def normalize(str):
    """文字列のUnicode正規化"""
    return unicodedata.normalize('NFKC', str)


def make_unique_list(seq):
    """リストの重複要素を削除"""
    seen = []
    return [x for x in seq if x not in seen and not seen.append(x)]


def parse_circle_list_comitia(soup):
    """コミティアのサークルリストを抽出"""
    circlelist = []
    
    alltr = soup.main.table.find_all('tr')
    for tr in alltr:
        if tr.td.has_attr('colspan'):
            # ブロック文字行は飛ばす
            continue
        alltd = tr.find_all('td')
        # 比較のため正規化したサークル名も取得
        circlelist.append({'position': alltd[0].string, 'name': alltd[1].string, \
            'normalizedname': normalize(alltd[1].string)})
    return circlelist


def parse_circle_list_sdf(soup):
    """SDFのサークルリストを抽出"""
    circlelist = []
    
    alltr = soup.find_all('tr')
    for tr in alltr:
        alltd = tr.find_all('td')
        if len(alltd) != 2:
            # サークル情報以外(カットなど)は飛ばす
            continue

        # サークル情報(先頭にサークル名、末尾に配置、途中はイベント名等)
        infos = list(str.strip() for str in alltd[0].strings)
        author = alltd[1].string

        circlelist.append({'position': infos[-1], 'name': infos[0], \
                           'normalizedname': normalize(infos[0]),
                           'author': author
                           })

    return circlelist


def get_circle_list(url):
    """サークルリストページのデータを取得し、リストを抽出する"""
    response = requests.get(url)
    
    # 文字コード判定はBeautifulSoupで行うため、バイト列で渡す
    soup = BeautifulSoup(response.content, 'html.parser')
    
    parsefuncs = [
        ('.*comitia.*', parse_circle_list_comitia),
        ('.*sdf-event.*', parse_circle_list_sdf),
    ]
    
    for parsefunc in parsefuncs:
        if re.match(parsefunc[0], url):
            return parsefunc[1](soup)
    
    return null


def match_circle_list(circlelist, favoritefilepath):
    """お気に入りサークルリストファイルの内容を取得し、サークルリストとマッチングする"""
    with open(favoritefilepath, 'r') as f:
        reader = csv.reader(f)
        header = next(reader)
        # サークルリストの比較
        checklist = []
        for row in reader:
            if row[0] == 'Circle':
                name = normalize(row[10])
            elif row[0] == 'UnKnown':
                name = normalize(row[1])
            else:
                continue

            for circle in circlelist:
                if name == circle['normalizedname']:
                    checklist.append(circle)
    checklist = make_unique_list(checklist)
    return checklist


def print_check_list(checklist):
    """チェックリストを出力"""
    checklist = sorted(checklist, key=lambda circle: normalize(circle['position']))

    pprint.pprint(['{0}:{1}'.format(c['position'], c['name']) for c in checklist])


circlelist = get_circle_list(sys.argv[1])
checklist = match_circle_list(circlelist, sys.argv[2])
print_check_list(checklist)