1. Goal

Repeat Part 1 using FinMind (https://finmindtrade.com) as the data source. The final dataset keeps only ordinary common stocks listed on the Taiwan Stock Exchange (TWSE). ETFs, funds, preferred stocks and other non-common securities are removed.

2. Load packages

# Install once (only if missing):
# install.packages(c("rmarkdown", "knitr", "httr2", "dplyr"))
library(httr2)   # send the web request to the FinMind API
library(dplyr)   # filter / clean / summarise data frames
library(knitr)   # kable() prints nice tables in the HTML

3. Download the data from the FinMind API

FinMind’s dataset for Taiwan stock information is called TaiwanStockInfo. Endpoint: https://api.finmindtrade.com/api/v4/data, parameter dataset=TaiwanStockInfo. Its columns are industry_category, stock_id, stock_name, type and date.

# A token is OPTIONAL. If you have one, set it in the R console (do NOT type it
# into this file):  Sys.setenv(FINMIND_TOKEN = "your_token_here")
token <- Sys.getenv("FINMIND_TOKEN")

# Build the request
req <- request("https://api.finmindtrade.com/api/v4/data") |>
  req_url_query(dataset = "TaiwanStockInfo") |>
  req_timeout(60)

# Add the token only if the user has set one
if (nzchar(token)) req <- req_auth_bearer_token(req, token)

# Send the request. req_perform() stops with an error for HTTP errors (4xx/5xx),
# so we wrap it in tryCatch() to give a readable message.
resp <- tryCatch(
  req_perform(req),
  error = function(e) stop("FinMind request failed: ", conditionMessage(e))
)

# Convert the JSON body into R objects (data part becomes a data frame)
body <- resp_body_json(resp, simplifyVector = TRUE)

# Check the API-level status (FinMind returns status = 200 when OK)
if (is.null(body$status) || body$status != 200) {
  stop("FinMind API error: ", body$msg)
}
if (is.null(body$data) || NROW(body$data) == 0) {
  stop("FinMind returned an empty dataset.")
}

raw <- as_tibble(body$data)

# Check that the columns we need exist
needed <- c("stock_id", "stock_name", "industry_category", "type")
missing_cols <- setdiff(needed, names(raw))
if (length(missing_cols) > 0) {
  stop("Missing expected columns: ", paste(missing_cols, collapse = ", "))
}

n_downloaded <- nrow(raw)
cat("Number of securities downloaded before filtering:", n_downloaded, "\n")
## Number of securities downloaded before filtering: 4340

4. Clean and filter (keep only ordinary common stocks)

# Step 0: make sure the key columns are text and remove extra spaces.
# stock_id MUST stay a character (text), never a number.
df <- raw |>
  mutate(
    stock_id          = trimws(as.character(stock_id)),
    stock_name        = trimws(as.character(stock_name)),
    industry_category = trimws(as.character(industry_category)),
    type              = tolower(trimws(as.character(type)))
  )

# Categories that FinMind uses for things that are NOT ordinary stocks
non_common_categories <- c(
  "ETF", "ETN", "Index", "\u5927\u76e4",          # ETF, ETN, index, 大盤
  "\u53d7\u76ca\u8b49\u5238",                     # 受益證券 (beneficiary certificates / funds)
  "\u5b58\u8a17\u6191\u8b49",                     # 存託憑證 (depositary receipts)
  "\u7279\u5225\u80a1",                           # 特別股 (preferred shares)
  "\u6b0a\u8b49", "\u8a8d\u8cfc\u6b0a\u8b49",     # 權證, 認購權證 (warrants)
  "\u8a8d\u552e\u6b0a\u8b49", "\u725b\u718a\u8b49",
  "\u7ba1\u7406\u80a1\u7968", "Holding", "Others"
)
# Keywords searched inside the CATEGORY text (a second safety net)
category_keywords <- "ETF|ETN|\u53d7\u76ca|\u5b58\u8a17|\u6b0a\u8b49|\u725b\u718a|\u7279\u5225\u80a1|\u6307\u6578"

# Each step removes rows; we count after every step for the report.
step1 <- df    |> filter(type == "twse")                       # listed on TWSE only
step2 <- step1 |> filter(!is.na(stock_id), stock_id != "",
                         !is.na(stock_name), stock_name != "") # remove missing records
step3 <- step2 |> filter(!is.na(industry_category), industry_category != "",
                         !(industry_category %in% non_common_categories),
                         !grepl(category_keywords, industry_category, ignore.case = TRUE))
# Ordinary TWSE stocks have exactly 4 digits, first digit not 0.
# ETFs (e.g. 0050, 00878), preferred shares (e.g. 2882A) and warrants
# (5-6 characters) do not match this pattern.
step4 <- step3 |> filter(grepl("^[1-9][0-9]{3}$", stock_id))

# Remove duplicated stock codes (keep the most recently updated row)
step5 <- step4 |>
  arrange(desc(date)) |>
  distinct(stock_id, .keep_all = TRUE)

# Summary of what each step removed
filter_log <- tibble(
  step = c("Downloaded", "Only type = twse", "Remove missing records",
           "Remove non-common categories (ETF/fund/DR/preferred/...)",
           "Keep 4-digit numeric codes only", "Remove duplicate stock_id"),
  rows_remaining = c(nrow(df), nrow(step1), nrow(step2),
                     nrow(step3), nrow(step4), nrow(step5))
) |>
  mutate(rows_removed = lag(rows_remaining, default = first(rows_remaining)) - rows_remaining)

kable(filter_log, caption = "Rows remaining / removed at each filtering step")
Rows remaining / removed at each filtering step
step rows_remaining rows_removed
Downloaded 4340 0
Only type = twse 2404 1936
Remove missing records 2404 0
Remove non-common categories (ETF/fund/DR/preferred/…) 2029 375
Keep 4-digit numeric codes only 1989 40
Remove duplicate stock_id 1216 773

5. Build the final dataset

FinMind’s TaiwanStockInfo does not contain the original listing date. Its date column is only the date FinMind updated the record, so it must not be treated as a listing date. The code uses a listing-date column only if FinMind provides one; otherwise listing_date is left as NA.

# Look for a real listing-date column in the FinMind data (none expected)
date_col <- intersect(c("listing_date", "list_date"), names(step5))

final <- step5 |>
  transmute(
    stock_id,
    name = stock_name,
    listing_date = if (length(date_col) > 0) as.Date(.data[[date_col[1]]]) else as.Date(NA),
    industry = industry_category,
    finmind_update_date = as.Date(date)   # for reference only, NOT the listing date
  ) |>
  arrange(stock_id)

n_final <- nrow(final)
cat("Securities downloaded before filtering:", n_downloaded, "\n")
## Securities downloaded before filtering: 4340
cat("Common stocks remaining after filtering:", n_final, "\n")
## Common stocks remaining after filtering: 1216
cat("Records removed:", n_downloaded - n_final, "\n")
## Records removed: 3124
cat("Rows with missing listing_date:", sum(is.na(final$listing_date)), "\n")
## Rows with missing listing_date: 1216

First 20 rows

kable(head(final, 20))
stock_id name listing_date industry finmind_update_date
1101 台泥 NA 水泥工業 2026-10-16
1102 亞泥 NA 水泥工業 2026-10-16
1103 嘉泥 NA 水泥工業 2026-10-16
1104 環泥 NA 水泥工業 2026-10-16
1107 建台 NA 其他 2024-12-04
1108 幸福 NA 水泥工業 2026-10-16
1109 信大 NA 水泥工業 2026-10-16
1110 東泥 NA 水泥工業 2026-10-16
1201 味全 NA 食品工業 2026-10-16
1203 味王 NA 食品工業 2026-10-16
1210 大成 NA 食品工業 2026-10-16
1213 大飲 NA 食品工業 2026-10-16
1215 卜蜂 NA 食品工業 2026-10-16
1216 統一 NA 食品工業 2026-10-16
1217 愛之味 NA 食品工業 2026-10-16
1218 泰山 NA 食品工業 2026-10-16
1219 福壽 NA 食品工業 2026-10-16
1220 台榮 NA 食品工業 2026-10-16
1225 福懋油 NA 食品工業 2026-10-16
1227 佳格 NA 食品工業 2026-10-16

Sanity check

If the filters worked, this table should be empty (no ETF/fund/preferred/DR wording left in the names, and no stock code that is not 4 digits).

suspicious <- final |>
  filter(grepl("ETF|ETN|\u57fa\u91d1|\u7279\u5225\u80a1|-DR", name, ignore.case = TRUE) |
         !grepl("^[1-9][0-9]{3}$", stock_id))
kable(suspicious, caption = "Possible leftovers (should be empty)")
Possible leftovers (should be empty)
stock_id name listing_date industry finmind_update_date
2883 凱基金 NA 金融保險 2026-10-16

6. Number of stocks by industry

industry_summary <- final |>
  count(industry, name = "n_stocks", sort = TRUE)
kable(industry_summary)
industry n_stocks
電子工業 258
半導體業 75
光電業 62
其他 62
化學生技醫療 57
建材營造 57
電機機械 51
金融保險 46
紡織纖維 43
汽車工業 42
其他電子業 39
通信網路業 35
鋼鐵工業 31
航運業 29
創新板股票 26
化學工業 25
食品工業 24
塑膠工業 23
綠能環保 23
電腦及週邊設備業 22
貿易百貨 20
電器電纜 20
觀光餐旅 19
電子零組件業 19
運動休閒 18
居家生活 11
數位雲端 11
橡膠工業 11
生技醫療業 11
油電燃氣業 9
電子通路業 9
資訊服務業 8
造紙工業 8
水泥工業 7
玻璃陶瓷 5

7. Save the cleaned dataset

# stock_id is text, so leading zeros (if any) are preserved.
write.csv(final, "finmind_tw_listed_stocks.csv",
          row.names = FALSE, na = "", fileEncoding = "UTF-8")
cat("Saved to:", normalizePath("finmind_tw_listed_stocks.csv"), "\n")
## Saved to: C:\Users\Dell\Downloads\finmind_tw_listed_stocks.csv

8. Limitations

LS0tDQp0aXRsZTogIkhvbWUgd29yayA0Ig0KYXV0aG9yOiAiMTE1MDM1MTE4Ig0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCiMgU2hvdyBjb2RlIGFuZCByZXN1bHRzIGluIHRoZSBIVE1MOyBrZWVwIHdhcm5pbmdzIG91dCBvZiB0aGUgcmVwb3J0DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFKQ0KYGBgDQoNCiMgMS4gR29hbA0KDQpSZXBlYXQgUGFydCAxIHVzaW5nICoqRmluTWluZCoqIChodHRwczovL2Zpbm1pbmR0cmFkZS5jb20pIGFzIHRoZSBkYXRhIHNvdXJjZS4NClRoZSBmaW5hbCBkYXRhc2V0IGtlZXBzIG9ubHkgKipvcmRpbmFyeSBjb21tb24gc3RvY2tzKiogbGlzdGVkIG9uIHRoZSBUYWl3YW4NClN0b2NrIEV4Y2hhbmdlIChUV1NFKS4gRVRGcywgZnVuZHMsIHByZWZlcnJlZCBzdG9ja3MgYW5kIG90aGVyIG5vbi1jb21tb24NCnNlY3VyaXRpZXMgYXJlIHJlbW92ZWQuDQoNCiMgMi4gTG9hZCBwYWNrYWdlcw0KDQpgYGB7ciBwYWNrYWdlc30NCiMgSW5zdGFsbCBvbmNlIChvbmx5IGlmIG1pc3NpbmcpOg0KIyBpbnN0YWxsLnBhY2thZ2VzKGMoInJtYXJrZG93biIsICJrbml0ciIsICJodHRyMiIsICJkcGx5ciIpKQ0KbGlicmFyeShodHRyMikgICAjIHNlbmQgdGhlIHdlYiByZXF1ZXN0IHRvIHRoZSBGaW5NaW5kIEFQSQ0KbGlicmFyeShkcGx5cikgICAjIGZpbHRlciAvIGNsZWFuIC8gc3VtbWFyaXNlIGRhdGEgZnJhbWVzDQpsaWJyYXJ5KGtuaXRyKSAgICMga2FibGUoKSBwcmludHMgbmljZSB0YWJsZXMgaW4gdGhlIEhUTUwNCmBgYA0KDQojIDMuIERvd25sb2FkIHRoZSBkYXRhIGZyb20gdGhlIEZpbk1pbmQgQVBJDQoNCkZpbk1pbmQncyBkYXRhc2V0IGZvciBUYWl3YW4gc3RvY2sgaW5mb3JtYXRpb24gaXMgY2FsbGVkICoqYFRhaXdhblN0b2NrSW5mb2AqKi4NCkVuZHBvaW50OiBgaHR0cHM6Ly9hcGkuZmlubWluZHRyYWRlLmNvbS9hcGkvdjQvZGF0YWAsIHBhcmFtZXRlciBgZGF0YXNldD1UYWl3YW5TdG9ja0luZm9gLg0KSXRzIGNvbHVtbnMgYXJlIGBpbmR1c3RyeV9jYXRlZ29yeWAsIGBzdG9ja19pZGAsIGBzdG9ja19uYW1lYCwgYHR5cGVgIGFuZCBgZGF0ZWAuDQoNCmBgYHtyIGRvd25sb2FkfQ0KIyBBIHRva2VuIGlzIE9QVElPTkFMLiBJZiB5b3UgaGF2ZSBvbmUsIHNldCBpdCBpbiB0aGUgUiBjb25zb2xlIChkbyBOT1QgdHlwZSBpdA0KIyBpbnRvIHRoaXMgZmlsZSk6ICBTeXMuc2V0ZW52KEZJTk1JTkRfVE9LRU4gPSAieW91cl90b2tlbl9oZXJlIikNCnRva2VuIDwtIFN5cy5nZXRlbnYoIkZJTk1JTkRfVE9LRU4iKQ0KDQojIEJ1aWxkIHRoZSByZXF1ZXN0DQpyZXEgPC0gcmVxdWVzdCgiaHR0cHM6Ly9hcGkuZmlubWluZHRyYWRlLmNvbS9hcGkvdjQvZGF0YSIpIHw+DQogIHJlcV91cmxfcXVlcnkoZGF0YXNldCA9ICJUYWl3YW5TdG9ja0luZm8iKSB8Pg0KICByZXFfdGltZW91dCg2MCkNCg0KIyBBZGQgdGhlIHRva2VuIG9ubHkgaWYgdGhlIHVzZXIgaGFzIHNldCBvbmUNCmlmIChuemNoYXIodG9rZW4pKSByZXEgPC0gcmVxX2F1dGhfYmVhcmVyX3Rva2VuKHJlcSwgdG9rZW4pDQoNCiMgU2VuZCB0aGUgcmVxdWVzdC4gcmVxX3BlcmZvcm0oKSBzdG9wcyB3aXRoIGFuIGVycm9yIGZvciBIVFRQIGVycm9ycyAoNHh4LzV4eCksDQojIHNvIHdlIHdyYXAgaXQgaW4gdHJ5Q2F0Y2goKSB0byBnaXZlIGEgcmVhZGFibGUgbWVzc2FnZS4NCnJlc3AgPC0gdHJ5Q2F0Y2goDQogIHJlcV9wZXJmb3JtKHJlcSksDQogIGVycm9yID0gZnVuY3Rpb24oZSkgc3RvcCgiRmluTWluZCByZXF1ZXN0IGZhaWxlZDogIiwgY29uZGl0aW9uTWVzc2FnZShlKSkNCikNCg0KIyBDb252ZXJ0IHRoZSBKU09OIGJvZHkgaW50byBSIG9iamVjdHMgKGRhdGEgcGFydCBiZWNvbWVzIGEgZGF0YSBmcmFtZSkNCmJvZHkgPC0gcmVzcF9ib2R5X2pzb24ocmVzcCwgc2ltcGxpZnlWZWN0b3IgPSBUUlVFKQ0KDQojIENoZWNrIHRoZSBBUEktbGV2ZWwgc3RhdHVzIChGaW5NaW5kIHJldHVybnMgc3RhdHVzID0gMjAwIHdoZW4gT0spDQppZiAoaXMubnVsbChib2R5JHN0YXR1cykgfHwgYm9keSRzdGF0dXMgIT0gMjAwKSB7DQogIHN0b3AoIkZpbk1pbmQgQVBJIGVycm9yOiAiLCBib2R5JG1zZykNCn0NCmlmIChpcy5udWxsKGJvZHkkZGF0YSkgfHwgTlJPVyhib2R5JGRhdGEpID09IDApIHsNCiAgc3RvcCgiRmluTWluZCByZXR1cm5lZCBhbiBlbXB0eSBkYXRhc2V0LiIpDQp9DQoNCnJhdyA8LSBhc190aWJibGUoYm9keSRkYXRhKQ0KDQojIENoZWNrIHRoYXQgdGhlIGNvbHVtbnMgd2UgbmVlZCBleGlzdA0KbmVlZGVkIDwtIGMoInN0b2NrX2lkIiwgInN0b2NrX25hbWUiLCAiaW5kdXN0cnlfY2F0ZWdvcnkiLCAidHlwZSIpDQptaXNzaW5nX2NvbHMgPC0gc2V0ZGlmZihuZWVkZWQsIG5hbWVzKHJhdykpDQppZiAobGVuZ3RoKG1pc3NpbmdfY29scykgPiAwKSB7DQogIHN0b3AoIk1pc3NpbmcgZXhwZWN0ZWQgY29sdW1uczogIiwgcGFzdGUobWlzc2luZ19jb2xzLCBjb2xsYXBzZSA9ICIsICIpKQ0KfQ0KDQpuX2Rvd25sb2FkZWQgPC0gbnJvdyhyYXcpDQpjYXQoIk51bWJlciBvZiBzZWN1cml0aWVzIGRvd25sb2FkZWQgYmVmb3JlIGZpbHRlcmluZzoiLCBuX2Rvd25sb2FkZWQsICJcbiIpDQpgYGANCg0KIyA0LiBDbGVhbiBhbmQgZmlsdGVyIChrZWVwIG9ubHkgb3JkaW5hcnkgY29tbW9uIHN0b2NrcykNCg0KYGBge3IgZmlsdGVyfQ0KIyBTdGVwIDA6IG1ha2Ugc3VyZSB0aGUga2V5IGNvbHVtbnMgYXJlIHRleHQgYW5kIHJlbW92ZSBleHRyYSBzcGFjZXMuDQojIHN0b2NrX2lkIE1VU1Qgc3RheSBhIGNoYXJhY3RlciAodGV4dCksIG5ldmVyIGEgbnVtYmVyLg0KZGYgPC0gcmF3IHw+DQogIG11dGF0ZSgNCiAgICBzdG9ja19pZCAgICAgICAgICA9IHRyaW13cyhhcy5jaGFyYWN0ZXIoc3RvY2tfaWQpKSwNCiAgICBzdG9ja19uYW1lICAgICAgICA9IHRyaW13cyhhcy5jaGFyYWN0ZXIoc3RvY2tfbmFtZSkpLA0KICAgIGluZHVzdHJ5X2NhdGVnb3J5ID0gdHJpbXdzKGFzLmNoYXJhY3RlcihpbmR1c3RyeV9jYXRlZ29yeSkpLA0KICAgIHR5cGUgICAgICAgICAgICAgID0gdG9sb3dlcih0cmltd3MoYXMuY2hhcmFjdGVyKHR5cGUpKSkNCiAgKQ0KDQojIENhdGVnb3JpZXMgdGhhdCBGaW5NaW5kIHVzZXMgZm9yIHRoaW5ncyB0aGF0IGFyZSBOT1Qgb3JkaW5hcnkgc3RvY2tzDQpub25fY29tbW9uX2NhdGVnb3JpZXMgPC0gYygNCiAgIkVURiIsICJFVE4iLCAiSW5kZXgiLCAiXHU1OTI3XHU3NmU0IiwgICAgICAgICAgIyBFVEYsIEVUTiwgaW5kZXgsIOWkp+ebpA0KICAiXHU1M2Q3XHU3NmNhXHU4YjQ5XHU1MjM4IiwgICAgICAgICAgICAgICAgICAgICAjIOWPl+ebiuitieWIuCAoYmVuZWZpY2lhcnkgY2VydGlmaWNhdGVzIC8gZnVuZHMpDQogICJcdTViNThcdThhMTdcdTYxOTFcdThiNDkiLCAgICAgICAgICAgICAgICAgICAgICMg5a2Y6KiX5oaR6K2JIChkZXBvc2l0YXJ5IHJlY2VpcHRzKQ0KICAiXHU3Mjc5XHU1MjI1XHU4MGExIiwgICAgICAgICAgICAgICAgICAgICAgICAgICAjIOeJueWIpeiCoSAocHJlZmVycmVkIHNoYXJlcykNCiAgIlx1NmIwYVx1OGI0OSIsICJcdThhOGRcdThjZmNcdTZiMGFcdThiNDkiLCAgICAgIyDmrIrorYksIOiqjeizvOasiuitiSAod2FycmFudHMpDQogICJcdThhOGRcdTU1MmVcdTZiMGFcdThiNDkiLCAiXHU3MjViXHU3MThhXHU4YjQ5IiwNCiAgIlx1N2JhMVx1NzQwNlx1ODBhMVx1Nzk2OCIsICJIb2xkaW5nIiwgIk90aGVycyINCikNCiMgS2V5d29yZHMgc2VhcmNoZWQgaW5zaWRlIHRoZSBDQVRFR09SWSB0ZXh0IChhIHNlY29uZCBzYWZldHkgbmV0KQ0KY2F0ZWdvcnlfa2V5d29yZHMgPC0gIkVURnxFVE58XHU1M2Q3XHU3NmNhfFx1NWI1OFx1OGExN3xcdTZiMGFcdThiNDl8XHU3MjViXHU3MThhfFx1NzI3OVx1NTIyNVx1ODBhMXxcdTYzMDdcdTY1NzgiDQoNCiMgRWFjaCBzdGVwIHJlbW92ZXMgcm93czsgd2UgY291bnQgYWZ0ZXIgZXZlcnkgc3RlcCBmb3IgdGhlIHJlcG9ydC4NCnN0ZXAxIDwtIGRmICAgIHw+IGZpbHRlcih0eXBlID09ICJ0d3NlIikgICAgICAgICAgICAgICAgICAgICAgICMgbGlzdGVkIG9uIFRXU0Ugb25seQ0Kc3RlcDIgPC0gc3RlcDEgfD4gZmlsdGVyKCFpcy5uYShzdG9ja19pZCksIHN0b2NrX2lkICE9ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShzdG9ja19uYW1lKSwgc3RvY2tfbmFtZSAhPSAiIikgIyByZW1vdmUgbWlzc2luZyByZWNvcmRzDQpzdGVwMyA8LSBzdGVwMiB8PiBmaWx0ZXIoIWlzLm5hKGluZHVzdHJ5X2NhdGVnb3J5KSwgaW5kdXN0cnlfY2F0ZWdvcnkgIT0gIiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIShpbmR1c3RyeV9jYXRlZ29yeSAlaW4lIG5vbl9jb21tb25fY2F0ZWdvcmllcyksDQogICAgICAgICAgICAgICAgICAgICAgICAgIWdyZXBsKGNhdGVnb3J5X2tleXdvcmRzLCBpbmR1c3RyeV9jYXRlZ29yeSwgaWdub3JlLmNhc2UgPSBUUlVFKSkNCiMgT3JkaW5hcnkgVFdTRSBzdG9ja3MgaGF2ZSBleGFjdGx5IDQgZGlnaXRzLCBmaXJzdCBkaWdpdCBub3QgMC4NCiMgRVRGcyAoZS5nLiAwMDUwLCAwMDg3OCksIHByZWZlcnJlZCBzaGFyZXMgKGUuZy4gMjg4MkEpIGFuZCB3YXJyYW50cw0KIyAoNS02IGNoYXJhY3RlcnMpIGRvIG5vdCBtYXRjaCB0aGlzIHBhdHRlcm4uDQpzdGVwNCA8LSBzdGVwMyB8PiBmaWx0ZXIoZ3JlcGwoIl5bMS05XVswLTldezN9JCIsIHN0b2NrX2lkKSkNCg0KIyBSZW1vdmUgZHVwbGljYXRlZCBzdG9jayBjb2RlcyAoa2VlcCB0aGUgbW9zdCByZWNlbnRseSB1cGRhdGVkIHJvdykNCnN0ZXA1IDwtIHN0ZXA0IHw+DQogIGFycmFuZ2UoZGVzYyhkYXRlKSkgfD4NCiAgZGlzdGluY3Qoc3RvY2tfaWQsIC5rZWVwX2FsbCA9IFRSVUUpDQoNCiMgU3VtbWFyeSBvZiB3aGF0IGVhY2ggc3RlcCByZW1vdmVkDQpmaWx0ZXJfbG9nIDwtIHRpYmJsZSgNCiAgc3RlcCA9IGMoIkRvd25sb2FkZWQiLCAiT25seSB0eXBlID0gdHdzZSIsICJSZW1vdmUgbWlzc2luZyByZWNvcmRzIiwNCiAgICAgICAgICAgIlJlbW92ZSBub24tY29tbW9uIGNhdGVnb3JpZXMgKEVURi9mdW5kL0RSL3ByZWZlcnJlZC8uLi4pIiwNCiAgICAgICAgICAgIktlZXAgNC1kaWdpdCBudW1lcmljIGNvZGVzIG9ubHkiLCAiUmVtb3ZlIGR1cGxpY2F0ZSBzdG9ja19pZCIpLA0KICByb3dzX3JlbWFpbmluZyA9IGMobnJvdyhkZiksIG5yb3coc3RlcDEpLCBucm93KHN0ZXAyKSwNCiAgICAgICAgICAgICAgICAgICAgIG5yb3coc3RlcDMpLCBucm93KHN0ZXA0KSwgbnJvdyhzdGVwNSkpDQopIHw+DQogIG11dGF0ZShyb3dzX3JlbW92ZWQgPSBsYWcocm93c19yZW1haW5pbmcsIGRlZmF1bHQgPSBmaXJzdChyb3dzX3JlbWFpbmluZykpIC0gcm93c19yZW1haW5pbmcpDQoNCmthYmxlKGZpbHRlcl9sb2csIGNhcHRpb24gPSAiUm93cyByZW1haW5pbmcgLyByZW1vdmVkIGF0IGVhY2ggZmlsdGVyaW5nIHN0ZXAiKQ0KYGBgDQoNCiMgNS4gQnVpbGQgdGhlIGZpbmFsIGRhdGFzZXQNCg0KRmluTWluZCdzIGBUYWl3YW5TdG9ja0luZm9gIGRvZXMgKipub3QqKiBjb250YWluIHRoZSBvcmlnaW5hbCBsaXN0aW5nIGRhdGUuIEl0cw0KYGRhdGVgIGNvbHVtbiBpcyBvbmx5IHRoZSBkYXRlIEZpbk1pbmQgdXBkYXRlZCB0aGUgcmVjb3JkLCBzbyBpdCBtdXN0ICoqbm90KioNCmJlIHRyZWF0ZWQgYXMgYSBsaXN0aW5nIGRhdGUuIFRoZSBjb2RlIHVzZXMgYSBsaXN0aW5nLWRhdGUgY29sdW1uIG9ubHkgaWYNCkZpbk1pbmQgcHJvdmlkZXMgb25lOyBvdGhlcndpc2UgYGxpc3RpbmdfZGF0ZWAgaXMgbGVmdCBhcyBgTkFgLg0KDQpgYGB7ciBmaW5hbH0NCiMgTG9vayBmb3IgYSByZWFsIGxpc3RpbmctZGF0ZSBjb2x1bW4gaW4gdGhlIEZpbk1pbmQgZGF0YSAobm9uZSBleHBlY3RlZCkNCmRhdGVfY29sIDwtIGludGVyc2VjdChjKCJsaXN0aW5nX2RhdGUiLCAibGlzdF9kYXRlIiksIG5hbWVzKHN0ZXA1KSkNCg0KZmluYWwgPC0gc3RlcDUgfD4NCiAgdHJhbnNtdXRlKA0KICAgIHN0b2NrX2lkLA0KICAgIG5hbWUgPSBzdG9ja19uYW1lLA0KICAgIGxpc3RpbmdfZGF0ZSA9IGlmIChsZW5ndGgoZGF0ZV9jb2wpID4gMCkgYXMuRGF0ZSguZGF0YVtbZGF0ZV9jb2xbMV1dXSkgZWxzZSBhcy5EYXRlKE5BKSwNCiAgICBpbmR1c3RyeSA9IGluZHVzdHJ5X2NhdGVnb3J5LA0KICAgIGZpbm1pbmRfdXBkYXRlX2RhdGUgPSBhcy5EYXRlKGRhdGUpICAgIyBmb3IgcmVmZXJlbmNlIG9ubHksIE5PVCB0aGUgbGlzdGluZyBkYXRlDQogICkgfD4NCiAgYXJyYW5nZShzdG9ja19pZCkNCg0Kbl9maW5hbCA8LSBucm93KGZpbmFsKQ0KY2F0KCJTZWN1cml0aWVzIGRvd25sb2FkZWQgYmVmb3JlIGZpbHRlcmluZzoiLCBuX2Rvd25sb2FkZWQsICJcbiIpDQpjYXQoIkNvbW1vbiBzdG9ja3MgcmVtYWluaW5nIGFmdGVyIGZpbHRlcmluZzoiLCBuX2ZpbmFsLCAiXG4iKQ0KY2F0KCJSZWNvcmRzIHJlbW92ZWQ6Iiwgbl9kb3dubG9hZGVkIC0gbl9maW5hbCwgIlxuIikNCmNhdCgiUm93cyB3aXRoIG1pc3NpbmcgbGlzdGluZ19kYXRlOiIsIHN1bShpcy5uYShmaW5hbCRsaXN0aW5nX2RhdGUpKSwgIlxuIikNCmBgYA0KDQojIyBGaXJzdCAyMCByb3dzDQoNCmBgYHtyIGhlYWR9DQprYWJsZShoZWFkKGZpbmFsLCAyMCkpDQpgYGANCg0KIyMgU2FuaXR5IGNoZWNrDQoNCklmIHRoZSBmaWx0ZXJzIHdvcmtlZCwgdGhpcyB0YWJsZSBzaG91bGQgYmUgZW1wdHkgKG5vIEVURi9mdW5kL3ByZWZlcnJlZC9EUg0Kd29yZGluZyBsZWZ0IGluIHRoZSBuYW1lcywgYW5kIG5vIHN0b2NrIGNvZGUgdGhhdCBpcyBub3QgNCBkaWdpdHMpLg0KDQpgYGB7ciBjaGVja30NCnN1c3BpY2lvdXMgPC0gZmluYWwgfD4NCiAgZmlsdGVyKGdyZXBsKCJFVEZ8RVROfFx1NTdmYVx1OTFkMXxcdTcyNzlcdTUyMjVcdTgwYTF8LURSIiwgbmFtZSwgaWdub3JlLmNhc2UgPSBUUlVFKSB8DQogICAgICAgICAhZ3JlcGwoIl5bMS05XVswLTldezN9JCIsIHN0b2NrX2lkKSkNCmthYmxlKHN1c3BpY2lvdXMsIGNhcHRpb24gPSAiUG9zc2libGUgbGVmdG92ZXJzIChzaG91bGQgYmUgZW1wdHkpIikNCmBgYA0KDQojIDYuIE51bWJlciBvZiBzdG9ja3MgYnkgaW5kdXN0cnkNCg0KYGBge3IgaW5kdXN0cnl9DQppbmR1c3RyeV9zdW1tYXJ5IDwtIGZpbmFsIHw+DQogIGNvdW50KGluZHVzdHJ5LCBuYW1lID0gIm5fc3RvY2tzIiwgc29ydCA9IFRSVUUpDQprYWJsZShpbmR1c3RyeV9zdW1tYXJ5KQ0KYGBgDQoNCiMgNy4gU2F2ZSB0aGUgY2xlYW5lZCBkYXRhc2V0DQoNCmBgYHtyIHNhdmV9DQojIHN0b2NrX2lkIGlzIHRleHQsIHNvIGxlYWRpbmcgemVyb3MgKGlmIGFueSkgYXJlIHByZXNlcnZlZC4NCndyaXRlLmNzdihmaW5hbCwgImZpbm1pbmRfdHdfbGlzdGVkX3N0b2Nrcy5jc3YiLA0KICAgICAgICAgIHJvdy5uYW1lcyA9IEZBTFNFLCBuYSA9ICIiLCBmaWxlRW5jb2RpbmcgPSAiVVRGLTgiKQ0KY2F0KCJTYXZlZCB0bzoiLCBub3JtYWxpemVQYXRoKCJmaW5taW5kX3R3X2xpc3RlZF9zdG9ja3MuY3N2IiksICJcbiIpDQpgYGANCg0KIyA4LiBMaW1pdGF0aW9ucw0KDQoqIEZpbk1pbmQgYFRhaXdhblN0b2NrSW5mb2AgaGFzIG5vIG9yaWdpbmFsIGxpc3RpbmcgZGF0ZS4gVGhlIGBkYXRlYCBjb2x1bW4gaXMgYW4NCiAgdXBkYXRlIGRhdGUsIHNvIGBsaXN0aW5nX2RhdGVgIGlzIGBOQWAgdW5sZXNzIEZpbk1pbmQgYWRkcyBzdWNoIGEgY29sdW1uLg0KKiAiTGlzdGVkIiBtZWFucyBgdHlwZSA9PSAidHdzZSJgIChUV1NFIG1haW4gYm9hcmQpLiBPVEMgKGB0cGV4YCkgc3RvY2tzIGFyZSBleGNsdWRlZC4NCiogRmlsdGVyaW5nIHJlbGllcyBvbiBGaW5NaW5kJ3MgYGluZHVzdHJ5X2NhdGVnb3J5YCBwbHVzIHRoZSA0LWRpZ2l0IGNvZGUgZm9ybWF0Lg0KICBGaW5NaW5kIG1heSByZW5hbWUgY2F0ZWdvcmllcyBpbiB0aGUgZnV0dXJlLCBzbyBjaGVjayB0aGUgc2FuaXR5IHRhYmxlIGFib3ZlLg0K