{"id":31733303,"url":"https://github.com/devtakkekar/qb-playtime","last_synced_at":"2026-04-16T00:32:22.791Z","repository":{"id":317826827,"uuid":"1068973418","full_name":"devtakkekar/qb-playtime","owner":"devtakkekar","description":"qb-core playtime script","archived":false,"fork":false,"pushed_at":"2025-10-03T08:32:40.000Z","size":10,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-13T09:57:03.573Z","etag":null,"topics":["easy","free","js","leaderboard","lightweight","lua","minimal","playtime","qbcore","ranking","script","sql"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/devtakkekar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-03T07:48:57.000Z","updated_at":"2025-10-03T08:34:47.000Z","dependencies_parsed_at":"2025-10-06T09:00:25.357Z","dependency_job_id":null,"html_url":"https://github.com/devtakkekar/qb-playtime","commit_stats":null,"previous_names":["devtakkekar/qb-playtime"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/devtakkekar/qb-playtime","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtakkekar%2Fqb-playtime","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtakkekar%2Fqb-playtime/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtakkekar%2Fqb-playtime/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtakkekar%2Fqb-playtime/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/devtakkekar","download_url":"https://codeload.github.com/devtakkekar/qb-playtime/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/devtakkekar%2Fqb-playtime/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31866325,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["easy","free","js","leaderboard","lightweight","lua","minimal","playtime","qbcore","ranking","script","sql"],"created_at":"2025-10-09T08:23:36.593Z","updated_at":"2026-04-16T00:32:22.769Z","avatar_url":"https://github.com/devtakkekar.png","language":"Lua","funding_links":[],"categories":[],"sub_categories":[],"readme":"#  $\\color{Orange}\\huge{\\textbf{QB-Core PLAYTIME SCRIPT}}$\nTrack player playtime with a clean NUI, personal stats, and a top-100 leaderboard for QBCore. Open the UI in-game with /ptime.\n\n## Preview\n\u003cimg width=\"627\" height=\"265\" alt=\"Screenshot_1\" src=\"https://github.com/user-attachments/assets/05020ad4-6c4b-4ab3-ace6-516ddd67c317\" /\u003e\n\n## Features\n- Personal stats: total playtime, rank, and first join date.  \n- Leaderboard: top 100 players by total seconds played.  \n- Accurate accounting: buffers per-minute playtime and persists every 5 minutes and on disconnect.  \n- Non-intrusive UI: opens only on /ptime, closes with Escape or the close button.  \n- Zero-config schema: auto-adds join_date if missing.  \n\n## Requirements\n- Framework: qb-core  \n- Database: oxmysql  \n- Server: FiveM (fxserver), Lua 5.4 enabled  \n\n## Installation\n1) Place the resource in your server:  \nPut this folder into resources\\[custom]\\qb-playtime  \n2) Ensure dependencies are started before this resource:  \nqb-core  \noxmysql  \n3) Import the SQL (optional; the resource will also create/alter on first run):\n```\nCREATE TABLE IF NOT EXISTS `playtime` (\n  `citizenid` varchar(50) NOT NULL,\n  `name` varchar(100) NOT NULL,\n  `seconds` int NOT NULL DEFAULT 0,\n  `join_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  PRIMARY KEY (`citizenid`),\n  KEY `seconds_idx` (`seconds`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;\n```\n4) Start the resource in your server.cfg:\n```\nensure qb-core\nensure oxmysql\nensure qb-playtime\n```\n\n## Usage  \nOpen UI: type /ptime in chat.  \nClose UI: press Esc or click the X button.  \nRelevant client command:  \n```\nRegisterCommand('ptime', function()\n\tQBCore.Functions.TriggerCallback('qb-playtime:getData', function(response)\n\t\tif not response then\n\t\t\tQBCore.Functions.Notify('Unable to load playtime data', 'error')\n\t\t\treturn\n\t\tend\n\t\topenUI(response)\n\tend)\nend)\n```\n\n## How it works  \n- Tracking: Every minute, each connected player's session buffer increments by 60 seconds. Every 5 minutes and on player drop, the buffer is persisted to MySQL.   \n- Leaderboard: Ranks are computed by total persisted seconds, with your current session buffer visually added to your entry.    \n- Join date: A join_date column is ensured at startup and displayed in the UI.    \nServer persistence and callback:    \n```\n-- Increase playtime every 60 seconds for connected players\nCreateThread(function()\n\twhile true do\n\t\tWait(60 * 1000)\n\t\tfor _, playerId in ipairs(QBCore.Functions.GetPlayers()) do\n\t\t\tplayerPlaytimeSeconds[playerId] = (playerPlaytimeSeconds[playerId] or 0) + 60\n\t\tend\n\tend\nend)\n```\n```\n-- Load personal and leaderboard data for UI\nQBCore.Functions.CreateCallback('qb-playtime:getData', function(source, cb)\n\tlocal src = source\n\tlocal Player = QBCore.Functions.GetPlayer(src)\n\tif not Player then cb(nil) return end\n\tlocal citizenid = Player.PlayerData.citizenid\n\tlocal name = (Player.PlayerData.charinfo and (Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname)) or Player.PlayerData.name or ('Player ' .. tostring(src))\n\n\t-- Fetch total seconds (persisted + current session buffer)\n\tlocal row = MySQL.single.await('SELECT seconds, join_date FROM playtime WHERE citizenid = ? LIMIT 1', { citizenid })\n\tlocal persisted = (row and row.seconds) or 0\n\tlocal current = playerPlaytimeSeconds[src] or 0\n\tlocal totalSeconds = persisted + current\n\n\t-- Rank is based on total seconds vs leaderboard\n\tlocal leaderboard = MySQL.query.await('SELECT name, citizenid, seconds FROM playtime ORDER BY seconds DESC LIMIT 100', {}) or {}\n\n\t-- Make the player's leaderboard entry reflect current session buffer too (visual consistency)\n\tfor _, r in ipairs(leaderboard) do\n\t\tif r.citizenid == citizenid then\n\t\t\tr.seconds = (r.seconds or 0) + current\n\t\t\tbreak\n\t\tend\n\tend\n\n\t-- Determine rank (1-indexed)\n\tlocal rank = nil\n\tfor i, r in ipairs(leaderboard) do\n\t\tif r.citizenid == citizenid then rank = i break end\n\tend\n\t-- If not in top 100, compute rank position using a count query\n\tif not rank then\n\t\tlocal cntRow = MySQL.single.await('SELECT COUNT(*) as cnt FROM playtime WHERE seconds \u003e ?', { totalSeconds })\n\t\trank = ((cntRow and cntRow.cnt) or 0) + 1\n\tend\n\n\t-- Convert leaderboard to include rank numbers\n\tfor i, r in ipairs(leaderboard) do\n\t\tr.rank = i\n\tend\n\n\tcb({\n\t\tplayer = {\n\t\t\tname = name,\n\t\t\tcitizenid = citizenid,\n\t\t\tseconds = totalSeconds,\n\t\t\trank = rank,\n\t\t\tjoin_date = row and row.join_date or nil\n\t\t},\n\t\tleaderboard = leaderboard\n\t})\nend)\n```\n## Configuration  \nNo explicit config file. The resource uses qb-core/oxmysql defaults.  \nUI opens only via /ptime (no keybind by default).  \nfxmanifest:  \n```\nfx_version 'cerulean'\ngame 'gta5'\n\nname 'qb-playtime'\nauthor 'devtakkekar'\ndescription 'Playtime tracker with /time UI and leaderboard for QBCore'\nversion '1.0.0'\n```\n\n## Performance\nTracking runs once per minute and writes every 5 minutes.   \nThis is very lightweight.    \nLeaderboard query is limited to top 100.  \n\n## Troubleshooting  \n- UI doesn’t open: Ensure qb-core and oxmysql are running and this resource is started after them.  \n- No data shown: Confirm the playtime table exists and the server can write to the database. Check your oxmysql connection in your core resources.  \n- Join date is “-”: The column is added automatically; if you imported an older schema, restart the resource/server so the migration runs. \n\n## License\nThis project is licensed under the MIT License.  \n\n## Credits  \ndevtakkekar - Initial work  \nQBCore Framework Team  \n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevtakkekar%2Fqb-playtime","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdevtakkekar%2Fqb-playtime","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdevtakkekar%2Fqb-playtime/lists"}