返回首頁 

Greasy Fork is available in English.

Search App Store

Search Apple app in your browser

// ==UserScript==// @name         Search App Store// @namespace    https://greasyfork.org/users/136230// @version      0.1.0// @description  Search Apple app in your browser// @author       eisen-stein// @include      https://*.apple.com/*// @connect      apple.com// @connect      imgur.com// @grant        GM.xmlHttpRequest// ==/UserScript==/*** @typedef {{*  advisories: string[];*  appletvScreenshotUrls: string[];*  artistId: number;*  artistName: string;*  artistViewUrl: string;*  artworkUrl100: string;*  artworkUrl512: string;*  artworkUrl60: string;*  averageUserRating: number;*  averageUserRatingForCurrentVersion: number;*  bundleId: string;*  contentAdvisoryRating: string;*  currency: string;*  currentVersionReleaseDate: string;*  description: string;*  features: string[];*  fileSizeBytes: number;*  formatedPrice: string;*  genreIds: string[];*  genres: string[];*  ipadScreenshotUrls: string[];*  isGameCenterEnabled: boolean;*  isVppDeviceBasedLicensingEnabled: boolean;*  kind: 'software' | 'music' | '';*  languageCodesISO2A: string[];*  minimumOsVersion: string;*  price: number;*  primaryGenreId: string;*  primaryGenreName: string;*  releaseDate: string;*  releaseNotes: string;*  screenshotUrls: string[];*  sellerName: string;*  sellerUrl: string;*  supportedDevices: string[];*  trackCensoredName: string;*  trackContentRating: string;*  trackId: number;*  trackName: string;*  trackViewUrl: string;*  userRatingCount: number;*  userRatingCountForCurrentVersion: number;*  version: string;*  wrapperType: 'software' | 'music' | '';* }} Software*/(function () {'use strict';DOMReady().then(async () => {modalView.create()inputView.create()await lo###iew.loadIcon()lo###iew.create()})async function makeRequest(details) {const params = typeof details == 'string' ? { url: details } : Object.assign({}, details)let resolve, reject;const promise = new Promise((res, rej) => {resolve = res;reject = rej;})GM.xmlHttpRequest({...params,method: params.method || 'GET',url: params.url,onload: function (r) {const response = {data: r.response,headers: parseHeaders(r.responseHeaders),status: r.status,ok: r.status == 200,finalUrl: r.finalUrl,}resolve(response)},onprogress: function (r) {params.onprogress && params.onprogress(r.loaded, r.total);},onerror: function (r) {resolve({data: null,headers: parseHeaders(r.responseHeaders),status: r.status,ok: false,problem: (r.status || 'error').toString(),})},ontimeout: function (r) {resolve({data: null,headers: parseHeaders(r.responseHeaders),status: r.status,ok: false,problem: 'TIMEOUT',})},onreadystatechange: function (r) {},})return promise;}function parseHeaders(headersString) {if (typeof headersString !== 'string') {return headersString}return headersString.split(/\r?\n/g).map(function (s) { return s.trim() }).filter(Boolean).reduce(function (acc, cur) {var res = cur.split(':')var key, valif (res[0]) {key = res[0].trim().toLowerCase()val = res.slice(1).join('').trim()acc[key] = val}return acc}, {})}/*** @param {{*  responseText: string;*  headers: { [x: string]: string };*  ignoreXML?: boolean;*  responseType?: string;* }} params*/function parseResponse(params) {var responseText = params.responseText,headers = params.headers,responseType = params.responseType;var isText = !responseType || responseType.toLowerCase() === 'text'var contentType = headers['content-type'] || ''var ignoreXML = params.ignoreXML === undefined ? true : false;if (isText&& contentType.indexOf('application/json') > -1) {return JSON.parse(responseText)}if (!ignoreXML&& isText&& (contentType.indexOf('text/html') > -1|| contentType.indexOf('text/xml') > -1)) {return createDocument(responseText)}return responseText}function URLEncode(value) {return encodeURIComponent(value.replace(/\s+/g, '+'))// return value.replace(/\s+/g, '+')}function URLSearch(params) {return Object.keys(params).map(key => {return `${key}=${URLEncode(params[key])}`}).join('&')}/*** @param {string} packageName*/async function searchAppStore(packageName) {const country = navigator.language.slice(0, 2)const params = {term: packageName,country,entity: 'software',}const url = 'https://itunes.apple.com/search?' + URLSearch(params)console.log('url = ', url)const response = await makeRequest(url)/** @type {{ r###ltCount: number; r###lts: Software[]; }} */const data = parseResponse({ responseText: response.data, headers: { 'content-type': 'application/json' } })storeView.create(data)modalView.show();}/*** @param {Software} data*/function createSoftwareView(data) {const view = createView(`<div class="app-store-software"><image class="app-store-software-icon" src="${data.artworkUrl100}"></image><div class="app-store-software-content"><div class="app-store-software-name" ><a class="name" href="${data.trackViewUrl}">${data.trackName}</a><span class="version">v${data.version}</span></div><div class="app-store-software-description"><span>${data.description.slice(0, 200)}</span></div><div class="app-store-software-meta"><div class="app-store-software-rating">рейтинг: ${data.averageUserRating.toFixed(2)}</div><div class="app-store-software-genres">${data.genres.join(', ')}</div><div class="app-store-software-author">${data.artistName}</div></div></div></div>`)return view;}/*** @param {string} html* @return {HTMLElement}*/function createView(html) {const div = document.createElement('div')div.innerHTML = (html || '').replace(/\s+/g, ' ').replace(/\r?\n/g, ' ').trim()return div.firstElementChild.cloneNode(true)}function createDocument(html, title) {title = title || ''var doc = document.implementation.createHTMLDocument(title);doc.documentElement.innerHTML = htmlreturn doc}function onSubmit(e) {e.preventDefault()const input = document.querySelector('#package-name')if (!input) {console.error('input not found')return}const packageName = input.value;searchAppStore(packageName).catch((e) => {console.error('searchAppStore error: ', e)}).then(() => {inputView.hide()lo###iew.show()});}function createMainView() {const mainView = createView(`<form class="main-view"><div class="input-view-content"></div></form>`)const input = createView('<input id="package-name" placeholder="Enter app name" type="text" class="package-name"></input>')const submit = createView('<input type="submit" style="display:none"></input>')const button = createView('<div class="submit">Submit</div>')const div = mainView.querySelector('div')div.appendChild(input)div.appendChild(submit)div.appendChild(button)button.addEventListener('click', onSubmit)mainView.addEventListener('submit', onSubmit);return mainView;}async function DOMReady() {if (document.readyState !== 'loading') {return}let resolveconst promise = new Promise(res => { resolve = res; })document.addEventListener('DOMContentLoaded', resolve)return promise}function arrayBufferToBase64(buffer) {var binary = '';const bytes = new Uint8Array(buffer);const len = bytes.byteLength;for (let i = 0; i < len; ++i) {binary += String.fromCharCode(bytes[i]);}return window.btoa(binary);}var modalView = {create: function () {var element = modalView.getElement()if (!element.parentNode) {const style = createView(`<style>${modalView.style}</style>`)document.head.appendChild(style)document.body.appendChild(element)}return element},/** @return {HTMLElement} */getElement: function () {if (modalView.element) {return modalView.element}var element = modalView.element = createView(`<div class="modal-wrapper"><input type="checkbox" style="display: none; z-index: 1000; position: fixed; top: 10px; left: 10px;" id="modal-checkbox" /><div class="modal-container"><label for="modal-checkbox" class="modal-close-background" ></label><div class="modal-content">${'' && '<div class="modal-header"><label for="modal-checkbox" title="close" class="modal-close-x"><div></div></label></div>'}<div class="modal-body"></div><div class="modal-footer"></div></div></div></div>`)return element},/** @param {boolean} checked */check: function (checked) {var element = modalView.getElement()element.querySelector('#modal-checkbox').checked = checked},show: function () {modalView.check(true)},hide: function () {modalView.check(false)},style: `.modal-container {position: fixed;opacity: 0;top: 0;right: 0;bottom: 0;left: 0;transition: all 0.25s;z-index: -1000;}#modal-checkbox {top: 20px;left: 20px;position: fixed;z-index: 9999999999999;display: block;}#modal-checkbox:checked + .modal-container {z-index: 9999999;opacity: 1;}#modal-checkbox:checked + .modal-container label {display: block;}#modal-checkbox:checked + .modal-container .modal-content {bottom: 0;transition: all 0.25s;display: flex;}.modal-content {position: absolute;background-color: gray;min-width: 400px;min-height: 225px;max-width: 500px;max-height: 280px;width: 40%;height: 40%;opacity: 1;flex-direction: column;align-items: center;right: 0;bottom: -20%;transition: all 0.25s;}.modal-header {display: flex;flex-direction: row;position: relative;align-items: center;width: 100%;}.modal-close-x {margin: 5px 10px 5px 0;z-index: 12;cursor: pointer;}.modal-close-x div {display: flex;flex-direction: row;justify-content: center;}.modal-close-x,.modal-close-x div {width: 24px;height: 24px;}.modal-close-x div:after,.modal-close-x div:before {content: "";position: absolute;background: #fff;width: 2.5px;height: 24px;display: block;transform: rotate(45deg);}.modal-close-x div:before {transform: rotate(-45deg);}.modal-close-background {position: absolute;background-color: black;width: 100%;height: 100%;opacity: 0.4;cursor: pointer;display: none;}`,}var storeView = {create: function (data) {var element = storeView.getElement(data)if (!element.parentNode) {const style = createView(`<style>${storeView.style}</style>`)document.head.appendChild(style)modalView.getElement().querySelector('.modal-body').appendChild(element)}return element},/** @param {{ r###ltCount: number; r###lts: Software[]; }} data */getElement: function (data) {if (!storeView.element) {storeView.element = createView('<div class="app-store-view"></div>')}storeView.element.innerHTML = ''for (const software of data.r###lts) {const sview = createSoftwareView(software)storeView.element.appendChild(sview)}return storeView.element},style: `.modal-content {background-color: rgba(255, 255, 255, 0.9);}.modal-body {overflow: auto;background-color: rgba(255, 255, 255, 0.9);}.app-store-view {display: flex;flex-direction: column;}.app-store-software {display: flex;flex-direction: row;margin: 5px 0;}.app-store-software-icon {object-fit: contain;width: 100px;height: 100px;}.app-store-software-name {font-weight: bold;display: flex;flex-direction: row;justify-content: space-between;}.app-store-software-content {display: flex;flex-direction: column;justify-content: space-between;margin-left: 10px;margin-right: 10px;flex: 1;}.app-store-software-description span {text-overflow: ellipsis;max-width: 350px;white-space: nowrap;overflow: hidden;display: block;}.app-store-software-meta {display: flex;flex-direction: row;justify-content: space-between;align-items: center;}.app-store-software-meta > * {flex: 1;text-align: center;}`,}var inputView = {create: function () {var element = inputView.getElement()if (!element.parentNode) {const style = createView(`<style>${inputView.style}</style>`)document.head.appendChild(style)document.body.appendChild(element)}return element},getElement: function () {if (!inputView.element) {inputView.element = createMainView()}return inputView.element},hide: function () {inputView.getElement().style.display = 'none'},show: function () {inputView.getElement().style.display = 'initial'},style: `.main-view {position: fixed;top: 60px;right: 10px;background: #fff;padding: 10px;z-index: 10000;}.input-view-content {padding: 25px;border-radius: 10px;border: 1px solid #eaeaea;}.package-name {line-height: 22px;font-size: 12px;padding-left: 10px;}.submit {color: #fff;background-color: #179ed0;border-radius: 5px;display: flex;padding: 3px;justify-content: center;align-items: center;margin-top: 5px;}`,}var lo###iew = {create: function () {var element = lo###iew.getElement()if (!element.parentNode) {const style = createView(`<style>${lo###iew.style}</style>`)document.head.appendChild(style)document.body.appendChild(element)}return element},getElement: function () {if (!lo###iew.element) {const logo = lo###iew.icon// 'https://i.imgur.com/SnBFon3.png'lo###iew.element = createView(`<image src="${logo}" class="app-store-logo" />`)lo###iew.element.addEventListener('click', () => {lo###iew.hide()inputView.show()})}return lo###iew.element},loadIcon: async function () {const response = await makeRequest({url: 'https://i.imgur.com/SnBFon3.png',responseType: 'arraybuffer',})if (response.ok) {const resource = arrayBufferToBase64(response.data);// URL.createObjectURL(response.data)lo###iew.icon = `data:image/png;base64,${resource}`}},icon: '',hide: function () {lo###iew.getElement().style.display = 'none'},show: function () {lo###iew.getElement().style.display = 'initial'},style: `.app-store-logo {position: fixed;bottom: 10px;right: 10px;z-index: 100000;width: 60px;height: 60px;object-fit: contain;cursor: pointer;}`,}})();