SHOWROOM 星集め・星投げツール スケジュールの詳細化 BreakDownSchedule() (三周のやり方を例に)
例えば「15時00分からの公式枠・○○さんの配信で3周する」という目標を立てます。そうするとそれを実現するために配信開始前・開始後にいろんな"作業"が必要になります。
(1)~(15)が必要な"作業"です。このブログの記事(とソース中のコメント)では、次のような"用語"を使うことにします。
・スケジュール
配信予定とそれに対する対応の計画をいいます。
「公式枠・○○さんの15時00分から90分の配信で最初3周+50カウントし、次の解除で1周+50カウントする。解除は15時15分」
みたいなやつです。スケジュールはスケジュールファイルとしてプログラムの入力となります。スケジュールファイルには複数のスケジュールを書くことができます。
・タスク
スケジュールを実現するために必要な作業の一つ一つを言います。星・種を集める、星・種を投げる、カウントするがそれに相当します。
上の図で(1)から(15)で示したものがそれです。
・タスクリスト
一日のすべてのタスクの集まりです。タスクリストファイルに書き込まれています。
・タスクグループ
指定した時刻に始めるべきタスクとそれに継続して実行する一連のタスクを言います。
上の図で配信開始時に行われる「(3) 1周目分の星投げ、(4) 2周目分の星集め、(5) 星投げ」の三つのタスクの集合がそれです。
-------------------------------------
このプログラムではプログラム実行開始時(と任意の時刻に)スケジュールをファイルから読み込み、タスクリストを作成してファイルに書き込みます。
その後、毎分0秒にタスクリストファイルを読み込み、もしタスク実行時間が現在時に一致するものが見つかれば、実行時間が一致するタスクから開始されるタスクグループを抽出します。
そして抽出したタスクグループにしたがって個々のタスクを実行していきます。
今回はスケジュールからタスクリストを作成しファイルに書き込む関数 BreakDownSchedule()のソースを紹介します。
なお、このソースはShowroomLibパッケージから該当部分を抜粋したものであり、このままではコンパイルできませんので念の為。
ShowroomLibパッケージのソース全体は後日公開します。
※ main()については 「SHOWROOM 新・自動三周ツール -- GO言語によるブラウザ制御 (1) main()」の記事にあります。
=========================================
スケジュールファイルの例
(15:30配信開始、配信時間は60分、解除は開始900秒後、3周、公式枠、アカウントはnamexxxx)
# 1. 配信開始時刻 HH:MM
#
# 2. 予定配信時間(分) 1時間配信+カウント枠のケースは(たとえば)90とします。
#
# 3. 配信開始から解除までの時間(秒) 配信開始が15時、解除が15時15分であれば900とします。
#
# 4. カテゴリー Official:公式枠、Amateur:アマチュア枠
#
# 5. 対象配信ルームのURLの最後のフィールド(アカウント)
#
# 6. 備考
#
15:30 60 900 3 Official namexxxx test
=========================================
タスクファイルの例
# 1. タスク種別 C:星(種)集め、T:星(種)投げ、N:50カウント
#
# 2. タスク開始時刻 HH:MM:SS
# ただし、99:99:99 は直前のタスクに引き続き実行する。
#
# 3. カテゴリー 0:公式枠、1:アマチュア枠
#
# 4. タスクが"C"のとき、集めるべき各色ごとの星(種)の数
# タスクが"T"のとき、星(種)投げの回数
# すべての色で10個(あるいは9個)投げるのを1回とします。
# タスクが"N"のとき、カウントの回数(通常50)
#
# 5. タスクが"C"のとき、1:配信ルームリストで昇順に集める
# -1:配信ルームリストで降順に集める
# (通常は昇順で、降順にするのは3周の2周目分のときです)
#
# 6 タスクが"C"のとき、true:現在の星数にかかわらずかならず一回星を集める
# false:目標星数があれば星集めはしない
# (通常はfalseで、trueにするのはいわゆる"捨て星"のときです)
#
# 7 タスクが"C"のとき、true:配信ルームリストを作り直さない
# false:配信ルームリストを作り直す。
# (通常はfalseで、trueにするのは星集め・星投げを2分割するときの2回目です)
#
# 8. タスクが"T"のとき、対象配信ルームのURLの最後のフィールド(アカウント)
#
C 13:44:20 0 99 1 false false n/a
C 14:44:20 0 99 1 true false n/a
T 15:30:00 0 10 0 false false namexxxx
C 99:99:99 0 90 -1 false false n/a
T 99:99:99 0 9 0 false false namexxxx
C 15:44:20 0 50 1 false false n/a
T 99:99:99 0 5 0 false false namexxxx
C 99:99:99 0 50 1 false true n/a
T 99:99:99 0 5 0 false false namexxxx
N 99:99:99 0 50 1 false false namexxxx
開始時刻が99時99分99秒となっているのは、直前のタスクに継続して順次実行することを意味しています。
したがってこのタスクファイルには4つのタスクグループがあります。
解除は開始900秒後ですから、3周目分の星集め開始時刻は15時45分となるわけですが、その前に配信ルームリストを作らなければならないので星集めのタスクの開始時刻は15時44分20秒となっています。実際に星を集め始めるのは15時45分00秒となります。
=========================================
main()
「SHOWROOM 新・自動三周ツール -- GO言語によるブラウザ制御 (1) main()」
SetEnvironment() 環境やポリシーに依存するパラメータを設定します。
SetBlackList() 星集め/種集めの対象としないルームのリストを作ります。
GetCategoryList() 公式枠/アマチュア枠に該当するジャンル(の位置)をオンライブ画面から検出します。
BreakDownSchedule() 配信スケジュールから星集め/種集め、星投げ/種投げ、カウントの
「SHOWROOM 星集め・星投げツール スケジュールの詳細化 BreakDownSchedule() (三周のやり方を例に)」
Scheduler() タスクリストにしたがって星集め/種集め、星投げ/種投げ、カウントを行います。
MakeTaskList() タスクリストから現在時(時分)に実行すべきタスクの集まり(タスクグループ)を抜き出します。
GetRoomList() 星集め/種集めの対象とするルームのリストを作ります。
CollectStarts() 星集め/種集めを行います。
ThrowStars() 星投げ/種投げを行います。
Count50() カウントを行います。
共通して使用する主な関数
MakeNewPage() ブラウザ画面を開きます。
ClosePage() ブラウザ画面を閉じます。
=========================================
=========================================
package ShowroomLib
import (
"bufio"
"log"
"os"
"time"
"fmt"
"math/rand"
"strconv"
"strings"
"github.com/sclevine/agouti"
)
// 配信者ルームの情報
type RoomInf struct {
URL string // 配信者ルームのURL
name string // 配信者ルームのタイトル
start int // 配信開始時刻は分で表したもの
// 例 現在時刻が08:20のとき 500 ( = 8*60 + 20 )
}
// プログラム実行に必要な環境や実行時の状態を保存している構造体です。
//
// 重要
// 最初の星を21時30分00秒に集め始めると星がもらえるのは21時30分30秒となります。
// この場合次の星集めができるのは22時00分00秒で、その星は22時30分30秒にもらえます。
// このプログラムのコメントでは、上の例の場合解除時刻は(22時00分30秒ではなく)22時00分00秒であるとしています。
// 人によって"解除"という言葉の使い方が違うようなので念のため書いておきます。
//
type Environment struct {
AgoutiDriver [2]*agouti.WebDriver // Webドライバーへのポインタ(0: 星集め用あるいは星集め・星投げ兼用ブラウザ、 1: 星投げ・視聴用ブラウザ)
Page [2]*agouti.Page // ブラウザ画面へのポインタ(〃)
ChromeTmpFolder [2]string // Chrome作業用フォルダ、事前にSHOWROOMを視聴できる環境を作っておきます(〃)
FileNameOfCatName string // ジャンル名とタイプ(公式枠/アマチュア枠)の対応リストのファイル名を指定します。
FileNameOfBlackList string // 星集め/種集めの対象としないルームのURLのリストのファイル名です。
FileNameOfLog string // ログファイルの名称
FileNameOfSchedule string // 誰のいつの配信で何周するかを記述したファイルの名称です。
FileNameOfTaskList string // 星集め・種集めや星投げ・種投げの時刻と方法を記述したファイルの名称です。
MaxNoOfRoom int // ルーム情報を取得するルーム(星・種を集めるルームの数)の数(上限)
// これも星集めを目的としたときのためにあります。星集めは10ルーム分集めればいいので
// 配信ルームのリストは(データを収集した直後に配信をやめたルームがあるを考えたり
// 投票モードになっているルームを除外したりしても20個もあれば十分ですから。
// なお実際の取得数( < 配信ルーム数)がここで指定したルーム数より少ないこともありえます。
ApplicableTime int // 星集め/種集めの対象となるルームの配信開始時刻の範囲を分で示したものです。
// 現在の時刻が12:00でApplicableTimeが50であれば配信開始時刻が11:11以後のルームが
// 星集め/種集めの対象となります。更新をしていないルームに複数回星集めに行くことを避けるためです。
Margin int // 星集めの指示を受け取ってから実際に星を集めはじめるまでの時間(秒)です。
// 星集めはその前に配信ルームリストを作る必要があり、それに要する時間は正確には予測できません。
// 星集めの開始時間を正確にコントロールするためにタスクリストに示された時刻からMarginで定められた時間が
// 経過したのちに星を集め始めます。
// したがってタスクリスト上の星集め開始時刻は、配信開始時刻と解除時間から計算される時刻より
// Marginで示された秒数を減じた時刻にする必要がありますが、この操作はMakeTaskList()で自動的に
// 行われます。
NoColFirst int // 星を前半・後半と2分割して投げるとき、前半に投げる各色の星の数です。
// 種が各色100個集められる場合は全部集めてから投げると1個損することになります。
// そこで種を途中まで集めて、いったんこれを投げ、残りをあらためて集めて投げるようにするとムダがなくなります。
// これが前半・後半と2分割して投げる理由です。
// これはあくまで星が100個集められるときの設定です。
// 三周する場合の1周目分を投げるときは星は各色99個しかありませんのでこの設定にかかわらず一回で投げてしまいます。
// また1個ムダにしてもいいので分割せずひとまとめに投げたいときは設定値を99とします。
Thtime int // いわゆるカウント枠の時間がこの設定(分)を越えたときは星投げを行います。
// 例えば Thtime = 20 であれば配信時間が80分(あるいは140分、200分、...)以上であればカウント枠での
// 星投げを行い、79分(139分、199分、...)以下であれば行いません。
// これはあくまでスケジュールに設定された配信予定に基づいて行います。
// 配信が伸びた、短くなった、というのに対応できるわけではありません。
WaitDst int // 配信開始が遅れた場合、開始を待つ時間(秒)
// ここからは設定値ではなく実行状況を示す変数です。
RoomCollectedLast int // 最後に星を集めたルームの配信ルームリスト上の位置(星集めを2回に分けておこなうときのため)
Delay int // 星集め対象のルームの配信が星集め中に終了したなどで、解除の時間が想定より遅くなった場合
// 遅れた時間(の累積)を設定します。初期値は0で
}
// タスク情報、タスクリスト・タスクグループはタスク情報の配列(スライス)になります。
type Task struct {
command string // タスクの種別 C:星集め、T:星投げ、N:カウント
hh int // タスクの開始時刻(時)
mm int // タスクの開始時刻(分)
ss int // タスクの開始時刻(秒) スケジュールは分単位ですが、タスクは秒単位で管理されます。
category int // カテゴリー Amateur:アマチュア枠、Official:公式枠
noofstars int // 星集めのとき 集める各色の星の数(10,20,...,90,99)
// 星投げのとき 各色星10個を単位とした回数(1,2,...,9,10)
// カウントのとき カウントの数(1~50)
order int // 星集めのとき 配信ルームリストを使う順番
// InOrder: 昇順(通常)
// InReverseOrder: 降順(2周目目分の星を集めるとき)
atleastonce bool // 星集めのとき 現状手持ちの星の数に関わらず星集めをするか?
// true: する(いわゆる捨て星のとき)
// false: しない(通常)
continued bool // 星集めのとき 配信ルームリストを更新しないで星集めを行うか?
// true: 更新しない(2分割した星集めの2回め)
// false: 更新する(通常)
shorturl string // 星投げのとき 星投げ対象の配信ルームのURLの最後のフィールド(アカウント)
}
// 配信ルームのリストを作るときの順番
// InOrder 昇順
// 公式はアイドル、タレント・モデル、ミュージック、...と進み
// 毎日○○、ONLIVEの順でリストを作っていきます。
// アマチュアは初配信、ONLIVEの順でリストを作っていきます。
const InOrder = 1
// InReverseOreder 降順
// 公式はお笑い・トーク、声優・アニメ、ミュージック、...と進み
// ONLIVE、毎日○○の順でリストを作っていきます。
// アマチュアはONLIVE、初配信の順でリストを作っていきます。
const InReversOrder = -1
// 公式枠・アマチュア枠
const Official = 0
const Amateur = 1
// 集めるべき数の星・種があっても一回だけは星・種を集める(3周の2周目(いわゆる捨て星)のとき)
const ATLEASTONCE = true
// 星集め/種集めで配信ルームリストを更新せず、直前の星集め/種集めの配信ルームリストを使う
// 3周するときの3周目のように星/種をを集め、投げる操作を2回に分けて行うときの2回めで使います。
const CONTINUED = true
------ 中略
/*
スケジュールをブレイク・ダウンします。
つまり、たとえば
15時00分から始まる○○さんのルームで3周する
という指示から
13時10分に星を10回集める
14時10分に1回だけ星を集める(いわゆる捨て星)
15時00分に配信が始まったら10回分の星を投げ、1回分の星を受け取る
星を投げ終わったら星を8回分集める
星を集め終わったら9回分の星を投げる
15時10分になったら星を5回集める
星を集め終わったら5回分の星を投げる
星を投げ終わったら星を5回分集める
星を集め終わったら5回分の星を投げる
という具体的な操作を作ります
作った操作はいったんファイルに書き出され、後続の処理はこのファイルを参照して進めます。
このファイルは(スケジュール変更が発生したときなど)プログラム実行中に作り直したり、手作業で修正したりできます。
*/
func BreakDownSchedule(environment *Environment) (status int) {
var hh, mm, dmm, dss, times, icategory int
var hht, mmt, sst int
var category, shorturl, remark string
// あとで配信開始時刻等はtime.Time形式にするために使用します。
t0 := time.Now()
log.Println(t0)
year, month, day := t0.Date()
// hour, min, sec := t0.Clock()
loc := t0.Location()
// t1 := time.Date(year, month, day, hour, min, sec, 0, loc)
// log.Println(t1)
// 配信スケジュールファイルを開きます。
// このファイルに書いてあるのは配信開始時刻、配信継続時間、配信開始から解除までの時間などです。
file, err := os.OpenFile((*environment).FileNameOfSchedule, os.O_RDONLY, 0644)
if err != nil {
log.Println(" Can't open file. [", (*environment).FileNameOfSchedule, "]")
status = -1
return
}
// 星集め、星投げ等の個々の操作(ここではタスクとします)を格納する変数です。これをタスクリストとしています。
tasklist := make([]Task, 0)
// 配信スケジュールファイルのすべての行について
s := bufio.NewScanner(file)
for s.Scan() {
istr := s.Text()
// "#"ではじまる行はコメント行として無視します。
if strings.HasPrefix(istr, "#") {
log.Println("comment ", istr)
continue
}
NoOfItem, _ := fmt.Sscanf(istr, "%d:%d%d%d%d%s%s%s",
&hh, &mm, &dmm, &dss, ×, &category, &shorturl, &remark)
log.Println(hh, mm, dmm, dss, times, category, shorturl, remark)
// カテゴリーの記述を内部形式に変化します。
switch category {
case "Amateur":
icategory = Amateur
case "Official":
icategory = Official
default:
log.Println("category must be Amateur or Official.")
status = 1
return
}
// 形式があっていない行がある場合は実行を打ち切ります。
if NoOfItem != 8 {
log.Println("Number of item =", NoOfItem)
log.Println("Input data must be \"hh:mm dmm dss 0|1|2|3 Amateur|Official shortURL remark\"")
status = 1
return
}
// 配信開始時刻(配信開始時刻は時分で指定されます)
t1 := time.Date(year, month, day, hh, mm, 00, 0, loc)
// タイミング設定
// 解除の時刻 - 配信開始時刻 = 配信開始から解除までの時間(設定値) - マージン + 解除時刻の遅延
// マージンは星集めの前に必要な配信ルームリストを作るための時間的余裕のことを言います。
dr_cA := time.Duration(dss-(*environment).Margin) * time.Second
// 上記時間から60分を引いたもの
// これを配信開始時刻にプラスすると3周の2周目用の種集め(いわゆる捨て星)
// あるいは2周目の1周目用の星集めの時刻となります。
dr_cB := dr_cA - 60*time.Minute
// さらに60分前
// これを配信開始時刻にプラスすると3周の一回目の星集めの時刻になります。
dr_cC := dr_cA - 120*time.Minute
// 星を100個集めて投げるとき、全部集めて投げると1個ムダになるので、集める/投げるの操作を2回繰り返します。
// 最初に集める星の数(投げる回数の10倍) colfirst、2回めに集める星の数 colsecond
// 最初に投げる回数(集める数の1/10) thrfirst、2回めに投げる回数 thrsecond
colfirst := (*environment).NoColFirst
colsecond := 0
thrsecond := 0
thrfirst := colfirst / 10
if colfirst == 100 {
colfirst = 99
} else {
colsecond = 100 - colfirst
thrsecond = colsecond / 10
}
switch times {
case -1:
// 指定時刻に星・種を投げます
// 気分で特定のルームに星を投げるようなケースですね。
// この場合は星は予定通り行われなかった配信で余ったものとか times=0 で集めたものを使います。
// 1周分の星・種を投げるタスク
hht, mmt, sst = t1.Clock()
tasklist = append(tasklist, Task{"T", hht, mmt, sst, icategory, 10, 0, false, false, shorturl})
case 0:
// 指定時刻に星・種を集めます
// 配信時刻がはっきりしない配信者さん用に事前に星・種を集めておく
// すてきな配信者さんがいたら投げるために用意しておく
// みたいなケースで使います。
// 星・種を集めるタスク
hht, mmt, sst = t1.Clock()
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, 99, InOrder, false, false, "n/a"})
case 1:
// 1周分星・種を集め、投げます。
// 前半分の星・種を集めるタスク
hht, mmt, sst = t1.Add(dr_cA).Clock()
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, colfirst, InOrder, false, false, "n/a"})
// 前半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrfirst, 0, false, false, shorturl})
if colsecond != 0 {
// 後半分の星・種を集めるタスク
tasklist = append(tasklist, Task{"C", 99, 99, 99, icategory, colsecond, InOrder, false, CONTINUED, "n/a"})
// 後半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrsecond, 0, false, false, shorturl})
}
case 2:
// 2周分星・種を集め、投げます。
// 1周目分の星・種を集めるタスク
hht, mmt, sst = t1.Add(dr_cB).Clock()
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, 90, InOrder, false, false, "n/a"})
hht, mmt, sst = t1.Clock()
// 1周目分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", hht, mmt, sst, icategory, 9, 0, false, false, shorturl})
hht, mmt, sst = t1.Add(dr_cA).Clock()
// 2周目前半分の星・種を集めるタスク
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, colfirst, InOrder, false, false, "n/a"})
// 2周目前半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrfirst, 0, false, false, shorturl})
if colsecond != 0 {
// 2周目後半分の星・種を集めるタスク
tasklist = append(tasklist, Task{"C", 99, 99, 99, icategory, colsecond, InOrder, false, CONTINUED, "n/a"})
// 2周目後半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrsecond, 0, false, false, shorturl})
}
case 3:
// 3周分星・種を集め、投げます。
// 1周目分の星・種を集めるタスク
hht, mmt, sst = t1.Add(dr_cC).Clock()
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, 99, InOrder, false, false, "n/a"})
// 2周目分の星・種を集める準備(いわゆる捨て星)のタスク
hht, mmt, sst = t1.Add(dr_cB).Clock()
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, 99, InOrder, ATLEASTONCE, false, "n/a"})
// 1周目分の星・種を投げるタスク
hht, mmt, sst = t1.Clock()
tasklist = append(tasklist, Task{"T", hht, mmt, sst, icategory, 10, 0, false, false, shorturl})
// 2周目分の星・種を集めるタスク
tasklist = append(tasklist, Task{"C", 99, 99, 99, icategory, 90, InReversOrder, false, false, "n/a"})
// 2周目分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, 9, 0, false, false, shorturl})
// 3周目前半分の星・種を集めるタスク
hht, mmt, sst = t1.Add(dr_cA).Clock()
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, colfirst, InOrder, false, false, "n/a"})
// 3周目前半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrfirst, 0, false, false, shorturl})
if colsecond != 0 {
// 3周目後半分の星・種を集めるタスク
tasklist = append(tasklist, Task{"C", 99, 99, 99, icategory, colsecond, InOrder, false, CONTINUED, "n/a"})
// 3周目後半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrsecond, 0, false, false, shorturl})
}
default:
log.Println("times must be -1, 0, 1, 2 or 3.")
}
if times != 0 {
// カウント
tasklist = append(tasklist, Task{"N", 99, 99, 99, icategory, 50, InOrder, false, false, shorturl})
}
// 配信が(1時間+Thtimeで設定された時間)以上ある場合は(更新されていれば)1時間ごとに星集め/種集め、星投げ/種投げを行います。
// たいていの配信者さんの配信終了時刻はたいてい伸びるので、Thtimeは適当に設定してください。デフォルトは20分です。
for i := 0; i < (dmm-(*environment).Thtime)/60; i++ {
hht, mmt, sst = t1.Add(dr_cA + time.Duration(i+1)*time.Hour).Clock()
// 4(5,6,...)周目前半分の星・種を集めるタスク
tasklist = append(tasklist, Task{"C", hht, mmt, sst, icategory, colfirst, InOrder, false, false, "n/a"})
// 4(5,6,...)周目前半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrfirst, 0, false, false, shorturl})
if colsecond != 0 {
// 4(5,6,...)周目後半分の星・種を集めるタスク
tasklist = append(tasklist, Task{"C", 99, 99, 99, icategory, colsecond, InOrder, false, CONTINUED, "n/a"})
// 4(5,6,...)周目後半分の星・種を投げるタスク
tasklist = append(tasklist, Task{"T", 99, 99, 99, icategory, thrsecond, 0, false, false, shorturl})
}
// カウント
tasklist = append(tasklist, Task{"N", 99, 99, 99, icategory, 50, InOrder, false, false, shorturl})
}
}
file.Close()
// 作成したタスクリストをファイルに書き出します。
TaskListFile, err := os.OpenFile((*environment).FileNameOfTaskList, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0666)
if err != nil {
panic("cannnot open logfile: " + environment.FileNameOfLog + err.Error())
}
defer TaskListFile.Close()
for _, task := range tasklist {
fmt.Fprintf(TaskListFile, "%s\t%02d:%02d:%02d\t%d\t%d\t%d\t%t\t%t\t%s\n",
task.command, task.hh, task.mm, task.ss, task.category, task.noofstars, task.order, task.atleastonce, task.continued, task.shorturl)
}
status = 0
return
}
------ 後略
« SHOWROOM 新・自動三周ツール -- GO言語によるブラウザ制御 (1) main() | トップページ | SHOWROOMのAPI - 「ライブ情報」の取得(GO言語のソースつき) »
「パソコン・インターネット」カテゴリの記事
- さくらインターネットのレンタルサーバーでGOで書いたCGIを動かした(苦労)話(2021.04.19)
- SHOWROOMのAPI - 「ライブ情報」の取得(GO言語のソースつき)(2019.10.26)
- SHOWROOM 星集め・星投げツール スケジュールの詳細化 BreakDownSchedule() (三周のやり方を例に)(2019.09.25)
- SHOWROOM 新・自動三周ツール -- GO言語によるブラウザ制御 (1) main()(2019.09.17)
この記事へのコメントは終了しました。
コメント