npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

get-contac

v1.0.0

Published

<div align="center"> <img src="https://img.shields.io/badge/Version-1.0.0-2563eb?style=for-the-badge&logo=typescript" alt="Version"> <img src="https://img.shields.io/badge/License-MIT-green?style=for-the-badge&logo=open-source-initiative" alt="Lic

Readme

get-contac


Table of Contents


What is get-contac?

get-contac is a collaborative contact tagging system that allows users to add tags to phone numbers. Multiple users can contribute tags to the same phone number, creating a crowdsourced knowledge base about phone numbers (e.g., "spam", "developer", "business", "friend").

Built on VeloxDB for high-performance binary storage and TypeScript for type safety.

get-contac is designed for:

  • Spam detection and reporting
  • Business contact identification
  • Collaborative contact labeling
  • Crowdsourced phone number intelligence
  • Android contact sync applications

Features

| Category | Features | |----------|----------| | Storage | VeloxDB binary format, Zero-config, Atomic writes | | Phone Numbers | Automatic normalization (0812 → 62812), International format | | Tagging | Multiple tags per number, Multi-contributor support | | Performance | Local LRU caching, Fast indexing, Async I/O | | Interfaces | Node.js API, CLI tool, Browser CDN, Android ready | | Security | Authentication, Rate limiting, IP whitelisting | | Privacy | Contributor tracking, No data collection, Self-hosted |


Installation

From NPM

npm install get-contac
npm install -g get-contac

Requirements

Requirement Minimum Recommended Node.js 18.0.0 20.0.0+ RAM 128 MB 512 MB+ Storage 50 MB 1 GB+ OS Linux 5.4+ Ubuntu 22.04+


Quick Start

As Node.js Module

import { ContactTaggingSystem } from 'get-contac';

async function main() {
  const db = new ContactTaggingSystem('./contact-db');
  
  await db.initialize('admin', 'password123', '127.0.0.1');
  
  await db.addContact('081234567890', 'developer', 'dimas');
  await db.addContact('6281234567890', 'spam', 'user123');
  
  const result = await db.searchPhone('081234567890');
  console.log(result);
  
  await db.close();
}

main();

Using CLI

get-contac config --host http://localhost:3000 --token your-token
get-contac search 6281234567890
get-contac add 6281234567890 spam --contributor john
get-contac stats

Using Browser CDN

<script src="https://your-cdn.com/get-contac.min.js" 
        data-api-url="https://api.yourserver.com"
        data-api-key="your-key"></script>

<script>
  const result = await window.contactLens.search('081234567890');
  console.log('Tags:', result.tags);
</script>

Architecture

Core Concept

Phone Number Tag Contributor 6281234567890 developer dimas 6281234567890 freelancer budi 6281234567890 trusted siti 6289876543210 spam user123

Flowchart

User opens app → Request permission → Read contacts → Normalize numbers → Send to API → Store in VeloxDB

Android Java Implementation

Step 1: Add Permissions in AndroidManifest.xml

<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.INTERNET" />

Step 2: Request Permission and Read Contacts

// MainActivity.java
package com.example.contacttag;

import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import org.json.JSONObject;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Scanner;

public class MainActivity extends AppCompatActivity {
    
    private static final int PERMISSION_REQUEST_CODE = 100;
    private TextView statusText;
    private Button syncButton;
    private String apiUrl = "https://your-api.com/api/add";
    private String apiToken = "your-api-token";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        statusText = findViewById(R.id.statusText);
        syncButton = findViewById(R.id.syncButton);
        
        syncButton.setOnClickListener(v -> checkPermissionAndSync());
    }
    
    private void checkPermissionAndSync() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.READ_CONTACTS},
                    PERMISSION_REQUEST_CODE);
        } else {
            new SyncContactsTask().execute();
        }
    }
    
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 
                                           @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == PERMISSION_REQUEST_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                new SyncContactsTask().execute();
            } else {
                Toast.makeText(this, "Permission denied! Cannot read contacts.", 
                              Toast.LENGTH_SHORT).show();
            }
        }
    }
    
    private class SyncContactsTask extends AsyncTask<Void, Void, String> {
        
        @Override
        protected void onPreExecute() {
            statusText.setText("Syncing contacts...");
            syncButton.setEnabled(false);
        }
        
        @Override
        protected String doInBackground(Void... voids) {
            return readAndSyncContacts();
        }
        
        @Override
        protected void onPostExecute(String result) {
            statusText.setText(result);
            syncButton.setEnabled(true);
            Toast.makeText(MainActivity.this, result, Toast.LENGTH_LONG).show();
        }
    }
    
    private String readAndSyncContacts() {
        StringBuilder log = new StringBuilder();
        int syncedCount = 0;
        int errorCount = 0;
        
        Cursor cursor = getContentResolver().query(
            ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
            null,
            null,
            null,
            null
        );
        
        if (cursor != null && cursor.getCount() > 0) {
            while (cursor.moveToNext()) {
                String name = cursor.getString(
                    cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)
                );
                String phone = cursor.getString(
                    cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
                );
                
                if (phone != null && !phone.trim().isEmpty()) {
                    boolean success = sendToApi(phone, name, "android-user");
                    if (success) {
                        syncedCount++;
                        log.append("✓ ").append(name).append(" - ").append(phone).append("\n");
                    } else {
                        errorCount++;
                        log.append("✗ ").append(name).append(" - ").append(phone).append("\n");
                    }
                }
            }
            cursor.close();
        } else {
            return "No contacts found!";
        }
        
        return String.format("Synced: %d contacts | Errors: %d\n%s", 
                            syncedCount, errorCount, log.toString());
    }
    
    private boolean sendToApi(String phone, String name, String contributor) {
        try {
            String normalized = normalizePhoneNumber(phone);
            URL url = new URL(apiUrl);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("Authorization", "Bearer " + apiToken);
            conn.setDoOutput(true);
            
            JSONObject json = new JSONObject();
            json.put("phone", normalized);
            json.put("tag", name);
            json.put("contributor", contributor);
            
            OutputStream os = conn.getOutputStream();
            os.write(json.toString().getBytes());
            os.flush();
            os.close();
            
            int responseCode = conn.getResponseCode();
            conn.disconnect();
            
            return responseCode == 200;
            
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    
    private String normalizePhoneNumber(String phone) {
        String cleaned = phone.replaceAll("[^\\d+]", "");
        
        if (cleaned.startsWith("+")) {
            cleaned = cleaned.substring(1);
        }
        if (cleaned.startsWith("0")) {
            cleaned = "62" + cleaned.substring(1);
        }
        if (cleaned.startsWith("8") && !cleaned.startsWith("62")) {
            cleaned = "62" + cleaned;
        }
        
        return cleaned;
    }
}

Step 3: Layout File (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Contact Tag Sync"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginBottom="24dp"/>

    <Button
        android:id="@+id/syncButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Sync Contacts"
        android:layout_marginBottom="24dp"/>

    <TextView
        android:id="@+id/statusText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Ready to sync"
        android:padding="12dp"
        android:background="#f0f0f0"/>

</LinearLayout>

Step 4: Gradle Dependencies (build.gradle)

dependencies {
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.9.0'
    implementation 'com.android.volley:volley:1.2.1'
}

Phone Number Normalization

get-contac automatically normalizes phone numbers to international format:

Input Output 081234567890 6281234567890 +6281234567890 6281234567890 6281234567890 6281234567890 0812-3456-7890 6281234567890

import { normalizePhoneNumber, validatePhoneNumber } from 'get-contac';

const normalized = normalizePhoneNumber('081234567890');
// Returns: '6281234567890'

const isValid = validatePhoneNumber('6281234567890');
// Returns: true

Privacy & Legal

Important Considerations

This is the most critical part of building a contact tagging system.

  1. User Consent: You MUST obtain explicit permission before accessing contacts
  2. Privacy Policy: Clearly state how you collect, store, and use contact data
  3. Data Ownership: Users should know their contacts are being shared
  4. Opt-out Mechanism: Provide easy way to delete contributed data
  5. GDPR Compliance: For EU users, you need proper consent and data deletion rights

Recommended Implementation

// Always ask for permission first
async function requestContactPermission() {
  if (DeviceRuntime.platform === 'android') {
    const permission = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.READ_CONTACTS,
      {
        title: 'Contact Access Permission',
        message: 'This app needs access to your contacts ' +
                 'to tag and identify phone numbers.',
        buttonNeutral: 'Ask Me Later',
        buttonNegative: 'Cancel',
        buttonPositive: 'OK'
      }
    );
    return permission === PermissionsAndroid.RESULTS.GRANTED;
  }
  return true;
}

Legal Disclaimer

By using get-contac, you agree that:

· You will obtain proper user consent before accessing contacts · You are responsible for compliance with local privacy laws · The author is not liable for any misuse of this software · You will not use this for malicious purposes (spam, harassment, stalking)


FAQ

Q1: How does phone number normalization work?

A: get-contac converts all formats (0812, +62812, 62812) to international format (62812) for consistent indexing.

Q2: Can multiple users tag the same number?

A: Yes! That's the core feature. Each phone number can have multiple tags from different contributors.

Q3: Is my contact data stored on your servers?

A: No. get-contac is self-hosted. You run your own database and API server.

Q4: How do I protect user privacy?

A: Always ask for permission, anonymize data where possible, provide opt-out mechanisms, and comply with local laws.

Q5: Can I use this for spam detection?

A: Yes. Many users tag spam numbers, creating a crowdsourced spam database.

Q6: What's the maximum number of contacts?

A: VeloxDB can handle millions of entries. Performance depends on your server hardware.


Terms of Service

By downloading, installing, or using get-contac (the "Software"), you agree to these Terms.

Intended Use

· Storing and retrieving contact tags for your applications · Building spam detection systems · Collaborative contact labeling

Prohibited Uses

You agree NOT to use get-contac for:

· Storing illegal content · Harvesting contacts without permission · Spamming or harassing phone numbers · Violating data protection laws (GDPR, CCPA) · Stalking or doxxing individuals

Responsibility

THE AUTHOR PROVIDES THIS SOFTWARE "AS IS" WITHOUT WARRANTIES. YOU BEAR FULL RESPONSIBILITY FOR YOUR ACTIONS AND LEGAL COMPLIANCE.

Data Privacy

get-contac does not collect telemetry or send data anywhere. All data stays on your infrastructure. You are responsible for securing your database.

Ethical Reminder

Please use this tool responsibly. Respect user privacy, follow data protection laws, and don't use this for harmful purposes.


License

MIT License

Copyright (c) 2026 Dimzxzzx07

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.