Skip to content

Instantly share code, notes, and snippets.

@fukubaya
Last active July 18, 2025 14:04
Show Gist options
  • Select an option

  • Save fukubaya/ad4eb94e223669dbf9cd7ca99cfc2e29 to your computer and use it in GitHub Desktop.

Select an option

Save fukubaya/ad4eb94e223669dbf9cd7ca99cfc2e29 to your computer and use it in GitHub Desktop.
find avalilable skills and lookup the unit with maximum points in staplo
(async () => {
const userId = 123456789; // Your user ID
const MAX_CARD_PAGE = 10; // Maximum number of pages to fetch for each rarity
const stacommuHighlightName = "スタコミュハイライト vol.1";
const stacommuHighlightName2 = "スタコミュハイライト vol.2";
const stacommuHighlightName3 = "スタコミュハイライト vol.3";
const stacommuAward2025Name1 = "スタコミュAWARD 2025 vol.1";
const stacommuAward2025Name2 = "スタコミュAWARD 2025 vol.2";
const skills = [
{name: "三国同盟", names: ["百田 夏菜子", "玉井 詩織", "高城 れに"], point: 300},
{name: "あやかなこ", names: ["百田 夏菜子", "佐々木 彩夏(ももいろクローバーZ)"], point: 300},
//{name: "あやかなこ", names: ["百田 夏菜子", "佐々木 彩夏(LumiUnion)"], point: 300},
{name: "ももたかぎ", names: ["百田 夏菜子", "高城 れに"], point: 300},
{name: "ももたまい", names: ["百田 夏菜子", "玉井 詩織"], point: 300},
{name: "た行", names: ["玉井 詩織", "高城 れに"], point: 300},
{name: "うさぎ先輩後輩", names: ["玉井 詩織", "佐々木 彩夏(ももいろクローバーZ)"], point: 300},
//{name: "うさぎ先輩後輩", names: ["玉井 詩織", "佐々木 彩夏(LumiUnion)"], point: 300},
{name: "チーム最年長最年少", names: ["高城 れに", "佐々木 彩夏(ももいろクローバーZ)"], point: 300},
//{name: "チーム最年長最年少", names: ["高城 れに", "佐々木 彩夏(LumiUnion)"], point: 300},
{name: "Wあーりん", names: ["佐々木 彩夏(ももいろクローバーZ)", "佐々木 彩夏(LumiUnion)"], point: 300},
{name: "エビ中姉メン", names: ["真山 りか", "安本 彩花", "中山 莉子"], point: 300},
{name: "エビ中妹メン", names: ["桜木 心菜", "小久保 柚乃", "風見 和香", "桜井 えま", "仲村 悠菜"], point: 300},
{name: "ココユノノカ", names: ["桜木 心菜", "小久保 柚乃", "風見 和香"], point: 300},
{name: "えまゆな", names: ["桜井 えま", "仲村 悠菜"], point: 300},
{name: "第2回スタコミュAWARD選抜(私立恵比寿中学)", names: ["真山 りか", "仲村 悠菜", "桜木 心菜"], point: 300},
{name: "第3回スタコミュAWARD選抜(私立恵比寿中学)", names: ["仲村 悠菜", "小久保 柚乃", "風見 和香"], point: 300},
{name: "杏仁", names: ["坂井 仁香", "杏 ジュリア"], point: 300},
{name: "よしこい", names: ["吉川 ひより", "小泉 遥香"], point: 300},
{name: "ジュリあき", names: ["杏 ジュリア", "菅田 愛貴"], point: 300},
{name: "かなはる", names: ["辻野 かなみ", "小泉 遥香"], point: 300},
{name: "ひーはー", names: ["坂井 仁香", "小泉 遥香"], point: 300},
{name: "あきぴよ", names: ["菅田 愛貴", "吉川 ひより"], point: 300},
{name: "ほのなお", names: ["秋本 帆華", "咲良 菜緒"], point: 300},
{name: "親子ケミ", names: ["咲良 菜緒", "大黒 柚姫"], point: 300},
{name: "さめぽて", names: ["秋本 帆華", "大黒 柚姫"], point: 300},
{name: "ほのハル", names: ["秋本 帆華", "坂本 遥奈"], point: 300},
{name: "はなおん", names: ["坂本 遥奈", "咲良 菜緒"], point: 300},
{name: "ばっしょー姉メン", names: ["希山 愛", "上田 理子", "春乃 きいな"], point: 300},
{name: "ばっしょー妹メン", names: ["蒼井 りるあ", "柳 美舞", "浅井 アヤネ", "逢里 みう", "日南 心那"], point: 300},
{name: "ばっしょー新世代", names: ["浅井 アヤネ", "逢里 みう", "日南 心那"], point: 300},
{name: "あいりこ", names: ["希山 愛", "上田 理子"], point: 300},
{name: "りるみゆ", names: ["柳 美舞", "蒼井 りるあ"], point: 300},
{name: "イヤギハジャ", names: ["吉瀬 真珠", "北美 梨寧"], point: 300},
{name: "ぱんださん", names: ["橘 花怜", "律月 ひかる"], point: 300},
{name: "轟", names: ["伊達 花彩", "桜 ひなの"], point: 300},
{name: "トーホク・ラブ・ストーリー", names: ["葉月 結菜", "藤谷 美海", "安杜 羽加"], point: 300},
{name: "とうほくちゃん", names: ["律月 ひかる", "北美 梨寧", "葉月 結菜", "安杜 羽加", "吉瀬 真珠"], point: 300},
{name: "いぎなりちゃん", names: ["橘 花怜", "桜 ひなの", "藤谷 美海", "伊達 花彩"], point: 300},
{name: "そらりじゅ", names: ["村星 りじゅ", "茜 空"], point: 300},
{name: "そらりじゅもあ", names: ["村星 りじゅ", "茜 空", "芹澤 もあ"], point: 300},
{name: "ゆうこは", names: ["宮沢 友", "若菜 こはる"], point: 300},
{name: "りなるり", names: ["結城 りな", "葵 るり"], point: 300},
{name: "第2回スタコミュAWARD選抜(ukka)", names: ["結城 りな", "芹澤 もあ"], point: 300},
{name: "第3回スタコミュAWARD選抜(ukka)", names: ["結城 りな", "葵 るり", "宮沢 友"], point: 300},
{name: "ゆづはな", names: ["市川 優月", "小島 はな"], point: 300},
{name: "あいらもえか", names: ["愛来", "鈴木 萌花"], point: 300},
{name: "第2回スタコミュAWARD選抜(AMEFURASSHI)", names: ["市川 優月", "鈴木 萌花", "小島 はな"], point: 300},
//{name: "圧", names: ["播磨 かな", "佐々木 彩夏(ももいろクローバーZ)"], point: 300},
{name: "圧", names: ["播磨 かな", "佐々木 彩夏(LumiUnion)"], point: 300},
{name: "ロマンスはエチュード", names: ["佐々木 彩夏(LumiUnion)", "里菜", "雪月 心愛", "青山 菜花"], point: 300},
{name: "あやなの", names: ["白浜 あや", "青山 菜花"], point: 300},
{name: "バディ・フィルム", names: ["播磨 かな", "内藤 るな"], point: 300},
{name: "二十歳", names: ["田中 咲帆", "千浜 もあな"], point: 300},
{name: "第2回スタコミュAWARD選抜(LumiUnion)", names: ["田中 咲帆", "雪月 心愛"], point: 300},
{name: "第3回スタコミュAWARD選抜(LumiUnion)", names: ["雪月 心愛", "内藤 るな"], point: 300},
{name: "みずき×みう×るり", names: ["安井 泉妃", "黒坂 美羽", "白石 るり"], point: 300},
{name: "あや×りり×りんな×みうみ", names: ["白浜 あや", "高月 凛々", "嶋田 莉奈", "西村 美海"], point: 300},
{name: "じゅりまい", names: ["原田 麻衣", "鈴江 珠莉"], point: 300},
{name: "ゆぴしのん", names: ["渡辺 ゆう", "百瀬 詩音"], point: 300},
{name: "ゆめみる", names: ["ゆめ", "みる"], point: 300},
{name: "ゆのりおな", names: ["華川 ゆの", "香月 りおな"], point: 300},
{name: "スタコミュAWARD 2025 最多配信部門", names: ["内藤 るな", "結城 りな", "咲良 菜緒"], point: 300},
{name: "スタコミュAWARD 2025 ベストクリエイター部門", names: ["風見 和香", "葵 るり", "雪月 心愛"], point: 300},
{name: "スタコミュAWARD 2025 ベストクセスゴ部門", names: ["仲村 悠菜", "秋本 帆華", "宮沢 友"], point: 300},
{name: "スタコミュAWARD 2025 スタコミュスタッフから推され部門", names: ["小久保 柚乃", "坂本 遥奈"], point: 300},
];
const birthmonthToNames = new Map([
[1, new Set([
"杏 ジュリア", "小泉 遥香", "希山 愛", "芹澤 もあ", "田中 咲帆",
])],
[2, new Set([
"坂本 遥奈", "宮沢 友", "鈴木 萌花", "小島 はな", "播磨 かな", "安井 泉妃", "西村 美海",
])],
[3, new Set([
"小久保 柚乃", "藤谷 美海", "伊達 花彩", "茜 空", "若菜 こはる",
])],
[4, new Set([
"結城 りな", "白浜 あや", "華川 ゆの",
])],
[5, new Set([
"仲村 悠菜", "柳 美舞", "北美 梨寧", "桜 ひなの", "高月 凛々", "渡辺 ゆう", "原田 麻衣",
])],
[6, new Set([
"玉井 詩織", "高城 れに", "佐々木 彩夏(ももいろクローバーZ)", "佐々木 彩夏(LumiUnion)",
"安本 彩花", "辻野 かなみ", "逢里 みう", "吉瀬 真珠", "里菜",
])],
[7, new Set([
"百田 夏菜子", "大黒 柚姫", "坂井 仁香", "春乃 きいな", "律月 ひかる", "葵 るり",
])],
[8, new Set([
"風見 和香", "吉川 ひより", "日南 心那",
])],
[9, new Set([
"桜木 心菜", "桜井 えま", "咲良 菜緒", "葉月 結菜", "千浜 もあな", "青山 菜花",
])],
[10, new Set([
"中山 莉子", "橘 花怜", "安杜 羽加", "村星 りじゅ", "嶋田 莉奈", "鈴江 珠莉", "百瀬 詩音",
"ゆめ", "みる",
])],
[11, new Set([
"秋本 帆華", "上田 理子", "市川 優月", "黒坂 美羽",
])],
[12, new Set([
"真山 りか", "菅田 愛貴", "蒼井 りるあ", "浅井 アヤネ", "愛来", "内藤 るな", "雪月 心愛",
"白石 るり", "香月 りおな",
])],
]);
// return true if all names in skill.names are in nameSet
const matchSkill = (nameSet, skill) => {
return skill.names.reduce((hasAll, name) => hasAll && nameSet.has(name), true);
};
// select the cards with the highest points
const selectMaxCardSet = (nameSet, cardsMap) => {
const baseResult = selectMaxCardSetForRarity(nameSet, cardsMap);
let maxCardSet = baseResult.cardSet;
let maxCardPoint = baseResult.cardPoint;
for (const onlyRarity of [5, 4, 3, 2]) {
const bonus = (onlyRarity - 1) * 10 + 100; // bonus points for rarity
const result = selectMaxCardSetForRarity(nameSet, cardsMap, onlyRarity);
if (result.cardPoint >= 0) {
if (maxCardPoint < result.cardPoint + bonus) {
maxCardSet = result.cardSet;
maxCardPoint = result.cardPoint + bonus;
}
}
}
return {cardSet: maxCardSet, cardPoint: maxCardPoint};
}
// select the cards with the highest points
const selectMaxCardSetForRarity = (nameSet, cardsMap, onlyRarity) => {
const cardSet = new Set();
let sum = 0;
let hasStacommuHighlight = false;
let hasStacommuHighlight2 = false;
let hasStacommuHighlight3 = false;
let hasStacommuAward2025vol1 = false;
let hasStacommuAward2025vol2 = false;
for (const name of nameSet){
const sorted = cardsMap.get(name).filter((c) => { return onlyRarity === undefined ? true : c.rarity === onlyRarity}).sort((a, b) => { return b.rarity - a.rarity; });
if (sorted.length <= 0) {
return {cardSet: new Set(), cardPoint: -1};
}
const stacomuHighlight = sorted.filter((c) => { return c.name == stacommuHighlightName });
const stacomuHighlight2 = sorted.filter((c) => { return c.name == stacommuHighlightName2; });
const stacomuHighlight3 = sorted.filter((c) => { return c.name == stacommuHighlightName3; });
const stacommuAward2025vol1 = sorted.filter((c) => { return c.name == stacommuAward2025Name1; });
const stacommuAward2025vol2 = sorted.filter((c) => { return c.name == stacommuAward2025Name2; });
/*if (!hasStacommuHighlight && stacomuHighlight.length > 0) {
cardSet.add(stacomuHighlight[0]);
sum += stacomuHighlight[0].rarity;
sum += 500; // stacommu highlight bonus
hasStacommuHighlight = true;
} else if (!hasStacommuHighlight2 && stacomuHighlight2.length > 0) {
cardSet.add(stacomuHighlight2[0]);
sum += stacomuHighlight2[0].rarity;
sum += 500; // stacommu highlight bonus
hasStacommuHighlight2 = true;
} else if (!hasStacommuHighlight3 && stacomuHighlight3.length > 0) {
cardSet.add(stacomuHighlight3[0]);
sum += stacomuHighlight3[0].rarity;
sum += 500; // stacommu highlight bonus
hasStacommuHighlight3 = true;
} else*/ if (!hasStacommuAward2025vol1 && stacommuAward2025vol1.length > 0 ) {
cardSet.add(stacommuAward2025vol1[0]);
sum += stacommuAward2025vol1[0].rarity;
sum += 500; // stacommu award 2025 vol.1 bonus
hasStacommuAward2025vol1 = true;
} else if (!hasStacommuAward2025vol2 && stacommuAward2025vol2.length > 0 ) {
cardSet.add(stacommuAward2025vol2[0]);
sum += stacommuAward2025vol2[0].rarity;
sum += 500; // stacommu award 2025 vol.2 bonus
hasStacommuAward2025vol2 = true;
} else {
cardSet.add(sorted[0]);
sum += sorted[0].rarity;
}
}
return {cardSet: cardSet, cardPoint: sum};
}
// Fetch all cards for the user
const fetchCards = async (userId) => {
const rarities = [5, 4, 3, 2];
const cardsMap = new Map();
// Fetch all cards for the user
for (const r of rarities) {
let page = 1;
while (true) {
const url = `https://api-stacommu.orical.jp/cards?partner_id=18&season_id=18000001&user_id=${userId}&card_type=non_memorial&ownership_type=owned&rarity=${r}&page=${page}&per=25`;
const cardList = await (await fetch(url)).json();
for (const card of cardList) {
if (card.person === undefined) {
continue; // Skip cards without person info
}
const personName = card.person.name;
if (!cardsMap.has(personName)) {
cardsMap.set(personName, []);
}
cardsMap.get(card.person.name).push({
rarity: card.rarity,
name: card.name,
personName: personName,
});
}
if (cardList.length < 25 || page > MAX_CARD_PAGE) {
break;
}
page++;
}
}
return cardsMap;
};
// Find available skills based on the user's cards
const findAvailableSkills = (nameSet, skills) => {
return skills.filter((skill) => {
return matchSkill(nameSet, skill);
});
};
// Lookup the unit with maximum points
const lookupMaxUnit = (cardsSet, birthmonthToNames, skills) => {
const thisMonth = (new Date()).getMonth() + 1;
const birthMonthNames = birthmonthToNames.get(thisMonth);
const nameList = Array.from(cardsMap.keys());
// list of cardsSet at maximum point
let cardsSetsAtMax = [];
// maximum point
let maxPoint = 0;
// list of skills at maximum point
let skillsAtMax = [];
for(let i = 0; i < nameList.length - 4; i++) {
for(let j = i + 1; j < nameList.length - 3; j++) {
for(let k = j + 1; k < nameList.length - 2; k++) {
for(let l = k + 1; l < nameList.length - 1; l++) {
for(let m = l + 1; m < nameList.length; m++) {
const candidateNameSet = new Set([nameList[i], nameList[j], nameList[k], nameList[l], nameList[m]]);
const skillNames = [];
let sum = 0;
// select cards
const {cardSet, cardPoint} = selectMaxCardSet(candidateNameSet, cardsMap);
sum += cardPoint;
// add rarity bonus skill names
for (const rarity of [5, 4, 3, 2]) {
const cards = Array.from(cardSet).filter((c) => c.rarity === rarity);
if (cards.length == 5) {
skillNames.push(`☆${rarity}コンボブースト`);
}
}
// birth month bonus
if (Array.from(candidateNameSet).some((c) => birthMonthNames.has(c))) {
sum += 100;
skillNames.push(`${thisMonth}月バースデーメンバーブースト`);
}
// skill points
for (const skill of skills) {
if (matchSkill(candidateNameSet, skill)) {
sum += skill.point;
skillNames.push(skill.name);
}
}
// add skill names with speficic cards
/*if (Array.from(cardSet).some((c) => c.name === stacommuHighlightName)) {
skillNames.push(stacommuHighlightName);
}
if (Array.from(cardSet).some((c) => c.name === stacommuHighlightName2)) {
skillNames.push(stacommuHighlightName2);
}
if (Array.from(cardSet).some((c) => c.name === stacommuHighlightName3)) {
skillNames.push(stacommuHighlightName3);
}*/
if (Array.from(cardSet).some((c) => c.name === stacommuAward2025Name1)) {
skillNames.push(stacommuAward2025Name1)
}
if (Array.from(cardSet).some((c) => c.name === stacommuAward2025Name2)) {
skillNames.push(stacommuAward2025Name2);
}
if (sum > maxPoint) {
maxPoint = sum;
cardsSetsAtMax = [cardSet];
skillsAtMax = [skillNames];
} else if (sum === maxPoint) {
cardsSetsAtMax.push(cardSet);
skillsAtMax.push(skillNames);
}
}
}
}
}
}
return {cardsSetsAtMax, skillsAtMax, maxPoint};
};
// fetch cards
const cardsMap = await fetchCards(userId);
// avaliable skills
const availableSkills = findAvailableSkills(new Set(cardsMap.keys()), skills);
console.log("available skills");
availableSkills.forEach((skill) => {
console.log(`skill found: ${skill.name}(${skill.names.join(",")}) with point ${skill.point}`);
});
// lookup max unit
const {cardsSetsAtMax, skillsAtMax, maxPoint} = lookupMaxUnit(cardsMap, birthmonthToNames, availableSkills);
console.log("Max Point:", maxPoint);
for (let i = 0; i < cardsSetsAtMax.length; i++) {
const cardNames = Array.from(cardsSetsAtMax[i]).map((c) => {return `${c.personName}☆${c.rarity}[${c.name}]`;});
console.log(`Candidate ${i + 1}:`, Array.from(skillsAtMax[i]).join(", ") + "\n" + cardNames.join("\n"));
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment