« Showroom ラスカルイベの最後の5分間 | トップページ | 超初心者のGo言語 - もっとも簡単なGoroutine(並列処理) »

2018年1月 9日 (火)

Showroomでひたすらリスナーレベルを上げるための星集めツール(Go/agouti)

Showroomでリスナーレベルをあげてどういう意味があるのかがよくわかりません。レベルに応じたアバターがもらえますが、逆にそれ以外に何もメリットがないようにも思えます。それでもレベルを上げたいという記事です。

リスナーレベルについては

  SHOWROOM アバター - レベル1~9アバター&リスナー累計ポイント

から始まる一連の記事が詳細でとても参考になります。レベル40台くらいだと10万ポイントでレベルが1上がるようですが、これは実体験とあってます。ちなみにレベル45までは(こういうツールに頼らず)“指”だけで到達しました。

なおShowroomのSHOWROOM 会員規約の第10条に「禁止事項」というのがあって、自動操作は禁止事項に該当する可能性があります。表向きの理由はともかくサーバーに負荷をかけるな、という意味だと思います。人間のやることを人間がやるのと同じくらいのスピードでやってる分には問題化することはないと思っていますが、そのあたりはご自身で判断してください。

そこそこコメントは入れてあるので詳細はプログラムを読んで確認していただきたいのですが、要点を書くと

  ・61分に1回ずつ星集めと種集めを行う。
  ・集まった星・種は10個単位で投げる。

ということをやっています。

レベルは投げた星・種に応じて上がるものなので投げることが重要です。そういう意味では“星集めツール”ではなく“星投げツール”と言った方がいいかもしれません。

カウントもしていますが、これは配信者に対するお礼の意味でやっているだけでレベルアップとは無関係です。はじめたばかりの配信者さんは星投げやカウントよりコメントを喜ばれるようなので、そのうち「こんにちは~」とか「星集めさせていただいています」みたいなコメントする機能も追加しようと思っています。

タイトルにあるようにGo言語/agouiを利用しています。“開発”はLiteIDEを使っています。なかなか便利です。

それからこのプログラムはShowroom Toolboxが導入されていることが前提です。Showroom Toolboxの設定は事前にしておきその環境下でプログラムを実行しています。環境の引継ぎ方ですが、まず

  "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --user-data-dir=c:\tmp\chrome

みたいなショートカットでChromeを起動して必要な設定をします。そのあと以下のプログラムのように書けばOKです。

それからStartTime.txtは次のような内容のファイルです。

10 24 1
9 53 1


上の行が星集めを開始する時刻(時、分)、下の行が種集めを開始する時刻(時、分)です(このファイルはプログラム実行中にエディターで変更することもできます)

種集めが終わると開始時刻は自動的に更新されます。つまり61分後が新しい星集めの時刻として設定されます。

二つの行の最後の数字は0か1です。
1のときはレベルアップが目的でとにかく最大限星を集め、集めた星がムダにならないように投げていきます。
0のときは集めるだけです。集めたものを投げなかったときは61分後に1回だけ星をもらいます。言うまでもなく“自動三周ツール”を作ることを前提とした機能です。次はこの数字を指定した時刻(例えば配信開始予定時刻の1時間45分前)に1から0に変更する部分を作ろうかと思っています。プログラムはひとまず意図した動きをするようになりましたがまだ作成途中です。不足する処理、冗長な処理、コードなどいろいろあると思います。ツールと別にShowroomを閲覧しているケースを考えるとなかなかに難しいところがあります。あんまり目くじらたてずに御覧くださるようお願いします。

-------------------------

参考

 

  「Showroom - 自動星集め・星投げ・カウントツール)」 (使用法とソースつき)
  「Showroom - 自動三周ツール(もう一つの自動星集め・星投げ・カウントツール)」 
  「Showroom - イベントの獲得ポイント数を取得して記録するツール
  「Showroom - 福引するプログラムとその結果 (1)

  「Showroom - イベント結果データ


  「Showroomの複数アカウント(複アカ、複垢)について考えてみた(1)
  Showroom - 複数アカウント(複垢)問題の真実 - 実験計画
  「Showroomの重複アカウント(複垢)減算はこうして起きる


  「Showroomでの自動星集めの試み (3) ガチイベ、最後の5分間
  「Showroom ラスカルイベの最後の5分間

  「Showroomでの自動星集めの試み (1)
  「Showroomでの自動星集めの試み (2) 配信ルームの一覧を作る
  「Showroomでの自動星集めの試み (4) 星集めツール
  「Showroomでひたすらリスナーレベルを上げるための星集めツール(Go/Agouti)

  「超初心者のGo言語/agouti - ブラウザ操作の基本の基本
  「超初心者のGo言語 - 複数の戻り値をもつ関数
  「超初心者のGo言語 - もっとも簡単なGoroutine(並列処理)

 

  ---------------------

  「GoDoc - package agouti

  「Qiita @0829 - Goではじめてみたブラウザの自動操作

 

  「Qiita @tenten0213 - agoutiというWebDriverクライアントを使って面倒な作業を自動化する
  「Qiita @masaru_b_cl - Windows上でGo言語初心者向け学習環境を作る

  「はじめてのGo言語
  「天才まくまくノート - まくまく Hugo/Go ノート - 関数を定義する (func)

 

  「Qiita @TakaakiFuruse - Golang Goの並列処理を学ぶ(goroutine, channel)
  「Qiita @To_BB - Rubyエンジニアがゴルーチン(Go言語)を学んでみた【初心者向け】
  「Qiita @fukumone - goroutine 使い方まとめ

// 星集め、種集めを定期的(61分おき)に行うツールです。
// Showroom Toolboxが導入され10星アイコン、全星アイコン、自動カウントが有効になっていることを前提としています。
// Showroomのウィンドーの横幅は十分広くとってください。そうしないと全星アイコンが表示されないことがあります。
// StartTime.txtの三つののフィールドの意味は以下のとおりです。
//		1:	最大限星・種を集め、ムダにならないように投げていく
//		0:	星・種が99個になるまで集める(ただし99個集まったら1個だけ捨てる。
//			この機能を2回続けて実行するのは三周の事前準備と同等

package main

import "github.com/sclevine/agouti"
import "fmt"
import "time"
import "strings"
import "os"

// 星集めを行うfuncです。
// 獲得ポイント 99*5で終了したときは1を、そうでない場合は0を戻します。
// 公式枠の場合 StartJ=2、StopJ=7 とします。
// アマチュア枠の場合 StartJ=8、StopJ=8 とします。
func GetStars(StartJ, StopJ, FThrow int) int {

	var hht int
	var mmt int
	var URL [40]string
	var ID string
	var Stat [40]int
	var RName [40]string
	var NStars, NStarsT int

	Done := 0

	FmtFrame := "#js-categorymenu-list > li:nth-child(%d)"
	FmtNumberOfListner := "#js-onlive-collection > div > section:nth-child(%d) > ul > li:nth-child(%d) > div > div > div.listcard-image > div.listcard-label > div.label-room.is-onlive"
	FmtStartTime := "#js-onlive-collection > div > section:nth-child(%d) > ul > li:nth-child(%d) > div > div > div.listcard-image > div.listcard-label > div.label-room.is-start-time"
	FmtName := "#js-onlive-collection > div > section:nth-child(%d) > ul > li:nth-child(%d) > div > div > div.listcard-info > h4"
	FmtURL := "#js-onlive-collection > div > section:nth-child(%d) > ul > li:nth-child(%d) > div > div > div.listcard-image > div.listcard-overview > div > a.js-room-link.listcard-join-btn"
	// FmtURL := "#js-onlive-collection > div > section:nth-child(%d) > ul > li:nth-child(%d)"
	FmtNoRoom := "#js-onlive-collection > div > section:nth-child(%d)"

	fmt.Println(time.Now())

	fmt.Println("from ", StartJ, " to ", StopJ)

	if StartJ == 0 {
		return 1
	}

	// Chromeを利用することを宣言
	agoutiDriver := agouti.ChromeDriver()
	agoutiDriver.Start()
	defer agoutiDriver.Stop()

	// Showroomにログインし、Showroom Toolboxが使える環境を作っておき
	// その環境を引き継げるようにShowroomを起動する。
	page, _ := agoutiDriver.NewPage(agouti.Desired(agouti.Capabilities{
		"chromeOptions": map[string][]string{
			"args": []string{
				"user-data-dir=C:\\Tmp\\Chrome",
			},
		},
	}),
	)

	// 時刻を表示します。
	t := time.Now()
	hh, mm, ss := t.Clock()
	yr, mt, dy := t.Date()

	// ログファイル名には日付時刻を使う。
	FileName := fmt.Sprintf("%1d%1d_%4d%02d%02d_%02d%02d%02d.txt", StartJ, StopJ, yr, mt, dy, hh, mm, ss)
	fmt.Println(FileName)

	f, _ := os.OpenFile(FileName, os.O_CREATE|os.O_WRONLY, 0644)
	fmt.Fprintf(f, "%s\n", strings.TrimRight(FileName, ".txt"))

	TotalPointThrown := 0
	TotalPointGotten := 0

	NoSamePoint := 0
	LastValue := " "
	LastPoint := 0
	First := 1

	// 3重ループを一気にブレークするときに使うラベルです
OuterLoop:

	// 逆向きに巡回しているのはあまり合理的ではないのですが、3周するときのことを考慮したためです。
	// アイドル枠からお笑い・トーク枠まで巡回するときは2~7、アマチュア枠は8~8
	for k := StopJ; k > StartJ-1; k-- {

		// 「まいにち○○」と「ONLIVE」を巡回する
		// あるいは「初配信(始めて間もないルーム)」と「ONLIVE」を巡回する
		for j := 2; j > 0; j-- {

			// Showroomオンライブ画面を開く
			page.Navigate("https://www.showroom-live.com/onlive")

			// 枠を選ぶ
			SelectorFrame := fmt.Sprintf(FmtFrame, k)
			page.Find(SelectorFrame).Click()
			page.Screenshot("Screenshot02.png")
			SelectorNoRoom := fmt.Sprintf(FmtNoRoom, j)
			NoRoom, _ := page.First(SelectorNoRoom).AllByClass("js-room-link").Count()

			t = time.Now()
			hh, mm, ss = t.Clock()
			hhmm := hh*60 + mm

			// ルームの数だけ繰り返す。
			n := 0
			for i := NoRoom; i > 0; i-- {
				SelectorNumberOfListner := fmt.Sprintf(FmtNumberOfListner, j, i)
				NumberOfListner, _ := page.Find(SelectorNumberOfListner).Text()
				SelectorRoomName := fmt.Sprintf(FmtName, j, i)

				// 配信開始時刻を求め、分単位にする
				SelectorStartTime := fmt.Sprintf(FmtStartTime, j, i)
				StartTime, _ := page.Find(SelectorStartTime).Text()
				fmt.Sscanf(StartTime, "%2d:%2d", &hht, &mmt)
				hhmmt := hht*60 + mmt
				// 前日の配信開始は1日分の分数を引いておきます。
				if hhmmt > hhmm {
					hhmmt = hhmmt - 1440
				}

				RoomName, _ := page.Find(SelectorRoomName).Text()
				fmt.Printf("%4d%4d %6s %d %d %s", j, i, NumberOfListner, hht, mmt, RoomName)

				// 配信開始時刻が一定の範囲にあるルームだけを星集めの対象とする。
				if hhmmt > hhmm-50 {
					// 配信者画面のURLのリストを作る
					SelectorURL := fmt.Sprintf(FmtURL, j, i)
					tURL, _ := page.Find(SelectorURL).Attribute("href")
					fmt.Println("<", tURL, ">")
					URL[n] = tURL
					Stat[n] = (k*10+j)*1000 + i
					RName[n] = RoomName
					n++
					// 巡回対象は(運が良ければ)最低20で済むので、40も対象を集めておけばじゅうぶんです。
					// 時間帯によってはアイドル枠だけで完了となります。
					// 真夜中だと全体でも20ルーム見つかりませんが....
					if n == 40 {
						break
					}
				} else {
					fmt.Println("\r\n")

				}
			}

			for i := 0; i < n; i++ {
				// for i := n - 1; i > -1; i-- {

				// 配信者の画面を開く(ふつうここでエラーが起きることはないですが....
				err := page.Navigate(URL[i])
				if err != nil {
					fmt.Fprintf(f, "Error!\n")
					fmt.Printf("Error!\n")
					continue
				}
				time.Sleep(2 * time.Second)

				// 処理開始時刻を取得しログファイルに書き出します。
				tti := time.Now()
				hhi, mmi, ssi := tti.Clock()
				fmt.Fprintf(f, "%02d%02d%02d%4d,", hhi, mmi, ssi, i)

				// 獲得ポイント数を取得する
				MyPoint, _ := page.Find("#room-gift-item-list > li:nth-child(12) > div").Text()
				ID = strings.TrimPrefix(URL[i], "https://www.showroom-live.com/")
				fmt.Fprintf(f, "%6d,<%s>,<%s>,<%s>", Stat[i], MyPoint, ID, RName[i])
				fmt.Printf("%6d,<%s>,<%s>,<%s>", Stat[i], MyPoint, ID, RName[i])

				// 選択したルームが配信中または星数が取得できない場合はスキップする。
				// 獲得ポイントは配信中であれば"× 80"のような、配信中でなければ""という文字列が得られます。
				// Showroom Toolboxがうまくセットされない場合などがあり、そのときはまた別の文字列が返ってきます。
				// その対策はまだ入っていません。
				if strings.Compare(MyPoint, "×") < 0 {
					fmt.Fprintf(f, " **Skip\n")
					continue
				}

				// 星・種の数を数値に変換する。
				fmt.Sscanf(MyPoint, "× %d", &NStars)

				// 投票機能が有効なときはスキップする(星)
				PImg, _ := page.Find("#room-gift-item-list > li:nth-child(12) > a > img").Attribute("src")
				if k != 8 && !strings.Contains(PImg, "1001") {
					fmt.Fprintf(f, " Vote?\n")
					continue
				}
				// 投票機能が有効なときはスキップする(種)
				if k == 8 && !strings.Contains(PImg, "1502") {
					fmt.Fprintf(f, " Vote?\n")
					continue
				}

				if First == 1 {
					LastPoint = NStars
					First = 0
				} else {
					// 星が増えていたら獲得ポイント以外ない
					if NStars > LastPoint {
						TotalPointGotten += NStars - LastPoint
						fmt.Fprintf(f, "TPG=%d(1)", TotalPointGotten)
						fmt.Print(" TPG=", TotalPointGotten, "(1)")
					} else {
						// 星が減っていたら投げたためだが、星はふつう10個単位で投げるので10の剰余は増えた分と考える
						// 他に画面を開いていて2画面以上で星が増えたケースや10個単位ではなく星を投げた場合は正しい結果にならない
						// これを判別する手段はないような....
						if NStars < LastPoint {
							TotalPointGotten += (LastPoint - NStars) % 10
						}
						fmt.Fprintf(f, "TPG=%d(2)", TotalPointGotten)
						fmt.Print(" TPG=", TotalPointGotten, "(2)")
					}
					LastPoint = NStars
				}
				if TotalPointGotten == 100 {
					fmt.Fprintf(f, "\nTPG eq. 100\n")
					break OuterLoop
				}

				// 99ポイントだったら処理を終了します。
				// ここで99ポイントのチェックをするのは表示にタイムラグがあるためです。
				// いわゆる「捨て星」について、ここでチェックしないと余計な捨て星が発生します。
				if FThrow == 0 && strings.Compare(MyPoint, "× 99") == 0 {
					page.Find("#room-gift-item-list > li:nth-child(12) > a").Click()
					time.Sleep(2 * time.Second)
					Done = 1
					fmt.Fprintf(f, "\n Points eq. 99.\n")
					break OuterLoop
				}
				// 同じポイントが4回続いたら処理を打ち切る
				// 星集めが順調に進んでいても同じポイントが2回続くことはよくあります
				// ただ4回という基準は大きすぎるかも
				if strings.Compare(MyPoint, LastValue) == 0 {
					NoSamePoint++
				} else {
					LastValue = MyPoint
					NoSamePoint = 0
				}
				if NoSamePoint > 2 {
					fmt.Fprintf(f, "\nPoints don't change.\n")
					break OuterLoop
				}
				// ポイントを獲得し次第投げるとき(リスナーレベル向上をめざすとき)
				if FThrow == 1 {

					//	fmt.Println("<", MyPoint, ">", "<", NStars, ">")
					if NStars >= 95 {
						page.FindByClass("starButton").Click()
						time.Sleep(2 * time.Second)
						MyPointT, _ := page.Find("#room-gift-item-list > li:nth-child(12) > div").Text()
						fmt.Sscanf(MyPointT, "× %d", &NStarsT)
						TotalPointThrown += NStars - NStarsT
						fmt.Printf("TPT=%d", TotalPointThrown)
						fmt.Fprintf(f, "TPT=%d", TotalPointThrown)
						// if TotalPointThrown == 100 {
						// 	fmt.Fprintf(f, "\nTPT eq. 100\n")
						// 	break OuterLoop
						// }
					}
				}

				// Showroom Toolboxの機能を使って自動カウントを行う
				page.FindByButton("自動カウント").Click()

				// 星、種をもらえるまで30秒ウェイトする。
				// 30秒ではカウントは15回程度しかできないことに留意する
				fmt.Print("Sleep start - ")
				time.Sleep(30 * time.Second)
				fmt.Print("Stop.")

				// 累計ポイントを取得する。
				MyPoint, _ = page.Find("#room-gift-item-list > li:nth-child(12) > div").Text()
				fmt.Fprintf(f, "<%s>\n", MyPoint)
				fmt.Printf("<%s>\n", MyPoint)
				// 99ポイント集まったら処理を終了する。
				// 実際には99ポイント集まってから99と表示されるまでタイムラグがあり
				// ループの先頭の判断でブレークする方が多いかも
				// 余計な画面を開かないという意味ではここで判断できるのがいいんですけど
				if FThrow == 0 && strings.Compare(MyPoint, "× 99") == 0 {
					page.Find("#room-gift-item-list > li:nth-child(12) > a").Click()
					time.Sleep(2 * time.Second)
					Done = 1
					fmt.Fprintf(f, "\nPoints eq. 99\n")
					break OuterLoop
				}
			}
		}
	}
	t = time.Now()
	hh, mm, ss = t.Clock()
	yr, mt, dy = t.Date()
	EndTime := fmt.Sprintf("%1d%1d_%4d%02d%02d_%02d%02d%02d", StartJ, StopJ, yr, mt, dy, hh, mm, ss)
	fmt.Fprintf(f, "\n%s\n", EndTime)

	f.Close()
	page.CloseWindow()
	time.Sleep(2 * time.Second)

	return Done
}
func main() {
	var OStartHH, OStartMM, OThrow int
	var AStartHH, AStartMM, AThrow int

	// fmt.Sscanf(os.Args[1], "%d%", &StartJ)
	// fmt.Sscanf(os.Args[2], "%d%", &StopJ)
	// if StartJ == 0 {StartJ = 2 }
	// if StopJ == 0 {StopJ = 7 }

	f, _ := os.OpenFile("StartTime.txt", os.O_RDONLY, 0644)
	fmt.Fscanln(f, &OStartHH, &OStartMM, &OThrow)
	fmt.Fscanln(f, &AStartHH, &AStartMM, &AThrow)
	f.Close()

	OStartHHMM := OStartHH*60 + OStartMM
	AStartHHMM := AStartHH*60 + AStartMM

	Count := 0
	for {
		Count++
		if Count == 8 {
			f, _ := os.OpenFile("StartTime.txt", os.O_RDONLY, 0644)
			fmt.Fscanln(f, &OStartHH, &OStartMM, &OThrow)
			fmt.Fscanln(f, &AStartHH, &AStartMM, &AThrow)
			f.Close()
			if OStartHH == 99 {
				break
			}
			OStartHHMM = OStartHH*60 + OStartMM
			AStartHHMM = AStartHH*60 + AStartMM
			Count = 0
			fmt.Println("========")
		}
		t := time.Now()
		hh, mm, _ := t.Clock()
		HHMM := hh*60 + mm
		fmt.Printf("%02d:%02d %02d:%02d %02d:%02d\n", hh, mm, OStartHH, OStartMM, AStartHH, AStartMM)
		if HHMM == OStartHHMM {
			status := GetStars(2, 7, OThrow)
			fmt.Printf("\nStatus = %d\n", status)
			OStartMM++
			if OStartMM > 59 {
				OStartMM = OStartMM - 60
				OStartHH++
			}
			OStartHH++
			if OStartHH > 23 {
				OStartHH = OStartHH - 24
			}
			f, _ := os.OpenFile("StartTime.txt", os.O_WRONLY, 0644)
			fmt.Fprintln(f, OStartHH, OStartMM, OThrow)
			fmt.Fprintln(f, AStartHH, AStartMM, AThrow)
			f.Close()
			OStartHHMM = OStartHH*60 + OStartMM
		}
		if HHMM == AStartHHMM {
			status := GetStars(8, 8, AThrow)
			fmt.Printf("\nStatus = %d\n", status)
			AStartMM++
			if AStartMM > 59 {
				AStartMM = AStartMM - 60
				AStartHH++
			}
			AStartHH++
			if AStartHH > 23 {
				AStartHH = AStartHH - 24
			}
			f, _ := os.OpenFile("StartTime.txt", os.O_WRONLY, 0644)
			fmt.Fprintln(f, OStartHH, OStartMM, OThrow)
			fmt.Fprintln(f, AStartHH, AStartMM, AThrow)
			f.Close()
			AStartHHMM = AStartHH*60 + AStartMM
		}
		time.Sleep(15 * time.Second)
	}

}

« Showroom ラスカルイベの最後の5分間 | トップページ | 超初心者のGo言語 - もっとも簡単なGoroutine(並列処理) »

パソコン・インターネット」カテゴリの記事

コメント

この記事へのコメントは終了しました。

トラックバック

« Showroom ラスカルイベの最後の5分間 | トップページ | 超初心者のGo言語 - もっとも簡単なGoroutine(並列処理) »

フォト

サイト内検索

  • 記事を探されるんでしたらこれがいちばん早くて確実です。私も使ってます (^^;; 検索窓が表示されるのにちょっと時間がかかるのはどうにかしてほしいです。

新着記事

リンク元別アクセス数

  • (アクセス元≒リンク元、原則PCのみ・ドメイン別、サイト内等除く)

人気記事ランキング

  • (原則PCのみ、直近2週間)
無料ブログはココログ