class.exe

blog.netaka.net

BudouXをつかって見出しに<wbr>を挿入してみました

1/25/2022

この記事を書いてから1年が経っています。内容が古いかもしれません。

BudouX1をつかって見出しに<wbr>を挿入してみました。

見出しを分解するコンポーネントを用意しました。

import Link from 'next/link'

import {loadDefaultJapaneseParser} from 'budoux'

const parser = loadDefaultJapaneseParser()

function Breakdown(title: string) {
    return parser.parse(title).map( text => {
                return <>{text}<wbr/></>
            })
}

export function Heading({props}: {props: any}) {
    return (
        <h2>{Breakdown(props.data.title)}</h2>
    )
}

Footnotes

  1. https://github.com/google/budoux

BudouXをつかって文章中に<wbr>を挿入してみました

12/15/2021

この記事を書いてから1年が経っています。内容が古いかもしれません。

BudouX1をつかって文章中に<wbr>を挿入してみました。このブログの記事はMarkdownで書いていて、remark-rehype2で変換しているので拡張するためのhandlerを用意します。

このhandlerはBudouXをつかってParagraphのtextノードに<wbr>を挿入します。

import {all} from 'mdast-util-to-hast'
import {u} from 'unist-builder'
import {loadDefaultJapaneseParser} from 'budoux'

const parser = loadDefaultJapaneseParser()

export function rehypeBudouxHandler(h, node) {
  const children = []
  all(h, node).forEach(child => {
    if (child.type == 'text') {
      parser.parse(child.value).forEach(text => {
        children.push([h.augment(node, u('text', text)))
        children.push(h(node, 'wbr'))
      })
    }
    else {
      children.push(child)
    }
  })
  return h(node, 'p', children)
}

このhandlerをremark-rehypeのオプションに指定します。

const processor = unified()
  .use(remarkParse)
  .use(remarkRehype, {
    handlers: { paragraph: rehypeBudouxHandler }
  })
  ...

たとえばこの文は下記のように変換されました。3

root[1] (1:1-1:23, 0-22)
└─0 element<p>[10] (1:1-1:23, 0-22)
    │ properties: {}
    ├─0 text "たとえば" (1:1-1:23, 0-22)
    ├─1 element<wbr>[0] (1:1-1:23, 0-22)
    │     properties: {}
    ├─2 text "この" (1:1-1:23, 0-22)
    ├─3 element<wbr>[0] (1:1-1:23, 0-22)
    │     properties: {}
    ├─4 text "文は" (1:1-1:23, 0-22)
    ├─5 element<wbr>[0] (1:1-1:23, 0-22)
    │     properties: {}
    ├─6 text "下記のように" (1:1-1:23, 0-22)
    ├─7 element<wbr>[0] (1:1-1:23, 0-22)
    │     properties: {}
    ├─8 text "変換されました。" (1:1-1:23, 0-22)
    └─9 element<wbr>[0] (1:1-1:23, 0-22)
          properties: {}

以上です。こちらの記事がhandlerを作るための参考になりました。

Remark で広げる Markdown の世界

mdast-uttil-to-hastパッケージにある各handlerの例も参考になりました。

mdast-util-to-hast/lib/handlers at main · syntax-tree/mdast-util-to-hast

Footnotes

  1. https://github.com/google/budoux

  2. https://github.com/remarkjs/remark-rehype

  3. 変換後のHastを表示するためにunist-util-inspectというパッケージを使っています。

Next.jsでブログを作り直しました

11/26/2021

この記事を書いてから2年が経っています。内容が古いかもしれません。

このブログをNext.jsで作り直しました。また、これまでのサーバーだとなぜかProduction Buildがこけるので、VPSやOSも含めて環境を一新しました。

サーバー: さくらのVPS -> さくらのVPS
さくらのVPS バージョン: v3 -> v5
リージョン: 石狩第1ゾーン -> 石狩第1ゾーン
CPU: 仮想3Core -> 仮想2Core
メモリ: 2GB -> 1GB
ストレージ: HDD 200GB -> SSD 50GB
OS: CentOS Linux release 7.9.2009 (Core) -> Ubuntu 20.04.3 LTS (Focal Fossa)

Ubuntu 20.04はISOインストールしました。インストール直後のVNCコンソールはファイルシステムのチェックが表示されています。

/dev/vda5: recovering journal
/dev/vda5: clean, 66234/3244032 files, 1234222/12975360 blocks

ログイン画面ではなかったので、Ctrl+Alt+F1で仮想コンソールを出してログインできました。

nuxt/contentとVuetifyで画像をオーバーレイ表示するコンポーネント

4/15/2021

この記事を書いてから2年が経っています。内容が古いかもしれません。

画像をクリックするとオリジナルサイズで拡大してオーバーレイ表示するコンポーネントを作ってみました。

Vuetifyのv-imgコンポーネントを利用したVueコンポーネントは下記のとおりです。

<template>
<div>
  <figure>
    <v-img
      @click="overlay = true"
      :src="src"
    ></v-img>
    <figcaption>{{img.caption}}</figcaption>
  </figure>
  <v-overlay
    :value="overlay"
    opacity="0.6"
  >
    <v-img
      @click="overlay = false"
      :src="src"
      width="100vw"
    ></v-img>
  </v-overlay>
</div>
</template>

<script>
export default {
  data() {
    return {
      src: require('~/static' + this.img.src),
      overlay: false
    }
  },
  props: ['img']
}
</script>

Markdownファイルには上記のVueコンポーネントをケバブケースで参照します。

<overlay-img :img="{'src':'/click.png', 'caption':'画像をクリックするとオリジナルサイズで拡大してオーバーレイ表示します'}"></overlay-img>

リファレンス

nuxt/contentとVuetifyでコードブロックのスタイルがおかしくなる

10/16/2020

この記事を書いてから3年が経っています。内容が古いかもしれません。

本ブログをnuxt/contentとVuetifyで作り直してみました。しかしながら、nuxt/contentで利用しているPrismJSとVuetifyが両方とも適用されたためかコードブロックのスタイルがおかしくなってしまいました。

二重にシンタックスハイライトされたコードブロック
二重にシンタックスハイライトされたコードブロック

使用しているバージョンはこちら

nameversion
Nuxt.js2.14.7
@nuxt/content1.6.0
Vuetify1.11.2

とくにテキストの背景色が不要です。Chromeのデベロッパーツールで確認すると.v-application codeセレクタで背景色が適用されていました。

.v-application code
.v-application code

Stack Overflow1でも同様の質問があったので、解決策通りにunsetします。

.v-application code {
  all: unset
}

結果、PrismJSだけのスタイルになったようです。

修正後
修正後

Footnotes

  1. vue.js - How to disable Vuetify's style? - Stack Overflow

トラッキングアプリにGASでカレンダー対応する

9/1/2019

この記事を書いてから4年が経っています。内容が古いかもしれません。

最後はいつ?というトラッキングアプリはシンプルなUIで使いやすいが、カレンダーに対応していない。GASでカレンダー対応してみる。

「最後はいつ? - あなたの大切な出来事をトラッキング」をApp Storeで

"1年経過: トイレ掃除"といった内容のカレンダーイベントを作成するのが目標。

同期化の設定を有効にして、Dropboxに保存されるファイルをカレンダーの登録に利用する。

"Dropbox/アプリ/Last Time/MainStore.V2/baselines"に同期するために必要なファイルが保存されていた。

ファイルの中身はJSON形式で、以下のような内容が記載されている(関係ない要素は省いている)。

    "changesByEntity": {
        "Event": [
            {
                "properties": [
                    {
                        "propertyName": "lasttime",
                        "value": [
                            "date",
                            1537707955000
                        ],
                        "type": 0
                    },
                    {
                        "propertyName": "name",
                        "value": "トイレ掃除",
                        "type": 0
                    },
                ],
            },
        ]
    }
}

nameプロパティにはイベント名、lasttimeプロパティには直近のイベント更新時のミリ秒のUNIX timeが記載されていた。

このファイルをDropboxの公開リンクからアクセスして、GASでカレンダ ー登録を処理する。

コードは以下の通り。

function getLastTimeJSON() {
  var url = "Dropbox 公開リンクURL";
  var response = UrlFetchApp.fetch(url);
  var content = response.getContentText("UTF-8");
  var json = JSON.parse(content);
  
  return json;
}

function makeLastTimeEvent() {
  var json = getLastTimeJSON();
  var events = json.changesByEntity.Event;
  
  var calendarEvents = [];
  
  for (var i in events) {
    var properties = events[i].properties;
    var _name;
    var _lasttime;
    for (var j in properties) {
      var property = properties[j];
      if (property.propertyName == "name") {
        _name = property.value;
      }
      if (property.propertyName == "lasttime") {
        _lasttime = property.value[1];
      }
    }
    var calendarEvent = {
      name: _name,
      lasttime: new Date(_lasttime)
    };
    calendarEvents.push(calendarEvent);
  }
  return calendarEvents;
}

function setLastTimeItem(toCalId) {
  var calendarEvents = makeLastTimeEvent();
  var targetCal = CalendarApp.getCalendarById(toCalId);
  
  for (var i in calendarEvents) {
    var next = new Date();
    var lasttime = calendarEvents[i].lasttime;
    var name = calendarEvents[i].name;

    next.setTime(lasttime);
    next.setYear(lasttime.getYear() + 1);

    var title = "1年経過: " + name;
    targetCal.createAllDayEvent(title, next);
}

var lastTimeCalId = "GoogleカレンダーID";
function main(){
  setLastTimeItem(lastTimeCalId);
}

参考

電子ペーパー名札をつくる その3

12/12/2018

この記事を書いてから4年が経っています。内容が古いかもしれません。

デモも動いたので名札っぽく自分の名前を表示してみましょう。

画像を用意する

せっかくの3色の電子ペーパーなので、黒色だけでなく赤色も用意します。デモコードをそのまま利用するので2枚画像を用意します。

黒色画像 black.bmp
黒色画像 black.bmp

赤色画像 red.bmp
赤色画像 red.bmp

市販の名札ケースに逆さに入れると、こんな感じに。これだけでもアイコンが表示できてオフ会とかで便利そうです。

実際にオフ会で使った時の写真
実際にオフ会で使った時の写真

しかし、Raspberry Piの電源用にUSBケーブルが見えてたり、裏側がやっつけだったりします。

Raspberry Piはマスキングテープで固定
Raspberry Piはマスキングテープで固定

電子ペーパー名札をつくる その2

12/6/2018

この記事を書いてから5年が経っています。内容が古いかもしれません。

さてRaspberry Piと接続して動作確認してみます。

ライブラリの準備

ありがたいことに開発に関してはWikiがあるのでさらっと一読しておきます。

4.2inch e-Paper Module (B) - Waveshare Wiki

今回はPythonでディスプレイを制御したかったので、まずは後述のデモを動かすのに必要なライブラリをいれておきます。同じくWikiに記載されています。

https://www.waveshare.com/wiki/Pioneer600#Install_Python_Library

Raspberry Piとの接続

Raspberry Pi上のGPIOとディスプレイのHATを繋げましょう。動作確認につかったRaspberry Piは古い世代なのかHATがそのままGPIOピンに刺さらず、コネクタ付きのワイヤで接続しました。

ネットで調べてコネクタをGPIOピンに接続
ネットで調べてコネクタをGPIOピンに接続

デモの確認

Wikiからたどってデモを準備しましょう。

https://www.waveshare.com/wiki/File:4.2inch-e-paper-module-b-code.7z

デモをダウンロードでして、7zを展開して動かしてみましょう。

pi@raspberrypi:~/demo/RaspberryPi/python2 $ sudo python main.py

フォントが無いとか怒られたら、適当なものに変えてみてください。

font = ImageFont.truetype('/opt/vc/src/hello_pi/hello_font/Vera.ttf', 24)

GPIOピンの接続がもし間違っていると、

砂嵐のような状態に
砂嵐のような状態に

接続も、デモもうまく動作するとこんなかんじです。

Waveshareのロゴとか簡単な図形が描かれます。
Waveshareのロゴとか簡単な図形が描かれます。

参考にしたのはこちら

ありがとうございます。

RaspberryPiによる電子ペーパーの制御 – Isaax Camp

電子ペーパー名札をつくる その1

12/5/2018

この記事を書いてから5年が経っています。内容が古いかもしれません。

電子ペーパー名札をつくってみました。まずは電子ペーパー選びです。

電子ペーパー

購入したのはWaveshareというブランドの4.2inchの電子ペーパーです。解像度は400x300で、赤黒白と3色表示ができます。

400x300, 4.2inch E-Ink raw display, three-color, SPI interface, without PCB | WFT0420CZ15

これはディスプレイだけです。Raspberry Piと接続したかったので、追加でHATも必要でした。

Universal e-Paper Driver HAT, supports various Waveshare SPI e-Paper raw panels

注文してみる

ディスプレイ、HAT、送料で$40.48でした。

送料込みで$40.48
送料込みで$40.48

2018/2/6に支払いをして、2/14には届きました。シンガポールからだったのですぐでしたね。

iOSヘルスケアアプリに体重データを一括入力する

10/13/2018

この記事を書いてから5年が経っています。内容が古いかもしれません。

利用していた体重記録アプリがiOSヘルスケアアプリに対応しませんでした。そこでエクスポートした体重データをヘルスケアアプリに一括入力してみました。

体重データをエクスポートする

シンプル・ダイエットというアプリから体重データをエクスポートします。データはGoogleドライブに保存されます。エクスポート元アプリはこちらです。

「シンプル・ダイエット 〜 記録するだけ!かんたん体重管理 〜」

エクスポート画面
エクスポート画面

以下のようなカラムでエクスポートされました。

SimpleWeight-Export.csv
SimpleWeight-Export.csv

エクスポートデータを編集する

今回はエクスポートしたデータを、ヘルスケアアプリに対応した別の体重記録アプリにインポートさせます。インポート先アプリはこちらです。

「RecStyle -ダイエット・体重管理-」

RecStyleは以下のようなフォーマットで扱われています。

RecStyleData.csv
RecStyleData.csv

体重、体脂肪率のデータをRecStyleData.csvの対応するカラムに貼り付けます。SimpleWeight-Export.csvとRecStyleData.csvのフォーマット違いは以下3点ぐらいでした。

  • A3セルにデータ数を入れる。
  • RecStyleData.csvの日時にはSimpleWeight-Export.csvと違い日付しかない。つまりインポート時に時刻は落ちてしまう点に注意。
  • RecStyleData.csvにはBMIのデータがある。

編集した体重データをインポートする

Dropboxに保存した編集済みRecStyleData.csvをRecSytleにコピーするかたちでインポートさせます。

インポート画面
インポート画面

うまくRecStyleにインポートできました。

データをインポートしました
データをインポートしました

インポートできたら、ヘルスケア連携を有効にします。

ヘルスケアへデータをエクスポートしています
ヘルスケアへデータをエクスポートしています

ヘルスケアアプリで体重データを確認する

体重データがインポートされたらヘルスケアアプリで確認してみます。うまくいけば以下のように表示されます。

ヘルスケアアプリ上のRecStyleの体重データ
ヘルスケアアプリ上のRecStyleの体重データ

さいごに

ヘルスケアに対応していないシンプル・ダイエットですが、詳しい経緯は公式サポートページに記載されていました。

iOSの「ヘルスケア」の対応について | シンプル・ダイエット

LINEのリッチメニューの表示期間を変更する

10/6/2018

この記事を書いてから5年が経っています。内容が古いかもしれません。

LINEのリッチメニューが便利なので設定していますが、表示期間を過ぎると再設定が必要です。いつもどこで設定するのか忘れてしまうのでメモを残します。

LINE@マネージャーにログインします

LINE@マネージャー

リッチメニューを編集します

  1. アカウント一覧から編集したいBotを選択します。
    000f9bfee871b5d9e757024ca39092a9
    000f9bfee871b5d9e757024ca39092a9
  2. 左ペインからリッチコンテンツ作成を選択します。
    679d1f6ebf4c853a94e53283aba2437c
    679d1f6ebf4c853a94e53283aba2437c
  3. 編集したいリッチメニューを選択します。
    select_the_rich_menu
    select_the_rich_menu

表示期間を変えます

適切な表示期間に変えましょう。

33e31752d8060e061a9a32f06f897bbc
33e31752d8060e061a9a32f06f897bbc

ドキュメントに書いてありました

リッチメニューを設定したくて、LINE@マネージャーではなくLINE Developersにログインしてしまうことが多かったです。探してみたらデフォルトのリッチメニューについてはドキュメントに書いてありました。

LINE@マネージャーで設定する

LINEでベクターキャノンを準備してもらう

9/17/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

LINEのMessaging APIで、ボットにセリフを喋ってもらいます。

ボット用アカウントをつくる

LINE Business Centerでボット用アカウントを作ります。詳しくは割愛しますが、個人用だったのでDeveloper Trialで作っています。

LINE Business Center

bot_account
bot_account

HTTPSが使えるサーバを準備する

Webhook使ってメッセージを受け取るので、HTTPSで受け取れるようなサーバを準備してください。

ボット用のSDKを手に入れて、実装する

便利なSDKがあるのでinstallしましょう。

line/line-bot-sdk-python: SDK of the LINE Messaging API for Python.

Usage:にサンプルがあるので利用します。セリフを喋るためにちょっと手を加えたのがこちらです。

from flask import Flask, request, abort
from dotenv import load_dotenv, find_dotenv
import time
import os

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)
load_dotenv(find_dotenv())

line_bot_api = LineBotApi(os.environ.get('CHANNEL_ACCESS_TOKEN'))
handler = WebhookHandler(os.environ.get('CHANNEL_SECRET'))


@app.route("/ada", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)
    print(body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    to = ''
    if event.source.type == 'group':
        to = event.source.group_id
    if event.source.type == 'user':
        to = event.source.user_id
    if event.message.text == "ベクターキャノン":
        line_bot_api.push_message(to, TextSendMessage(text='ベクターキャノンモードへ移行'))
        time.sleep(3)
        line_bot_api.push_message(to, TextSendMessage(text='エネルギーライン全段直結'))
        time.sleep(2)
        line_bot_api.push_message(to, TextSendMessage(text='ランディングギア、アイゼン、ロック'))
        time.sleep(3)
        line_bot_api.push_message(to, TextSendMessage(text='チャンバー内、正常加圧中'))
        time.sleep(2)
        line_bot_api.push_message(to, TextSendMessage(text='ライフリング回転開始'))
        time.sleep(7)
        line_bot_api.push_message(to, TextSendMessage(text='撃てます'))


if __name__ == "__main__":
    app.run()

できました

こちらが「ベクターキャノン」と言うと、ボットは某ゲームのセリフを喋ります。

line_ada_bot
line_ada_bot

退社したらお疲れさまと言ってもらう

9/3/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

退社したらお疲れさまと言ってもらいます。IFTTTでやってみます。

トリガー

Locationサービスで会社の建物を設定します。

trigger
trigger

アクション

LINEサービスで通知メッセージを設定します。

action
action

レシピ

できあがったレシピはこんなのです。

recipe
recipe

LINE Notifyから、お疲れさま、って言ってもらえました。

line-notify
line-notify

さくらのVPSでストレージのリソースが制限されてた

8/12/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

このブログの読み込みがなぜか重かったので、さくらのVPSのコンパネを確認したら、ストレージのリソースが制限されていました。

restriction
restriction

かくにん

リソースの制限についてはこちら。

リソース制限表示について – さくらのサポート情報

月曜日からDISK I/Oの負荷が増え始めて、水曜日に制限され始めたようです。同じサーバではこのブログ以外に自分専用にMastodon1も動かしています。どちらかと言えばMastodonのほうが高負荷そうです。

disk_io
disk_io

ためす

負荷が高そうと予想されるMastodonをとりあえず止めてみます2。バージョンはv1.4.7でした。

$ docker-compose stop
Stopping mastodon_sidekiq_1 ... done
Stopping mastodon_streaming_1 ... done
Stopping mastodon_web_1 ... done
Stopping mastodon_db_1 ... done
Stopping mastodon_redis_1 ... done

さいかくにん

土曜日12時頃に止めたところDISK I/Oの負荷が減りました。その一時間後にはリソース制限も解除されました。

disk_io_2
disk_io_2

といっても、Mastodonを止め続けておくわけにはいかないので、DISK I/Oへの高負荷の原因をあとで調べないといけないです。

Footnotes

  1. tootsuite/mastodon: A GNU Social-compatible microblogging server

  2. 止める前にsidekiqのログとか見ておけばよかったですね。

Wunderlistの完了タスクをグラフにした

7/30/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

ToDoリストアプリのWunderlist1から完了タスクを取得してグラフにしてみました。Pythonをつかって、直近7日間の完了タスク数をグラフにしています。

環境は以下の通りです。

$ python -V
Python 2.7.10
$ pip freeze
matplotlib==2.0.0
python-dateutil==2.6.0
pytz==2016.10
requests-oauthlib==0.8.0

手順

  1. アプリを登録して、APIに必要なアクセストークン等を取得する(詳細は割愛)
  2. 全リストのIDを取得
  3. IDから完了タスクを取得
  4. 日付ごとに完了タスクをカウント
  5. matplotlibでグラフ化

コード

# coding:utf-8

import json
from requests_oauthlib import OAuth2Session
from dateutil import parser
from datetime import timedelta, datetime
import matplotlib.pyplot as plt
from pytz import timezone


class Wunderlist:
    def __init__(self):
        self.api = OAuth2Session()
        self.api.headers['X-Client-ID'] = 'your_client_id'
        self.api.headers['X-Access-Token'] = 'your_access_token'

    def fetch_list_id(self):
        req = self.api.get('https://a.wunderlist.com/api/v1/lists',
                           params={})

        if req.status_code != 200:
            print ('Error: %d' % req.status_code)
            return []

        lists = json.loads(req.text)
        id = [l['id'] for l in lists]
        return id

    def fetch_completed_at(self, id):
        req = self.api.get('https://a.wunderlist.com/api/v1/tasks',
                           params={'completed': 'true', 'list_id': id})

        if req.status_code != 200:
            print ("Error: %d" % req.status_code)
            return []

        tasks = json.loads(req.text)
        completed_at = [t['completed_at'] for t in tasks]
        return completed_at

    def fetch_completed_at_all(self):
        list_id = self.fetch_list_id()
        completed_at = []
        for id in list_id:
            completed_at.extend(self.fetch_completed_at(id))
        return completed_at


def make_complete_counts(completed_at, start_date, x):
    dates = [parser.parse(date).astimezone(timezone('Asia/Tokyo')).date() for date in completed_at]
    dates = [date for date in dates if date &gt;= start_date]

    diff_dates = [(date - start_date).days for date in dates]

    result = [diff_dates.count(days) for days in x]
    return result


def make_date_ticks(start_date, x):
    dates = [(start_date + timedelta(days=var)).strftime('%m/%d') for var in x]
    return dates


def main():
    today = datetime.now(timezone('Asia/Tokyo')).date()
    start_date = today - timedelta(days=6)

    wunderlist = Wunderlist()
    completed_at = wunderlist.fetch_completed_at_all()

    x = range(7)
    y = make_complete_counts(completed_at, start_date, x)
    x_ticks = make_date_ticks(start_date, x)

    plt.xkcd()
    plt.bar(x, y)
    plt.xticks(x, x_ticks)
    plt.savefig('wunder_graph.png')


if __name__ == '__main__':
    main()

できました

wunder_graph
wunder_graph

参考ページ

Footnotes

  1. Wunderlist | To-do リスト、リマインダー、タスク管理 - App of the Year!

Crowi(crowi-plus)をインストールした

7/26/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

素でMarkdownが使えるWikiのCrowi1を自宅のNUCにインストールしてみました。正確にはCrowiではなくcrowi-plus2ですが。環境は次の通り。

  • Ubuntu Server 16.04 LTS
  • Intel NUC Kit D54250WYK

Dockerのインストール

Dockerを使ってインストールしてみます。

$ sudo apt install docker
$ sudo apt install docker-compose
$ docker -v
Docker version 1.12.6, build 78d1802

crowi-plusのインストール

READMEどおりに、インストールしてみます。

$ git clone https://github.com/weseek/crowi-plus-docker-compose.git crowi-plus
$ cd crowi-plus
$ docker-compose up

すると、エラーが出ます。

ERROR: Version in "./docker-compose.yml" is unsupported. You might be seeing this error because you're using the wrong Compose file version. Either specify a version of "2" (or "2.0") and place your service definitions under the `services` key, or omit the `version` key and place your service definitions at the root of the file to use version 1.
For more on the Compose file format versions, see https://docs.docker.com/compose/compose-file/

やりなおし

古かったようです。ということで、Dockerの公式マニュアルにそってやりなおします。

Get Docker CE for Ubuntu | Docker Documentation

$ sudo apt remove docker-compose
$ sudo apt remove docker
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"
$ sudo apt update

準備できたので、インストールしなおします。

$ sudo apt install docker-ce
$ sudo -i
# curl -L https://github.com/docker/compose/releases/download/1.14.0/docker-compose-`uname -s`-`uname -m` &gt; /usr/local/bin/docker-compose
# chmod +x /usr/local/bin/docker-compose
# exit

今度こそ大丈夫そうです。

$ docker -v
Docker version 17.06.0-ce, build 02c1d87

だめです

大丈夫じゃなかったです。

$ docker-compose up -d
ERROR: Couldn't connect to Docker daemon at http+docker://localunixsocket - is it running?

こちらを参考にdockerグループにユーザーを追加します。

Dockerコマンドをsudoなしで実行する方法 - Qiita

つながらないです

localhostからだけだったので、docker-compose.ymlのポートのマッピングを変更しました。

$ git diff
diff --git a/docker-compose.yml b/docker-compose.yml
index dae65d3..d176205 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -6,7 +6,7 @@ services:
       context: .
       dockerfile: ./Dockerfile
     ports:
-      - 127.0.0.1:3000:3000
+      - 3000:3000
     links:
       - mongo:mongo
       - redis:redis
$ docker-compose ps
          Name                         Command               State           Ports          
-------------------------------------------------------------------------------------------
crowiplus_app_1             dockerize -wait tcp://mong ...   Up      0.0.0.0:3000-&gt;3000/tcp 
crowiplus_elasticsearch_1   /docker-entrypoint.sh sh - ...   Up      9200/tcp, 9300/tcp     
crowiplus_mongo_1           docker-entrypoint.sh mongod      Up      27017/tcp              
crowiplus_redis_1           docker-entrypoint.sh redis ...   Up      6379/tcp
$ sudo lsof -i:3000
COMMAND     PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
docker-pr 25053 root    4u  IPv6  93412      0t0  TCP *:3000 (LISTEN)

できました

install
install

Footnotes

  1. crowi/crowi: Crowi - Wiki

  2. weseek/crowi-plus: crowi-plus - Enhanced Crowi

Ubuntu on NUCでWi-Fi接続した

7/23/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

Ubuntu Server 16.04 LTSをインストールしたNUCでWi-Fi接続してみました。環境、バージョン等は以下の通り。

iwconfigを利用可能にする

iwconfigはwireless-toolsパッケージに入っているようです。

$ iwconfig
The program 'iwconfig' is currently not installed. You can install it by typing:
sudo apt install wireless-tools
$ sudo apt install wireless-tools

iwconfigでWi-Fiモジュールを確認する

iwconfigでWi-Fiモジュールが確認できました。wlp2s0というインタフェースのようです。

$ iwconfig
lo        no wireless extensions.

eno1      no wireless extensions.

wlp2s0    IEEE 802.11abgn  ESSID:off/any  
          Mode:Managed  Access Point: Not-Associated   Tx-Power=0 dBm   
          Retry short limit:7   RTS thr:off   Fragment thr:off
          Power Management:on

wpa_passphrase, wpa_supplicantを利用可能にする

wpasupplicantパッケージをインストールします。

$ sudo apt install wpasupplicant

設定情報を作成する

wpa_passphraseでパスフレーズを暗号化して、設定情報をwpa_supplicant.confというファイルで保存します。

$ wpa_passphrase "ESSID" "passphrase"
network={
    ssid="ESSID"
    #psk="passphrase"
    psk=d5b7ea2dda330fb59753d126e0b98bc968644423abb76bb52374a027718219c6
}
$ wpa_passphrase "ESSID" "passphrase" &gt; wpa_supplicant.conf

お家のアクセスポイントはSSIDがステレスなので、scan_ssid=1を追記しています。

wpa_supplicantコマンドで接続する

wpa_supplicantコマンドでアクセスポイントに接続します。

$ sudo wpa_supplicant -i wlp2s0 -c wpa_supplicant.conf -B

そして、DHCPクライアントでIPアドレスを取得します。

$ sudo dhclient wlp2s0

起動時に接続するようにする

こちらを参考に、systemdサービス用に、[email protected]を作成します。参考ページの記載とほぼ同じですが、wpa_supplicantのパスを/sbin/wpa_supplicantにしているのと、ExecStartPostdhclientを追記しています。

$ sudo mv wpa_supplicant.conf /etc/wpa_supplicant/wpa_supplicant-wlp2s0.conf
$ sudo vim /etc/systemd/system/[email protected]
[Unit]
Description=WPA supplicant daemon (interface-specific version)
Requires=sys-subsystem-net-devices-%i.device
After=sys-subsystem-net-devices-%i.device

[Service]
Type=simple
ExecStart=/sbin/wpa_supplicant -c/etc/wpa_supplicant/wpa_supplicant-%I.conf -i%I
ExecStartPost=/sbin/dhclient %i

[Install]
Alias=multi-user.target.wants/wpa_supplicant@%i.service

無線インタフェースだけで運用する

有線を繋げていないと起動時にStarting Raise network interfaces...で待たされてしまうので、networking.serviceを止めてしまう。

$ sudo systemctl disable networking.service

追記

こっちのほうが適当そう。eno1をコメントアウトする。

/etc/network/interfaces
# The primary network interface
# auto eno1
# iface eno1 inet dhcp

参考ページ

NUCのBIOSをアップデートした

7/23/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

Intel NUC Kit D54250WYKのBIOSが古かったのでアップデートしてみました。

Ubuntu Server 16.04 LTSをD54250WYKにインストールしたところ、ブートがうまくいかなかったので、試しにBIOSをアップデートしてみました。1

bioファイルをダウンロード

ダウンロードページからWY0045.bioをダウンロードします。ダウンロードしたら適当なメディア(USBメモリ等)に保存します。

ダウンロード BIOS アップデート [WYLPT10H.86A]

bioファイルを選択

BIOSスクリーン上にBIOS Versionが記載されているので、右端のUpdate >を選択して、保存したbioファイルを選択する。2

visual_bios
visual_bios

BIOSをアップデート

選択後、BIOSスクリーンを出てからアップデートが始まります。

参考ページ

D54250WYKにUbuntuを入れてみたが、シャットダウンできない... - tuttitanの日記

Footnotes

  1. 結局、ブートできるようになったけどBIOSをアップデートしたおかげなのかは不明。 

  2. スクリーンショットはバージョン 0045にアップデートした後のもの。

QNAP NASにDokuWikiをインストールした

5/14/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

QNAP NASにDokuWikiをインストールしたときのメモです。

バージョン

使用しているNASは以下の通り。

  • Model: TS-219P II
  • QTS: 4.2.3

アプリ

QTSからDokuWiki - Beta1というアプリが用意されているのでインストールします。

下記ディレクトリにあります。

home/Qhttpd/Web/dokuwiki/

アップグレード

DokuWikiのバージョンが古かったのでアップグレードします。

アップグレードはプラグインを使用しました。

plugin:upgrade [DokuWiki]

以下はアップグレード時のエラーについて。

書き込み権限で失敗する

ほとんどの更新ファイルが権限がなく書き込みができないので、httpdusrの権限を変更しました。

ダウンロードしてきたファイルが展開できない

プラグインは下記から更新用アーカイブを手に入れますが、展開に失敗するのか更新ファイルがあるディレクトリが空っぽ2になってしまいます。そのため、なんのファイルも更新できずに、アップグレードが終了します。

https://github.com/splitbrain/dokuwiki/archive/stable.tar.gz

対応として、自前で展開させたら、とりあえずうまくいきました。

# cd /home/Qhttpd/Web/dokuwiki/data/tmp
# tar -xzf dokuwiki-upgrade.tgz
# mv dokuwiki-stable/ dokuwiki-upgrade

Footnotes

  1. https://www.qnap.com/en-in/app_releasenotes/list.php?app_choose=DokuWiki

  2. ディレクトリが空っぽの理由は不明。

Dockerでメールサーバーを建てたときのメモ

4/30/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

メールサーバーをDockerで建ててみました。

imageとか

全部入りなimageを利用しました。

tomav/docker-mailserver: A fullstack but simple mailserver (smtp, imap, antispam, antivirus, ssl...) using Docker.

建て方はREADME.mdを参照

ハマったこと

接続できなくなる

接続に失敗1しすぎて、fail2banというソフトからBANされてました。

/var/log/fail2ban.log

2017-04-29 13:01:33,060 fail2ban.filter         [1022]: INFO    [dovecot] Found 203.0.113.0
2017-04-29 13:02:15,153 fail2ban.filter         [1022]: INFO    [dovecot] Found 203.0.113.0
2017-04-29 13:04:02,309 fail2ban.filter         [1022]: INFO    [dovecot] Found 203.0.113.0
2017-04-29 13:04:03,302 fail2ban.actions        [1022]: NOTICE  [dovecot] Ban 203.0.113.0

Let's Encryptの設定

推奨されているLet's EncryptをSSLに使う場合、wikiに書かれている通りLet's Encrypt関連の全体を含むパスの指定がdocker-compose.ymlに必要でした。ここを間違うと、つながるけど不正な証明書が届いたりしました。

volumes:
- /etc/letsencrypt:/etc/letsencrypt:ro

できあがったdocker-compose.yml

docker-compose.yml.distとの差分は以下のようになりました。

--- a/docker-compose.yml.dist
+++ b/docker-compose.yml
@@ -2,9 +2,9 @@ version: '2'
 
 services:
   mail:
-    image: tvial/docker-mailserver:2.1
+    image: tvial/docker-mailserver:latest
     hostname: mail
-    domainname: domain.com
+    domainname: netaka.net
     container_name: mail
     ports:
     - "25:25"
@@ -15,12 +15,14 @@ services:
     - maildata:/var/mail
     - mailstate:/var/mail-state
     - ./config/:/tmp/docker-mailserver/
+    - /etc/letsencrypt:/etc/letsencrypt:ro
     environment:
     - ENABLE_SPAMASSASSIN=1
     - ENABLE_CLAMAV=1
     - ENABLE_FAIL2BAN=1
     - ONE_DIR=1
-    - DMS_DEBUG=0
+    - DMS_DEBUG=1
+    - SSL_TYPE=letsencrypt
     cap_add:
     - NET_ADMIN
     restart: always

そして[email protected]のメールアカウントを作成した場合、メールクライアント2での設定は以下のようになりました。

Footnotes

  1. 単純にユーザー名等を間違えてただけでした。

  2. 図はmacOSのメール バージョン10.2 (3259)

Docker + nginx + WordPressに移行したときのメモ

4/25/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

Docker + nginx + WordPressに移行した際にハマったときのメモです。

Dockerの公式WordPressイメージで不足なPHPライブラリ

最初は公式のWordPressイメージ1をそのまま使いました。しかし、Duplicator2というWordPressプラグインが"Zip Archive Enabled"を要求していたので、Dockerfileを修正して必要なライブラリを追加しました。

https://github.com/docker-library/wordpress/blob/master/php5.6/apache/Dockerfile

        apt-get install -y \
                libjpeg-dev \
                libpng12-dev \
+               zip \
+               unzip \
        ; \
        rm -rf /var/lib/apt/lists/*; \
        \
        docker-php-ext-configure gd --with-png-dir=/usr --with-jpeg-dir=/usr; \
-       docker-php-ext-install gd mysqli opcache
+       docker-php-ext-install gd mysqli opcache zip

管理画面がリダイレクトループする

nginxで内部のhttp://127.0.0.1:8080/に対してリバースプロキシをおこなっていましたが、管理画面がリダイレクトループしてしまいました。wp-config.phpFORCE_SSL_ADMINの定義が必要でした。

管理画面での SSL 通信 - WordPress Codex 日本語版

管理画面が真っ白になる

上記のFORCE_SSL_ADMINの記載場所が間違っていたようです。

WordPress + HTTPS + リバースプロキシ = このページにアクセスする権限がありません。 | yohgaki's blog

Footnotes

  1. library/wordpress - Docker Hub

  2. Duplicator — WordPress Plugins

Node.jsを使ってnasneの録画本数を取得する

1/29/2017

この記事を書いてから6年が経っています。内容が古いかもしれません。

Node.jsを使ってネットワークレコーダーのnasneの録画本数を取得してみました。

こちらのpackageを利用しています。

upnp-device-client

実装とか

packageをインストールします。

$ npm install upnp-device-client

コードはこちらです。

var Client = require('upnp-device-client');
var fs = require('fs');
// nasneの名称とdevice description URLが記述されたJSONファイル
var obj = JSON.parse(fs.readFileSync('./nasne_list.json', 'utf8'));

obj.forEach(function(nasne) {
  var client = new Client(nasne.url);

  // "ビデオ/すべて"にぶら下がるオブジェクトを取得する。
  // 今回は録画本数として'TotalMatches'をそのまま利用している。
  client.callAction('ContentDirectory', 'Browse', { ObjectID: '0/video/all', BrowseFlag: 'BrowseDirectChildren', Filter: '', StartingIndex: 0, RequestedCount: 0, SortCriteria: '' }, function(err, result) {
    if(err) throw err;
    console.log(nasne.name + ": " + result.TotalMatches);
  });
});

コード中の'nasne_list.json'はこちらです。

[
  {
    "name": "nasne-1",
    "url": "http://192.168.1.11:58888"
  },
  {
    "name": "nasne-2",
    "url": "http://192.168.1.12:58888"
  }
]

結果

結果はこちらです。

$ node nasne.js
nasne-1: 873
nasne-2: 973

参考サイト

こちらを参考にしました。

Tumblrのダッシュボード新着記事数をLaMetric Timeに表示する

6/24/2016

この記事を書いてから7年が経っています。内容が古いかもしれません。

Tumblrの新着ポストが気になってしまい、ついついDashboardを見てしまいます。そこで新着ポスト数を示せれば、数ポストのために頻繁に見ることも防げるのではないかと思いました。今回はTumblrのDashboard新着ポスト数をLaMetric Timeに表示してみました。

新着ポスト数の定義は、Dashboardの最新のポストから自分がリブログしたポストまでの数としました。Dashboardみてリブログしないなんてことはまずないので、つまり最後のリブログ以降は新着ということに。

Dashboardの新着ポスト数を取得する

こちらの記事を参考にTumblr用にアプリを作成してみました。

Tumblr APIでwebサービスを作りたい全ての人に向けて書きました

次に、指定した"blog_name"のポストが取得したDashboardのうち何番目のポストか調べます。最大取得ポストは20件ということなので、もし該当するポストが含まれていなかったら、オフセットを指定して次の20件を試みます。インデックスを返すコードは以下のような感じです。自分がリブログしたポストが出てきたら、そこまでのオフセットが新着ポスト数になります。

<?php
require './tumblr-oauth-library.php'; // OAuth関連は上の記事から流用

function getDashboardIndex($name, $offset) {
    $access_token = '';
    $access_token_secret = '';
    $request_url = 'https://api.tumblr.com/v2/user/dashboard';
    $request_method = 'GET';
    $params = array(
        'oauth_token' => $access_token ,
        'oauth_token_secret' => $access_token_secret ,
        'offset' => $offset ,
    );

    $json = tumblr_oauth($request_url, $request_method, $params);
    $array = json_decode($json, true);
    $posts = $array["response"]["posts"];
    $count = 0;
    foreach($posts as $post) {
        if($post["blog_name"] == $name) {
            return $count;
        }
        $count++;
    }
    return -1;
}
?>

新着ポスト数をLaMetric Timeに表示する

最後に新着ポスト数をLaMetric Timeに表示します。次の記事を参考に、LaMetric用アプリを作成しました。

ネットとつながる LED 時計の LaMetric を手に入れたので遊んでみた - 凹みTips

Indicator AppでPollで設定して、JSON形式で新着ポスト数を返します。

PHPでJSONのデータを処理する方法

できました

今回は256件までを上限としました。それ以上のポストを取得しても自分のリブログが見つからなかったら打ち切りとし、「256+」と表示するようにしています。

Dashboard_LaMetric
Dashboard_LaMetric

Pythonでプロフィールの説明を更新する

6/30/2015

この記事を書いてから8年が経っています。内容が古いかもしれません。

Pythonでプロフィールの説明を更新してみました。

以下を参考にしました。

コードはこちらです。別ファイルのtwitter_oauth.pyにOAUTHに必要なトークン等が記載されています。

# -*- coding: utf-8 -*-
from twython import Twython
import twitter_oauth

def update_profile(text):

    twitter = Twython(
        twitter_oauth.CONSUMER_KEY,
        twitter_oauth.CONSUMER_SECRET,
        twitter_oauth.ACCESS_KEY,
        twitter_oauth.ACCESS_SECRET
        )

    twitter.update_profile(description=text)

def main():
    text = 'A long time ago, in a galaxy far, far away...'
    update_profile(text)

if __name__ == '__main__':
    main()

とりあえずこのコードを使って、1日おきにプロフィールの説明を更新するようにしています。

PHP FastCGIの自動起動について

6/28/2015

この記事を書いてから8年が経っています。内容が古いかもしれません。

さくらのVPSがメンテナンスで再起動させられてたので、netaka.netが動かなくなっていました。おそらく、php-fastcgiが止まっています。下記コマンドで起動させました。

sudo /etc/init.d/php-fastcgi start

また再起動したときに手間なので自動起動するように設定します。詳しくはLinux - chkconfigまとめ - Qiita

$ sudo chkconfig php-fastcgi on
サービス php-fastcgi は、chkconfig をサポートしていません。

あれれ。chkconfigに関する設定がphp-fastcgiに記述されてないといけないようでした。詳しくはWordPress サイトに nginx を導入する | dogmap.jp。以下を追加して、

# chkconfig:   - 80 20

リトライしたらうまく登録出来ました。めでたしめでたし。

$ chkconfig --list
php-fastcgi     0:off   1:off   2:on    3:on    4:on    5:on    6:off

WordPressをさくらVPSに移行してみる

4/29/2015

この記事を書いてから8年が経っています。内容が古いかもしれません。

さくらのレンタルサーバーで動かしていたWordPressをさくらVPSに移してみました。

参考にした記事

こちらを参考に移行しました。

移行ではまった点

はまった点をいくつか記載します。

NginxでPHPを設定した後、Not Foundになる

rootの設定が間違っていました。

nginx + php-fpm で設定したが index.php にアクセスするとNOT FOUND(404)になってしまう : saba nano - へっぽこ管理者のサーバ管理日誌(LV.2)

WordPressインストール後、プラグインがインストール出来ない トップページが表示され一見問題がなさそうでしたが、プラグインがインストール出来ませんでした。

「要求されたアクションを実行するには、WordPress が Web サーバーにアクセスする必要があります。 」って怒られた - Qiita

パーマリンク設定後、404 Not Found になる index.phpへrewriteする必要があるようです。

WordPressのパーマリンクを投稿名にする方法(nginx) » EC2にWordPressをNginxで! - Tommy Sys

.phpがダウンロードされる

.phpファイル本体がそのままダウンロードされていました。

nginxでバーチャルホスト設定したらphpがダウンロードされる件 « Demence/Cup

PebbleにTwitterのアイコンを表示する

12/29/2014

この記事を書いてから8年が経っています。内容が古いかもしれません。

PebbleにTwitterのアイコンを表示してみました。

以下を参考にしました。

CloudPeppleにあるサンプルを組み合わせて、Twitterのアイコンを出してみました。まずはテンプレートのExamples->Draw Bitmapを選びます。

Create Draw Bitmap
Create Draw Bitmap

あとは適当に画像の位置を調整して、同じくテンプレートのSDK demos->HelloWorldも参考にしてテキスト表示させたコードが以下になります。

#include "pebble.h"

static GBitmap *image;
static TextLayer *text_layer;

void text_init(Window *window) {
    text_layer = text_layer_create( GRect(0, 128, 144, 32) );

    text_layer_set_text(text_layer, "@netaka");
    text_layer_set_font(text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28_BOLD));
    text_layer_set_text_alignment(text_layer, GTextAlignmentCenter);

    layer_add_child(window_get_root_layer(window), text_layer_get_layer(text_layer));
}

static void layer_update_callback(Layer *me, GContext* ctx) {
    GRect bounds = image-&gt;bounds;
    graphics_draw_bitmap_in_rect(ctx, image, (GRect) { .origin = { 8, 0 }, .size = bounds.size });
}

int main(void) {
    Window *window = window_create();
    window_stack_push(window, true);

    Layer *window_layer = window_get_root_layer(window);
    layer_set_update_proc(window_layer, layer_update_callback);

    image = gbitmap_create_with_resource(RESOURCE_ID_IMAGE_TWITTER);

    text_init(window);

    app_event_loop();

    text_layer_destroy(text_layer);
    gbitmap_destroy(image);
    window_destroy(window);
}

そして表示したい画像をメニューにあるRESOURCESからアップロードしましょう。最後にビルドしてPebbleにインストールすると写真のようになりました。

pebble_image
pebble_image

はじめ、Watch Faceにしたかったのに、どうしてもWatch Appでビルドされてしまうのでどうしてなのかと小一時間悩みましたが、メニューにあるSETTINGSから切り替えられました。

ChangeWatchface
ChangeWatchface

ほんとはTwitterのAPIにアクセスしてアイコン取り寄せるとかそこまでしたいですね。それにしても簡単に画像が表示できるので、LINEのQRコードとかを表示したりすると友達追加の時に便利そうです。

Chromeの拡張機能をいじってみる

12/20/2014

この記事を書いてから8年が経っています。内容が古いかもしれません。

Chromeの拡張機能をいじってみました。

以下を参考にしました。

Just Tweet ボタンという見ているページをTwitterに共有する拡張機能があるのですが、ツイートの先頭に" << "が入ってしまいます。これを取り除いてみようと思います。

ツイート画面
ツイート画面

デベロッパーモードにして、バックグラウンド ページを開くと編集画面にいけます。

デベロッパーモードをオン
デベロッパーモードをオン

バックグラウンド ページを開く
バックグラウンド ページを開く

そしたら、取り除きたい" << "がすぐに見つかると思うのでそこを削除しちゃいましょう。

編集します
編集します

簡単でしたね。はじめは、background.jsが格納されているところまでいって、直接編集しないといけないのかと思っていました。Macだと、このPathにありました。"/Users/Name/Library/Application Support/Google/Chrome/Default/Extensions/feikojefkpembojkeegfajbbfecocddd/1.0_0"。でもそんなことすると破損しているだとか、マルウェアによって改ざんされているとかで無効にされちゃうので、直接編集するべきではないようです。

破損しているといわれました
破損しているといわれました

PythonでTwitterのタイムラインを取得する

12/7/2014

この記事を書いてから9年が経っています。内容が古いかもしれません。

PythonでTwitterのタイムラインを取得してみました。

以下を参考にしました。

コードはこちらです。別ファイルのtwitter_oauth.pyにOAUTHに必要なトークン等が記載されています。

# -*- coding: utf-8 -*-
from twython import Twython
import twitter_oauth

def get_timeline(user, num):
    twitter = Twython(
        twitter_oauth.CONSUMER_KEY,
        twitter_oauth.CONSUMER_SECRET,
        twitter_oauth.ACCESS_KEY,
        twitter_oauth.ACCESS_SECRET
    )

    timeline = twitter.get_user_timeline(user_id=user, count=num)
    return timeline

def main():
    user = 'netaka'
    timeline = get_timeline(user, 10)
    for tweet in timeline:
        print tweet['text']

if __name__ == '__main__':
    main()

取得できるツイート件数のデフォルトは20件のようなのでご注意ください。

Pythonでツイートを投稿する

12/7/2014

この記事を書いてから9年が経っています。内容が古いかもしれません。

Pythonでツイートを投稿してみました。

以下を参考にしました。

コードはこちらです。別ファイルのtwitter_oauth.pyにOAUTHに必要なトークン等が記載されています。

# -*- coding: utf-8 -*-
from twython import Twython
import twitter_oauth

def post_tweet(text):
    twitter = Twython(
        twitter_oauth.CONSUMER_KEY,
        twitter_oauth.CONSUMER_SECRET,
        twitter_oauth.ACCESS_KEY,
        twitter_oauth.ACCESS_SECRET
    )

    twitter.update_status(status=text)

def main():
    text = "にゃんぱすー"
    post_tweet(text)

if __name__ == '__main__':
    main()

簡単にツイートできて便利ですね。

PythonでTwitterのリプライを取得する

12/2/2014

この記事を書いてから9年が経っています。内容が古いかもしれません。

PythonでTwitterのリプライを取得してみました。

以下を参考にしました。

コードはこちらです。別ファイルのtwitter_oauth.pyにOAUTHに必要なトークン等が記載されています。

# -*- coding: utf-8 -*-
from twython import Twython
import twitter_oauth

def get_mentions(status_id):

    twitter = Twython(
        twitter_oauth.CONSUMER_KEY,
        twitter_oauth.CONSUMER_SECRET,
        twitter_oauth.ACCESS_KEY,
        twitter_oauth.ACCESS_SECRET
    )

    mentions = twitter.get_mentions_timeline(count=200, since_id=status_id)

    return mentions

def get_reply(status_id):

    mentions = get_mentions(status_id)
    result = ""
    for mention in mentions:
        if mention['in_reply_to_status_id'] == status_id:
            result = mention['text']

    return result

def main():
    print get_reply(534338621193474048)

if __name__ == '__main__':
    main()

get_reply(status_id)は取得できる200件のmentionのうち、指定したステータスIDの一番古いリプライを返すはずです。実行結果は以下な感じです。

$ python reply_twitter.py
@netaka ねたか ずっと探してたのよ

Pythonでプロフィール画像を更新する

11/26/2014

この記事を書いてから9年が経っています。内容が古いかもしれません。

Pythonでプロフィール画像を更新してみました。

以下を参考にしました。

コードはこちらです。別ファイルのtwitter_oauth.pyにOAUTHに必要なトークン等が記載されています。

# -*- coding: utf-8 -*-
from twython import Twython
import twitter_oauth
import base64

def update_profile_image(filename):
    file = open(filename, 'rt').read()
    enc_image = base64.b64encode(file)

    twitter = Twython(
        twitter_oauth.CONSUMER_KEY,
        twitter_oauth.CONSUMER_SECRET,
        twitter_oauth.ACCESS_KEY,
        twitter_oauth.ACCESS_SECRET
    )

    twitter.update_profile_image(image=enc_image)

def main():
    filename = './kuro.png'
    update_profile_image(filename)

if __name__ == '__main__':
    main()

base64エンコード、めっちゃ楽ですね。とりあえずこのコードを使って、3時間おきにプロフィール画像を更新するようにしています。

Pythonで埋め込みツイートを取得する

11/24/2014

この記事を書いてから9年が経っています。内容が古いかもしれません。

Pythonで指定したステータスIDから埋め込みツイートを取得してみました。

以下を参考に取得しています。

コードはこちらです。別ファイルのtwitter_oauth.pyにOAUTHに必要なトークン等が記載されています。

# -*- coding: utf-8 -*-
from twython import Twython
import twitter_oauth

def get_embed_tweet(embed_id):
    twitter = Twython(
        twitter_oauth.CONSUMER_KEY,
        twitter_oauth.CONSUMER_SECRET,
        twitter_oauth.ACCESS_KEY,
        twitter_oauth.ACCESS_SECRET
    )

    embed = twitter.get_oembed_tweet(id=embed_id)

    if embed.has_key('html'):
        return embed['html']

    return ""

def main():
    id = 536174907856404481
    print get_embed_tweet(id)

if __name__ == '__main__':
    main()

取得した埋め込みツイートはこちらです。

PythonでTwitterにツイート

8/6/2013

この記事を書いてから10年が経っています。内容が古いかもしれません。

python-twitterでTwitterにツイートしていましたが、動かなくなっていました。

python-twitterではなく、twythonならツイート出来ました。 Python-twitterをつかったTwitter API 1.1のツイート取得と投稿 - VivoWiki こちらも試しましたがうまくいかず。環境はさくらVPSです。

TS-219PIIにPS3 Media Serverを入れようとした

9/29/2012

この記事を書いてから11年が経っています。内容が古いかもしれません。

QNAPのNAS、TS-219PIIにPS3 Media Serverをいれようとしたメモです。

下記の2chのログを参考にしました。

【静音・高機能NAS】QNAP part7【自宅サーバー】 - 2ちゃんねるキャッシュ

>>461 にある内容を元に、インストールを進めていきましたが、mkfifoが見つからないと言われて、よくわからなくなり諦めました。おしまい。

Flex 4 SDKをつかって、Adobe AIRでHello World!

8/6/2011

この記事を書いてから12年が経っています。内容が古いかもしれません。

アプリケーションのインストール
アプリケーションのインストール

Adobe AIRでHello World!するための備忘録です。流れとしては、こちらが参考になります。

ではSDKのインストールから、Hello World!の作成までは以下から。

準備しましょう

まずは、Adobe AIRとFlex 4 SDKをインストールしましょう。

SDKは解凍して、/Developer/SDKsにおきました。中のbinディレクトリをパスに通しましょう。

SWFファイルをつくろう

今度はHello World!のプログラムを保存しましょう。

package
{
    import flash.display.*;
    import flash.text.*;
    public class main extends Sprite
    {
        public function main()
        {
            var textField:TextField = new TextField();
            textField.text = "Hello World!";
            addChild( textField );
        }
    }
}

保存したファイルを、main.asとします。amxmlcでコンパイルしましょう。

$ amxmlc main.as

main.swfができました。ブラウザに渡せば表示されるはずです。

AIRアプリケーションをつくろう

AIRのファイルを作るには、XMLファイルが必要になります。SDKを解凍した中にある、flex_sdk_4.5.1/templates/air/descriptor-template.xmlがテンプレートになります。必要なのはこれぐらいになります。

<application xmlns="http://ns.adobe.com/air/application/2.6">
 <id>class-exe.helloworld.</id>
 <filename>HelloWorld</filename>
 <versionNumber>1.0.0</versionNumber>
 <initialWindow>
 <content>main.swf</content>
 <title>HelloWorld</title>
 <visible>true</visible>
 </initialWindow>
</application>

はまったのが、どこかのXMLファイルをコピペしたら、xmlnsの末尾の数字が適切ではなくて、うまくいかないことでした。

あとは最初に紹介したサイトを参考にして、adtコマンドを使用して、署名ファイルを生成して、パッケージ化すると

$ adt -certificate -cn SelfSigned 1024-RSA main.p12 password
$ adt -package -storetype pkcs12 -keystore main.p12 -storepass password main.air main.xml main.swf

airファイルができるはずです。

airファイルをクリックすれば、

アプリケーションのインストール
アプリケーションのインストール

インストールが表示されます。実行すれば、

Adobe AIRでHello World!
Adobe AIRでHello World!

デスクトップのアプリケーションとして表示されました。

簡易カウントダウン(秒)

1/11/2011

この記事を書いてから12年が経っています。内容が古いかもしれません。

カウントダウン
カウントダウン

簡易なカウントダウンをjQueryで作ってみました。カウントダウンのプラグインを利用しています。プラグインはこちらjQuery Countdownです。機能は日付と時刻の変更と、全画面表示のみです。クリックすると全画面になります。

実際に動いているカウントダウンは以下から。

OpenCVでスクリーンショット(Windows)

1/7/2011

この記事を書いてから12年が経っています。内容が古いかもしれません。

スクリーンショット(Windows)
スクリーンショット(Windows)

OpenCVでデスクトップの画像を扱う方法です。前回のWindows版です。OpenCVのバージョンは前回と同じく2.2です。適切なライブラリを指定してください。

こちらを参考にしました。

うまくいけば、画像のようになるはずです。ソースコードは以下から。

#include <opencv2/opencv.hpp>

int main (int argc, char * const argv[])
{
    /* デスクトップのサイズ */
    HWND desktop = GetDesktopWindow();
    RECT rect;
    GetWindowRect(desktop, &rect);
    int width = rect.right;
    int height = rect.bottom;

    /* RGB用と反転用とリサイズ用のIplImageの作成 */
    IplImage *iplimage = cvCreateImageHeader(cvSize(width, height), IPL_DEPTH_8U, 3);
    IplImage *flipimage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
    IplImage *resizeimage = cvCreateImage(cvSize(width/2, height/2), IPL_DEPTH_8U, 3);

    /* DIBの情報を設定する */
    BITMAPINFO bmpInfo;
    bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmpInfo.bmiHeader.biWidth = width;
    bmpInfo.bmiHeader.biHeight = height;
    bmpInfo.bmiHeader.biPlanes = 1;
    bmpInfo.bmiHeader.biBitCount = 24;
    bmpInfo.bmiHeader.biCompression = BI_RGB;

    /* DIBSection作成 */
    LPDWORD lpPixel;
    HDC hDC = GetDC(desktop);
    HBITMAP hBitmap = CreateDIBSection(hDC, &bmpInfo, DIB_RGB_COLORS, (void**)&lpPixel, NULL, 0);
    HDC hMemDC = CreateCompatibleDC(hDC);
    SelectObject(hMemDC, hBitmap);

    /* IplImageヘッダにデータをセット */
    iplimage->imageData = (char *)lpPixel;

    /* ウィンドウ */
    cv::namedWindow("Screenshot", CV_WINDOW_AUTOSIZE);

    while (true)
    {
        /* デスクトップから取得 */
        BitBlt(hMemDC, 0, 0, width, height, hDC, 0, 0, SRCCOPY);

        /* 上下反転して,リサイズ */
        cvFlip(iplimage, flipimage);
        cvResize(flipimage, resizeimage);

        /* ウィンドウへ表示 */
        cv::imshow("Screenshot", resizeimage);
        if(cv::waitKey(30) >= 0) break;
    }

    /* 解放 */
    cvReleaseImageHeader(&iplimage);
    cvReleaseImage(&flipimage);
    cvReleaseImage(&resizeimage);
    ReleaseDC(desktop, hDC);
    DeleteDC(hMemDC);
    DeleteObject(hBitmap);

    return 0;
}

OpenCVでスクリーンショット(Mac OS X)

12/31/2010

この記事を書いてから12年が経っています。内容が古いかもしれません。

スクリーンショット(Mac OSX)
スクリーンショット(Mac OSX)

OpenCVでデスクトップの画像を処理する方法です。直接、スクリーンショットを取得します。OpenCVのバージョンは2.2を使用しています。Xcodeで「ApplicationServices.framework」を追加しておいてください。

こちらを参考にしました。

うまくいけば、画像のようになるはずです。ソースコードは以下から。

#include
#include

int main (int argc, char * const argv[])
{
    /* ディスプレイのサイズ */
    size_t width = CGDisplayPixelsWide(CGMainDisplayID());
    size_t height = CGDisplayPixelsHigh(CGMainDisplayID());

    /* RGBA用とBGR用のIplImageの作成 */
    IplImage *iplimage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 4);
    IplImage *bgrimage = cvCreateImage(cvSize(width, height), IPL_DEPTH_8U, 3);
    IplImage *resizeimage = cvCreateImage(cvSize(width/2, height/2), IPL_DEPTH_8U, 3);

    /* グラフィックコンテキストの作成 */
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef contextRef = CGBitmapContextCreate(
        iplimage->imageData, width, height,
        iplimage->depth, iplimage->widthStep,
        colorSpace, kCGImageAlphaPremultipliedLast|kCGBitmapByteOrderDefault);

    /* ウィンドウ */
    cv::namedWindow("Screenshot", CV_WINDOW_AUTOSIZE);

    while (true)
    {
        /* メインディスプレイからCGImageRefを取得 */
        CGImageRef imageRef = CGDisplayCreateImage(CGMainDisplayID());
            CGContextDrawImage(contextRef,
            CGRectMake(0, 0, width, height),
            imageRef);

        /* RGBAからBGRに変換して,リサイズ */
        cvCvtColor(iplimage, bgrimage, CV_RGBA2BGR);
        cvResize(bgrimage, resizeimage);
        /* ウィンドウへ表示 */
        cv::imshow("Screenshot", resizeimage);

        CGImageRelease(imageRef);
        if(cv::waitKey(30) >= 0) break;
    }

    /* 解放 */
    cvReleaseImage(&iplimage);
    cvReleaseImage(&bgrimage);
    cvReleaseImage(&resizeimage);
    CGContextRelease(contextRef);
    CGColorSpaceRelease(colorSpace);

    return 0;
}

Metasequoiaで作ったモデルをSolidWorksで読み込む方法

12/20/2010

この記事を書いてから12年が経っています。内容が古いかもしれません。

Metasequoia上のモデル
Metasequoia上のモデル

Metasequoiaで作ったモデルを、SolidWorksに読み込ませてから3Dプリンタなどに出力したい時は次の手順でできます。いくつかのソフトを使ってSolidWorksで読めるstlファイルに最終的に変換する方法です。まず用意するもの。

Google SketchUp単体ではstlファイルに保存はできないので、Convert Sketchup SKP files to DXF or STL | Download Sketchup pluginにあるプラグインを導入しておいてください。

まず、Metasequioaでモデルを作ります。ファイルはmqoで保存します。

3DAce上のモデル
3DAce上のモデル

次に、入手した3DAceで保存したmqoファイルを開きます。ここでは特にやることなく「名前を付けて保存」で「ファイルの種類」を「3DSファイル(*.3ds)」で保存します。

Google SketchUp上のモデル
Google SketchUp上のモデル

次に、Google SketchUpで、保存した3dsファイルを開きます。「ファイル → インポート」から開きます。開いたら今度は「ツール → Export to DXF or STL」で保存します。ダイアログが出てくるので「Export unit」は単位を選んで、「Export to DXF options」は「stl」を選んで保存します。

SolidWorks上のモデル
SolidWorks上のモデル

この手順でstlファイルに変換できるので、SolidWorksに読み込ませることができます。ですが、SolidWorksが使える(お金がある)なら、(わざわざ無料版使わずに)有料版のMetasequoiaでプラグインを導入して直接stlに変換してしまったほうが早いかもしれません。ちなみに作成したstlファイルをCatalystEXで読み込ませた場合は下のようになります。

CatalystEX上のモデル
CatalystEX上のモデル

※ネットなどで拾ったモデルを、単にファイル変換しただけでは、CatalystEX上ではうまくいかない事もあるので少し弄る必要はあります。

小文字文章メーカー

12/19/2010

この記事を書いてから12年が経っています。内容が古いかもしれません。

小文字文章メーカー
小文字文章メーカー

㍆㍉㌜㌏㌜(クリスマス)な小文字の単位の文章を簡単に作れるように、小文字文章メーカーを作ってみました。jQueryを使っています。

jQueryで<textarea>のカーソル位置に文字を挿入するのに、textareaのカーソル位置に文字列挿入 - PSLブログ(仮)、要素をソートするのに、Sorting elements with jQuery – James Padolseyを参考にしました(というかコピペしました)。

使い方は、対応するボタンを押してください。二つあるテキストボックスのうち、上に小文字の文章が、下に小文字の下部分の読みを表示します。完成したらTwitterに投稿してみてください

曜日は怖いよプログラム

11/16/2010

この記事を書いてから13年が経っています。内容が古いかもしれません。

monday
monday

明日は月曜日ネタのAAをプログラムにしました。プログラムはこちらです。右上の[x]では閉じないので、キーボードのどれかを押下してください。

││┝┥┌──────────────────┴┐<  明日は月曜日
│││┝┥┌──────────────────┴┐\  / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
││││┝┥┌──────────────────┴┐< 明日は月曜日
│││││┝┥        .明日は月曜日          [×]|  \/ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
┤│││││┝━━━━━━━━━━━━━━━━━━| \カチ<  明日は月曜日
└┤│││││    |      ̄ ̄ ̄    ヽ         ヽゝカチ \ / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
  └┤││││    | ̄ ̄ ̄月曜日 ̄ ̄ ̄)           │ カチ <  明日は月曜日
    └┤│││    | ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄.\           | カチ   \ / ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
      └┤││    |ヽ-=・=-′ ヽ-=・=-  / やあ       | カチ   < 明日は月曜日
        └┤│    |::    \___/    /          | カチ    \___________
          └┤    |:::::::    \/     /           | カチ
            └─────────────────――┘ カチ

MATLABのGUIで複数のグラフを選ぶ方法

11/16/2010

この記事を書いてから13年が経っています。内容が古いかもしれません。

MATLABのGUI
MATLABのGUI

こんな感じにGUIの中にグラフが複数ある場合、plot(x, y) するには、

plot(handles.axes1, x, y)

という風に指定できる。handles.axes1はGUIDEに表示されていて、タグというのがaxes1に該当します。

ここを参考にしました。複数の座標軸をもつ GUI :: GUIDE GUI の例 (MATLAB®)